Bv2.1 8.5: FFOC and LFOC should be present and have particular values.
[libdcp.git] / test / verify_test.cc
1 /*
2     Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 #include "verify.h"
35 #include "util.h"
36 #include "j2k.h"
37 #include "reel.h"
38 #include "reel_mono_picture_asset.h"
39 #include "reel_sound_asset.h"
40 #include "cpl.h"
41 #include "dcp.h"
42 #include "openjpeg_image.h"
43 #include "mono_picture_asset.h"
44 #include "stereo_picture_asset.h"
45 #include "mono_picture_asset_writer.h"
46 #include "interop_subtitle_asset.h"
47 #include "smpte_subtitle_asset.h"
48 #include "reel_closed_caption_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_subtitle_asset.h"
51 #include "reel_markers_asset.h"
52 #include "compose.hpp"
53 #include "test.h"
54 #include <boost/test/unit_test.hpp>
55 #include <boost/foreach.hpp>
56 #include <boost/algorithm/string.hpp>
57 #include <cstdio>
58 #include <iostream>
59
60 using std::list;
61 using std::pair;
62 using std::string;
63 using std::vector;
64 using std::make_pair;
65 using std::make_shared;
66 using boost::optional;
67 using std::shared_ptr;
68
69
70 static list<pair<string, optional<boost::filesystem::path> > > stages;
71
72 static void
73 stage (string s, optional<boost::filesystem::path> p)
74 {
75         stages.push_back (make_pair (s, p));
76 }
77
78 static void
79 progress (float)
80 {
81
82 }
83
84 static void
85 prepare_directory (boost::filesystem::path path)
86 {
87         using namespace boost::filesystem;
88         remove_all (path);
89         create_directories (path);
90 }
91
92
93 static vector<boost::filesystem::path>
94 setup (int reference_number, int verify_test_number)
95 {
96         prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
97         for (auto i: boost::filesystem::directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
98                 boost::filesystem::copy_file (i.path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i.path().filename());
99         }
100
101         return { dcp::String::compose("build/test/verify_test%1", verify_test_number) };
102
103 }
104
105
106 static
107 void
108 write_dcp_with_single_asset (boost::filesystem::path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::SMPTE)
109 {
110         auto reel = make_shared<dcp::Reel>();
111         reel->add (reel_asset);
112         reel->add (simple_markers());
113
114         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
115         cpl->add (reel);
116         auto dcp = make_shared<dcp::DCP>(dir);
117         dcp->add (cpl);
118         dcp->write_xml (standard);
119 }
120
121
122 /** Class that can alter a file by searching and replacing strings within it.
123  *  On destruction modifies the file whose name was given to the constructor.
124  */
125 class Editor
126 {
127 public:
128         Editor (boost::filesystem::path path)
129                 : _path(path)
130         {
131                 _content = dcp::file_to_string (_path);
132         }
133
134         ~Editor ()
135         {
136                 auto f = fopen(_path.string().c_str(), "w");
137                 BOOST_REQUIRE (f);
138                 fwrite (_content.c_str(), _content.length(), 1, f);
139                 fclose (f);
140         }
141
142         void replace (string a, string b)
143         {
144                 auto old_content = _content;
145                 boost::algorithm::replace_all (_content, a, b);
146                 BOOST_REQUIRE (_content != old_content);
147         }
148
149 private:
150         boost::filesystem::path _path;
151         std::string _content;
152 };
153
154
155 static
156 void
157 dump_notes (vector<dcp::VerificationNote> const & notes)
158 {
159         for (auto i: notes) {
160                 std::cout << dcp::note_to_string(i) << "\n";
161         }
162 }
163
164
165 static
166 void
167 check_verify_result (vector<boost::filesystem::path> dir, vector<std::pair<dcp::VerificationNote::Type, dcp::VerificationNote::Code>> types_and_codes)
168 {
169         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
170         dump_notes (notes);
171         BOOST_REQUIRE_EQUAL (notes.size(), types_and_codes.size());
172         auto i = notes.begin();
173         auto j = types_and_codes.begin();
174         while (i != notes.end()) {
175                 BOOST_CHECK_EQUAL (i->type(), j->first);
176                 BOOST_CHECK_EQUAL (i->code(), j->second);
177                 ++i;
178                 ++j;
179         }
180 }
181
182
183 static
184 void
185 check_verify_result_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
186 {
187         auto directories = setup (1, n);
188
189         {
190                 Editor e (file(n));
191                 e.replace (from, to);
192         }
193
194         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
195
196         BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
197         auto i = notes.begin();
198         auto j = codes.begin();
199         while (i != notes.end()) {
200                 BOOST_CHECK_EQUAL (i->code(), *j);
201                 ++i;
202                 ++j;
203         }
204 }
205
206
207 /* Check DCP as-is (should be OK) */
208 BOOST_AUTO_TEST_CASE (verify_test1)
209 {
210         stages.clear ();
211         auto directories = setup (1, 1);
212         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
213
214         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
215         boost::filesystem::path const pkl_file = "build/test/verify_test1/pkl_63c3aece-c581-4603-b612-75e43f0c0430.xml";
216         boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
217
218         auto st = stages.begin();
219         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
220         BOOST_REQUIRE (st->second);
221         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
222         ++st;
223         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
224         BOOST_REQUIRE (st->second);
225         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
226         ++st;
227         BOOST_CHECK_EQUAL (st->first, "Checking reel");
228         BOOST_REQUIRE (!st->second);
229         ++st;
230         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
231         BOOST_REQUIRE (st->second);
232         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
233         ++st;
234         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
235         BOOST_REQUIRE (st->second);
236         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
237         ++st;
238         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
239         BOOST_REQUIRE (st->second);
240         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
241         ++st;
242         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
243         BOOST_REQUIRE (st->second);
244         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
245         ++st;
246         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
247         BOOST_REQUIRE (st->second);
248         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
249         ++st;
250         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
251         BOOST_REQUIRE (st->second);
252         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
253         ++st;
254         BOOST_REQUIRE (st == stages.end());
255
256         BOOST_CHECK_EQUAL (notes.size(), 0);
257 }
258
259 /* Corrupt the MXFs and check that this is spotted */
260 BOOST_AUTO_TEST_CASE (verify_test2)
261 {
262         auto directories = setup (1, 2);
263
264         auto mod = fopen("build/test/verify_test2/video.mxf", "r+b");
265         BOOST_REQUIRE (mod);
266         fseek (mod, 4096, SEEK_SET);
267         int x = 42;
268         fwrite (&x, sizeof(x), 1, mod);
269         fclose (mod);
270
271         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
272         BOOST_REQUIRE (mod);
273         BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
274         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
275         fclose (mod);
276
277         dcp::ASDCPErrorSuspender sus;
278         check_verify_result (
279                 directories,
280                 {
281                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_HASH_INCORRECT },
282                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::SOUND_HASH_INCORRECT }
283                 });
284 }
285
286 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
287 BOOST_AUTO_TEST_CASE (verify_test3)
288 {
289         auto directories = setup (1, 3);
290
291         {
292                 Editor e ("build/test/verify_test3/pkl_63c3aece-c581-4603-b612-75e43f0c0430.xml");
293                 e.replace ("<Hash>", "<Hash>x");
294         }
295
296         check_verify_result (
297                 directories,
298                 {
299                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT },
300                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DIFFER },
301                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER },
302                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
303                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
304                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
305                 });
306 }
307
308 /* Corrupt the ContentKind in the CPL */
309 BOOST_AUTO_TEST_CASE (verify_test4)
310 {
311         auto directories = setup (1, 4);
312
313         {
314                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
315                 e.replace ("<ContentKind>", "<ContentKind>x");
316         }
317
318         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
319
320         BOOST_REQUIRE_EQUAL (notes.size(), 1);
321         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
322         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xtrailer'");
323 }
324
325 static
326 boost::filesystem::path
327 cpl (int n)
328 {
329         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
330 }
331
332 static
333 boost::filesystem::path
334 pkl (int n)
335 {
336         return dcp::String::compose("build/test/verify_test%1/pkl_63c3aece-c581-4603-b612-75e43f0c0430.xml", n);
337 }
338
339 static
340 boost::filesystem::path
341 asset_map (int n)
342 {
343         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
344 }
345
346
347 /* FrameRate */
348 BOOST_AUTO_TEST_CASE (verify_test5)
349 {
350         check_verify_result_after_replace (
351                         5, &cpl,
352                         "<FrameRate>24 1", "<FrameRate>99 1",
353                         { dcp::VerificationNote::CPL_HASH_INCORRECT,
354                           dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE }
355                         );
356 }
357
358 /* Missing asset */
359 BOOST_AUTO_TEST_CASE (verify_test6)
360 {
361         auto directories = setup (1, 6);
362
363         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
364         check_verify_result (directories, {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET }});
365 }
366
367 static
368 boost::filesystem::path
369 assetmap (int n)
370 {
371         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
372 }
373
374 /* Empty asset filename in ASSETMAP */
375 BOOST_AUTO_TEST_CASE (verify_test7)
376 {
377         check_verify_result_after_replace (
378                         7, &assetmap,
379                         "<Path>video.mxf</Path>", "<Path></Path>",
380                         { dcp::VerificationNote::EMPTY_ASSET_PATH }
381                         );
382 }
383
384 /* Mismatched standard */
385 BOOST_AUTO_TEST_CASE (verify_test8)
386 {
387         check_verify_result_after_replace (
388                         8, &cpl,
389                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
390                         { dcp::VerificationNote::MISMATCHED_STANDARD,
391                           dcp::VerificationNote::XML_VALIDATION_ERROR,
392                           dcp::VerificationNote::CPL_HASH_INCORRECT }
393                         );
394 }
395
396 /* Badly formatted <Id> in CPL */
397 BOOST_AUTO_TEST_CASE (verify_test9)
398 {
399         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
400         check_verify_result_after_replace (
401                         9, &cpl,
402                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
403                         { dcp::VerificationNote::XML_VALIDATION_ERROR }
404                         );
405 }
406
407 /* Badly formatted <IssueDate> in CPL */
408 BOOST_AUTO_TEST_CASE (verify_test10)
409 {
410         check_verify_result_after_replace (
411                         10, &cpl,
412                         "<IssueDate>", "<IssueDate>x",
413                         { dcp::VerificationNote::XML_VALIDATION_ERROR,
414                           dcp::VerificationNote::CPL_HASH_INCORRECT }
415                         );
416 }
417
418 /* Badly-formatted <Id> in PKL */
419 BOOST_AUTO_TEST_CASE (verify_test11)
420 {
421         check_verify_result_after_replace (
422                 11, &pkl,
423                 "<Id>urn:uuid:63c", "<Id>urn:uuid:x3c",
424                 { dcp::VerificationNote::XML_VALIDATION_ERROR }
425                 );
426 }
427
428 /* Badly-formatted <Id> in ASSETMAP */
429 BOOST_AUTO_TEST_CASE (verify_test12)
430 {
431         check_verify_result_after_replace (
432                 12, &asset_map,
433                 "<Id>urn:uuid:63c", "<Id>urn:uuid:x3c",
434                 { dcp::VerificationNote::XML_VALIDATION_ERROR }
435                 );
436 }
437
438 /* Basic test of an Interop DCP */
439 BOOST_AUTO_TEST_CASE (verify_test13)
440 {
441         stages.clear ();
442         auto directories = setup (3, 13);
443         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
444
445         boost::filesystem::path const cpl_file = "build/test/verify_test13/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
446         boost::filesystem::path const pkl_file = "build/test/verify_test13/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
447         boost::filesystem::path const assetmap_file = "build/test/verify_test13/ASSETMAP";
448
449         auto st = stages.begin();
450         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
451         BOOST_REQUIRE (st->second);
452         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13"));
453         ++st;
454         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
455         BOOST_REQUIRE (st->second);
456         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
457         ++st;
458         BOOST_CHECK_EQUAL (st->first, "Checking reel");
459         BOOST_REQUIRE (!st->second);
460         ++st;
461         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
462         BOOST_REQUIRE (st->second);
463         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
464         ++st;
465         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
466         BOOST_REQUIRE (st->second);
467         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
468         ++st;
469         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
470         BOOST_REQUIRE (st->second);
471         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
472         ++st;
473         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
474         BOOST_REQUIRE (st->second);
475         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
476         ++st;
477         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
478         BOOST_REQUIRE (st->second);
479         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
480         ++st;
481         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
482         BOOST_REQUIRE (st->second);
483         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
484         ++st;
485         BOOST_REQUIRE (st == stages.end());
486
487         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
488         auto i = notes.begin ();
489         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
490         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
491 }
492
493 /* DCP with a short asset */
494 BOOST_AUTO_TEST_CASE (verify_test14)
495 {
496         auto directories = setup (8, 14);
497         check_verify_result (
498                 directories,
499                 {
500                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
501                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
502                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL },
503                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
504                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL }
505                 });
506 }
507
508
509 static
510 void
511 dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir)
512 {
513         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
514         boost::filesystem::create_directories (dir);
515         auto writer = asset->start_write (dir / "pic.mxf", true);
516         for (int i = 0; i < 24; ++i) {
517                 writer->write (frame.data(), frame.size());
518         }
519         writer->finalize ();
520
521         auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
522         write_dcp_with_single_asset (dir, reel_asset);
523 }
524
525
526 /* DCP with an over-sized JPEG2000 frame */
527 BOOST_AUTO_TEST_CASE (verify_test15)
528 {
529         int const too_big = 1302083 * 2;
530
531         /* Compress a black image */
532         auto image = black_image ();
533         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
534         BOOST_REQUIRE (frame.size() < too_big);
535
536         /* Place it in a bigger block with some zero padding at the end */
537         dcp::ArrayData oversized_frame(too_big);
538         memcpy (oversized_frame.data(), frame.data(), frame.size());
539         memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
540
541         boost::filesystem::path const dir("build/test/verify_test15");
542         boost::filesystem::remove_all (dir);
543         dcp_from_frame (oversized_frame, dir);
544
545         check_verify_result (
546                 { dir },
547                 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES }}
548                 );
549 }
550
551
552 /* DCP with a nearly over-sized JPEG2000 frame */
553 BOOST_AUTO_TEST_CASE (verify_test16)
554 {
555         int const nearly_too_big = 1302083 * 0.98;
556
557         /* Compress a black image */
558         auto image = black_image ();
559         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
560         BOOST_REQUIRE (frame.size() < nearly_too_big);
561
562         /* Place it in a bigger block with some zero padding at the end */
563         dcp::ArrayData oversized_frame(nearly_too_big);
564         memcpy (oversized_frame.data(), frame.data(), frame.size());
565         memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
566
567         boost::filesystem::path const dir("build/test/verify_test16");
568         boost::filesystem::remove_all (dir);
569         dcp_from_frame (oversized_frame, dir);
570
571         check_verify_result (
572                 { dir },
573                 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES }}
574                 );
575 }
576
577
578 /* DCP with a within-range JPEG2000 frame */
579 BOOST_AUTO_TEST_CASE (verify_test17)
580 {
581         /* Compress a black image */
582         auto image = black_image ();
583         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
584         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
585
586         boost::filesystem::path const dir("build/test/verify_test17");
587         boost::filesystem::remove_all (dir);
588         dcp_from_frame (frame, dir);
589
590         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
591         BOOST_REQUIRE_EQUAL (notes.size(), 0);
592 }
593
594
595 /* DCP with valid Interop subtitles */
596 BOOST_AUTO_TEST_CASE (verify_test18)
597 {
598         boost::filesystem::path const dir("build/test/verify_test18");
599         prepare_directory (dir);
600         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
601         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
602         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
603         write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
604
605         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE }});
606 }
607
608
609 /* DCP with broken Interop subtitles */
610 BOOST_AUTO_TEST_CASE (verify_test19)
611 {
612         boost::filesystem::path const dir("build/test/verify_test19");
613         prepare_directory (dir);
614         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
615         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
616         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
617         write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
618
619         {
620                 Editor e (dir / "subs.xml");
621                 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
622         }
623
624         check_verify_result (
625                 { dir },
626                 {
627                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
628                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
629                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
630                 });
631 }
632
633
634 /* DCP with valid SMPTE subtitles */
635 BOOST_AUTO_TEST_CASE (verify_test20)
636 {
637         boost::filesystem::path const dir("build/test/verify_test20");
638         prepare_directory (dir);
639         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
640         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
641         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
642         write_dcp_with_single_asset (dir, reel_asset);
643
644         check_verify_result ({dir}, {});
645 }
646
647
648 /* DCP with broken SMPTE subtitles */
649 BOOST_AUTO_TEST_CASE (verify_test21)
650 {
651         boost::filesystem::path const dir("build/test/verify_test21");
652         prepare_directory (dir);
653         boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
654         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
655         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
656         write_dcp_with_single_asset (dir, reel_asset);
657
658         check_verify_result (
659                 { dir },
660                 {
661                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
662                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
663                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
664                 });
665 }
666
667
668 /* VF */
669 BOOST_AUTO_TEST_CASE (verify_test22)
670 {
671         boost::filesystem::path const ov_dir("build/test/verify_test22_ov");
672         prepare_directory (ov_dir);
673
674         auto image = black_image ();
675         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
676         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
677         dcp_from_frame (frame, ov_dir);
678
679         dcp::DCP ov (ov_dir);
680         ov.read ();
681
682         boost::filesystem::path const vf_dir("build/test/verify_test22_vf");
683         prepare_directory (vf_dir);
684
685         write_dcp_with_single_asset (vf_dir, ov.cpls().front()->reels().front()->main_picture());
686
687         check_verify_result (
688                 { vf_dir },
689                 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET }});
690 }
691
692
693 /* DCP with valid CompositionMetadataAsset */
694 BOOST_AUTO_TEST_CASE (verify_test23)
695 {
696         boost::filesystem::path const dir("build/test/verify_test23");
697         prepare_directory (dir);
698
699         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
700         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
701         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
702
703         auto reel = make_shared<dcp::Reel>();
704         reel->add (reel_asset);
705
706         reel->add (simple_markers(16 * 24 - 1));
707
708         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
709         cpl->add (reel);
710         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
711         cpl->set_main_sound_sample_rate (48000);
712         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
713         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
714
715         dcp::DCP dcp (dir);
716         dcp.add (cpl);
717         dcp.write_xml (dcp::SMPTE);
718
719         check_verify_result ({dir}, {});
720 }
721
722
723 boost::filesystem::path find_cpl (boost::filesystem::path dir)
724 {
725         for (auto i: boost::filesystem::directory_iterator(dir)) {
726                 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
727                         return i.path();
728                 }
729         }
730
731         BOOST_REQUIRE (false);
732         return {};
733 }
734
735
736 /* DCP with invalid CompositionMetadataAsset */
737 BOOST_AUTO_TEST_CASE (verify_test24)
738 {
739         boost::filesystem::path const dir("build/test/verify_test24");
740         prepare_directory (dir);
741
742         auto reel = make_shared<dcp::Reel>();
743         reel->add (black_picture_asset(dir));
744         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
745         cpl->add (reel);
746         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
747         cpl->set_main_sound_sample_rate (48000);
748         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
749         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
750
751         reel->add (simple_markers());
752
753         dcp::DCP dcp (dir);
754         dcp.add (cpl);
755         dcp.write_xml (dcp::SMPTE);
756
757         {
758                 Editor e (find_cpl("build/test/verify_test24"));
759                 e.replace ("MainSound", "MainSoundX");
760         }
761
762         check_verify_result (
763                 { dir },
764                 {
765                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
766                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
767                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
768                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }
769                 });
770 }
771
772
773 /* DCP with invalid CompositionMetadataAsset */
774 BOOST_AUTO_TEST_CASE (verify_test25)
775 {
776         boost::filesystem::path const dir("build/test/verify_test25");
777         prepare_directory (dir);
778
779         auto reel = make_shared<dcp::Reel>();
780         reel->add (black_picture_asset(dir));
781         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
782         cpl->add (reel);
783         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
784         cpl->set_main_sound_sample_rate (48000);
785         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
786         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
787
788         dcp::DCP dcp (dir);
789         dcp.add (cpl);
790         dcp.write_xml (dcp::SMPTE);
791
792         {
793                 Editor e (find_cpl("build/test/verify_test25"));
794                 e.replace ("meta:Width", "meta:WidthX");
795         }
796
797         check_verify_result (
798                 { dir },
799                 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::GENERAL_READ }}
800                 );
801 }
802
803
804 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
805 BOOST_AUTO_TEST_CASE (verify_test26)
806 {
807         boost::filesystem::path const dir("build/test/verify_test26");
808         prepare_directory (dir);
809         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
810         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
811         asset->_language = "wrong-andbad";
812         asset->write (dir / "subs.mxf");
813         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
814         reel_asset->_language = "badlang";
815         write_dcp_with_single_asset (dir, reel_asset);
816
817         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
818         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
819         auto i = notes.begin();
820         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
821         BOOST_REQUIRE (i->note());
822         BOOST_CHECK_EQUAL (*i->note(), "badlang");
823         i++;
824         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
825         BOOST_REQUIRE (i->note());
826         BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
827 }
828
829
830 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
831 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages)
832 {
833         boost::filesystem::path const dir("build/test/verify_invalid_closed_caption_languages");
834         prepare_directory (dir);
835         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
836         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
837         asset->_language = "wrong-andbad";
838         asset->write (dir / "subs.mxf");
839         auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
840         reel_asset->_language = "badlang";
841         write_dcp_with_single_asset (dir, reel_asset);
842
843         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
844         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
845         auto i = notes.begin ();
846         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
847         BOOST_REQUIRE (i->note());
848         BOOST_CHECK_EQUAL (*i->note(), "badlang");
849         i++;
850         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
851         BOOST_REQUIRE (i->note());
852         BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
853 }
854
855
856 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
857  * the release territory.
858  */
859 BOOST_AUTO_TEST_CASE (verify_various_invalid_languages)
860 {
861         boost::filesystem::path const dir("build/test/verify_various_invalid_languages");
862         prepare_directory (dir);
863
864         auto picture = simple_picture (dir, "foo");
865         auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
866         auto reel = make_shared<dcp::Reel>();
867         reel->add (reel_picture);
868         auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
869         auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
870         reel->add (reel_sound);
871         reel->add (simple_markers());
872
873         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
874         cpl->add (reel);
875         cpl->_additional_subtitle_languages.push_back("this-is-wrong");
876         cpl->_additional_subtitle_languages.push_back("andso-is-this");
877         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
878         cpl->set_main_sound_sample_rate (48000);
879         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
880         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
881         cpl->_release_territory = "fred-jim";
882         auto dcp = make_shared<dcp::DCP>(dir);
883         dcp->add (cpl);
884         dcp->write_xml (dcp::SMPTE);
885
886         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
887         BOOST_REQUIRE_EQUAL (notes.size(), 4U);
888         auto i = notes.begin ();
889         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
890         BOOST_REQUIRE (i->note());
891         BOOST_CHECK_EQUAL (*i->note(), "this-is-wrong");
892         ++i;
893         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
894         BOOST_REQUIRE (i->note());
895         BOOST_CHECK_EQUAL (*i->note(), "andso-is-this");
896         ++i;
897         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
898         BOOST_REQUIRE (i->note());
899         BOOST_CHECK_EQUAL (*i->note(), "fred-jim");
900         ++i;
901         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
902         BOOST_REQUIRE (i->note());
903         BOOST_CHECK_EQUAL (*i->note(), "frobozz");
904         ++i;
905 }
906
907
908 static
909 vector<dcp::VerificationNote>
910 check_picture_size (int width, int height, int frame_rate, bool three_d)
911 {
912         using namespace boost::filesystem;
913
914         path dcp_path = "build/test/verify_picture_test";
915         remove_all (dcp_path);
916         create_directories (dcp_path);
917
918         shared_ptr<dcp::PictureAsset> mp;
919         if (three_d) {
920                 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
921         } else {
922                 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
923         }
924         auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
925
926         auto image = black_image (dcp::Size(width, height));
927         auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
928         int const length = three_d ? frame_rate * 2 : frame_rate;
929         for (int i = 0; i < length; ++i) {
930                 picture_writer->write (j2c.data(), j2c.size());
931         }
932         picture_writer->finalize ();
933
934         auto d = make_shared<dcp::DCP>(dcp_path);
935         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
936         cpl->set_annotation_text ("A Test DCP");
937         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
938
939         auto reel = make_shared<dcp::Reel>();
940
941         if (three_d) {
942                 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
943         } else {
944                 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
945         }
946
947         reel->add (simple_markers(frame_rate));
948
949         cpl->add (reel);
950
951         d->add (cpl);
952         d->write_xml (dcp::SMPTE);
953
954         return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
955 }
956
957
958 static
959 void
960 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
961 {
962         auto notes = check_picture_size(width, height, frame_rate, three_d);
963         dump_notes (notes);
964         BOOST_CHECK_EQUAL (notes.size(), 0U);
965 }
966
967
968 static
969 void
970 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
971 {
972         auto notes = check_picture_size(width, height, frame_rate, three_d);
973         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
974         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
975         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS);
976 }
977
978
979 static
980 void
981 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
982 {
983         auto notes = check_picture_size(width, height, frame_rate, three_d);
984         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
985         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
986         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K);
987 }
988
989
990 static
991 void
992 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
993 {
994         auto notes = check_picture_size(width, height, frame_rate, three_d);
995         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
996         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
997         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K);
998 }
999
1000
1001 BOOST_AUTO_TEST_CASE (verify_picture_size)
1002 {
1003         using namespace boost::filesystem;
1004
1005         /* 2K scope */
1006         check_picture_size_ok (2048, 858, 24, false);
1007         check_picture_size_ok (2048, 858, 25, false);
1008         check_picture_size_ok (2048, 858, 48, false);
1009         check_picture_size_ok (2048, 858, 24, true);
1010         check_picture_size_ok (2048, 858, 25, true);
1011         check_picture_size_ok (2048, 858, 48, true);
1012
1013         /* 2K flat */
1014         check_picture_size_ok (1998, 1080, 24, false);
1015         check_picture_size_ok (1998, 1080, 25, false);
1016         check_picture_size_ok (1998, 1080, 48, false);
1017         check_picture_size_ok (1998, 1080, 24, true);
1018         check_picture_size_ok (1998, 1080, 25, true);
1019         check_picture_size_ok (1998, 1080, 48, true);
1020
1021         /* 4K scope */
1022         check_picture_size_ok (4096, 1716, 24, false);
1023
1024         /* 4K flat */
1025         check_picture_size_ok (3996, 2160, 24, false);
1026
1027         /* Bad frame size */
1028         check_picture_size_bad_frame_size (2050, 858, 24, false);
1029         check_picture_size_bad_frame_size (2048, 658, 25, false);
1030         check_picture_size_bad_frame_size (1920, 1080, 48, true);
1031         check_picture_size_bad_frame_size (4000, 3000, 24, true);
1032
1033         /* Bad 2K frame rate */
1034         check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1035         check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1036         check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1037
1038         /* Bad 4K frame rate */
1039         check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1040         check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1041
1042         /* No 4K 3D */
1043         auto notes = check_picture_size(3996, 2160, 24, true);
1044         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1045         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1046         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_4K_3D);
1047 }
1048
1049
1050 static
1051 void
1052 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1053 {
1054         asset->add (
1055                 make_shared<dcp::SubtitleString>(
1056                         optional<string>(),
1057                         false,
1058                         false,
1059                         false,
1060                         dcp::Colour(),
1061                         42,
1062                         1,
1063                         dcp::Time(start_frame, 24, 24),
1064                         dcp::Time(end_frame, 24, 24),
1065                         0,
1066                         dcp::HALIGN_CENTER,
1067                         v_position,
1068                         dcp::VALIGN_CENTER,
1069                         dcp::DIRECTION_LTR,
1070                         text,
1071                         dcp::NONE,
1072                         dcp::Colour(),
1073                         dcp::Time(),
1074                         dcp::Time()
1075                 )
1076         );
1077 }
1078
1079
1080 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1081 {
1082         boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
1083         prepare_directory (dir);
1084
1085         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1086         for (int i = 0; i < 2048; ++i) {
1087                 add_test_subtitle (asset, i * 24, i * 24 + 20);
1088         }
1089         asset->set_language (dcp::LanguageTag("de-DE"));
1090         asset->write (dir / "subs.mxf");
1091         auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1092         write_dcp_with_single_asset (dir, reel_asset);
1093
1094         check_verify_result (
1095                 { dir },
1096                 {
1097                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1098                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES },
1099                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1100                 });
1101 }
1102
1103
1104 static
1105 shared_ptr<dcp::SMPTESubtitleAsset>
1106 make_large_subtitle_asset (boost::filesystem::path font_file)
1107 {
1108         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1109         dcp::ArrayData big_fake_font(1024 * 1024);
1110         big_fake_font.write (font_file);
1111         for (int i = 0; i < 116; ++i) {
1112                 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1113         }
1114         return asset;
1115 }
1116
1117
1118 template <class T>
1119 void
1120 verify_timed_text_asset_too_large (string name)
1121 {
1122         auto const dir = boost::filesystem::path("build/test") / name;
1123         prepare_directory (dir);
1124         auto asset = make_large_subtitle_asset (dir / "font.ttf");
1125         add_test_subtitle (asset, 0, 20);
1126         asset->set_language (dcp::LanguageTag("de-DE"));
1127         asset->write (dir / "subs.mxf");
1128
1129         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1130         write_dcp_with_single_asset (dir, reel_asset);
1131
1132         check_verify_result (
1133                 { dir },
1134                 {
1135                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES },
1136                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES },
1137                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1138                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1139                 });
1140 }
1141
1142
1143 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1144 {
1145         verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1146         verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1147 }
1148
1149
1150 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1151 {
1152         boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1153         prepare_directory (dir);
1154         auto dcp = make_simple (dir, 1, 240);
1155
1156         string const xml =
1157                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1158                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1159                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1160                 "<ContentTitleText>Content</ContentTitleText>"
1161                 "<AnnotationText>Annotation</AnnotationText>"
1162                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1163                 "<ReelNumber>1</ReelNumber>"
1164                 "<EditRate>25 1</EditRate>"
1165                 "<TimeCodeRate>25</TimeCodeRate>"
1166                 "<StartTime>00:00:00:00</StartTime>"
1167                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1168                 "<SubtitleList>"
1169                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1170                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1171                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1172                 "</Subtitle>"
1173                 "</Font>"
1174                 "</SubtitleList>"
1175                 "</SubtitleReel>";
1176
1177         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1178         BOOST_REQUIRE (xml_file);
1179         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1180         fclose (xml_file);
1181         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1182         subs->write (dir / "subs.mxf");
1183
1184         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1185         dcp->cpls().front()->reels().front()->add(reel_subs);
1186         dcp->write_xml (dcp::SMPTE);
1187
1188         check_verify_result (
1189                 { dir },
1190                 {
1191                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE },
1192                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1193                 });
1194 }
1195
1196
1197 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1198 {
1199         boost::filesystem::path path ("build/test/verify_inconsistent_subtitle_languages");
1200         auto dcp = make_simple (path, 2, 240);
1201         auto cpl = dcp->cpls()[0];
1202
1203         {
1204                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1205                 subs->set_language (dcp::LanguageTag("de-DE"));
1206                 subs->add (simple_subtitle());
1207                 subs->write (path / "subs1.mxf");
1208                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1209                 cpl->reels()[0]->add(reel_subs);
1210         }
1211
1212         {
1213                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1214                 subs->set_language (dcp::LanguageTag("en-US"));
1215                 subs->add (simple_subtitle());
1216                 subs->write (path / "subs2.mxf");
1217                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1218                 cpl->reels()[1]->add(reel_subs);
1219         }
1220
1221         dcp->write_xml (dcp::SMPTE);
1222
1223         check_verify_result (
1224                 { path },
1225                 {
1226                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1227                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_LANGUAGES_DIFFER },
1228                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
1229                 });
1230 }
1231
1232
1233 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1234 {
1235         boost::filesystem::path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1236         prepare_directory (dir);
1237         auto dcp = make_simple (dir, 1, 240);
1238
1239         string const xml =
1240                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1241                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1242                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1243                 "<ContentTitleText>Content</ContentTitleText>"
1244                 "<AnnotationText>Annotation</AnnotationText>"
1245                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1246                 "<ReelNumber>1</ReelNumber>"
1247                 "<Language>de-DE</Language>"
1248                 "<EditRate>25 1</EditRate>"
1249                 "<TimeCodeRate>25</TimeCodeRate>"
1250                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1251                 "<SubtitleList>"
1252                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1253                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1254                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1255                 "</Subtitle>"
1256                 "</Font>"
1257                 "</SubtitleList>"
1258                 "</SubtitleReel>";
1259
1260         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1261         BOOST_REQUIRE (xml_file);
1262         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1263         fclose (xml_file);
1264         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1265         subs->write (dir / "subs.mxf");
1266
1267         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1268         dcp->cpls().front()->reels().front()->add(reel_subs);
1269         dcp->write_xml (dcp::SMPTE);
1270
1271         check_verify_result (
1272                 { dir },
1273                 {
1274                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1275                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1276                 });
1277 }
1278
1279
1280 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1281 {
1282         boost::filesystem::path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1283         prepare_directory (dir);
1284         auto dcp = make_simple (dir, 1, 240);
1285
1286         string const xml =
1287                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1288                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1289                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1290                 "<ContentTitleText>Content</ContentTitleText>"
1291                 "<AnnotationText>Annotation</AnnotationText>"
1292                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1293                 "<ReelNumber>1</ReelNumber>"
1294                 "<Language>de-DE</Language>"
1295                 "<EditRate>25 1</EditRate>"
1296                 "<TimeCodeRate>25</TimeCodeRate>"
1297                 "<StartTime>00:00:02:00</StartTime>"
1298                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1299                 "<SubtitleList>"
1300                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1301                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1302                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1303                 "</Subtitle>"
1304                 "</Font>"
1305                 "</SubtitleList>"
1306                 "</SubtitleReel>";
1307
1308         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1309         BOOST_REQUIRE (xml_file);
1310         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1311         fclose (xml_file);
1312         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1313         subs->write (dir / "subs.mxf");
1314
1315         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1316         dcp->cpls().front()->reels().front()->add(reel_subs);
1317         dcp->write_xml (dcp::SMPTE);
1318
1319         check_verify_result (
1320                 { dir },
1321                 {
1322                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO },
1323                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1324                 });
1325 }
1326
1327
1328 class TestText
1329 {
1330 public:
1331         TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1332                 : in(in_)
1333                 , out(out_)
1334                 , v_position(v_position_)
1335                 , text(text_)
1336         {}
1337
1338         int in;
1339         int out;
1340         float v_position;
1341         string text;
1342 };
1343
1344
1345 template <class T>
1346 void
1347 dcp_with_text (boost::filesystem::path dir, vector<TestText> subs)
1348 {
1349         prepare_directory (dir);
1350         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1351         asset->set_start_time (dcp::Time());
1352         for (auto i: subs) {
1353                 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1354         }
1355         asset->set_language (dcp::LanguageTag("de-DE"));
1356         asset->write (dir / "subs.mxf");
1357
1358         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1359         write_dcp_with_single_asset (dir, reel_asset);
1360 }
1361
1362
1363 BOOST_AUTO_TEST_CASE (verify_text_too_early)
1364 {
1365         auto const dir = boost::filesystem::path("build/test/verify_text_too_early");
1366         /* Just too early */
1367         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1368         check_verify_result (
1369                 { dir },
1370                 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }});
1371 }
1372
1373
1374 BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
1375 {
1376         auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early");
1377         /* Just late enough */
1378         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1379         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1380         BOOST_REQUIRE (notes.empty());
1381 }
1382
1383
1384 BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
1385 {
1386         auto const dir = boost::filesystem::path("build/test/verify_text_early_on_second_reel");
1387         prepare_directory (dir);
1388
1389         auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1390         asset1->set_start_time (dcp::Time());
1391         /* Just late enough */
1392         add_test_subtitle (asset1, 4 * 24, 5 * 24);
1393         asset1->set_language (dcp::LanguageTag("de-DE"));
1394         asset1->write (dir / "subs1.mxf");
1395         auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1396         auto reel1 = make_shared<dcp::Reel>();
1397         reel1->add (reel_asset1);
1398         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1399         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1400         reel1->add (markers1);
1401
1402         auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1403         asset2->set_start_time (dcp::Time());
1404         /* This would be too early on first reel but should be OK on the second */
1405         add_test_subtitle (asset2, 0, 4 * 24);
1406         asset2->set_language (dcp::LanguageTag("de-DE"));
1407         asset2->write (dir / "subs2.mxf");
1408         auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1409         auto reel2 = make_shared<dcp::Reel>();
1410         reel2->add (reel_asset2);
1411         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1412         markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1413         reel2->add (markers2);
1414
1415         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1416         cpl->add (reel1);
1417         cpl->add (reel2);
1418         auto dcp = make_shared<dcp::DCP>(dir);
1419         dcp->add (cpl);
1420         dcp->write_xml (dcp::SMPTE);
1421
1422         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1423         BOOST_REQUIRE (notes.empty());
1424 }
1425
1426
1427 BOOST_AUTO_TEST_CASE (verify_text_too_close)
1428 {
1429         auto const dir = boost::filesystem::path("build/test/verify_text_too_close");
1430         dcp_with_text<dcp::ReelSubtitleAsset> (
1431                 dir,
1432                 {
1433                         { 4 * 24,     5 * 24 },
1434                         { 5 * 24 + 1, 6 * 24 },
1435                 });
1436         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_CLOSE }});
1437 }
1438
1439
1440 BOOST_AUTO_TEST_CASE (verify_text_not_too_close)
1441 {
1442         auto const dir = boost::filesystem::path("build/test/verify_text_not_too_close");
1443         dcp_with_text<dcp::ReelSubtitleAsset> (
1444                 dir,
1445                 {
1446                         { 4 * 24,      5 * 24 },
1447                         { 5 * 24 + 16, 8 * 24 },
1448                 });
1449         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1450         BOOST_REQUIRE (notes.empty());
1451 }
1452
1453
1454 BOOST_AUTO_TEST_CASE (verify_text_too_short)
1455 {
1456         auto const dir = boost::filesystem::path("build/test/verify_text_too_short");
1457         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1458         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_SHORT }});
1459 }
1460
1461
1462 BOOST_AUTO_TEST_CASE (verify_text_not_too_short)
1463 {
1464         auto const dir = boost::filesystem::path("build/test/verify_text_not_too_short");
1465         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1466         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1467         BOOST_REQUIRE (notes.empty());
1468 }
1469
1470
1471 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1)
1472 {
1473         auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines1");
1474         dcp_with_text<dcp::ReelSubtitleAsset> (
1475                 dir,
1476                 {
1477                         { 96, 200, 0.0, "We" },
1478                         { 96, 200, 0.1, "have" },
1479                         { 96, 200, 0.2, "four" },
1480                         { 96, 200, 0.3, "lines" }
1481                 });
1482         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}});
1483 }
1484
1485
1486 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1)
1487 {
1488         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_subtitle_lines1");
1489         dcp_with_text<dcp::ReelSubtitleAsset> (
1490                 dir,
1491                 {
1492                         { 96, 200, 0.0, "We" },
1493                         { 96, 200, 0.1, "have" },
1494                         { 96, 200, 0.2, "four" },
1495                 });
1496         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1497         BOOST_REQUIRE (notes.empty());
1498 }
1499
1500
1501 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2)
1502 {
1503         auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines2");
1504         dcp_with_text<dcp::ReelSubtitleAsset> (
1505                 dir,
1506                 {
1507                         { 96, 300, 0.0, "We" },
1508                         { 96, 300, 0.1, "have" },
1509                         { 150, 180, 0.2, "four" },
1510                         { 150, 180, 0.3, "lines" }
1511                 });
1512         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}});
1513 }
1514
1515
1516 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2)
1517 {
1518         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_subtitle_lines2");
1519         dcp_with_text<dcp::ReelSubtitleAsset> (
1520                 dir,
1521                 {
1522                         { 96, 300, 0.0, "We" },
1523                         { 96, 300, 0.1, "have" },
1524                         { 150, 180, 0.2, "four" },
1525                         { 190, 250, 0.3, "lines" }
1526                 });
1527         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1528         BOOST_REQUIRE (notes.empty());
1529 }
1530
1531
1532 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1)
1533 {
1534         auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long1");
1535         dcp_with_text<dcp::ReelSubtitleAsset> (
1536                 dir,
1537                 {
1538                         { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1539                 });
1540         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_LONGER_THAN_RECOMMENDED }});
1541 }
1542
1543
1544 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2)
1545 {
1546         auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long2");
1547         dcp_with_text<dcp::ReelSubtitleAsset> (
1548                 dir,
1549                 {
1550                         { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1551                 });
1552         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_TOO_LONG }});
1553 }
1554
1555
1556 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1)
1557 {
1558         auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines1");
1559         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1560                 dir,
1561                 {
1562                         { 96, 200, 0.0, "We" },
1563                         { 96, 200, 0.1, "have" },
1564                         { 96, 200, 0.2, "four" },
1565                         { 96, 200, 0.3, "lines" }
1566                 });
1567         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}});
1568 }
1569
1570
1571 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1)
1572 {
1573         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_closed_caption_lines1");
1574         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1575                 dir,
1576                 {
1577                         { 96, 200, 0.0, "We" },
1578                         { 96, 200, 0.1, "have" },
1579                         { 96, 200, 0.2, "four" },
1580                 });
1581         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1582         BOOST_REQUIRE (notes.empty());
1583 }
1584
1585
1586 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2)
1587 {
1588         auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines2");
1589         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1590                 dir,
1591                 {
1592                         { 96, 300, 0.0, "We" },
1593                         { 96, 300, 0.1, "have" },
1594                         { 150, 180, 0.2, "four" },
1595                         { 150, 180, 0.3, "lines" }
1596                 });
1597         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}});
1598 }
1599
1600
1601 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2)
1602 {
1603         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_closed_caption_lines2");
1604         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1605                 dir,
1606                 {
1607                         { 96, 300, 0.0, "We" },
1608                         { 96, 300, 0.1, "have" },
1609                         { 150, 180, 0.2, "four" },
1610                         { 190, 250, 0.3, "lines" }
1611                 });
1612         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1613         BOOST_REQUIRE (notes.empty());
1614 }
1615
1616
1617 BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1)
1618 {
1619         auto const dir = boost::filesystem::path ("build/test/verify_closed_caption_lines_too_long1");
1620         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1621                 dir,
1622                 {
1623                         { 96, 300, 0.0, "0123456789012345678901234567890123" }
1624                 });
1625         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_LINE_TOO_LONG }});
1626 }
1627
1628
1629 BOOST_AUTO_TEST_CASE (verify_sound_sampling_rate_must_be_48k)
1630 {
1631         boost::filesystem::path const dir("build/test/verify_sound_sampling_rate_must_be_48k");
1632         prepare_directory (dir);
1633
1634         auto picture = simple_picture (dir, "foo");
1635         auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1636         auto reel = make_shared<dcp::Reel>();
1637         reel->add (reel_picture);
1638         auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1639         auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1640         reel->add (reel_sound);
1641         reel->add (simple_markers());
1642         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1643         cpl->add (reel);
1644         auto dcp = make_shared<dcp::DCP>(dir);
1645         dcp->add (cpl);
1646         dcp->write_xml (dcp::SMPTE);
1647
1648         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE }});
1649 }
1650
1651
1652 BOOST_AUTO_TEST_CASE (verify_cpl_must_have_annotation_text)
1653 {
1654         boost::filesystem::path const dir("build/test/verify_cpl_must_have_annotation_text");
1655         auto dcp = make_simple (dir);
1656         dcp->write_xml (dcp::SMPTE);
1657         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1658
1659         {
1660                 BOOST_REQUIRE (dcp->cpls()[0]->file());
1661                 Editor e(dcp->cpls()[0]->file().get());
1662                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1663         }
1664
1665         check_verify_result (
1666                 {dir},
1667                 {
1668                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_ANNOTATION_TEXT_IN_CPL },
1669                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }
1670                 });
1671 }
1672
1673
1674 BOOST_AUTO_TEST_CASE (verify_cpl_annotation_text_should_be_same_as_content_title_text)
1675 {
1676         boost::filesystem::path const dir("build/test/verify_cpl_annotation_text_should_be_same_as_content_title_text");
1677         auto dcp = make_simple (dir);
1678         dcp->write_xml (dcp::SMPTE);
1679         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1680
1681         {
1682                 BOOST_REQUIRE (dcp->cpls()[0]->file());
1683                 Editor e(dcp->cpls()[0]->file().get());
1684                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1685         }
1686
1687         check_verify_result (
1688                 {dir},
1689                 {
1690                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::CPL_ANNOTATION_TEXT_DIFFERS_FROM_CONTENT_TITLE_TEXT },
1691                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }
1692                 });
1693 }
1694
1695
1696 BOOST_AUTO_TEST_CASE (verify_reel_assets_durations_must_match)
1697 {
1698         boost::filesystem::path const dir("build/test/verify_reel_assets_durations_must_match");
1699         boost::filesystem::remove_all (dir);
1700         boost::filesystem::create_directories (dir);
1701         shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1702         shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::TRAILER));
1703
1704         shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1705         shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1706
1707         auto reel = make_shared<dcp::Reel>(
1708                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1709                 make_shared<dcp::ReelSoundAsset>(ms, 0)
1710                 );
1711
1712         reel->add (simple_markers());
1713         cpl->add (reel);
1714
1715         dcp->add (cpl);
1716         dcp->write_xml (dcp::SMPTE);
1717
1718         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION }});
1719 }
1720
1721
1722
1723 static
1724 void
1725 verify_subtitles_must_be_in_all_reels_check (boost::filesystem::path dir, bool add_to_reel1, bool add_to_reel2)
1726 {
1727         boost::filesystem::remove_all (dir);
1728         boost::filesystem::create_directories (dir);
1729         auto dcp = make_shared<dcp::DCP>(dir);
1730         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1731
1732         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1733         subs->set_language (dcp::LanguageTag("de-DE"));
1734         subs->set_start_time (dcp::Time());
1735         subs->add (simple_subtitle());
1736         subs->write (dir / "subs.mxf");
1737         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1738
1739         auto reel1 = make_shared<dcp::Reel>(
1740                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1741                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1742                 );
1743
1744         if (add_to_reel1) {
1745                 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1746         }
1747
1748         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1749         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1750         reel1->add (markers1);
1751
1752         cpl->add (reel1);
1753
1754         auto reel2 = make_shared<dcp::Reel>(
1755                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1756                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1757                 );
1758
1759         if (add_to_reel2) {
1760                 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1761         }
1762
1763         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1764         markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
1765         reel2->add (markers2);
1766
1767         cpl->add (reel2);
1768
1769         dcp->add (cpl);
1770         dcp->write_xml (dcp::SMPTE);
1771 }
1772
1773
1774 BOOST_AUTO_TEST_CASE (verify_subtitles_must_be_in_all_reels)
1775 {
1776         {
1777                 boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
1778                 verify_subtitles_must_be_in_all_reels_check (dir, true, false);
1779                 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MAIN_SUBTITLE_NOT_IN_ALL_REELS}});
1780         }
1781
1782         {
1783                 boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
1784                 verify_subtitles_must_be_in_all_reels_check (dir, true, true);
1785                 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1786                 BOOST_REQUIRE (notes.empty());
1787         }
1788
1789         {
1790                 boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
1791                 verify_subtitles_must_be_in_all_reels_check (dir, false, false);
1792                 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1793                 BOOST_REQUIRE (notes.empty());
1794         }
1795 }
1796
1797
1798 static
1799 void
1800 verify_closed_captions_must_be_in_all_reels_check (boost::filesystem::path dir, int caps_in_reel1, int caps_in_reel2)
1801 {
1802         boost::filesystem::remove_all (dir);
1803         boost::filesystem::create_directories (dir);
1804         auto dcp = make_shared<dcp::DCP>(dir);
1805         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1806
1807         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1808         subs->set_language (dcp::LanguageTag("de-DE"));
1809         subs->set_start_time (dcp::Time());
1810         subs->add (simple_subtitle());
1811         subs->write (dir / "subs.mxf");
1812
1813         auto reel1 = make_shared<dcp::Reel>(
1814                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1815                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1816                 );
1817
1818         for (int i = 0; i < caps_in_reel1; ++i) {
1819                 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1820         }
1821
1822         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1823         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1824         reel1->add (markers1);
1825
1826         cpl->add (reel1);
1827
1828         auto reel2 = make_shared<dcp::Reel>(
1829                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1830                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1831                 );
1832
1833         for (int i = 0; i < caps_in_reel2; ++i) {
1834                 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1835         }
1836
1837         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1838         markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
1839         reel2->add (markers2);
1840
1841         cpl->add (reel2);
1842
1843         dcp->add (cpl);
1844         dcp->write_xml (dcp::SMPTE);
1845
1846 }
1847
1848
1849 BOOST_AUTO_TEST_CASE (verify_closed_captions_must_be_in_all_reels)
1850 {
1851         {
1852                 boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels1");
1853                 verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
1854                 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_ASSET_COUNTS_DIFFER }});
1855         }
1856
1857         {
1858                 boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
1859                 verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
1860                 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1861                 BOOST_REQUIRE (notes.empty());
1862         }
1863
1864         {
1865                 boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
1866                 verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
1867                 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1868                 BOOST_REQUIRE (notes.empty());
1869         }
1870 }
1871
1872
1873 template <class T>
1874 void
1875 verify_text_entry_point_check (boost::filesystem::path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
1876 {
1877         boost::filesystem::remove_all (dir);
1878         boost::filesystem::create_directories (dir);
1879         auto dcp = make_shared<dcp::DCP>(dir);
1880         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1881
1882         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1883         subs->set_language (dcp::LanguageTag("de-DE"));
1884         subs->set_start_time (dcp::Time());
1885         subs->add (simple_subtitle());
1886         subs->write (dir / "subs.mxf");
1887         auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
1888         adjust (reel_text);
1889
1890         auto reel = make_shared<dcp::Reel>(
1891                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1892                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1893                 );
1894
1895         reel->add (reel_text);
1896
1897         reel->add (simple_markers(240));
1898
1899         cpl->add (reel);
1900
1901         dcp->add (cpl);
1902         dcp->write_xml (dcp::SMPTE);
1903
1904         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, code }});
1905 }
1906
1907
1908 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
1909 {
1910         verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
1911                 "build/test/verify_subtitle_entry_point_must_be_present",
1912                 dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT,
1913                 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
1914                         asset->unset_entry_point ();
1915                         }
1916                 );
1917
1918         verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
1919                 "build/test/verify_subtitle_entry_point_must_be_zero",
1920                 dcp::VerificationNote::SUBTITLE_ENTRY_POINT_NON_ZERO,
1921                 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
1922                         asset->set_entry_point (4);
1923                         }
1924                 );
1925
1926         verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
1927                 "build/test/verify_closed_caption_entry_point_must_be_present",
1928                 dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT,
1929                 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
1930                         asset->unset_entry_point ();
1931                         }
1932                 );
1933
1934         verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
1935                 "build/test/verify_closed_caption_entry_point_must_be_zero",
1936                 dcp::VerificationNote::CLOSED_CAPTION_ENTRY_POINT_NON_ZERO,
1937                 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
1938                         asset->set_entry_point (9);
1939                         }
1940                 );
1941 }
1942
1943
1944 BOOST_AUTO_TEST_CASE (verify_assets_must_have_hashes)
1945 {
1946         RNGFixer fix;
1947
1948         boost::filesystem::path const dir("build/test/verify_assets_must_have_hashes");
1949         auto dcp = make_simple (dir);
1950         dcp->write_xml (dcp::SMPTE);
1951         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1952
1953         {
1954                 BOOST_REQUIRE (dcp->cpls()[0]->file());
1955                 Editor e(dcp->cpls()[0]->file().get());
1956                 e.replace("<Hash>cb1OLhgHG9svy7G8hoTSPpltzhw=</Hash>", "");
1957         }
1958
1959         check_verify_result (
1960                 {dir},
1961                 {
1962                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT },
1963                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_HASH }
1964                 });
1965 }
1966
1967
1968 static
1969 void
1970 verify_markers_test (
1971         boost::filesystem::path dir,
1972         vector<pair<dcp::Marker, dcp::Time>> markers,
1973         vector<std::pair<dcp::VerificationNote::Type, dcp::VerificationNote::Code>> types_and_codes
1974         )
1975 {
1976         auto dcp = make_simple (dir);
1977         dcp->cpls()[0]->set_content_kind (dcp::FEATURE);
1978         auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
1979         for (auto const& i: markers) {
1980                 markers_asset->set (i.first, i.second);
1981         }
1982         dcp->cpls()[0]->reels()[0]->add(markers_asset);
1983         dcp->write_xml (dcp::SMPTE);
1984         check_verify_result ({dir}, types_and_codes);
1985 }
1986
1987
1988 BOOST_AUTO_TEST_CASE (verify_markers)
1989 {
1990         verify_markers_test (
1991                 "build/test/verify_markers_all_correct",
1992                 {
1993                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
1994                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
1995                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
1996                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
1997                 },
1998                 {}
1999                 );
2000
2001         verify_markers_test (
2002                 "build/test/verify_markers_missing_ffec",
2003                 {
2004                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2005                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2006                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2007                 },
2008                 {
2009                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE }
2010                 });
2011
2012         verify_markers_test (
2013                 "build/test/verify_markers_missing_ffmc",
2014                 {
2015                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2016                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2017                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2018                 },
2019                 {
2020                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE }
2021                 });
2022
2023         verify_markers_test (
2024                 "build/test/verify_markers_missing_ffoc",
2025                 {
2026                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2027                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2028                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2029                 },
2030                 {
2031                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC}
2032                 });
2033
2034         verify_markers_test (
2035                 "build/test/verify_markers_missing_lfoc",
2036                 {
2037                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2038                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2039                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2040                 },
2041                 {
2042                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC }
2043                 });
2044
2045         verify_markers_test (
2046                 "build/test/verify_markers_incorrect_ffoc",
2047                 {
2048                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2049                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2050                         { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2051                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2052                 },
2053                 {
2054                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_FFOC }
2055                 });
2056
2057         verify_markers_test (
2058                 "build/test/verify_markers_incorrect_lfoc",
2059                 {
2060                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2061                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2062                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2063                         { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2064                 },
2065                 {
2066                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_LFOC }
2067                 });
2068 }
2069