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