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