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