Cleanup: tidy enum names for verification codes.
[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::INCORRECT_PICTURE_HASH },
303                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INCORRECT_SOUND_HASH }
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::MISMATCHED_CPL_HASHES },
321                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_PICTURE_HASHES },
322                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_SOUND_HASHES },
323                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
324                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
325                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML }
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::FAILED_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::MISMATCHED_CPL_HASHES,
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::INVALID_XML,
412                           dcp::VerificationNote::INVALID_XML,
413                           dcp::VerificationNote::INVALID_XML,
414                           dcp::VerificationNote::INVALID_XML,
415                           dcp::VerificationNote::INVALID_XML,
416                           dcp::VerificationNote::MISMATCHED_CPL_HASHES }
417                         );
418 }
419
420 /* Badly formatted <Id> in CPL */
421 BOOST_AUTO_TEST_CASE (verify_test9)
422 {
423         /* There's no MISMATCHED_CPL_HASHES 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::INVALID_XML }
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::INVALID_XML,
438                           dcp::VerificationNote::MISMATCHED_CPL_HASHES }
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::INVALID_XML }
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::INVALID_XML }
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::INVALID_STANDARD);
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::INVALID_STANDARD },
525                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION },
526                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION },
527                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION },
528                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION }
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         prepare_directory (dir);
567         dcp_from_frame (oversized_frame, dir);
568
569         check_verify_result (
570                 { dir },
571                 {
572                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_PICTURE_FRAME_SIZE_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         prepare_directory (dir);
595         dcp_from_frame (oversized_frame, dir);
596
597         check_verify_result (
598                 { dir },
599                 {
600                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_PICTURE_FRAME_SIZE_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         prepare_directory (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::INVALID_STANDARD }});
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::INVALID_STANDARD },
655                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
656                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML }
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::INVALID_XML },
689                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
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::INVALID_XML },
810                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
811                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
812                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES }
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::FAILED_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::INVALID_LANGUAGE, string("badlang") },
871                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_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::INVALID_LANGUAGE, string("badlang") },
894                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_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::INVALID_LANGUAGE, string("this-is-wrong") },
941                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("andso-is-this") },
942                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("fred-jim") },
943                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_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         prepare_directory (dcp_path);
956
957         shared_ptr<dcp::PictureAsset> mp;
958         if (three_d) {
959                 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
960         } else {
961                 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
962         }
963         auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
964
965         auto image = black_image (dcp::Size(width, height));
966         auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
967         int const length = three_d ? frame_rate * 2 : frame_rate;
968         for (int i = 0; i < length; ++i) {
969                 picture_writer->write (j2c.data(), j2c.size());
970         }
971         picture_writer->finalize ();
972
973         auto d = make_shared<dcp::DCP>(dcp_path);
974         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
975         cpl->set_annotation_text ("A Test DCP");
976         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
977         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
978         cpl->set_main_sound_sample_rate (48000);
979         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
980         cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
981         cpl->set_version_number (1);
982
983         auto reel = make_shared<dcp::Reel>();
984
985         if (three_d) {
986                 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
987         } else {
988                 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
989         }
990
991         reel->add (simple_markers(frame_rate));
992
993         cpl->add (reel);
994
995         d->add (cpl);
996         d->write_xml (
997                 dcp::SMPTE,
998                 dcp::String::compose("libdcp %1", dcp::version),
999                 dcp::String::compose("libdcp %1", dcp::version),
1000                 dcp::LocalTime().as_string(),
1001                 "A Test DCP"
1002                 );
1003
1004         return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1005 }
1006
1007
1008 static
1009 void
1010 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1011 {
1012         auto notes = check_picture_size(width, height, frame_rate, three_d);
1013         dump_notes (notes);
1014         BOOST_CHECK_EQUAL (notes.size(), 0U);
1015 }
1016
1017
1018 static
1019 void
1020 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1021 {
1022         auto notes = check_picture_size(width, height, frame_rate, three_d);
1023         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1024         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1025         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_SIZE_IN_PIXELS);
1026 }
1027
1028
1029 static
1030 void
1031 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1032 {
1033         auto notes = check_picture_size(width, height, frame_rate, three_d);
1034         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1035         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1036         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1037 }
1038
1039
1040 static
1041 void
1042 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1043 {
1044         auto notes = check_picture_size(width, height, frame_rate, three_d);
1045         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1046         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1047         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1048 }
1049
1050
1051 BOOST_AUTO_TEST_CASE (verify_picture_size)
1052 {
1053         using namespace boost::filesystem;
1054
1055         /* 2K scope */
1056         check_picture_size_ok (2048, 858, 24, false);
1057         check_picture_size_ok (2048, 858, 25, false);
1058         check_picture_size_ok (2048, 858, 48, false);
1059         check_picture_size_ok (2048, 858, 24, true);
1060         check_picture_size_ok (2048, 858, 25, true);
1061         check_picture_size_ok (2048, 858, 48, true);
1062
1063         /* 2K flat */
1064         check_picture_size_ok (1998, 1080, 24, false);
1065         check_picture_size_ok (1998, 1080, 25, false);
1066         check_picture_size_ok (1998, 1080, 48, false);
1067         check_picture_size_ok (1998, 1080, 24, true);
1068         check_picture_size_ok (1998, 1080, 25, true);
1069         check_picture_size_ok (1998, 1080, 48, true);
1070
1071         /* 4K scope */
1072         check_picture_size_ok (4096, 1716, 24, false);
1073
1074         /* 4K flat */
1075         check_picture_size_ok (3996, 2160, 24, false);
1076
1077         /* Bad frame size */
1078         check_picture_size_bad_frame_size (2050, 858, 24, false);
1079         check_picture_size_bad_frame_size (2048, 658, 25, false);
1080         check_picture_size_bad_frame_size (1920, 1080, 48, true);
1081         check_picture_size_bad_frame_size (4000, 3000, 24, true);
1082
1083         /* Bad 2K frame rate */
1084         check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1085         check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1086         check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1087
1088         /* Bad 4K frame rate */
1089         check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1090         check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1091
1092         /* No 4K 3D */
1093         auto notes = check_picture_size(3996, 2160, 24, true);
1094         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1095         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1096         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1097 }
1098
1099
1100 static
1101 void
1102 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1103 {
1104         asset->add (
1105                 make_shared<dcp::SubtitleString>(
1106                         optional<string>(),
1107                         false,
1108                         false,
1109                         false,
1110                         dcp::Colour(),
1111                         42,
1112                         1,
1113                         dcp::Time(start_frame, 24, 24),
1114                         dcp::Time(end_frame, 24, 24),
1115                         0,
1116                         dcp::HALIGN_CENTER,
1117                         v_position,
1118                         dcp::VALIGN_CENTER,
1119                         dcp::DIRECTION_LTR,
1120                         text,
1121                         dcp::NONE,
1122                         dcp::Colour(),
1123                         dcp::Time(),
1124                         dcp::Time()
1125                 )
1126         );
1127 }
1128
1129
1130 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1131 {
1132         boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
1133         prepare_directory (dir);
1134
1135         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1136         for (int i = 0; i < 2048; ++i) {
1137                 add_test_subtitle (asset, i * 24, i * 24 + 20);
1138         }
1139         asset->set_language (dcp::LanguageTag("de-DE"));
1140         asset->write (dir / "subs.mxf");
1141         auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1142         write_dcp_with_single_asset (dir, reel_asset);
1143
1144         check_verify_result (
1145                 { dir },
1146                 {
1147                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1148                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES },
1149                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1150                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA },
1151                 });
1152 }
1153
1154
1155 static
1156 shared_ptr<dcp::SMPTESubtitleAsset>
1157 make_large_subtitle_asset (boost::filesystem::path font_file)
1158 {
1159         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1160         dcp::ArrayData big_fake_font(1024 * 1024);
1161         big_fake_font.write (font_file);
1162         for (int i = 0; i < 116; ++i) {
1163                 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1164         }
1165         return asset;
1166 }
1167
1168
1169 template <class T>
1170 void
1171 verify_timed_text_asset_too_large (string name)
1172 {
1173         auto const dir = boost::filesystem::path("build/test") / name;
1174         prepare_directory (dir);
1175         auto asset = make_large_subtitle_asset (dir / "font.ttf");
1176         add_test_subtitle (asset, 0, 20);
1177         asset->set_language (dcp::LanguageTag("de-DE"));
1178         asset->write (dir / "subs.mxf");
1179
1180         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1181         write_dcp_with_single_asset (dir, reel_asset);
1182
1183         check_verify_result (
1184                 { dir },
1185                 {
1186                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_SIZE_IN_BYTES },
1187                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES },
1188                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1189                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1190                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA },
1191                 });
1192 }
1193
1194
1195 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1196 {
1197         verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1198         verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1199 }
1200
1201
1202 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1203 {
1204         boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1205         prepare_directory (dir);
1206         auto dcp = make_simple (dir, 1, 240);
1207
1208         string const xml =
1209                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1210                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1211                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1212                 "<ContentTitleText>Content</ContentTitleText>"
1213                 "<AnnotationText>Annotation</AnnotationText>"
1214                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1215                 "<ReelNumber>1</ReelNumber>"
1216                 "<EditRate>25 1</EditRate>"
1217                 "<TimeCodeRate>25</TimeCodeRate>"
1218                 "<StartTime>00:00:00:00</StartTime>"
1219                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1220                 "<SubtitleList>"
1221                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1222                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1223                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1224                 "</Subtitle>"
1225                 "</Font>"
1226                 "</SubtitleList>"
1227                 "</SubtitleReel>";
1228
1229         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1230         BOOST_REQUIRE (xml_file);
1231         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1232         fclose (xml_file);
1233         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1234         subs->write (dir / "subs.mxf");
1235
1236         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1237         dcp->cpls().front()->reels().front()->add(reel_subs);
1238         dcp->write_xml (
1239                 dcp::SMPTE,
1240                 dcp::String::compose("libdcp %1", dcp::version),
1241                 dcp::String::compose("libdcp %1", dcp::version),
1242                 dcp::LocalTime().as_string(),
1243                 "A Test DCP"
1244                 );
1245
1246         check_verify_result (
1247                 { dir },
1248                 {
1249                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE },
1250                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1251                 });
1252 }
1253
1254
1255 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1256 {
1257         boost::filesystem::path path ("build/test/verify_inconsistent_subtitle_languages");
1258         auto dcp = make_simple (path, 2, 240);
1259         auto cpl = dcp->cpls()[0];
1260
1261         {
1262                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1263                 subs->set_language (dcp::LanguageTag("de-DE"));
1264                 subs->add (simple_subtitle());
1265                 subs->write (path / "subs1.mxf");
1266                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1267                 cpl->reels()[0]->add(reel_subs);
1268         }
1269
1270         {
1271                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1272                 subs->set_language (dcp::LanguageTag("en-US"));
1273                 subs->add (simple_subtitle());
1274                 subs->write (path / "subs2.mxf");
1275                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1276                 cpl->reels()[1]->add(reel_subs);
1277         }
1278
1279         dcp->write_xml (
1280                 dcp::SMPTE,
1281                 dcp::String::compose("libdcp %1", dcp::version),
1282                 dcp::String::compose("libdcp %1", dcp::version),
1283                 dcp::LocalTime().as_string(),
1284                 "A Test DCP"
1285                 );
1286
1287         check_verify_result (
1288                 { path },
1289                 {
1290                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1291                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_SUBTITLE_LANGUAGES },
1292                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
1293                 });
1294 }
1295
1296
1297 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1298 {
1299         boost::filesystem::path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1300         prepare_directory (dir);
1301         auto dcp = make_simple (dir, 1, 240);
1302
1303         string const xml =
1304                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1305                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1306                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1307                 "<ContentTitleText>Content</ContentTitleText>"
1308                 "<AnnotationText>Annotation</AnnotationText>"
1309                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1310                 "<ReelNumber>1</ReelNumber>"
1311                 "<Language>de-DE</Language>"
1312                 "<EditRate>25 1</EditRate>"
1313                 "<TimeCodeRate>25</TimeCodeRate>"
1314                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1315                 "<SubtitleList>"
1316                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1317                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1318                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1319                 "</Subtitle>"
1320                 "</Font>"
1321                 "</SubtitleList>"
1322                 "</SubtitleReel>";
1323
1324         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1325         BOOST_REQUIRE (xml_file);
1326         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1327         fclose (xml_file);
1328         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1329         subs->write (dir / "subs.mxf");
1330
1331         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1332         dcp->cpls().front()->reels().front()->add(reel_subs);
1333         dcp->write_xml (
1334                 dcp::SMPTE,
1335                 dcp::String::compose("libdcp %1", dcp::version),
1336                 dcp::String::compose("libdcp %1", dcp::version),
1337                 dcp::LocalTime().as_string(),
1338                 "A Test DCP"
1339                 );
1340
1341         check_verify_result (
1342                 { dir },
1343                 {
1344                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1345                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1346                 });
1347 }
1348
1349
1350 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1351 {
1352         boost::filesystem::path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1353         prepare_directory (dir);
1354         auto dcp = make_simple (dir, 1, 240);
1355
1356         string const xml =
1357                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1358                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1359                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1360                 "<ContentTitleText>Content</ContentTitleText>"
1361                 "<AnnotationText>Annotation</AnnotationText>"
1362                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1363                 "<ReelNumber>1</ReelNumber>"
1364                 "<Language>de-DE</Language>"
1365                 "<EditRate>25 1</EditRate>"
1366                 "<TimeCodeRate>25</TimeCodeRate>"
1367                 "<StartTime>00:00:02:00</StartTime>"
1368                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1369                 "<SubtitleList>"
1370                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1371                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1372                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1373                 "</Subtitle>"
1374                 "</Font>"
1375                 "</SubtitleList>"
1376                 "</SubtitleReel>";
1377
1378         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1379         BOOST_REQUIRE (xml_file);
1380         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1381         fclose (xml_file);
1382         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1383         subs->write (dir / "subs.mxf");
1384
1385         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1386         dcp->cpls().front()->reels().front()->add(reel_subs);
1387         dcp->write_xml (
1388                 dcp::SMPTE,
1389                 dcp::String::compose("libdcp %1", dcp::version),
1390                 dcp::String::compose("libdcp %1", dcp::version),
1391                 dcp::LocalTime().as_string(),
1392                 "A Test DCP"
1393                 );
1394
1395         check_verify_result (
1396                 { dir },
1397                 {
1398                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SUBTITLE_START_TIME },
1399                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1400                 });
1401 }
1402
1403
1404 class TestText
1405 {
1406 public:
1407         TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1408                 : in(in_)
1409                 , out(out_)
1410                 , v_position(v_position_)
1411                 , text(text_)
1412         {}
1413
1414         int in;
1415         int out;
1416         float v_position;
1417         string text;
1418 };
1419
1420
1421 template <class T>
1422 void
1423 dcp_with_text (boost::filesystem::path dir, vector<TestText> subs)
1424 {
1425         prepare_directory (dir);
1426         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1427         asset->set_start_time (dcp::Time());
1428         for (auto i: subs) {
1429                 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1430         }
1431         asset->set_language (dcp::LanguageTag("de-DE"));
1432         asset->write (dir / "subs.mxf");
1433
1434         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1435         write_dcp_with_single_asset (dir, reel_asset);
1436 }
1437
1438
1439 BOOST_AUTO_TEST_CASE (verify_text_too_early)
1440 {
1441         auto const dir = boost::filesystem::path("build/test/verify_text_too_early");
1442         /* Just too early */
1443         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1444         check_verify_result (
1445                 { dir },
1446                 {
1447                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1448                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1449                 });
1450
1451 }
1452
1453
1454 BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
1455 {
1456         auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early");
1457         /* Just late enough */
1458         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1459         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1460 }
1461
1462
1463 BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
1464 {
1465         auto const dir = boost::filesystem::path("build/test/verify_text_early_on_second_reel");
1466         prepare_directory (dir);
1467
1468         auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1469         asset1->set_start_time (dcp::Time());
1470         /* Just late enough */
1471         add_test_subtitle (asset1, 4 * 24, 5 * 24);
1472         asset1->set_language (dcp::LanguageTag("de-DE"));
1473         asset1->write (dir / "subs1.mxf");
1474         auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1475         auto reel1 = make_shared<dcp::Reel>();
1476         reel1->add (reel_asset1);
1477         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1478         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1479         reel1->add (markers1);
1480
1481         auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1482         asset2->set_start_time (dcp::Time());
1483         /* This would be too early on first reel but should be OK on the second */
1484         add_test_subtitle (asset2, 0, 4 * 24);
1485         asset2->set_language (dcp::LanguageTag("de-DE"));
1486         asset2->write (dir / "subs2.mxf");
1487         auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1488         auto reel2 = make_shared<dcp::Reel>();
1489         reel2->add (reel_asset2);
1490         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1491         markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1492         reel2->add (markers2);
1493
1494         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1495         cpl->add (reel1);
1496         cpl->add (reel2);
1497         auto dcp = make_shared<dcp::DCP>(dir);
1498         dcp->add (cpl);
1499         dcp->write_xml (
1500                 dcp::SMPTE,
1501                 dcp::String::compose("libdcp %1", dcp::version),
1502                 dcp::String::compose("libdcp %1", dcp::version),
1503                 dcp::LocalTime().as_string(),
1504                 "hello"
1505                 );
1506
1507
1508         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1509 }
1510
1511
1512 BOOST_AUTO_TEST_CASE (verify_text_too_close)
1513 {
1514         auto const dir = boost::filesystem::path("build/test/verify_text_too_close");
1515         dcp_with_text<dcp::ReelSubtitleAsset> (
1516                 dir,
1517                 {
1518                         { 4 * 24,     5 * 24 },
1519                         { 5 * 24 + 1, 6 * 24 },
1520                 });
1521         check_verify_result (
1522                 {dir},
1523                 {
1524                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_SPACING },
1525                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1526                 });
1527 }
1528
1529
1530 BOOST_AUTO_TEST_CASE (verify_text_not_too_close)
1531 {
1532         auto const dir = boost::filesystem::path("build/test/verify_text_not_too_close");
1533         dcp_with_text<dcp::ReelSubtitleAsset> (
1534                 dir,
1535                 {
1536                         { 4 * 24,      5 * 24 },
1537                         { 5 * 24 + 16, 8 * 24 },
1538                 });
1539         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1540 }
1541
1542
1543 BOOST_AUTO_TEST_CASE (verify_text_too_short)
1544 {
1545         auto const dir = boost::filesystem::path("build/test/verify_text_too_short");
1546         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1547         check_verify_result (
1548                 {dir},
1549                 {
1550                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_DURATION },
1551                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1552                 });
1553 }
1554
1555
1556 BOOST_AUTO_TEST_CASE (verify_text_not_too_short)
1557 {
1558         auto const dir = boost::filesystem::path("build/test/verify_text_not_too_short");
1559         dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1560         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1561 }
1562
1563
1564 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1)
1565 {
1566         auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines1");
1567         dcp_with_text<dcp::ReelSubtitleAsset> (
1568                 dir,
1569                 {
1570                         { 96, 200, 0.0, "We" },
1571                         { 96, 200, 0.1, "have" },
1572                         { 96, 200, 0.2, "four" },
1573                         { 96, 200, 0.3, "lines" }
1574                 });
1575         check_verify_result (
1576                 {dir},
1577                 {
1578                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1579                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1580                 });
1581 }
1582
1583
1584 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1)
1585 {
1586         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_subtitle_lines1");
1587         dcp_with_text<dcp::ReelSubtitleAsset> (
1588                 dir,
1589                 {
1590                         { 96, 200, 0.0, "We" },
1591                         { 96, 200, 0.1, "have" },
1592                         { 96, 200, 0.2, "four" },
1593                 });
1594         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1595 }
1596
1597
1598 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2)
1599 {
1600         auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines2");
1601         dcp_with_text<dcp::ReelSubtitleAsset> (
1602                 dir,
1603                 {
1604                         { 96, 300, 0.0, "We" },
1605                         { 96, 300, 0.1, "have" },
1606                         { 150, 180, 0.2, "four" },
1607                         { 150, 180, 0.3, "lines" }
1608                 });
1609         check_verify_result (
1610                 {dir},
1611                 {
1612                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1613                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1614                 });
1615 }
1616
1617
1618 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2)
1619 {
1620         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_subtitle_lines2");
1621         dcp_with_text<dcp::ReelSubtitleAsset> (
1622                 dir,
1623                 {
1624                         { 96, 300, 0.0, "We" },
1625                         { 96, 300, 0.1, "have" },
1626                         { 150, 180, 0.2, "four" },
1627                         { 190, 250, 0.3, "lines" }
1628                 });
1629         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1630 }
1631
1632
1633 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1)
1634 {
1635         auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long1");
1636         dcp_with_text<dcp::ReelSubtitleAsset> (
1637                 dir,
1638                 {
1639                         { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1640                 });
1641         check_verify_result (
1642                 {dir},
1643                 {
1644                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1645                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1646                 });
1647 }
1648
1649
1650 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2)
1651 {
1652         auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long2");
1653         dcp_with_text<dcp::ReelSubtitleAsset> (
1654                 dir,
1655                 {
1656                         { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1657                 });
1658         check_verify_result (
1659                 {dir},
1660                 {
1661                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_LENGTH },
1662                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1663                 });
1664 }
1665
1666
1667 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1)
1668 {
1669         auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines1");
1670         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1671                 dir,
1672                 {
1673                         { 96, 200, 0.0, "We" },
1674                         { 96, 200, 0.1, "have" },
1675                         { 96, 200, 0.2, "four" },
1676                         { 96, 200, 0.3, "lines" }
1677                 });
1678         check_verify_result (
1679                 {dir},
1680                 {
1681                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1682                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1683                 });
1684 }
1685
1686
1687 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1)
1688 {
1689         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_closed_caption_lines1");
1690         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1691                 dir,
1692                 {
1693                         { 96, 200, 0.0, "We" },
1694                         { 96, 200, 0.1, "have" },
1695                         { 96, 200, 0.2, "four" },
1696                 });
1697         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1698 }
1699
1700
1701 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2)
1702 {
1703         auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines2");
1704         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1705                 dir,
1706                 {
1707                         { 96, 300, 0.0, "We" },
1708                         { 96, 300, 0.1, "have" },
1709                         { 150, 180, 0.2, "four" },
1710                         { 150, 180, 0.3, "lines" }
1711                 });
1712         check_verify_result (
1713                 {dir},
1714                 {
1715                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1716                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1717                 });
1718 }
1719
1720
1721 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2)
1722 {
1723         auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_closed_caption_lines2");
1724         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1725                 dir,
1726                 {
1727                         { 96, 300, 0.0, "We" },
1728                         { 96, 300, 0.1, "have" },
1729                         { 150, 180, 0.2, "four" },
1730                         { 190, 250, 0.3, "lines" }
1731                 });
1732         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1733 }
1734
1735
1736 BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1)
1737 {
1738         auto const dir = boost::filesystem::path ("build/test/verify_closed_caption_lines_too_long1");
1739         dcp_with_text<dcp::ReelClosedCaptionAsset> (
1740                 dir,
1741                 {
1742                         { 96, 300, 0.0, "0123456789012345678901234567890123" }
1743                 });
1744         check_verify_result (
1745                 {dir},
1746                 {
1747                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1748                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1749                 });
1750 }
1751
1752
1753 BOOST_AUTO_TEST_CASE (verify_sound_sampling_rate_must_be_48k)
1754 {
1755         boost::filesystem::path const dir("build/test/verify_sound_sampling_rate_must_be_48k");
1756         prepare_directory (dir);
1757
1758         auto picture = simple_picture (dir, "foo");
1759         auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1760         auto reel = make_shared<dcp::Reel>();
1761         reel->add (reel_picture);
1762         auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1763         auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1764         reel->add (reel_sound);
1765         reel->add (simple_markers());
1766         auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1767         cpl->add (reel);
1768         auto dcp = make_shared<dcp::DCP>(dir);
1769         dcp->add (cpl);
1770         dcp->write_xml (
1771                 dcp::SMPTE,
1772                 dcp::String::compose("libdcp %1", dcp::version),
1773                 dcp::String::compose("libdcp %1", dcp::version),
1774                 dcp::LocalTime().as_string(),
1775                 "hello"
1776                 );
1777
1778         check_verify_result (
1779                 {dir},
1780                 {
1781                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE },
1782                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1783                 });
1784 }
1785
1786
1787 BOOST_AUTO_TEST_CASE (verify_cpl_must_have_annotation_text)
1788 {
1789         boost::filesystem::path const dir("build/test/verify_cpl_must_have_annotation_text");
1790         auto dcp = make_simple (dir);
1791         dcp->write_xml (
1792                 dcp::SMPTE,
1793                 dcp::String::compose("libdcp %1", dcp::version),
1794                 dcp::String::compose("libdcp %1", dcp::version),
1795                 dcp::LocalTime().as_string(),
1796                 "A Test DCP"
1797                 );
1798
1799         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1800
1801         {
1802                 BOOST_REQUIRE (dcp->cpls()[0]->file());
1803                 Editor e(dcp->cpls()[0]->file().get());
1804                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1805         }
1806
1807         check_verify_result (
1808                 {dir},
1809                 {
1810                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_ANNOTATION_TEXT },
1811                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES }
1812                 });
1813 }
1814
1815
1816 BOOST_AUTO_TEST_CASE (verify_cpl_annotation_text_should_be_same_as_content_title_text)
1817 {
1818         boost::filesystem::path const dir("build/test/verify_cpl_annotation_text_should_be_same_as_content_title_text");
1819         auto dcp = make_simple (dir);
1820         dcp->write_xml (
1821                 dcp::SMPTE,
1822                 dcp::String::compose("libdcp %1", dcp::version),
1823                 dcp::String::compose("libdcp %1", dcp::version),
1824                 dcp::LocalTime().as_string(),
1825                 "A Test DCP"
1826                 );
1827
1828         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1829
1830         {
1831                 BOOST_REQUIRE (dcp->cpls()[0]->file());
1832                 Editor e(dcp->cpls()[0]->file().get());
1833                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1834         }
1835
1836         check_verify_result (
1837                 {dir},
1838                 {
1839                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISMATCHED_CPL_ANNOTATION_TEXT },
1840                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES }
1841                 });
1842 }
1843
1844
1845 BOOST_AUTO_TEST_CASE (verify_reel_assets_durations_must_match)
1846 {
1847         boost::filesystem::path const dir("build/test/verify_reel_assets_durations_must_match");
1848         prepare_directory (dir);
1849         shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1850         shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::TRAILER));
1851
1852         shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1853         shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1854
1855         auto reel = make_shared<dcp::Reel>(
1856                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1857                 make_shared<dcp::ReelSoundAsset>(ms, 0)
1858                 );
1859
1860         reel->add (simple_markers());
1861         cpl->add (reel);
1862
1863         dcp->add (cpl);
1864         dcp->write_xml (
1865                 dcp::SMPTE,
1866                 dcp::String::compose("libdcp %1", dcp::version),
1867                 dcp::String::compose("libdcp %1", dcp::version),
1868                 dcp::LocalTime().as_string(),
1869                 "A Test DCP"
1870                 );
1871
1872         check_verify_result (
1873                 {dir},
1874                 {
1875                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION },
1876                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1877                 });
1878 }
1879
1880
1881
1882 static
1883 void
1884 verify_subtitles_must_be_in_all_reels_check (boost::filesystem::path dir, bool add_to_reel1, bool add_to_reel2)
1885 {
1886         prepare_directory (dir);
1887         auto dcp = make_shared<dcp::DCP>(dir);
1888         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1889
1890         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1891         subs->set_language (dcp::LanguageTag("de-DE"));
1892         subs->set_start_time (dcp::Time());
1893         subs->add (simple_subtitle());
1894         subs->write (dir / "subs.mxf");
1895         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1896
1897         auto reel1 = make_shared<dcp::Reel>(
1898                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1899                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1900                 );
1901
1902         if (add_to_reel1) {
1903                 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1904         }
1905
1906         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1907         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1908         reel1->add (markers1);
1909
1910         cpl->add (reel1);
1911
1912         auto reel2 = make_shared<dcp::Reel>(
1913                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1914                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1915                 );
1916
1917         if (add_to_reel2) {
1918                 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1919         }
1920
1921         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1922         markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
1923         reel2->add (markers2);
1924
1925         cpl->add (reel2);
1926
1927         dcp->add (cpl);
1928         dcp->write_xml (
1929                 dcp::SMPTE,
1930                 dcp::String::compose("libdcp %1", dcp::version),
1931                 dcp::String::compose("libdcp %1", dcp::version),
1932                 dcp::LocalTime().as_string(),
1933                 "A Test DCP"
1934                 );
1935 }
1936
1937
1938 BOOST_AUTO_TEST_CASE (verify_subtitles_must_be_in_all_reels)
1939 {
1940         {
1941                 boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
1942                 verify_subtitles_must_be_in_all_reels_check (dir, true, false);
1943                 check_verify_result (
1944                         { dir },
1945                         {
1946                                 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
1947                                 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1948                         });
1949
1950         }
1951
1952         {
1953                 boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
1954                 verify_subtitles_must_be_in_all_reels_check (dir, true, true);
1955                 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1956         }
1957
1958         {
1959                 boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
1960                 verify_subtitles_must_be_in_all_reels_check (dir, false, false);
1961                 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1962         }
1963 }
1964
1965
1966 static
1967 void
1968 verify_closed_captions_must_be_in_all_reels_check (boost::filesystem::path dir, int caps_in_reel1, int caps_in_reel2)
1969 {
1970         prepare_directory (dir);
1971         auto dcp = make_shared<dcp::DCP>(dir);
1972         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1973
1974         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1975         subs->set_language (dcp::LanguageTag("de-DE"));
1976         subs->set_start_time (dcp::Time());
1977         subs->add (simple_subtitle());
1978         subs->write (dir / "subs.mxf");
1979
1980         auto reel1 = make_shared<dcp::Reel>(
1981                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1982                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1983                 );
1984
1985         for (int i = 0; i < caps_in_reel1; ++i) {
1986                 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1987         }
1988
1989         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1990         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1991         reel1->add (markers1);
1992
1993         cpl->add (reel1);
1994
1995         auto reel2 = make_shared<dcp::Reel>(
1996                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1997                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1998                 );
1999
2000         for (int i = 0; i < caps_in_reel2; ++i) {
2001                 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2002         }
2003
2004         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2005         markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2006         reel2->add (markers2);
2007
2008         cpl->add (reel2);
2009
2010         dcp->add (cpl);
2011         dcp->write_xml (
2012                 dcp::SMPTE,
2013                 dcp::String::compose("libdcp %1", dcp::version),
2014                 dcp::String::compose("libdcp %1", dcp::version),
2015                 dcp::LocalTime().as_string(),
2016                 "A Test DCP"
2017                 );
2018 }
2019
2020
2021 BOOST_AUTO_TEST_CASE (verify_closed_captions_must_be_in_all_reels)
2022 {
2023         {
2024                 boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels1");
2025                 verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2026                 check_verify_result (
2027                         {dir},
2028                         {
2029                                 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2030                                 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
2031                         });
2032         }
2033
2034         {
2035                 boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2036                 verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2037                 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
2038         }
2039
2040         {
2041                 boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2042                 verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2043                 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
2044         }
2045 }
2046
2047
2048 template <class T>
2049 void
2050 verify_text_entry_point_check (boost::filesystem::path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2051 {
2052         prepare_directory (dir);
2053         auto dcp = make_shared<dcp::DCP>(dir);
2054         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2055
2056         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2057         subs->set_language (dcp::LanguageTag("de-DE"));
2058         subs->set_start_time (dcp::Time());
2059         subs->add (simple_subtitle());
2060         subs->write (dir / "subs.mxf");
2061         auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2062         adjust (reel_text);
2063
2064         auto reel = make_shared<dcp::Reel>(
2065                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2066                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2067                 );
2068
2069         reel->add (reel_text);
2070
2071         reel->add (simple_markers(240));
2072
2073         cpl->add (reel);
2074
2075         dcp->add (cpl);
2076         dcp->write_xml (
2077                 dcp::SMPTE,
2078                 dcp::String::compose("libdcp %1", dcp::version),
2079                 dcp::String::compose("libdcp %1", dcp::version),
2080                 dcp::LocalTime().as_string(),
2081                 "A Test DCP"
2082                 );
2083
2084         check_verify_result (
2085                 {dir},
2086                 {
2087                         { dcp::VerificationNote::VERIFY_BV21_ERROR, code },
2088                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
2089                 });
2090 }
2091
2092
2093 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2094 {
2095         verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2096                 "build/test/verify_subtitle_entry_point_must_be_present",
2097                 dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT,
2098                 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2099                         asset->unset_entry_point ();
2100                         }
2101                 );
2102
2103         verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2104                 "build/test/verify_subtitle_entry_point_must_be_zero",
2105                 dcp::VerificationNote::INCORRECT_SUBTITLE_ENTRY_POINT,
2106                 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2107                         asset->set_entry_point (4);
2108                         }
2109                 );
2110
2111         verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2112                 "build/test/verify_closed_caption_entry_point_must_be_present",
2113                 dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2114                 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2115                         asset->unset_entry_point ();
2116                         }
2117                 );
2118
2119         verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2120                 "build/test/verify_closed_caption_entry_point_must_be_zero",
2121                 dcp::VerificationNote::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2122                 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2123                         asset->set_entry_point (9);
2124                         }
2125                 );
2126 }
2127
2128
2129 BOOST_AUTO_TEST_CASE (verify_assets_must_have_hashes)
2130 {
2131         RNGFixer fix;
2132
2133         boost::filesystem::path const dir("build/test/verify_assets_must_have_hashes");
2134         auto dcp = make_simple (dir);
2135         dcp->write_xml (
2136                 dcp::SMPTE,
2137                 dcp::String::compose("libdcp %1", dcp::version),
2138                 dcp::String::compose("libdcp %1", dcp::version),
2139                 dcp::LocalTime().as_string(),
2140                 "A Test DCP"
2141                 );
2142
2143         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2144
2145         {
2146                 BOOST_REQUIRE (dcp->cpls()[0]->file());
2147                 Editor e(dcp->cpls()[0]->file().get());
2148                 e.replace("<Hash>XGhFVrqZqapOJx5Fh2SLjj48Yjg=</Hash>", "");
2149         }
2150
2151         check_verify_result (
2152                 {dir},
2153                 {
2154                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2155                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_HASH }
2156                 });
2157 }
2158
2159
2160 static
2161 void
2162 verify_markers_test (
2163         boost::filesystem::path dir,
2164         vector<pair<dcp::Marker, dcp::Time>> markers,
2165         vector<dcp::VerificationNote> test_notes
2166         )
2167 {
2168         auto dcp = make_simple (dir);
2169         dcp->cpls()[0]->set_content_kind (dcp::FEATURE);
2170         auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2171         for (auto const& i: markers) {
2172                 markers_asset->set (i.first, i.second);
2173         }
2174         dcp->cpls()[0]->reels()[0]->add(markers_asset);
2175         dcp->write_xml (
2176                 dcp::SMPTE,
2177                 dcp::String::compose("libdcp %1", dcp::version),
2178                 dcp::String::compose("libdcp %1", dcp::version),
2179                 dcp::LocalTime().as_string(),
2180                 "A Test DCP"
2181                 );
2182
2183         check_verify_result ({dir}, test_notes);
2184 }
2185
2186
2187 BOOST_AUTO_TEST_CASE (verify_markers)
2188 {
2189         verify_markers_test (
2190                 "build/test/verify_markers_all_correct",
2191                 {
2192                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2193                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2194                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2195                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2196                 },
2197                 {}
2198                 );
2199
2200         verify_markers_test (
2201                 "build/test/verify_markers_missing_ffec",
2202                 {
2203                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2204                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2205                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2206                 },
2207                 {
2208                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE }
2209                 });
2210
2211         verify_markers_test (
2212                 "build/test/verify_markers_missing_ffmc",
2213                 {
2214                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2215                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2216                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2217                 },
2218                 {
2219                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE }
2220                 });
2221
2222         verify_markers_test (
2223                 "build/test/verify_markers_missing_ffoc",
2224                 {
2225                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2226                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2227                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2228                 },
2229                 {
2230                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC}
2231                 });
2232
2233         verify_markers_test (
2234                 "build/test/verify_markers_missing_lfoc",
2235                 {
2236                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2237                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2238                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2239                 },
2240                 {
2241                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC }
2242                 });
2243
2244         verify_markers_test (
2245                 "build/test/verify_markers_incorrect_ffoc",
2246                 {
2247                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2248                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2249                         { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2250                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2251                 },
2252                 {
2253                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_FFOC }
2254                 });
2255
2256         verify_markers_test (
2257                 "build/test/verify_markers_incorrect_lfoc",
2258                 {
2259                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2260                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2261                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2262                         { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2263                 },
2264                 {
2265                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_LFOC }
2266                 });
2267 }
2268
2269
2270 BOOST_AUTO_TEST_CASE (verify_cpl_metadata_version)
2271 {
2272         boost::filesystem::path dir = "build/test/verify_cpl_metadata_version";
2273         prepare_directory (dir);
2274         auto dcp = make_simple (dir);
2275         dcp->cpls()[0]->unset_version_number();
2276         dcp->write_xml (
2277                 dcp::SMPTE,
2278                 dcp::String::compose("libdcp %1", dcp::version),
2279                 dcp::String::compose("libdcp %1", dcp::version),
2280                 dcp::LocalTime().as_string(),
2281                 "A Test DCP"
2282                 );
2283
2284         check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER }});
2285 }
2286
2287
2288 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata1)
2289 {
2290         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata1";
2291         auto dcp = make_simple (dir);
2292         dcp->write_xml (
2293                 dcp::SMPTE,
2294                 dcp::String::compose("libdcp %1", dcp::version),
2295                 dcp::String::compose("libdcp %1", dcp::version),
2296                 dcp::LocalTime().as_string(),
2297                 "A Test DCP"
2298                 );
2299
2300         {
2301                 Editor e (dcp->cpls()[0]->file().get());
2302                 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2303         }
2304
2305         check_verify_result (
2306                 {dir},
2307                 {
2308                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2309                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA }
2310                 });
2311 }
2312
2313
2314 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata2)
2315 {
2316         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata2";
2317         auto dcp = make_simple (dir);
2318         dcp->write_xml (
2319                 dcp::SMPTE,
2320                 dcp::String::compose("libdcp %1", dcp::version),
2321                 dcp::String::compose("libdcp %1", dcp::version),
2322                 dcp::LocalTime().as_string(),
2323                 "A Test DCP"
2324                 );
2325
2326         {
2327                 Editor e (dcp->cpls()[0]->file().get());
2328                 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2329         }
2330
2331         check_verify_result (
2332                 {dir},
2333                 {
2334                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2335                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA }
2336                 });
2337 }
2338
2339
2340 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata3)
2341 {
2342         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata3";
2343         auto dcp = make_simple (dir);
2344         dcp->write_xml (
2345                 dcp::SMPTE,
2346                 dcp::String::compose("libdcp %1", dcp::version),
2347                 dcp::String::compose("libdcp %1", dcp::version),
2348                 dcp::LocalTime().as_string(),
2349                 "A Test DCP"
2350                 );
2351
2352         {
2353                 Editor e (dcp->cpls()[0]->file().get());
2354                 e.replace ("<meta:Name>A", "<meta:NameX>A");
2355                 e.replace ("n</meta:Name>", "n</meta:NameX>");
2356         }
2357
2358         check_verify_result (
2359                 {dir},
2360                 {
2361                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2362                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2363                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2364                 });
2365 }
2366
2367
2368 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata4)
2369 {
2370         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata4";
2371         auto dcp = make_simple (dir);
2372         dcp->write_xml (
2373                 dcp::SMPTE,
2374                 dcp::String::compose("libdcp %1", dcp::version),
2375                 dcp::String::compose("libdcp %1", dcp::version),
2376                 dcp::LocalTime().as_string(),
2377                 "A Test DCP"
2378                 );
2379
2380         {
2381                 Editor e (dcp->cpls()[0]->file().get());
2382                 e.replace ("Application", "Fred");
2383         }
2384
2385         check_verify_result (
2386                 {dir},
2387                 {
2388                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2389                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> property should be 'Application'") },
2390                 });
2391 }
2392
2393
2394 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata5)
2395 {
2396         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata5";
2397         auto dcp = make_simple (dir);
2398         dcp->write_xml (
2399                 dcp::SMPTE,
2400                 dcp::String::compose("libdcp %1", dcp::version),
2401                 dcp::String::compose("libdcp %1", dcp::version),
2402                 dcp::LocalTime().as_string(),
2403                 "A Test DCP"
2404                 );
2405         {
2406                 Editor e (dcp->cpls()[0]->file().get());
2407                 e.replace ("DCP Constraints Profile", "Fred");
2408         }
2409
2410         check_verify_result (
2411                 {dir},
2412                 {
2413                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2414                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'") },
2415                 });
2416 }
2417
2418
2419 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata6)
2420 {
2421         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata6";
2422         auto dcp = make_simple (dir);
2423         dcp->write_xml (
2424                 dcp::SMPTE,
2425                 dcp::String::compose("libdcp %1", dcp::version),
2426                 dcp::String::compose("libdcp %1", dcp::version),
2427                 dcp::LocalTime().as_string(),
2428                 "A Test DCP"
2429                 );
2430
2431         {
2432                 Editor e (dcp->cpls()[0]->file().get());
2433                 e.replace ("<meta:Value>", "<meta:ValueX>");
2434                 e.replace ("</meta:Value>", "</meta:ValueX>");
2435         }
2436
2437         check_verify_result (
2438                 {dir},
2439                 {
2440                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2441                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2442                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2443                 });
2444 }
2445
2446
2447 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata7)
2448 {
2449         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata7";
2450         auto dcp = make_simple (dir);
2451         dcp->write_xml (
2452                 dcp::SMPTE,
2453                 dcp::String::compose("libdcp %1", dcp::version),
2454                 dcp::String::compose("libdcp %1", dcp::version),
2455                 dcp::LocalTime().as_string(),
2456                 "A Test DCP"
2457                 );
2458         {
2459                 Editor e (dcp->cpls()[0]->file().get());
2460                 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2461         }
2462
2463         check_verify_result (
2464                 {dir},
2465                 {
2466                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2467                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'") },
2468                 });
2469 }
2470
2471
2472 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata8)
2473 {
2474         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata8";
2475         auto dcp = make_simple (dir);
2476         dcp->write_xml (
2477                 dcp::SMPTE,
2478                 dcp::String::compose("libdcp %1", dcp::version),
2479                 dcp::String::compose("libdcp %1", dcp::version),
2480                 dcp::LocalTime().as_string(),
2481                 "A Test DCP"
2482                 );
2483         {
2484                 Editor e (dcp->cpls()[0]->file().get());
2485                 e.replace ("<meta:Property>", "<meta:PropertyX>");
2486                 e.replace ("</meta:Property>", "</meta:PropertyX>");
2487         }
2488
2489         check_verify_result (
2490                 {dir},
2491                 {
2492                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2493                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2494                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2495                 });
2496 }
2497
2498
2499 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata9)
2500 {
2501         boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata9";
2502         auto dcp = make_simple (dir);
2503         dcp->write_xml (
2504                 dcp::SMPTE,
2505                 dcp::String::compose("libdcp %1", dcp::version),
2506                 dcp::String::compose("libdcp %1", dcp::version),
2507                 dcp::LocalTime().as_string(),
2508                 "A Test DCP"
2509                 );
2510         {
2511                 Editor e (dcp->cpls()[0]->file().get());
2512                 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2513                 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2514         }
2515
2516         check_verify_result (
2517                 {dir},
2518                 {
2519                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2520                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML },
2521                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2522                 });
2523 }
2524
2525
2526
2527 BOOST_AUTO_TEST_CASE (verify_encrypted_cpl_is_signed)
2528 {
2529         boost::filesystem::path dir = "build/test/verify_encrypted_cpl_is_signed";
2530         prepare_directory (dir);
2531         for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/encryption_test")) {
2532                 boost::filesystem::copy_file (i.path(), dir / i.path().filename());
2533         }
2534
2535         {
2536                 Editor e (dir / "cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
2537                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2538         }
2539
2540         check_verify_result (
2541                 {dir},
2542                 {
2543                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2544                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT },
2545                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2546                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2547                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2548                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2549                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_CPL_METADATA },
2550                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT }
2551                 });
2552 }
2553
2554
2555 BOOST_AUTO_TEST_CASE (verify_encrypted_pkl_is_signed)
2556 {
2557         boost::filesystem::path dir = "build/test/verify_encrypted_pkl_is_signed";
2558         prepare_directory (dir);
2559         for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/encryption_test")) {
2560                 boost::filesystem::copy_file (i.path(), dir / i.path().filename());
2561         }
2562
2563         {
2564                 Editor e (dir / "pkl_93182bd2-b1e8-41a3-b5c8-6e6564273bff.xml");
2565                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2566         }
2567
2568         check_verify_result (
2569                 {dir},
2570                 {
2571                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT },
2572                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2573                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2574                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2575                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2576                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_CPL_METADATA },
2577                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT }
2578                 });
2579 }
2580
2581
2582 BOOST_AUTO_TEST_CASE (verify_unencrypted_pkl_can_be_unsigned)
2583 {
2584         boost::filesystem::path dir = "build/test/verify_unencrypted_pkl_can_be_unsigned";
2585         prepare_directory (dir);
2586         for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/dcp_test1")) {
2587                 boost::filesystem::copy_file (i.path(), dir / i.path().filename());
2588         }
2589
2590         {
2591                 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2592                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2593         }
2594
2595         check_verify_result ({dir}, {});
2596 }
2597
2598
2599 BOOST_AUTO_TEST_CASE (verify_must_not_be_partially_encrypted)
2600 {
2601         boost::filesystem::path dir ("build/test/verify_must_not_be_partially_encrypted");
2602         prepare_directory (dir);
2603
2604         dcp::DCP d (dir);
2605
2606         auto signer = make_shared<dcp::CertificateChain>();
2607         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2608         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2609         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2610         signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2611
2612         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2613
2614         dcp::Key key;
2615
2616         auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::SMPTE);
2617         mp->set_key (key);
2618
2619         auto writer = mp->start_write (dir / "video.mxf", false);
2620         dcp::ArrayData j2c ("test/data/flat_red.j2c");
2621         for (int i = 0; i < 24; ++i) {
2622                 writer->write (j2c.data(), j2c.size());
2623         }
2624         writer->finalize ();
2625
2626         auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2627
2628         auto reel = make_shared<dcp::Reel>(
2629                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2630                 make_shared<dcp::ReelSoundAsset>(ms, 0)
2631                 );
2632
2633         reel->add (simple_markers());
2634
2635         cpl->add (reel);
2636
2637         cpl->set_content_version (
2638                 {"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"}
2639                 );
2640         cpl->set_annotation_text ("A Test DCP");
2641         cpl->set_issuer ("OpenDCP 0.0.25");
2642         cpl->set_creator ("OpenDCP 0.0.25");
2643         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2644         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2645         cpl->set_main_sound_sample_rate (48000);
2646         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2647         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2648         cpl->set_version_number (1);
2649
2650         d.add (cpl);
2651
2652         d.write_xml (dcp::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2653
2654         check_verify_result ({dir}, {{dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PARTIALLY_ENCRYPTED}});
2655 }
2656