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