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