fixup: valid content version label text
[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
35 #include "compose.hpp"
36 #include "cpl.h"
37 #include "dcp.h"
38 #include "file.h"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
45 #include "reel.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
57 #include "test.h"
58 #include "util.h"
59 #include "verify.h"
60 #include "verify_j2k.h"
61 #include <boost/algorithm/string.hpp>
62 #include <boost/random.hpp>
63 #include <boost/test/unit_test.hpp>
64 #include <cstdio>
65 #include <iostream>
66 #include <tuple>
67
68
69 using std::list;
70 using std::make_pair;
71 using std::make_shared;
72 using std::pair;
73 using std::shared_ptr;
74 using std::string;
75 using std::vector;
76 using boost::optional;
77 using namespace boost::filesystem;
78
79
80 static list<pair<string, optional<path>>> stages;
81
82 static string filename_to_id(boost::filesystem::path path)
83 {
84         return path.string().substr(4, path.string().length() - 8);
85 }
86
87 static
88 boost::filesystem::path
89 dcp_test1_pkl()
90 {
91         return find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
92 }
93
94 static
95 string
96 dcp_test1_pkl_id()
97 {
98         return filename_to_id(dcp_test1_pkl());
99 }
100
101 static
102 boost::filesystem::path
103 dcp_test1_cpl()
104 {
105         return find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
106 }
107
108 static
109 string
110 dcp_test1_cpl_id()
111 {
112         return filename_to_id(dcp_test1_cpl());
113 }
114
115 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
116
117 static
118 string
119 encryption_test_cpl_id()
120 {
121         return filename_to_id(find_file("test/ref/DCP/encryption_test", "cpl_").filename());
122 }
123
124 static
125 string
126 encryption_test_pkl_id()
127 {
128         return filename_to_id(find_file("test/ref/DCP/encryption_test", "pkl_").filename());
129 }
130
131 static void
132 stage (string s, optional<path> p)
133 {
134         stages.push_back (make_pair (s, p));
135 }
136
137 static void
138 progress (float)
139 {
140
141 }
142
143 static void
144 prepare_directory (path path)
145 {
146         using namespace boost::filesystem;
147         remove_all (path);
148         create_directories (path);
149 }
150
151
152 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
153  *  to make a new sacrificial test DCP.
154  */
155 static path
156 setup (int reference_number, string verify_test_suffix)
157 {
158         auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
159         prepare_directory (dir);
160         for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
161                 copy_file (i.path(), dir / i.path().filename());
162         }
163
164         return dir;
165 }
166
167
168 static
169 shared_ptr<dcp::CPL>
170 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
171 {
172         auto reel = make_shared<dcp::Reel>();
173         reel->add (reel_asset);
174         reel->add (simple_markers());
175
176         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
177         cpl->add (reel);
178         auto dcp = make_shared<dcp::DCP>(dir);
179         dcp->add (cpl);
180         dcp->set_annotation_text("hello");
181         dcp->write_xml ();
182
183         return cpl;
184 }
185
186
187 LIBDCP_DISABLE_WARNINGS
188 static
189 void
190 dump_notes (vector<dcp::VerificationNote> const & notes)
191 {
192         for (auto i: notes) {
193                 std::cout << dcp::note_to_string(i) << "\n";
194         }
195 }
196 LIBDCP_ENABLE_WARNINGS
197
198
199 static
200 string
201 to_string(dcp::VerificationNote const& note)
202 {
203         string s = note_to_string(note) + dcp::String::compose(
204                 "\n  [%1 %2 %3 %4 %5 %6 ",
205                 static_cast<int>(note.type()),
206                 static_cast<int>(note.code()),
207                 note.note().get_value_or("<none>"),
208                 note.file().get_value_or("<none>"),
209                 note.line().get_value_or(0),
210                 note.frame().get_value_or(0)
211                 );
212
213         s += dcp::String::compose(
214                 "%1 %2 %3 %4 %5]\n",
215                 note.id().get_value_or("<none>"),
216                 note.other_id().get_value_or("<none>"),
217                 note.cpl_id().get_value_or("<none>"),
218                 note.reference_hash().get_value_or("<none>"),
219                 note.calculated_hash().get_value_or("<none>")
220                 );
221
222         return s;
223 }
224
225
226 static
227 void
228 check_verify_result(vector<dcp::VerificationNote> notes, vector<dcp::VerificationNote> test_notes)
229 {
230         std::sort(notes.begin(), notes.end());
231         std::sort(test_notes.begin(), test_notes.end());
232
233         string message = "\n";
234
235         vector<dcp::VerificationNote> not_expected;
236         for (auto note: notes) {
237                 auto iter = std::find_if(test_notes.begin(), test_notes.end(), [note](dcp::VerificationNote const& n) { return note.type() == n.type() && note.code() == n.code(); });
238                 if (iter != test_notes.end() && *iter != note) {
239                         message += "Wrong details:\n --seen     " + to_string(note) + " --expected " + to_string(*iter) + "\n";
240                 } else if (iter == test_notes.end()) {
241                         not_expected.push_back(note);
242                 }
243         }
244
245         vector<dcp::VerificationNote> not_seen;
246         for (auto note: test_notes) {
247                 auto iter = std::find_if(notes.begin(), notes.end(), [note](dcp::VerificationNote const& n) { return note.type() == n.type() && note.code() == n.code(); });
248                 if (iter == notes.end()) {
249                         not_seen.push_back(note);
250                 }
251         }
252
253         for (auto note: not_expected) {
254                 message += "Not expected:\n" + to_string(note) + "\n";
255         }
256
257         for (auto note: not_seen) {
258                 message += "Not seen:\n" + to_string(note) + "\n";
259         }
260
261         BOOST_REQUIRE_MESSAGE(notes == test_notes, message);
262 }
263
264
265 static
266 void
267 check_verify_result(vector<path> dir, vector<dcp::DecryptedKDM> kdm, vector<dcp::VerificationNote> test_notes)
268 {
269         check_verify_result(dcp::verify({dir}, kdm, &stage, &progress, {}, xsd_test).notes, test_notes);
270 }
271
272
273 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
274  * replacing from with to.  Verify the resulting DCP and check that the results match the given
275  * list of codes.
276  */
277 static
278 void
279 replace(string suffix, boost::function<path (string)> file, string from, string to)
280 {
281         auto dir = setup (1, suffix);
282
283         {
284                 Editor e (file(suffix));
285                 e.replace (from, to);
286         }
287 }
288
289
290 static
291 void
292 add_font(shared_ptr<dcp::SubtitleAsset> asset)
293 {
294         dcp::ArrayData fake_font(1024);
295         asset->add_font("font", fake_font);
296 }
297
298
299 class HashCalculator
300 {
301 public:
302         HashCalculator(boost::filesystem::path path)
303                 : _path(path)
304                 , _old_hash(dcp::make_digest(path, [](int64_t, int64_t) {}))
305         {}
306
307         std::string old_hash() const {
308                 return _old_hash;
309         }
310
311         std::string new_hash() const {
312                 return dcp::make_digest(_path, [](int64_t, int64_t) {});
313         }
314
315 private:
316         boost::filesystem::path _path;
317         std::string _old_hash;
318 };
319
320
321 static
322 dcp::VerificationNote
323 ok(dcp::VerificationNote::Code code, shared_ptr<const dcp::CPL> cpl)
324 {
325         return dcp::VerificationNote(dcp::VerificationNote::Type::OK, code).set_cpl_id(cpl->id());
326 }
327
328
329 static
330 dcp::VerificationNote
331 ok(dcp::VerificationNote::Code code, string note, shared_ptr<const dcp::CPL> cpl)
332 {
333         return dcp::VerificationNote(dcp::VerificationNote::Type::OK, code, note).set_cpl_id(cpl->id());
334 }
335
336
337 static
338 dcp::VerificationNote
339 ok(dcp::VerificationNote::Code code, boost::filesystem::path path, shared_ptr<const dcp::CPL> cpl)
340 {
341         return dcp::VerificationNote(dcp::VerificationNote::Type::OK, code, path).set_cpl_id(cpl->id());
342 }
343
344
345 void
346 add(vector<dcp::VerificationNote>& notes, vector<dcp::VerificationNote> const& add)
347 {
348         for (auto i: add) {
349                 notes.push_back(i);
350         }
351 }
352
353
354 path
355 find_prefix(path dir, string prefix)
356 {
357         auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
358                 return boost::starts_with(p.filename().string(), prefix);
359         });
360
361         BOOST_REQUIRE(iter != directory_iterator());
362         return iter->path();
363 }
364
365
366 path find_cpl (path dir)
367 {
368         return find_prefix(dir, "cpl_");
369 }
370
371
372 path
373 find_pkl(path dir)
374 {
375         return find_prefix(dir, "pkl_");
376 }
377
378
379 path
380 find_asset_map(path dir)
381 {
382         return find_prefix(dir, "ASSETMAP");
383 }
384
385
386 BOOST_AUTO_TEST_CASE (verify_no_error)
387 {
388         stages.clear ();
389         auto dir = setup (1, "no_error");
390         auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
391
392         path const cpl_file = dir / dcp_test1_cpl();
393         path const pkl_file = dir / dcp_test1_pkl();
394         path const assetmap_file = dir / "ASSETMAP.xml";
395
396         auto st = stages.begin();
397         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
398         BOOST_REQUIRE (st->second);
399         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
400         ++st;
401         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
402         BOOST_REQUIRE (st->second);
403         BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
404         ++st;
405         BOOST_CHECK_EQUAL (st->first, "Checking reel");
406         BOOST_REQUIRE (!st->second);
407         ++st;
408         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
409         BOOST_REQUIRE (st->second);
410         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
411         ++st;
412         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
413         BOOST_REQUIRE (st->second);
414         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
415         ++st;
416         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
417         BOOST_REQUIRE (st->second);
418         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
419         ++st;
420         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
421         BOOST_REQUIRE (st->second);
422         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
423         ++st;
424         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
425         BOOST_REQUIRE (st->second);
426         BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
427         ++st; BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
428         BOOST_REQUIRE (st->second);
429         BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
430         ++st;
431         BOOST_REQUIRE (st == stages.end());
432
433         for (auto note: notes) {
434                 BOOST_CHECK(note.type() == dcp::VerificationNote::Type::OK);
435         }
436 }
437
438
439 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
440 {
441         using namespace boost::filesystem;
442
443         auto dir = setup (1, "incorrect_picture_sound_hash");
444         auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
445
446         auto video_path = path(dir / "video.mxf");
447         HashCalculator video_calc(video_path);
448         auto mod = fopen(video_path.string().c_str(), "r+b");
449         BOOST_REQUIRE (mod);
450         BOOST_REQUIRE_EQUAL(fseek(mod, -16, SEEK_END), 0);
451         int x = 42;
452         BOOST_REQUIRE(fwrite(&x, sizeof(x), 1, mod) == 1);
453         fclose (mod);
454
455         auto audio_path = path(dir / "audio.mxf");
456         HashCalculator audio_calc(audio_path);
457         mod = fopen(audio_path.string().c_str(), "r+b");
458         BOOST_REQUIRE (mod);
459         BOOST_REQUIRE_EQUAL(fseek(mod, 0, SEEK_END), 0);
460         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
461         fclose (mod);
462
463         dcp::ASDCPErrorSuspender sus;
464         check_verify_result (
465                 { dir },
466                 {},
467                 {
468                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
469                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
470                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
471                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
472                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
473                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
474                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
475                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
476                         dcp::VerificationNote(
477                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path)
478                                 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(video_calc.old_hash()).set_calculated_hash(video_calc.new_hash()),
479                         dcp::VerificationNote(
480                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path)
481                                 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(audio_calc.old_hash()).set_calculated_hash(audio_calc.new_hash()),
482                 });
483 }
484
485
486 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
487 {
488         using namespace boost::filesystem;
489
490         auto dir = setup (1, "mismatched_picture_sound_hashes");
491         auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
492
493         HashCalculator calc(dir / dcp_test1_cpl());
494
495         {
496                 Editor e (dir / dcp_test1_pkl());
497                 e.replace ("<Hash>", "<Hash>x");
498         }
499
500         check_verify_result (
501                 { dir },
502                 {},
503                 {
504                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
505                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
506                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
507                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
508                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
509                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
510                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
511                         dcp::VerificationNote(
512                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl())
513                                 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash("x" + calc.old_hash()).set_calculated_hash(calc.old_hash()),
514                         dcp::VerificationNote(
515                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf")
516                                 ).set_cpl_id(dcp_test1_cpl_id()),
517                         dcp::VerificationNote(
518                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf")
519                                 ).set_cpl_id(dcp_test1_cpl_id()),
520                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'x3M7YTgvFKXXMEGLkIbV4miC90FE=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 28 },
521                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xskI+5b/9LA/y6h0mcyxysJYanxI=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 12 },
522                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xvsVjRV9vhTBPUWfE/TT1o2vdQsI=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 20 },
523                 });
524 }
525
526
527 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
528 {
529         auto dir = setup (1, "failed_read_content_kind");
530
531         HashCalculator calc(dir / dcp_test1_cpl());
532
533         {
534                 Editor e (dir / dcp_test1_cpl());
535                 e.replace ("<ContentKind>", "<ContentKind>x");
536         }
537
538         auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
539
540         check_verify_result (
541                 { dir },
542                 {},
543                 {
544                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
545                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
546                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
547                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
548                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
549                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
550                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
551                         dcp::VerificationNote(
552                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl())
553                                 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
554                         dcp::VerificationNote(
555                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer")
556                                 ).set_cpl_id(dcp_test1_cpl_id())
557                 });
558 }
559
560
561 static
562 path
563 dcp_test1_cpl_path(string suffix)
564 {
565         return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl());
566 }
567
568
569 static
570 path
571 dcp_test1_pkl_path(string suffix)
572 {
573         return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl());
574 }
575
576
577 static
578 path
579 asset_map (string suffix)
580 {
581         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
582 }
583
584
585 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
586 {
587         auto const suffix = "invalid_picture_frame_rate";
588
589         replace(suffix, &dcp_test1_cpl_path, "<FrameRate>24 1", "<FrameRate>99 1");
590
591         auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
592         auto const cpl_path = find_cpl(dir);
593         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
594
595         std::vector<dcp::VerificationNote> expected =
596                 {
597                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
598                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
599                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
600                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
601                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
602                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
603                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
604                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
605                         dcp::VerificationNote(
606                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
607                                 ).set_cpl_id(cpl->id()).set_calculated_hash("7n7GQ2TbxQbmHYuAR8ml7XDOep8=").set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI="),
608                         dcp::VerificationNote(
609                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, string{"99/1"}
610                                 ).set_cpl_id(cpl->id())
611                 };
612
613         check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
614 }
615
616
617 BOOST_AUTO_TEST_CASE (verify_missing_asset)
618 {
619         auto dir = setup (1, "missing_asset");
620         remove (dir / "video.mxf");
621
622         auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
623
624         check_verify_result (
625                 { dir },
626                 {},
627                 {
628                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
629                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
630                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
631                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
632                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
633                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
634                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
635                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
636                 });
637 }
638
639
640 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
641 {
642         auto const suffix = "empty_asset_path";
643
644         replace("empty_asset_path", &asset_map, "<Path>video.mxf</Path>", "<Path></Path>");
645
646         auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
647         auto const cpl_path = find_cpl(dir);
648         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
649
650         std::vector<dcp::VerificationNote> expected = {
651                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
652                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
653                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
654                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
655                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
656                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
657                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
658                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
659                 };
660
661         check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
662 }
663
664
665 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
666 {
667         auto const suffix = "mismatched_standard";
668
669         replace(suffix, &dcp_test1_cpl_path, "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#");
670
671         auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
672         auto const cpl_path = find_cpl(dir);
673         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
674
675         std::vector<dcp::VerificationNote> expected = {
676                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
677                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
678                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
679                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
680                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
681                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
682                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
683                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
684                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_STANDARD },
685                         dcp::VerificationNote(
686                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "invalid character encountered", canonical(cpl_path), 42
687                                 ).set_cpl_id(cpl->id()),
688                         dcp::VerificationNote(
689                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'Id'", canonical(cpl_path), 53
690                                 ).set_cpl_id(cpl->id()),
691                         dcp::VerificationNote(
692                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'EditRate'", canonical(cpl_path), 54
693                                 ).set_cpl_id(cpl->id()),
694                         dcp::VerificationNote(
695                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'IntrinsicDuration'", canonical(cpl_path), 55
696                                 ).set_cpl_id(cpl->id()),
697                         dcp::VerificationNote(
698                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
699                                 "element 'Id' is not allowed for content model '(Id,AnnotationText?,EditRate,IntrinsicDuration,"
700                                 "EntryPoint?,Duration?,FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
701                                 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,MainSoundSampleRate,"
702                                 "MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,ExtensionMetadataList?,)'",
703                                 canonical(cpl_path), 149
704                                 ).set_cpl_id(cpl->id()),
705                         dcp::VerificationNote(
706                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
707                                 ).set_cpl_id(cpl->id()).set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI=").set_calculated_hash("FZ9E7L/pOuJ6aZfbiaANTv8BFOo=")
708                 };
709
710         check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
711 }
712
713
714 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
715 {
716         auto const suffix = "invalid_xml_cpl_id";
717
718         /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
719         replace("invalid_xml_cpl_id", &dcp_test1_cpl_path, "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a");
720
721         auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
722         auto const cpl_path = find_cpl(dir);
723         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
724
725         std::vector<dcp::VerificationNote> expected = {
726                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
727                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
728                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
729                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
730                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
731                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
732                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
733                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
734                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
735                         dcp::VerificationNote(
736                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
737                                 "value 'urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a' does not match regular expression "
738                                 "facet 'urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'", canonical(cpl_path), 3
739                                 ).set_cpl_id(cpl->id())
740                 };
741
742         check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
743 }
744
745 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
746 {
747         auto const suffix = "invalid_xml_issue_date";
748
749         replace("invalid_xml_issue_date", &dcp_test1_cpl_path, "<IssueDate>", "<IssueDate>x");
750
751         auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
752         auto const cpl_path = find_cpl(dir);
753         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
754
755         std::vector<dcp::VerificationNote> expected = {
756                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
757                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
758                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
759                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
760                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
761                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
762                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
763                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
764                         dcp::VerificationNote(
765                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
766                                 ).set_cpl_id(cpl->id()).set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI=").set_calculated_hash("sz3BeIugJ567q3HMnA62JeRw4TE="),
767                         dcp::VerificationNote(
768                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
769                                 "invalid character encountered",
770                                 canonical(cpl_path), 5
771                                 ).set_cpl_id(cpl->id()),
772                 };
773
774         check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
775 }
776
777
778 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
779 {
780         auto const suffix = "invalid_xml_pkl_id";
781
782         replace("invalid_xml_pkl_id", &dcp_test1_pkl_path, "<Id>urn:uuid:" + dcp_test1_pkl_id().substr(0, 3), "<Id>urn:uuid:x" + dcp_test1_pkl_id().substr(1, 2));
783
784         auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
785         auto const pkl_path = find_pkl(dir);
786         auto const cpl_path = find_cpl(dir);
787         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
788
789         std::vector<dcp::VerificationNote> expected = {
790                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
791                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
792                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
793                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
794                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
795                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
796                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
797                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
798                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
799                         dcp::VerificationNote(
800                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
801                                 "value 'urn:uuid:x199d58b-5ef8-4d49-b270-07e590ccb280' does not match regular "
802                                 "expression facet 'urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'",
803                                 canonical(pkl_path), 3
804                                 ),
805         };
806
807         check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
808 }
809
810
811 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
812 {
813         auto const suffix = "invalid_xml_asset_map_id";
814
815         replace("invalid_xml_asset_map_id", &asset_map, "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3), "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2));
816
817         auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
818         auto const cpl_path = find_cpl(dir);
819         auto const asset_map_path = find_asset_map(dir);
820         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
821
822         std::vector<dcp::VerificationNote> expected = {
823                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
824                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
825                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
826                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
827                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
828                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
829                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
830                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
831                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
832                         dcp::VerificationNote(
833                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
834                                 "value 'urn:uuid:x17b3de4-6dda-408d-b19b-6711354b0bc3' does not match regular "
835                                 "expression facet 'urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'",
836                                 canonical(asset_map_path), 3
837                                 ),
838         };
839
840         check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
841 }
842
843
844 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
845 {
846         stages.clear ();
847         auto dir = setup (3, "verify_invalid_standard");
848         auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
849
850         path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
851         path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
852         path const assetmap_file = dir / "ASSETMAP";
853         auto cpl = std::make_shared<dcp::CPL>(cpl_file);
854
855         auto st = stages.begin();
856         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
857         BOOST_REQUIRE (st->second);
858         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
859         ++st;
860         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
861         BOOST_REQUIRE (st->second);
862         BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
863         ++st;
864         BOOST_CHECK_EQUAL (st->first, "Checking reel");
865         BOOST_REQUIRE (!st->second);
866         ++st;
867         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
868         BOOST_REQUIRE (st->second);
869         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
870         ++st;
871         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
872         BOOST_REQUIRE (st->second);
873         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
874         ++st;
875         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
876         BOOST_REQUIRE (st->second);
877         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
878         ++st;
879         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
880         BOOST_REQUIRE (st->second);
881         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
882         ++st;
883         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
884         BOOST_REQUIRE (st->second);
885         BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
886         ++st;
887         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
888         BOOST_REQUIRE (st->second);
889         BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
890         ++st;
891         BOOST_REQUIRE (st == stages.end());
892
893         vector<dcp::VerificationNote> expected = {
894                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
895                 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
896                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"feature"}, cpl),
897                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
898                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
899                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
900                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"), cpl),
901                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"), cpl)
902         };
903
904         for (int j = 0; j < 24; ++j) {
905                 expected.push_back(
906                         dcp::VerificationNote(
907                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2")
908                                 ).set_cpl_id(cpl->id())
909                 );
910         }
911
912         check_verify_result(notes, expected);
913 }
914
915 /* DCP with a short asset */
916 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
917 {
918         auto dir = setup (8, "invalid_duration");
919
920         dcp::DCP dcp(dir);
921         dcp.read();
922         BOOST_REQUIRE(dcp.cpls().size() == 1);
923         auto cpl = dcp.cpls()[0];
924
925         vector<dcp::VerificationNote> expected = {
926                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
927                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
928                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
929                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"feature"}, cpl),
930                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "j2c_d7576dcb-a361-4139-96b8-267f5f8d7f91.mxf"), cpl),
931                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "j2c_d7576dcb-a361-4139-96b8-267f5f8d7f91.mxf"), cpl),
932                 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
933                 dcp::VerificationNote(
934                         dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91")
935                         ).set_cpl_id(cpl->id()),
936                 dcp::VerificationNote(
937                         dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91")
938                         ).set_cpl_id(cpl->id()),
939                 dcp::VerificationNote(
940                         dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626")
941                         ).set_cpl_id(cpl->id()),
942                 dcp::VerificationNote(
943                         dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626")
944                         ).set_cpl_id(cpl->id()),
945                 dcp::VerificationNote(
946                         dcp::VerificationNote::Type::WARNING,
947                         dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT,
948                         cpl->file().get()
949                         ).set_cpl_id(cpl->id())
950         };
951
952         for (int i = 0; i < 23; ++i) {
953                 expected.push_back(
954                         dcp::VerificationNote(
955                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2")
956                                 ).set_cpl_id(cpl->id())
957                         );
958         }
959
960         check_verify_result({ dir }, {}, expected);
961 }
962
963
964 static
965 shared_ptr<dcp::CPL>
966 dcp_from_frame (dcp::ArrayData const& frame, path dir)
967 {
968         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
969         create_directories (dir);
970         auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
971         for (int i = 0; i < 24; ++i) {
972                 writer->write (frame.data(), frame.size());
973         }
974         writer->finalize ();
975
976         auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
977         return write_dcp_with_single_asset (dir, reel_asset);
978 }
979
980
981 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
982 {
983         int const too_big = 1302083 * 2;
984
985         /* Compress a black image */
986         auto image = black_image ();
987         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
988         BOOST_REQUIRE (frame.size() < too_big);
989
990         /* Place it in a bigger block with some zero padding at the end */
991         dcp::ArrayData oversized_frame(too_big);
992         memcpy (oversized_frame.data(), frame.data(), frame.size());
993         memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
994
995         path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
996         prepare_directory (dir);
997         auto cpl = dcp_from_frame (oversized_frame, dir);
998
999         vector<dcp::VerificationNote> expected = {
1000                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1001                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1002                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1003                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1004                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1005                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1006                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "pic.mxf"), cpl),
1007         };
1008
1009         for (auto i = 0; i < 24; ++i) {
1010                 expected.push_back(
1011                         dcp::VerificationNote(
1012                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
1013                                 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
1014                         );
1015         }
1016
1017         for (auto i = 0; i < 24; ++i) {
1018                 expected.push_back(
1019                         dcp::VerificationNote(
1020                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
1021                                 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
1022                         );
1023         }
1024
1025         expected.push_back(
1026                 dcp::VerificationNote(
1027                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1028                         ).set_cpl_id(cpl->id())
1029                 );
1030
1031         check_verify_result({ dir }, {}, expected);
1032 }
1033
1034
1035 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
1036 {
1037         int const nearly_too_big = 1302083 * 0.98;
1038
1039         /* Compress a black image */
1040         auto image = black_image ();
1041         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
1042         BOOST_REQUIRE (frame.size() < nearly_too_big);
1043
1044         /* Place it in a bigger block with some zero padding at the end */
1045         dcp::ArrayData oversized_frame(nearly_too_big);
1046         memcpy (oversized_frame.data(), frame.data(), frame.size());
1047         memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
1048
1049         path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
1050         prepare_directory (dir);
1051         auto cpl = dcp_from_frame (oversized_frame, dir);
1052
1053         vector<dcp::VerificationNote> expected = {
1054                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "pic.mxf"), cpl),
1055                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1056                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1057                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1058                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1059                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1060                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1061         };
1062
1063         for (auto i = 0; i < 24; ++i) {
1064                 expected.push_back(
1065                         dcp::VerificationNote(
1066                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
1067                                 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
1068                         );
1069         }
1070
1071         for (auto i = 0; i < 24; ++i) {
1072                 expected.push_back(
1073                         dcp::VerificationNote(
1074                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
1075                                 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
1076                 );
1077         }
1078
1079         expected.push_back(
1080                 dcp::VerificationNote(
1081                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1082                         ).set_cpl_id(cpl->id())
1083                 );
1084
1085         check_verify_result ({ dir }, {}, expected);
1086 }
1087
1088
1089 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
1090 {
1091         /* Compress a black image */
1092         auto image = black_image ();
1093         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
1094         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
1095
1096         path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
1097         prepare_directory (dir);
1098         auto cpl = dcp_from_frame (frame, dir);
1099
1100         check_verify_result(
1101                 { dir },
1102                 {},
1103                 {
1104                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "pic.mxf"), cpl),
1105                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1106                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1107                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1108                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1109                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1110                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1111                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "pic.mxf"), cpl),
1112                         dcp::VerificationNote(dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()).set_cpl_id(cpl->id())
1113                 });
1114 }
1115
1116
1117 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
1118 {
1119         path const dir("build/test/verify_valid_interop_subtitles");
1120         prepare_directory (dir);
1121         copy_file ("test/data/subs1.xml", dir / "subs.xml");
1122         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1123         auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1124         auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1125
1126         check_verify_result (
1127                 {dir},
1128                 {},
1129                 {
1130                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1131                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1132                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1133                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1134                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1135                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1136                         dcp::VerificationNote(
1137                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1138                                 ).set_cpl_id(cpl->id())
1139                 });
1140 }
1141
1142
1143 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
1144 {
1145         path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
1146         prepare_directory(dir);
1147         copy_file("test/data/subs1.xml", dir / "ccap.xml");
1148         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
1149         auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1150         auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1151
1152         check_verify_result (
1153                 {dir},
1154                 {},
1155                 {
1156                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1157                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1158                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1159                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1160                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1161                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1162                         dcp::VerificationNote(
1163                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1164                                 ).set_cpl_id(cpl->id())
1165                 });
1166 }
1167
1168
1169 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
1170 {
1171         using namespace boost::filesystem;
1172
1173         path const dir("build/test/verify_invalid_interop_subtitles");
1174         prepare_directory (dir);
1175         copy_file ("test/data/subs1.xml", dir / "subs.xml");
1176         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1177         auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1178         auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1179
1180         {
1181                 Editor e (dir / "subs.xml");
1182                 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
1183         }
1184
1185         check_verify_result (
1186                 { dir },
1187                 {},
1188                 {
1189                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1190                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1191                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1192                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1193                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1194                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1195                         dcp::VerificationNote(
1196                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5
1197                                 ).set_cpl_id(cpl->id()),
1198                         dcp::VerificationNote(
1199                                 dcp::VerificationNote::Type::ERROR,
1200                                 dcp::VerificationNote::Code::INVALID_XML,
1201                                 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
1202                                 path(),
1203                                 29
1204                                 ).set_cpl_id(cpl->id()),
1205                         dcp::VerificationNote(
1206                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1207                                 ).set_cpl_id(cpl->id())
1208                 });
1209 }
1210
1211
1212 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
1213 {
1214         path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
1215         prepare_directory(dir);
1216         copy_file("test/data/subs4.xml", dir / "subs.xml");
1217         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1218         auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1219         auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1220
1221         check_verify_result (
1222                 { dir },
1223                 {},
1224                 {
1225                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1226                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1227                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1228                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1229                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1230                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1231                         dcp::VerificationNote(
1232                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get())
1233                                 ).set_cpl_id(cpl->id()),
1234                         dcp::VerificationNote(
1235                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1236                                 ).set_cpl_id(cpl->id())
1237                 });
1238
1239 }
1240
1241
1242 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
1243 {
1244         path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
1245         prepare_directory(dir);
1246         copy_file("test/data/subs5.xml", dir / "subs.xml");
1247         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1248         auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1249         auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1250
1251         check_verify_result (
1252                 { dir },
1253                 {},
1254                 {
1255                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1256                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1257                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1258                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1259                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1260                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1261                         dcp::VerificationNote(
1262                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"}
1263                                 ).set_cpl_id(cpl->id())
1264                 });
1265
1266 }
1267
1268
1269 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
1270 {
1271         path const dir("build/test/verify_valid_smpte_subtitles");
1272         prepare_directory (dir);
1273         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1274         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1275         auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1276         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1277
1278         check_verify_result(
1279                 {dir},
1280                 {},
1281                 {
1282                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1283                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1284                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1285                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1286                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1287                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1288                         dcp::VerificationNote(
1289                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1290                                 ).set_cpl_id(cpl->id()),
1291                         dcp::VerificationNote(
1292                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"}
1293                                 ).set_cpl_id(cpl->id()),
1294                         dcp::VerificationNote(
1295                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1296                                 ).set_cpl_id(cpl->id()),
1297                 });
1298 }
1299
1300
1301 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
1302 {
1303         using namespace boost::filesystem;
1304
1305         path const dir("build/test/verify_invalid_smpte_subtitles");
1306         prepare_directory (dir);
1307         /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
1308         copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
1309         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1310         auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1311         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1312
1313         check_verify_result (
1314                 { dir },
1315                 {},
1316                 {
1317                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1318                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1319                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1320                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1321                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1322                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1323                         dcp::VerificationNote(
1324                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2
1325                                 ).set_cpl_id(cpl->id()),
1326                         dcp::VerificationNote(
1327                                 dcp::VerificationNote::Type::ERROR,
1328                                 dcp::VerificationNote::Code::INVALID_XML,
1329                                 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
1330                                 path(),
1331                                 2
1332                                 ).set_cpl_id(cpl->id()),
1333                         dcp::VerificationNote(
1334                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1335                                 ).set_cpl_id(cpl->id()),
1336                         dcp::VerificationNote(
1337                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1338                                 ).set_cpl_id(cpl->id()),
1339                         dcp::VerificationNote(
1340                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"}
1341                                 ).set_cpl_id(cpl->id()),
1342                         dcp::VerificationNote(
1343                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1344                                 ).set_cpl_id(cpl->id()),
1345                 });
1346 }
1347
1348
1349 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
1350 {
1351         path const dir("build/test/verify_empty_text_node_in_subtitles");
1352         prepare_directory (dir);
1353         copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
1354         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1355         auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1356         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1357
1358         check_verify_result (
1359                 { dir },
1360                 {},
1361                 {
1362                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1363                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1364                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1365                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1366                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1367                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1368                         dcp::VerificationNote(
1369                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT
1370                                 ).set_cpl_id(cpl->id()),
1371                         dcp::VerificationNote(
1372                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1373                                 ).set_cpl_id(cpl->id()),
1374                         dcp::VerificationNote(
1375                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf")
1376                                 ).set_cpl_id(cpl->id()),
1377                         dcp::VerificationNote(
1378                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1379                                 ).set_cpl_id(cpl->id()),
1380                         dcp::VerificationNote(
1381                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"}
1382                                 ).set_cpl_id(cpl->id()),
1383                         dcp::VerificationNote(
1384                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1385                                 ).set_cpl_id(cpl->id())
1386                 });
1387 }
1388
1389
1390 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
1391 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
1392 {
1393         path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
1394         prepare_directory (dir);
1395         copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
1396         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1397         auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1398         auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
1399
1400         check_verify_result (
1401                 { dir },
1402                 {},
1403                 {
1404                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1405                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1406                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1407                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1408                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1409                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1410                         dcp::VerificationNote(
1411                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"}
1412                                 ).set_cpl_id(cpl->id())
1413                 });
1414 }
1415
1416
1417 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
1418 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
1419 {
1420         path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
1421         prepare_directory (dir);
1422         copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
1423         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1424         auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1425         auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
1426
1427         check_verify_result (
1428                 { dir },
1429                 {},
1430                 {
1431                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1432                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1433                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1434                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1435                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1436                         dcp::VerificationNote(
1437                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get())
1438                                 ).set_cpl_id(cpl->id()),
1439                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1440                         dcp::VerificationNote(
1441                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT
1442                                 ).set_cpl_id(cpl->id()),
1443                         dcp::VerificationNote(
1444                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"}
1445                                 ).set_cpl_id(cpl->id())
1446                 });
1447 }
1448
1449
1450 BOOST_AUTO_TEST_CASE (verify_external_asset)
1451 {
1452         path const ov_dir("build/test/verify_external_asset");
1453         prepare_directory (ov_dir);
1454
1455         auto image = black_image ();
1456         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
1457         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
1458         dcp_from_frame (frame, ov_dir);
1459
1460         dcp::DCP ov (ov_dir);
1461         ov.read ();
1462
1463         path const vf_dir("build/test/verify_external_asset_vf");
1464         prepare_directory (vf_dir);
1465
1466         auto picture = ov.cpls()[0]->reels()[0]->main_picture();
1467         auto cpl = write_dcp_with_single_asset (vf_dir, picture);
1468
1469         check_verify_result (
1470                 { vf_dir },
1471                 {},
1472                 {
1473                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1474                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1475                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1476                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1477                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1478                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1479                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
1480                         dcp::VerificationNote(
1481                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1482                                 ).set_cpl_id(cpl->id())
1483                 });
1484 }
1485
1486
1487 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
1488 {
1489         path const dir("build/test/verify_valid_cpl_metadata");
1490         prepare_directory (dir);
1491
1492         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1493         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1494         auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1495
1496         auto reel = make_shared<dcp::Reel>();
1497         reel->add (reel_asset);
1498
1499         reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1500         reel->add (simple_markers(16 * 24));
1501
1502         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1503         cpl->add (reel);
1504         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1505         cpl->set_main_sound_sample_rate (48000);
1506         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1507         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1508         cpl->set_version_number (1);
1509
1510         dcp::DCP dcp (dir);
1511         dcp.add (cpl);
1512         dcp.set_annotation_text("hello");
1513         dcp.write_xml ();
1514 }
1515
1516
1517 /* DCP with invalid CompositionMetadataAsset */
1518 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1519 {
1520         using namespace boost::filesystem;
1521
1522         path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1523         prepare_directory (dir);
1524
1525         auto reel = make_shared<dcp::Reel>();
1526         reel->add (black_picture_asset(dir));
1527         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1528         cpl->add (reel);
1529         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1530         cpl->set_main_sound_sample_rate (48000);
1531         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1532         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1533         cpl->set_version_number (1);
1534
1535         reel->add (simple_markers());
1536
1537         dcp::DCP dcp (dir);
1538         dcp.add (cpl);
1539         dcp.set_annotation_text("hello");
1540         dcp.write_xml();
1541
1542         HashCalculator calc(find_cpl(dir));
1543
1544         {
1545                 Editor e (find_cpl(dir));
1546                 e.replace ("MainSound", "MainSoundX");
1547         }
1548
1549         check_verify_result (
1550                 { dir },
1551                 {},
1552                 {
1553                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "pic.mxf"), cpl),
1554                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1555                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1556                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1557                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1558                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1559                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1440x1080"}, cpl),
1560                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "pic.mxf"), cpl),
1561                         dcp::VerificationNote(
1562                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50
1563                                 ).set_cpl_id(cpl->id()),
1564                         dcp::VerificationNote(
1565                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51
1566                                 ).set_cpl_id(cpl->id()),
1567                         dcp::VerificationNote(
1568                                 dcp::VerificationNote::Type::ERROR,
1569                                 dcp::VerificationNote::Code::INVALID_XML,
1570                                 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1571                                        "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1572                                        "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1573                                        "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1574                                        "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1575                                        "ExtensionMetadataList?,)'"),
1576                                 canonical(cpl->file().get()),
1577                                 71).set_cpl_id(cpl->id()),
1578                         dcp::VerificationNote(
1579                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
1580                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
1581                 });
1582 }
1583
1584
1585 /* DCP with invalid CompositionMetadataAsset */
1586 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1587 {
1588         path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1589         prepare_directory (dir);
1590
1591         auto reel = make_shared<dcp::Reel>();
1592         reel->add (black_picture_asset(dir));
1593         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1594         cpl->add (reel);
1595         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1596         cpl->set_main_sound_sample_rate (48000);
1597         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1598         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1599
1600         dcp::DCP dcp (dir);
1601         dcp.add (cpl);
1602         dcp.set_annotation_text("hello");
1603         dcp.write_xml();
1604
1605         {
1606                 Editor e (find_cpl(dir));
1607                 e.replace ("meta:Width", "meta:WidthX");
1608         }
1609
1610         check_verify_result (
1611                 { dir },
1612                 {},
1613                 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1614                 );
1615 }
1616
1617
1618 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1619 {
1620         path const dir("build/test/verify_invalid_language1");
1621         prepare_directory (dir);
1622         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1623         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1624         asset->_language = "wrong-andbad";
1625         asset->write (dir / "subs.mxf");
1626         auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1627         reel_asset->_language = "badlang";
1628         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1629
1630         check_verify_result (
1631                 { dir },
1632                 {},
1633                 {
1634                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1635                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1636                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1637                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1638                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1639                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1640                         dcp::VerificationNote(
1641                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang")
1642                                 ).set_cpl_id(cpl->id()),
1643                         dcp::VerificationNote(
1644                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad")
1645                                 ).set_cpl_id(cpl->id()),
1646                         dcp::VerificationNote(
1647                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1648                                 ).set_cpl_id(cpl->id())
1649                 });
1650 }
1651
1652
1653 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1654 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1655 {
1656         path const dir("build/test/verify_invalid_language2");
1657         prepare_directory (dir);
1658         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1659         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1660         asset->_language = "wrong-andbad";
1661         asset->write (dir / "subs.mxf");
1662         auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1663         reel_asset->_language = "badlang";
1664         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1665
1666         check_verify_result (
1667                 {dir},
1668                 {},
1669                 {
1670                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1671                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1672                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1673                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1674                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1675                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1676                         dcp::VerificationNote(
1677                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang")
1678                                 ).set_cpl_id(cpl->id()),
1679                         dcp::VerificationNote(
1680                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad")
1681                                 ).set_cpl_id(cpl->id()),
1682                         dcp::VerificationNote(
1683                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1684                                 ).set_cpl_id(cpl->id())
1685                 });
1686 }
1687
1688
1689 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1690  * the release territory.
1691  */
1692 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1693 {
1694         path const dir("build/test/verify_invalid_language3");
1695         prepare_directory (dir);
1696
1697         auto picture = simple_picture (dir, "foo");
1698         auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1699         auto reel = make_shared<dcp::Reel>();
1700         reel->add (reel_picture);
1701         auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1702         auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1703         reel->add (reel_sound);
1704         reel->add (simple_markers());
1705
1706         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1707         cpl->add (reel);
1708         cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1709         cpl->_additional_subtitle_languages.push_back("andso-is-this");
1710         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1711         cpl->set_main_sound_sample_rate (48000);
1712         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1713         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1714         cpl->set_version_number (1);
1715         cpl->_release_territory = "fred-jim";
1716         auto dcp = make_shared<dcp::DCP>(dir);
1717         dcp->add (cpl);
1718         dcp->set_annotation_text("hello");
1719         dcp->write_xml();
1720
1721         check_verify_result (
1722                 { dir },
1723                 {},
1724                 {
1725                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "videofoo.mxf"), cpl),
1726                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1727                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1728                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1729                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1730                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1731                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
1732                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1440x1080"}, cpl),
1733                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "videofoo.mxf"), cpl),
1734                         dcp::VerificationNote(
1735                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong")
1736                                 ).set_cpl_id(cpl->id()),
1737                         dcp::VerificationNote(
1738                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this")
1739                                 ).set_cpl_id(cpl->id()),
1740                         dcp::VerificationNote(
1741                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim")
1742                                 ).set_cpl_id(cpl->id()),
1743                         dcp::VerificationNote(
1744                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz")
1745                                 ).set_cpl_id(cpl->id()),
1746                 });
1747 }
1748
1749
1750 static
1751 std::tuple<vector<dcp::VerificationNote>, shared_ptr<dcp::CPL>, boost::filesystem::path>
1752 check_picture_size (int width, int height, int frame_rate, bool three_d)
1753 {
1754         using namespace boost::filesystem;
1755
1756         path dcp_path = "build/test/verify_picture_test";
1757         prepare_directory (dcp_path);
1758
1759         shared_ptr<dcp::PictureAsset> mp;
1760         if (three_d) {
1761                 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1762         } else {
1763                 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1764         }
1765         auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1766
1767         auto image = black_image (dcp::Size(width, height));
1768         auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1769         int const length = three_d ? frame_rate * 2 : frame_rate;
1770         for (int i = 0; i < length; ++i) {
1771                 picture_writer->write (j2c.data(), j2c.size());
1772         }
1773         picture_writer->finalize ();
1774
1775         auto d = make_shared<dcp::DCP>(dcp_path);
1776         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1777         cpl->set_annotation_text ("A Test DCP");
1778         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1779         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1780         cpl->set_main_sound_sample_rate (48000);
1781         cpl->set_main_picture_stored_area(dcp::Size(width, height));
1782         cpl->set_main_picture_active_area(dcp::Size(width, height));
1783         cpl->set_version_number (1);
1784
1785         auto reel = make_shared<dcp::Reel>();
1786
1787         if (three_d) {
1788                 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1789         } else {
1790                 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1791         }
1792
1793         reel->add (simple_markers(frame_rate));
1794
1795         cpl->add (reel);
1796
1797         d->add (cpl);
1798         d->set_annotation_text("A Test DCP");
1799         d->write_xml();
1800
1801         return { dcp::verify({dcp_path}, {}, &stage, &progress, {}, xsd_test).notes, cpl, dcp_path };
1802 }
1803
1804
1805 static
1806 void
1807 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1808 {
1809         vector<dcp::VerificationNote> notes;
1810         shared_ptr<dcp::CPL> cpl;
1811         boost::filesystem::path dir;
1812         std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1813
1814         std::vector<dcp::VerificationNote> expected = {
1815                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1816                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1817                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1818                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
1819                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1820                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1821                 ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, dcp::String::compose("%1x%2", width, height), cpl),
1822                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
1823                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl)
1824         };
1825         check_verify_result(notes, expected);
1826 }
1827
1828
1829 static
1830 void
1831 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1832 {
1833         vector<dcp::VerificationNote> notes;
1834         shared_ptr<dcp::CPL> cpl;
1835         boost::filesystem::path dir;
1836         std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1837
1838         std::vector<dcp::VerificationNote> expected = {
1839                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1840                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1841                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1842                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
1843                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1844                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1845                 ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, dcp::String::compose("%1x%2", width, height), cpl),
1846                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
1847                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
1848                 dcp::VerificationNote(
1849                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS, dcp::String::compose("%1x%2", width, height), canonical(dir / "video.mxf")
1850                         ).set_cpl_id(cpl->id())
1851         };
1852         check_verify_result(notes, expected);
1853 }
1854
1855
1856 static
1857 void
1858 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1859 {
1860         vector<dcp::VerificationNote> notes;
1861         shared_ptr<dcp::CPL> cpl;
1862         boost::filesystem::path dir;
1863         std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1864
1865         std::vector<dcp::VerificationNote> expected = {
1866                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1867                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1868                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1869                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
1870                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1871                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1872                 ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, dcp::String::compose("%1x%2", width, height), cpl),
1873                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
1874                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
1875                 dcp::VerificationNote(
1876                         dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, dcp::String::compose("%1/1", frame_rate * (three_d ? 2 : 1))
1877                         ).set_cpl_id(cpl->id()),
1878                 dcp::VerificationNote(
1879                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K, dcp::String::compose("%1/1", frame_rate), canonical(dir / "video.mxf")
1880                         ).set_cpl_id(cpl->id())
1881         };
1882
1883         check_verify_result(notes, expected);
1884 }
1885
1886
1887 static
1888 void
1889 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1890 {
1891         vector<dcp::VerificationNote> notes;
1892         shared_ptr<dcp::CPL> cpl;
1893         boost::filesystem::path dir;
1894         std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1895
1896         std::vector<dcp::VerificationNote> expected = {
1897                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1898                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1899                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1900                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
1901                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1902                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1903                 ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, dcp::String::compose("%1x%2", width, height), cpl),
1904                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
1905                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
1906                 dcp::VerificationNote(
1907                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K, dcp::String::compose("%1/1", frame_rate), canonical(dir / "video.mxf")
1908                         ).set_cpl_id(cpl->id())
1909         };
1910
1911         check_verify_result(notes, expected);
1912 }
1913
1914
1915 BOOST_AUTO_TEST_CASE (verify_picture_size)
1916 {
1917         using namespace boost::filesystem;
1918
1919         /* 2K scope */
1920         check_picture_size_ok (2048, 858, 24, false);
1921         check_picture_size_ok (2048, 858, 25, false);
1922         check_picture_size_ok (2048, 858, 48, false);
1923         check_picture_size_ok (2048, 858, 24, true);
1924         check_picture_size_ok (2048, 858, 25, true);
1925         check_picture_size_ok (2048, 858, 48, true);
1926
1927         /* 2K flat */
1928         check_picture_size_ok (1998, 1080, 24, false);
1929         check_picture_size_ok (1998, 1080, 25, false);
1930         check_picture_size_ok (1998, 1080, 48, false);
1931         check_picture_size_ok (1998, 1080, 24, true);
1932         check_picture_size_ok (1998, 1080, 25, true);
1933         check_picture_size_ok (1998, 1080, 48, true);
1934
1935         /* 4K scope */
1936         check_picture_size_ok (4096, 1716, 24, false);
1937
1938         /* 4K flat */
1939         check_picture_size_ok (3996, 2160, 24, false);
1940
1941         /* Bad frame size */
1942         check_picture_size_bad_frame_size (2050, 858, 24, false);
1943         check_picture_size_bad_frame_size (2048, 658, 25, false);
1944         check_picture_size_bad_frame_size (1920, 1080, 48, true);
1945         check_picture_size_bad_frame_size (4000, 2000, 24, true);
1946
1947         /* Bad 2K frame rate */
1948         check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1949         check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1950         check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1951
1952         /* Bad 4K frame rate */
1953         check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1954         check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1955
1956         /* No 4K 3D */
1957         vector<dcp::VerificationNote> notes;
1958         shared_ptr<dcp::CPL> cpl;
1959         boost::filesystem::path dir;
1960         std::tie(notes, cpl, dir) = check_picture_size(3996, 2160, 24, true);
1961
1962         std::vector<dcp::VerificationNote> expected = {
1963                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1964                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
1965                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
1966                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
1967                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
1968                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
1969                 ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, dcp::String::compose("%1x%2", 3996, 2160), cpl),
1970                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
1971                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
1972                 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D },
1973         };
1974 }
1975
1976
1977 static
1978 void
1979 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, dcp::VAlign v_align = dcp::VAlign::CENTER, string text = "Hello")
1980 {
1981         asset->add (
1982                 std::make_shared<dcp::SubtitleString>(
1983                         optional<string>(),
1984                         false,
1985                         false,
1986                         false,
1987                         dcp::Colour(),
1988                         42,
1989                         1,
1990                         dcp::Time(start_frame, 24, 24),
1991                         dcp::Time(end_frame, 24, 24),
1992                         0,
1993                         dcp::HAlign::CENTER,
1994                         v_position,
1995                         v_align,
1996                         0,
1997                         dcp::Direction::LTR,
1998                         text,
1999                         dcp::Effect::NONE,
2000                         dcp::Colour(),
2001                         dcp::Time(),
2002                         dcp::Time(),
2003                         0,
2004                         std::vector<dcp::Ruby>()
2005                 )
2006         );
2007 }
2008
2009
2010 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
2011 {
2012         path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
2013         prepare_directory (dir);
2014
2015         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2016         for (int i = 0; i < 2048; ++i) {
2017                 add_test_subtitle (asset, i * 24, i * 24 + 20);
2018         }
2019         add_font(asset);
2020         asset->set_language (dcp::LanguageTag("de-DE"));
2021         asset->write (dir / "subs.mxf");
2022         auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
2023         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
2024
2025         check_verify_result (
2026                 { dir },
2027                 {},
2028                 {
2029                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2030                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2031                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2032                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2033                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2034                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2035                         dcp::VerificationNote(
2036                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
2037                                 ).set_cpl_id(cpl->id()),
2038                         dcp::VerificationNote(
2039                                 dcp::VerificationNote::Type::BV21_ERROR,
2040                                 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
2041                                 string("419371"),
2042                                 canonical(dir / "subs.mxf")
2043                                 ).set_cpl_id(cpl->id()),
2044                         dcp::VerificationNote(
2045                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2046                                 ).set_cpl_id(cpl->id()),
2047                         dcp::VerificationNote(
2048                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2049                                 ).set_cpl_id(cpl->id())
2050                 });
2051 }
2052
2053
2054 static
2055 shared_ptr<dcp::SMPTESubtitleAsset>
2056 make_large_subtitle_asset (path font_file)
2057 {
2058         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2059         dcp::ArrayData big_fake_font(1024 * 1024);
2060         big_fake_font.write (font_file);
2061         for (int i = 0; i < 116; ++i) {
2062                 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
2063         }
2064         return asset;
2065 }
2066
2067
2068 template <class T>
2069 void
2070 verify_timed_text_asset_too_large (string name)
2071 {
2072         auto const dir = path("build/test") / name;
2073         prepare_directory (dir);
2074         auto asset = make_large_subtitle_asset (dir / "font.ttf");
2075         add_test_subtitle (asset, 0, 240);
2076         asset->set_language (dcp::LanguageTag("de-DE"));
2077         asset->write (dir / "subs.mxf");
2078
2079         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
2080         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
2081
2082         check_verify_result (
2083                 { dir },
2084                 {},
2085                 {
2086                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2087                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2088                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2089                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2090                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2091                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2092                         dcp::VerificationNote(
2093                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, dcp::raw_convert<string>(121698284), canonical(dir / "subs.mxf")
2094                                 ).set_cpl_id(cpl->id()),
2095                         dcp::VerificationNote(
2096                                 dcp::VerificationNote::Type::BV21_ERROR,
2097                                 dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES,
2098                                 dcp::raw_convert<string>(121634816),
2099                                 canonical(dir / "subs.mxf")
2100                                 ).set_cpl_id(cpl->id()),
2101                         dcp::VerificationNote(
2102                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
2103                                 ).set_cpl_id(cpl->id()),
2104                         dcp::VerificationNote(
2105                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2106                                 ).set_cpl_id(cpl->id()),
2107                         dcp::VerificationNote(
2108                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2109                                 ).set_cpl_id(cpl->id())
2110                 });
2111 }
2112
2113
2114 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
2115 {
2116         verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
2117         verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
2118 }
2119
2120
2121 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
2122 {
2123         path dir = "build/test/verify_missing_subtitle_language";
2124         prepare_directory (dir);
2125         auto dcp = make_simple (dir, 1, 106);
2126
2127         string const xml =
2128                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2129                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
2130                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
2131                 "<ContentTitleText>Content</ContentTitleText>"
2132                 "<AnnotationText>Annotation</AnnotationText>"
2133                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2134                 "<ReelNumber>1</ReelNumber>"
2135                 "<EditRate>24 1</EditRate>"
2136                 "<TimeCodeRate>24</TimeCodeRate>"
2137                 "<StartTime>00:00:00:00</StartTime>"
2138                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
2139                 "<SubtitleList>"
2140                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2141                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2142                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2143                 "</Subtitle>"
2144                 "</Font>"
2145                 "</SubtitleList>"
2146                 "</SubtitleReel>";
2147
2148         dcp::File xml_file(dir / "subs.xml", "w");
2149         BOOST_REQUIRE (xml_file);
2150         xml_file.write(xml.c_str(), xml.size(), 1);
2151         xml_file.close();
2152         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2153         subs->write (dir / "subs.mxf");
2154
2155         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
2156         auto cpl = dcp->cpls()[0];
2157         cpl->reels()[0]->add(reel_subs);
2158         dcp->write_xml();
2159
2160         check_verify_result (
2161                 { dir },
2162                 {},
2163                 {
2164                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2165                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2166                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2167                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2168                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2169                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
2170                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
2171                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
2172                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
2173                         dcp::VerificationNote(
2174                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf")
2175                                 ).set_cpl_id(cpl->id()),
2176                         dcp::VerificationNote(
2177                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2178                                 ).set_cpl_id(cpl->id())
2179                 });
2180 }
2181
2182
2183 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
2184 {
2185         path path ("build/test/verify_mismatched_subtitle_languages");
2186         auto constexpr reel_length = 192;
2187         auto dcp = make_simple (path, 2, reel_length);
2188         auto cpl = dcp->cpls()[0];
2189
2190         {
2191                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2192                 subs->set_language (dcp::LanguageTag("de-DE"));
2193                 subs->add (simple_subtitle());
2194                 add_font(subs);
2195                 subs->write (path / "subs1.mxf");
2196                 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2197                 cpl->reels()[0]->add(reel_subs);
2198         }
2199
2200         {
2201                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2202                 subs->set_language (dcp::LanguageTag("en-US"));
2203                 subs->add (simple_subtitle());
2204                 add_font(subs);
2205                 subs->write (path / "subs2.mxf");
2206                 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2207                 cpl->reels()[1]->add(reel_subs);
2208         }
2209
2210         dcp->write_xml();
2211
2212         check_verify_result (
2213                 { path },
2214                 {},
2215                 {
2216                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video0.mxf"), cpl),
2217                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video1.mxf"), cpl),
2218                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2219                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2220                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2221                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2222                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2223                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
2224                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
2225                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "video0.mxf"), cpl),
2226                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "video1.mxf"), cpl),
2227                         dcp::VerificationNote(
2228                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf")
2229                                 ).set_cpl_id(cpl->id()),
2230                         dcp::VerificationNote(
2231                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf")
2232                                 ).set_cpl_id(cpl->id()),
2233                         dcp::VerificationNote(
2234                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES
2235                                 ).set_cpl_id(cpl->id()),
2236                 });
2237 }
2238
2239
2240 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
2241 {
2242         path path ("build/test/verify_multiple_closed_caption_languages_allowed");
2243         auto constexpr reel_length = 192;
2244         auto dcp = make_simple (path, 2, reel_length);
2245         auto cpl = dcp->cpls()[0];
2246
2247         {
2248                 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
2249                 ccaps->set_language (dcp::LanguageTag("de-DE"));
2250                 ccaps->add (simple_subtitle());
2251                 add_font(ccaps);
2252                 ccaps->write (path / "subs1.mxf");
2253                 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
2254                 cpl->reels()[0]->add(reel_ccaps);
2255         }
2256
2257         {
2258                 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
2259                 ccaps->set_language (dcp::LanguageTag("en-US"));
2260                 ccaps->add (simple_subtitle());
2261                 add_font(ccaps);
2262                 ccaps->write (path / "subs2.mxf");
2263                 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
2264                 cpl->reels()[1]->add(reel_ccaps);
2265         }
2266
2267         dcp->write_xml();
2268
2269         check_verify_result (
2270                 { path },
2271                 {},
2272                 {
2273                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2274                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2275                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2276                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2277                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2278                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
2279                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
2280                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video0.mxf"), cpl),
2281                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video1.mxf"), cpl),
2282                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "video0.mxf"), cpl),
2283                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "video1.mxf"), cpl),
2284                         dcp::VerificationNote(
2285                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf")
2286                                 ).set_cpl_id(cpl->id()),
2287                         dcp::VerificationNote(
2288                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf")
2289                                 ).set_cpl_id(cpl->id())
2290                 });
2291 }
2292
2293
2294 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
2295 {
2296         path dir = "build/test/verify_missing_subtitle_start_time";
2297         prepare_directory (dir);
2298         auto dcp = make_simple (dir, 1, 106);
2299
2300         string const xml =
2301                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2302                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
2303                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
2304                 "<ContentTitleText>Content</ContentTitleText>"
2305                 "<AnnotationText>Annotation</AnnotationText>"
2306                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2307                 "<ReelNumber>1</ReelNumber>"
2308                 "<Language>de-DE</Language>"
2309                 "<EditRate>24 1</EditRate>"
2310                 "<TimeCodeRate>24</TimeCodeRate>"
2311                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
2312                 "<SubtitleList>"
2313                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2314                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2315                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2316                 "</Subtitle>"
2317                 "</Font>"
2318                 "</SubtitleList>"
2319                 "</SubtitleReel>";
2320
2321         dcp::File xml_file(dir / "subs.xml", "w");
2322         BOOST_REQUIRE (xml_file);
2323         xml_file.write(xml.c_str(), xml.size(), 1);
2324         xml_file.close();
2325         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2326         subs->write (dir / "subs.mxf");
2327
2328         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
2329         auto cpl = dcp->cpls()[0];
2330         cpl->reels()[0]->add(reel_subs);
2331         dcp->write_xml();
2332
2333         check_verify_result (
2334                 { dir },
2335                 {},
2336                 {
2337                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
2338                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2339                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2340                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2341                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2342                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2343                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
2344                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
2345                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
2346                         dcp::VerificationNote(
2347                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
2348                                 ).set_cpl_id(cpl->id()),
2349                         dcp::VerificationNote(
2350                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2351                                 ).set_cpl_id(cpl->id())
2352                 });
2353 }
2354
2355
2356 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
2357 {
2358         path dir = "build/test/verify_invalid_subtitle_start_time";
2359         prepare_directory (dir);
2360         auto dcp = make_simple (dir, 1, 106);
2361
2362         string const xml =
2363                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2364                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
2365                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
2366                 "<ContentTitleText>Content</ContentTitleText>"
2367                 "<AnnotationText>Annotation</AnnotationText>"
2368                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2369                 "<ReelNumber>1</ReelNumber>"
2370                 "<Language>de-DE</Language>"
2371                 "<EditRate>24 1</EditRate>"
2372                 "<TimeCodeRate>24</TimeCodeRate>"
2373                 "<StartTime>00:00:02:00</StartTime>"
2374                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
2375                 "<SubtitleList>"
2376                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2377                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2378                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2379                 "</Subtitle>"
2380                 "</Font>"
2381                 "</SubtitleList>"
2382                 "</SubtitleReel>";
2383
2384         dcp::File xml_file(dir / "subs.xml", "w");
2385         BOOST_REQUIRE (xml_file);
2386         xml_file.write(xml.c_str(), xml.size(), 1);
2387         xml_file.close();
2388         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2389         subs->write (dir / "subs.mxf");
2390
2391         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
2392         auto cpl = dcp->cpls()[0];
2393         cpl->reels().front()->add(reel_subs);
2394         dcp->write_xml();
2395
2396         check_verify_result (
2397                 { dir },
2398                 {},
2399                 {
2400                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2401                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2402                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
2403                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2404                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2405                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2406                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
2407                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
2408                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
2409                         dcp::VerificationNote(
2410                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
2411                                 ).set_cpl_id(cpl->id()),
2412                         dcp::VerificationNote(
2413                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2414                                 ).set_cpl_id(cpl->id())
2415                 });
2416 }
2417
2418
2419 class TestText
2420 {
2421 public:
2422         TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
2423                 : in(in_)
2424                 , out(out_)
2425                 , v_position(v_position_)
2426                 , v_align(v_align_)
2427                 , text(text_)
2428         {}
2429
2430         int in;
2431         int out;
2432         float v_position;
2433         dcp::VAlign v_align;
2434         string text;
2435 };
2436
2437
2438 template <class T>
2439 shared_ptr<dcp::CPL>
2440 dcp_with_text(path dir, vector<TestText> subs, optional<dcp::Key> key = boost::none, optional<string> key_id = boost::none)
2441 {
2442         prepare_directory (dir);
2443         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2444         asset->set_start_time (dcp::Time());
2445         for (auto i: subs) {
2446                 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
2447         }
2448         asset->set_language (dcp::LanguageTag("de-DE"));
2449         if (key && key_id) {
2450                 asset->set_key(*key);
2451                 asset->set_key_id(*key_id);
2452         }
2453         add_font(asset);
2454         asset->write (dir / "subs.mxf");
2455
2456         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
2457         return write_dcp_with_single_asset (dir, reel_asset);
2458 }
2459
2460
2461 template <class T>
2462 shared_ptr<dcp::CPL>
2463 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
2464 {
2465         prepare_directory (dir);
2466         auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
2467         asset->set_start_time (dcp::Time());
2468         asset->set_language (dcp::LanguageTag("de-DE"));
2469
2470         auto subs_mxf = dir / "subs.mxf";
2471         asset->write (subs_mxf);
2472
2473         /* The call to write() puts the asset into the DCP correctly but it will have
2474          * XML re-written by our parser.  Overwrite the MXF using the given file's verbatim
2475          * contents.
2476          */
2477         ASDCP::TimedText::MXFWriter writer;
2478         ASDCP::WriterInfo writer_info;
2479         writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2480         unsigned int c;
2481         Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2482         DCP_ASSERT (c == Kumu::UUID_Length);
2483         ASDCP::TimedText::TimedTextDescriptor descriptor;
2484         descriptor.ContainerDuration = asset->intrinsic_duration();
2485         Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
2486         DCP_ASSERT (c == Kumu::UUID_Length);
2487         ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
2488         BOOST_REQUIRE (!ASDCP_FAILURE(r));
2489         r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
2490         BOOST_REQUIRE (!ASDCP_FAILURE(r));
2491         writer.Finalize ();
2492
2493         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
2494         return write_dcp_with_single_asset (dir, reel_asset);
2495 }
2496
2497
2498 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
2499 {
2500         auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
2501         /* Just too early */
2502         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
2503         check_verify_result (
2504                 { dir },
2505                 {},
2506                 {
2507                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2508                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2509                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2510                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2511                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2512                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2513                         dcp::VerificationNote(
2514                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2515                                 ).set_cpl_id(cpl->id()),
2516                         dcp::VerificationNote(
2517                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2518                                 ).set_cpl_id(cpl->id())
2519                 });
2520
2521 }
2522
2523
2524 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
2525 {
2526         auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
2527         /* Just late enough */
2528         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
2529         check_verify_result(
2530                 {dir},
2531                 {},
2532                 {
2533                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2534                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2535                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2536                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2537                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2538                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2539                         dcp::VerificationNote(
2540                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2541                                 ).set_cpl_id(cpl->id())
2542                 });
2543 }
2544
2545
2546 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
2547 {
2548         auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
2549         prepare_directory (dir);
2550
2551         auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
2552         asset1->set_start_time (dcp::Time());
2553         /* Just late enough */
2554         add_test_subtitle (asset1, 4 * 24, 5 * 24);
2555         asset1->set_language (dcp::LanguageTag("de-DE"));
2556         add_font(asset1);
2557         asset1->write (dir / "subs1.mxf");
2558         auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
2559         auto reel1 = make_shared<dcp::Reel>();
2560         reel1->add (reel_asset1);
2561         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
2562         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2563         reel1->add (markers1);
2564
2565         auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
2566         asset2->set_start_time (dcp::Time());
2567         add_font(asset2);
2568         /* This would be too early on first reel but should be OK on the second */
2569         add_test_subtitle (asset2, 3, 4 * 24);
2570         asset2->set_language (dcp::LanguageTag("de-DE"));
2571         asset2->write (dir / "subs2.mxf");
2572         auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
2573         auto reel2 = make_shared<dcp::Reel>();
2574         reel2->add (reel_asset2);
2575         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
2576         markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
2577         reel2->add (markers2);
2578
2579         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2580         cpl->add (reel1);
2581         cpl->add (reel2);
2582         auto dcp = make_shared<dcp::DCP>(dir);
2583         dcp->add (cpl);
2584         dcp->set_annotation_text("hello");
2585         dcp->write_xml();
2586
2587         check_verify_result(
2588                 {dir},
2589                 {},
2590                 {
2591                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2592                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2593                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2594                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2595                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2596                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2597                         dcp::VerificationNote(
2598                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2599                                 ).set_cpl_id(cpl->id())
2600                 });
2601 }
2602
2603
2604 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
2605 {
2606         auto const dir = path("build/test/verify_invalid_subtitle_spacing");
2607         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2608                 dir,
2609                 {
2610                         { 4 * 24,     5 * 24 },
2611                         { 5 * 24 + 1, 6 * 24 },
2612                 });
2613         check_verify_result (
2614                 {dir},
2615                 {},
2616                 {
2617                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2618                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2619                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2620                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2621                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2622                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2623                         dcp::VerificationNote(
2624                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING
2625                                 ).set_cpl_id(cpl->id()),
2626                         dcp::VerificationNote(
2627                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2628                                 ).set_cpl_id(cpl->id())
2629                 });
2630 }
2631
2632
2633 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
2634 {
2635         auto const dir = path("build/test/verify_valid_subtitle_spacing");
2636         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2637                 dir,
2638                 {
2639                         { 4 * 24,      5 * 24 },
2640                         { 5 * 24 + 16, 8 * 24 },
2641                 });
2642
2643         check_verify_result(
2644                 {dir},
2645                 {},
2646                 {
2647                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2648                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2649                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2650                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2651                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2652                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2653                         dcp::VerificationNote(
2654                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2655                                 ).set_cpl_id(cpl->id())
2656                 });
2657 }
2658
2659
2660 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
2661 {
2662         auto const dir = path("build/test/verify_invalid_subtitle_duration");
2663         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
2664         check_verify_result (
2665                 {dir},
2666                 {},
2667                 {
2668                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2669                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2670                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2671                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2672                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2673                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2674                         dcp::VerificationNote(
2675                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION
2676                                 ).set_cpl_id(cpl->id()),
2677                         dcp::VerificationNote(
2678                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2679                                 ).set_cpl_id(cpl->id())
2680                 });
2681 }
2682
2683
2684 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
2685 {
2686         auto const dir = path("build/test/verify_valid_subtitle_duration");
2687         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
2688
2689         check_verify_result(
2690                 {dir},
2691                 {},
2692                 {
2693                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2694                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2695                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2696                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2697                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2698                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2699                         dcp::VerificationNote(
2700                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2701                                 ).set_cpl_id(cpl->id())
2702                 });
2703 }
2704
2705
2706 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
2707 {
2708         auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
2709         prepare_directory (dir);
2710         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2711         asset->set_start_time (dcp::Time());
2712         add_test_subtitle (asset, 0, 4 * 24);
2713         add_font(asset);
2714         asset->set_language (dcp::LanguageTag("de-DE"));
2715         asset->write (dir / "subs.mxf");
2716
2717         auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
2718         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
2719         check_verify_result (
2720                 {dir},
2721                 {},
2722                 {
2723                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2724                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2725                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2726                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2727                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2728                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2729                         dcp::VerificationNote(
2730                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get())
2731                                 ).set_cpl_id(cpl->id()),
2732                         dcp::VerificationNote(
2733                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2734                                 ).set_cpl_id(cpl->id()),
2735                         dcp::VerificationNote(
2736                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY
2737                                 ).set_cpl_id(cpl->id()),
2738                         dcp::VerificationNote(
2739                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2740                                 ).set_cpl_id(cpl->id())
2741                 });
2742
2743 }
2744
2745
2746 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
2747 {
2748         auto const dir = path ("build/test/invalid_subtitle_line_count1");
2749         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2750                 dir,
2751                 {
2752                         { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2753                         { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2754                         { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2755                         { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2756                 });
2757         check_verify_result (
2758                 {dir},
2759                 {},
2760                 {
2761                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2762                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2763                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2764                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2765                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2766                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2767                         dcp::VerificationNote(
2768                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT
2769                                 ).set_cpl_id(cpl->id()),
2770                         dcp::VerificationNote(
2771                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2772                                 ).set_cpl_id(cpl->id())
2773                 });
2774 }
2775
2776
2777 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
2778 {
2779         auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
2780         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2781                 dir,
2782                 {
2783                         { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2784                         { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2785                         { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2786                 });
2787
2788         check_verify_result(
2789                 {dir},
2790                 {},
2791                 {
2792                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2793                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2794                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2795                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2796                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2797                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2798                         dcp::VerificationNote(
2799                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2800                                 ).set_cpl_id(cpl->id())
2801                 });
2802 }
2803
2804
2805 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
2806 {
2807         auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
2808         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2809                 dir,
2810                 {
2811                         { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2812                         { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2813                         { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2814                         { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2815                 });
2816         check_verify_result (
2817                 {dir},
2818                 {},
2819                 {
2820                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2821                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2822                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2823                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2824                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2825                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2826                         dcp::VerificationNote(
2827                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT
2828                                 ).set_cpl_id(cpl->id()),
2829                         dcp::VerificationNote(
2830                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2831                                 ).set_cpl_id(cpl->id())
2832                 });
2833 }
2834
2835
2836 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
2837 {
2838         auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
2839         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2840                 dir,
2841                 {
2842                         { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2843                         { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2844                         { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2845                         { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2846                 });
2847
2848         check_verify_result(
2849                 {dir},
2850                 {},
2851                 {
2852                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2853                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2854                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2855                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2856                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2857                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2858                         dcp::VerificationNote(
2859                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2860                                 ).set_cpl_id(cpl->id())
2861                 });
2862 }
2863
2864
2865 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
2866 {
2867         auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
2868         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2869                 dir,
2870                 {
2871                         { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2872                 });
2873         check_verify_result (
2874                 {dir},
2875                 {},
2876                 {
2877                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2878                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2879                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2880                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2881                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2882                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2883                         dcp::VerificationNote(
2884                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH
2885                                 ).set_cpl_id(cpl->id()),
2886                         dcp::VerificationNote(
2887                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2888                                 ).set_cpl_id(cpl->id())
2889                 });
2890 }
2891
2892
2893 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2894 {
2895         auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2896         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2897                 dir,
2898                 {
2899                         { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2900                 });
2901         check_verify_result (
2902                 {dir},
2903                 {},
2904                 {
2905                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2906                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2907                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2908                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2909                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2910                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2911                         dcp::VerificationNote(
2912                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH
2913                                 ).set_cpl_id(cpl->id()),
2914                         dcp::VerificationNote(
2915                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2916                                 ).set_cpl_id(cpl->id())
2917                 });
2918 }
2919
2920
2921 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2922 {
2923         auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2924         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2925                 dir,
2926                 {
2927                         { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2928                         { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2929                         { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2930                         { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2931                 });
2932         check_verify_result (
2933                 {dir},
2934                 {},
2935                 {
2936                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2937                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2938                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2939                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2940                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2941                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2942                         dcp::VerificationNote(
2943                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT
2944                                 ).set_cpl_id(cpl->id()),
2945                         dcp::VerificationNote(
2946                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2947                                 ).set_cpl_id(cpl->id())
2948                 });
2949 }
2950
2951
2952 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2953 {
2954         auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2955         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2956                 dir,
2957                 {
2958                         { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2959                         { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2960                         { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2961                 });
2962
2963         check_verify_result(
2964                 {dir},
2965                 {},
2966                 {
2967                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2968                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2969                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2970                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2971                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
2972                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
2973                         dcp::VerificationNote(
2974                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2975                                 ).set_cpl_id(cpl->id())
2976                 });
2977 }
2978
2979
2980 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2981 {
2982         auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2983         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2984                 dir,
2985                 {
2986                         { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2987                         { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2988                         { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2989                         { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2990                 });
2991         check_verify_result (
2992                 {dir},
2993                 {},
2994                 {
2995                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2996                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
2997                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
2998                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
2999                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3000                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3001                         dcp::VerificationNote(
3002                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT
3003                                 ).set_cpl_id(cpl->id()),
3004                         dcp::VerificationNote(
3005                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3006                                 ).set_cpl_id(cpl->id())
3007                 });
3008 }
3009
3010
3011 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
3012 {
3013         auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
3014         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
3015                 dir,
3016                 {
3017                         { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
3018                         { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
3019                         { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
3020                         { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
3021                 });
3022
3023         check_verify_result(
3024                 {dir},
3025                 {},
3026                 {
3027                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3028                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3029                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3030                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3031                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3032                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3033                         dcp::VerificationNote(
3034                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3035                                 ).set_cpl_id(cpl->id())
3036                 });
3037 }
3038
3039
3040 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
3041 {
3042         auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
3043         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
3044                 dir,
3045                 {
3046                         { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
3047                 });
3048
3049         check_verify_result (
3050                 {dir},
3051                 {},
3052                 {
3053                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3054                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3055                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3056                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3057                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3058                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3059                         dcp::VerificationNote(
3060                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3061                                 ).set_cpl_id(cpl->id())
3062                 });
3063 }
3064
3065
3066 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
3067 {
3068         auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
3069         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
3070                 dir,
3071                 {
3072                         { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
3073                 });
3074         check_verify_result (
3075                 {dir},
3076                 {},
3077                 {
3078                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3079                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3080                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3081                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3082                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3083                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3084                         dcp::VerificationNote(
3085                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH
3086                                 ).set_cpl_id(cpl->id()),
3087                         dcp::VerificationNote(
3088                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3089                                 ).set_cpl_id(cpl->id())
3090                 });
3091 }
3092
3093
3094 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
3095 {
3096         auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
3097         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
3098                 dir,
3099                 {
3100                         { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
3101                         { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
3102                         { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
3103                 });
3104         check_verify_result (
3105                 {dir},
3106                 {},
3107                 {
3108                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3109                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3110                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3111                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3112                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3113                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3114                         dcp::VerificationNote(
3115                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3116                                 ).set_cpl_id(cpl->id())
3117                 });
3118 }
3119
3120
3121 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
3122 {
3123         auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
3124         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
3125                 dir,
3126                 {
3127                         { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
3128                         { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
3129                         { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
3130                 });
3131         check_verify_result (
3132                 {dir},
3133                 {},
3134                 {
3135                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3136                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3137                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3138                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3139                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3140                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3141                         dcp::VerificationNote(
3142                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN
3143                                 ).set_cpl_id(cpl->id()),
3144                         dcp::VerificationNote(
3145                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3146                                 ).set_cpl_id(cpl->id())
3147                 });
3148 }
3149
3150
3151 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
3152 {
3153         auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
3154         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
3155                 dir,
3156                 {
3157                         { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
3158                         { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
3159                         { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
3160                 });
3161
3162         check_verify_result(
3163                 {dir},
3164                 {},
3165                 {
3166                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3167                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3168                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3169                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3170                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3171                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3172                         dcp::VerificationNote(
3173                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3174                                 ).set_cpl_id(cpl->id())
3175                 });
3176 }
3177
3178
3179 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
3180 {
3181         auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
3182         auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
3183                 dir,
3184                 {
3185                         { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
3186                         { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
3187                         { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
3188                 });
3189
3190         check_verify_result(
3191                 {dir},
3192                 {},
3193                 {
3194                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3195                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3196                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3197                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3198                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3199                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3200                         dcp::VerificationNote(
3201                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3202                                 ).set_cpl_id(cpl->id())
3203                 });
3204 }
3205
3206
3207 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
3208 {
3209         auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
3210         auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
3211         check_verify_result (
3212                 {dir},
3213                 {},
3214                 {
3215                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3216                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3217                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3218                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3219                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3220                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3221                         dcp::VerificationNote(
3222                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING
3223                                 ).set_cpl_id(cpl->id()),
3224                         dcp::VerificationNote(
3225                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3226                                 ).set_cpl_id(cpl->id())
3227                 });
3228 }
3229
3230
3231 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
3232 {
3233         auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
3234         auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
3235
3236         check_verify_result(
3237                 {dir},
3238                 {},
3239                 {
3240                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3241                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3242                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3243                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3244                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3245                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3246                         dcp::VerificationNote(
3247                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3248                                 ).set_cpl_id(cpl->id())
3249                 });
3250 }
3251
3252
3253
3254 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
3255 {
3256         path const dir("build/test/verify_invalid_sound_frame_rate");
3257         prepare_directory (dir);
3258
3259         auto picture = simple_picture (dir, "foo");
3260         auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
3261         auto reel = make_shared<dcp::Reel>();
3262         reel->add (reel_picture);
3263         auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
3264         auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
3265         reel->add (reel_sound);
3266         reel->add (simple_markers());
3267         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3268         cpl->add (reel);
3269         auto dcp = make_shared<dcp::DCP>(dir);
3270         dcp->add (cpl);
3271         dcp->set_annotation_text("hello");
3272         dcp->write_xml();
3273
3274         check_verify_result (
3275                 {dir},
3276                 {},
3277                 {
3278                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3279                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3280                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
3281                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3282                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3283                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "videofoo.mxf"), cpl),
3284                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3285                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "videofoo.mxf"), cpl),
3286                         dcp::VerificationNote(
3287                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf")
3288                                 ).set_cpl_id(cpl->id()),
3289                         dcp::VerificationNote(
3290                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3291                                 ).set_cpl_id(cpl->id())
3292                 });
3293 }
3294
3295
3296 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
3297 {
3298         path const dir("build/test/verify_missing_cpl_annotation_text");
3299         auto dcp = make_simple (dir);
3300         dcp->write_xml();
3301
3302         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3303
3304         auto const cpl = dcp->cpls()[0];
3305
3306         HashCalculator calc(cpl->file().get());
3307
3308         {
3309                 BOOST_REQUIRE (cpl->file());
3310                 Editor e(cpl->file().get());
3311                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
3312         }
3313
3314         check_verify_result (
3315                 {dir},
3316                 {},
3317                 {
3318                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3319                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
3320                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3321                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3322                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
3323                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
3324                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3325                         dcp::VerificationNote(
3326                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, canonical(cpl->file().get())
3327                                 ).set_cpl_id(cpl->id()),
3328                         dcp::VerificationNote(
3329                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
3330                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
3331                 });
3332 }
3333
3334
3335 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
3336 {
3337         path const dir("build/test/verify_mismatched_cpl_annotation_text");
3338         auto dcp = make_simple (dir);
3339         dcp->write_xml();
3340
3341         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3342         auto const cpl = dcp->cpls()[0];
3343
3344         HashCalculator calc(cpl->file().get());
3345
3346         {
3347                 BOOST_REQUIRE (cpl->file());
3348                 Editor e(cpl->file().get());
3349                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
3350         }
3351
3352         check_verify_result (
3353                 {dir},
3354                 {},
3355                 {
3356                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3357                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3358                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3359                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
3360                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
3361                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3362                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
3363                         dcp::VerificationNote(
3364                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, canonical(cpl->file().get())
3365                                 ).set_cpl_id(cpl->id()),
3366                         dcp::VerificationNote(
3367                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
3368                                 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()).set_cpl_id(cpl->id())
3369                 });
3370 }
3371
3372
3373 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
3374 {
3375         path const dir("build/test/verify_mismatched_asset_duration");
3376         prepare_directory (dir);
3377         shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
3378         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3379
3380         shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
3381         shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
3382
3383         auto reel = make_shared<dcp::Reel>(
3384                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3385                 make_shared<dcp::ReelSoundAsset>(ms, 0)
3386                 );
3387
3388         reel->add (simple_markers());
3389         cpl->add (reel);
3390
3391         dcp->add (cpl);
3392         dcp->set_annotation_text("A Test DCP");
3393         dcp->write_xml();
3394
3395         check_verify_result (
3396                 {dir},
3397                 {},
3398                 {
3399                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3400                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3401                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3402                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3403                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
3404                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3405                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3406                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
3407                         dcp::VerificationNote(
3408                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION
3409                                 ).set_cpl_id(cpl->id()),
3410                         dcp::VerificationNote(
3411                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl->file().get())
3412                                 ).set_cpl_id(cpl->id())
3413                 });
3414 }
3415
3416
3417
3418 static
3419 shared_ptr<dcp::CPL>
3420 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
3421 {
3422         prepare_directory (dir);
3423         auto dcp = make_shared<dcp::DCP>(dir);
3424         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3425
3426         auto constexpr reel_length = 192;
3427
3428         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
3429         subs->set_language (dcp::LanguageTag("de-DE"));
3430         subs->set_start_time (dcp::Time());
3431         subs->add (simple_subtitle());
3432         add_font(subs);
3433         subs->write (dir / "subs.mxf");
3434         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
3435
3436         auto reel1 = make_shared<dcp::Reel>(
3437                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
3438                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
3439                 );
3440
3441         if (add_to_reel1) {
3442                 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3443         }
3444
3445         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3446         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
3447         reel1->add (markers1);
3448
3449         cpl->add (reel1);
3450
3451         auto reel2 = make_shared<dcp::Reel>(
3452                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
3453                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
3454                 );
3455
3456         if (add_to_reel2) {
3457                 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3458         }
3459
3460         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3461         markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
3462         reel2->add (markers2);
3463
3464         cpl->add (reel2);
3465
3466         dcp->add (cpl);
3467         dcp->set_annotation_text("A Test DCP");
3468         dcp->write_xml();
3469
3470         return cpl;
3471 }
3472
3473
3474 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
3475 {
3476         {
3477                 path dir ("build/test/missing_main_subtitle_from_some_reels");
3478                 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
3479                 check_verify_result (
3480                         { dir },
3481                         {},
3482                         {
3483                                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3484                                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3485                                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3486                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3487                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3488                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video1.mxf"), cpl),
3489                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video2.mxf"), cpl),
3490                                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3491                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video1.mxf"), cpl),
3492                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video2.mxf"), cpl),
3493                                 dcp::VerificationNote(
3494                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS
3495                                         ).set_cpl_id(cpl->id()),
3496                                 dcp::VerificationNote(
3497                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3498                                         ).set_cpl_id(cpl->id())
3499                         });
3500
3501         }
3502
3503         {
3504                 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
3505                 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
3506                 check_verify_result(
3507                         {dir},
3508                         {},
3509                         {
3510                                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3511                                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3512                                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3513                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3514                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3515                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video1.mxf"), cpl),
3516                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video2.mxf"), cpl),
3517                                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3518                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video1.mxf"), cpl),
3519                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video2.mxf"), cpl),
3520                                 dcp::VerificationNote(
3521                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3522                                         ).set_cpl_id(cpl->id())
3523                         });
3524         }
3525
3526         {
3527                 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
3528                 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
3529                 check_verify_result(
3530                         {dir},
3531                         {},
3532                         {
3533                                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3534                                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3535                                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3536                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3537                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3538                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video1.mxf"), cpl),
3539                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video2.mxf"), cpl),
3540                                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3541                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video1.mxf"), cpl),
3542                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video2.mxf"), cpl),
3543                                 dcp::VerificationNote(
3544                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3545                                         ).set_cpl_id(cpl->id())
3546                         });
3547         }
3548 }
3549
3550
3551 static
3552 shared_ptr<dcp::CPL>
3553 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
3554 {
3555         prepare_directory (dir);
3556         auto dcp = make_shared<dcp::DCP>(dir);
3557         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3558
3559         auto constexpr reel_length = 192;
3560
3561         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
3562         subs->set_language (dcp::LanguageTag("de-DE"));
3563         subs->set_start_time (dcp::Time());
3564         subs->add (simple_subtitle());
3565         add_font(subs);
3566         subs->write (dir / "subs.mxf");
3567
3568         auto reel1 = make_shared<dcp::Reel>(
3569                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
3570                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
3571                 );
3572
3573         for (int i = 0; i < caps_in_reel1; ++i) {
3574                 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3575         }
3576
3577         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3578         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
3579         reel1->add (markers1);
3580
3581         cpl->add (reel1);
3582
3583         auto reel2 = make_shared<dcp::Reel>(
3584                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
3585                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
3586                 );
3587
3588         for (int i = 0; i < caps_in_reel2; ++i) {
3589                 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3590         }
3591
3592         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3593         markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
3594         reel2->add (markers2);
3595
3596         cpl->add (reel2);
3597
3598         dcp->add (cpl);
3599         dcp->set_annotation_text("A Test DCP");
3600         dcp->write_xml();
3601
3602         return cpl;
3603 }
3604
3605
3606 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
3607 {
3608         {
3609                 path dir ("build/test/mismatched_closed_caption_asset_counts");
3610                 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
3611                 check_verify_result (
3612                         {dir},
3613                         {},
3614                         {
3615                                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3616                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video1.mxf"), cpl),
3617                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video2.mxf"), cpl),
3618                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3619                                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3620                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video1.mxf"), cpl),
3621                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video2.mxf"), cpl),
3622                                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3623                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3624                                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3625                                 dcp::VerificationNote(
3626                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS
3627                                         ).set_cpl_id(cpl->id()),
3628                                 dcp::VerificationNote(
3629                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3630                                         ).set_cpl_id(cpl->id())
3631                         });
3632         }
3633
3634         {
3635                 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
3636                 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
3637                 check_verify_result(
3638                         {dir},
3639                         {},
3640                         {
3641                                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3642                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video1.mxf"), cpl),
3643                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video2.mxf"), cpl),
3644                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3645                                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3646                                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3647                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3648                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video1.mxf"), cpl),
3649                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video2.mxf"), cpl),
3650                                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3651                                 dcp::VerificationNote(
3652                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3653                                         ).set_cpl_id(cpl->id())
3654                         });
3655         }
3656
3657         {
3658                 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
3659                 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
3660                 check_verify_result(
3661                         {dir},
3662                         {},
3663                         {
3664                                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3665                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video1.mxf"), cpl),
3666                                 ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video2.mxf"), cpl),
3667                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3668                                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3669                                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3670                                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3671                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video1.mxf"), cpl),
3672                                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video2.mxf"), cpl),
3673                                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3674                                 dcp::VerificationNote(
3675                                         dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3676                                         ).set_cpl_id(cpl->id())
3677                         });
3678         }
3679 }
3680
3681
3682 template <class T>
3683 void
3684 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
3685 {
3686         prepare_directory (dir);
3687         auto dcp = make_shared<dcp::DCP>(dir);
3688         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3689
3690         auto constexpr reel_length = 192;
3691
3692         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
3693         subs->set_language (dcp::LanguageTag("de-DE"));
3694         subs->set_start_time (dcp::Time());
3695         subs->add (simple_subtitle());
3696         add_font(subs);
3697         subs->write (dir / "subs.mxf");
3698         auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
3699         adjust (reel_text);
3700
3701         auto reel = make_shared<dcp::Reel>(
3702                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
3703                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
3704                 );
3705
3706         reel->add (reel_text);
3707
3708         reel->add (simple_markers(reel_length));
3709
3710         cpl->add (reel);
3711
3712         dcp->add (cpl);
3713         dcp->set_annotation_text("A Test DCP");
3714         dcp->write_xml();
3715
3716         check_verify_result (
3717                 {dir},
3718                 {},
3719                 {
3720                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3721                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3722                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
3723                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3724                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3725                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
3726                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3727                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3728                         dcp::VerificationNote(
3729                                 dcp::VerificationNote::Type::BV21_ERROR, code, subs->id()
3730                                 ).set_cpl_id(cpl->id()),
3731                         dcp::VerificationNote(
3732                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3733                                 ).set_cpl_id(cpl->id())
3734                 });
3735 }
3736
3737
3738 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
3739 {
3740         verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
3741                 "build/test/verify_subtitle_entry_point_must_be_present",
3742                 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
3743                 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
3744                         asset->unset_entry_point ();
3745                         }
3746                 );
3747
3748         verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
3749                 "build/test/verify_subtitle_entry_point_must_be_zero",
3750                 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
3751                 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
3752                         asset->set_entry_point (4);
3753                         }
3754                 );
3755
3756         verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
3757                 "build/test/verify_closed_caption_entry_point_must_be_present",
3758                 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
3759                 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
3760                         asset->unset_entry_point ();
3761                         }
3762                 );
3763
3764         verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
3765                 "build/test/verify_closed_caption_entry_point_must_be_zero",
3766                 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
3767                 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
3768                         asset->set_entry_point (9);
3769                         }
3770                 );
3771 }
3772
3773
3774 BOOST_AUTO_TEST_CASE (verify_missing_hash)
3775 {
3776         RNGFixer fix;
3777
3778         path const dir("build/test/verify_missing_hash");
3779         auto dcp = make_simple (dir);
3780         dcp->write_xml();
3781
3782         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3783         auto const cpl = dcp->cpls()[0];
3784         BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
3785         BOOST_REQUIRE (cpl->reels()[0]->main_picture());
3786         auto asset_id = cpl->reels()[0]->main_picture()->id();
3787
3788         HashCalculator calc(cpl->file().get());
3789
3790         {
3791                 BOOST_REQUIRE (cpl->file());
3792                 Editor e(cpl->file().get());
3793                 e.delete_first_line_containing("<Hash>");
3794         }
3795
3796         check_verify_result (
3797                 {dir},
3798                 {},
3799                 {
3800                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3801                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3802                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3803                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
3804                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3805                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
3806                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
3807                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3808                         dcp::VerificationNote(
3809                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3810                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3811                         dcp::VerificationNote(
3812                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id
3813                                 ).set_cpl_id(cpl->id())
3814                 });
3815 }
3816
3817
3818 static
3819 void
3820 verify_markers_test (
3821         path dir,
3822         vector<pair<dcp::Marker, dcp::Time>> markers,
3823         vector<dcp::VerificationNote> test_notes
3824         )
3825 {
3826         auto dcp = make_simple (dir);
3827         auto cpl = dcp->cpls()[0];
3828         cpl->set_content_kind(dcp::ContentKind::FEATURE);
3829         auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
3830         for (auto const& i: markers) {
3831                 markers_asset->set (i.first, i.second);
3832         }
3833         cpl->reels()[0]->add(markers_asset);
3834         dcp->write_xml();
3835
3836         for (auto& note: test_notes) {
3837                 note.set_cpl_id(cpl->id());
3838         }
3839
3840         test_notes.push_back(ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl));
3841         test_notes.push_back(ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl));
3842         test_notes.push_back(ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl));
3843         test_notes.push_back(ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl));
3844         test_notes.push_back(ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"feature"}, cpl));
3845         test_notes.push_back(ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl));
3846         test_notes.push_back(ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl));
3847         test_notes.push_back(ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl));
3848         test_notes.push_back(ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl));
3849
3850         check_verify_result({dir}, {}, test_notes);
3851 }
3852
3853
3854 BOOST_AUTO_TEST_CASE (verify_markers)
3855 {
3856         verify_markers_test (
3857                 "build/test/verify_markers_all_correct",
3858                 {
3859                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3860                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3861                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3862                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3863                 },
3864                 {}
3865                 );
3866
3867         verify_markers_test (
3868                 "build/test/verify_markers_missing_ffec",
3869                 {
3870                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3871                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3872                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3873                 },
3874                 {
3875                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
3876                 });
3877
3878         verify_markers_test (
3879                 "build/test/verify_markers_missing_ffmc",
3880                 {
3881                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3882                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3883                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3884                 },
3885                 {
3886                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
3887                 });
3888
3889         verify_markers_test (
3890                 "build/test/verify_markers_missing_ffoc",
3891                 {
3892                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3893                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3894                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3895                 },
3896                 {
3897                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
3898                 });
3899
3900         verify_markers_test (
3901                 "build/test/verify_markers_missing_lfoc",
3902                 {
3903                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3904                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3905                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
3906                 },
3907                 {
3908                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
3909                 });
3910
3911         verify_markers_test (
3912                 "build/test/verify_markers_incorrect_ffoc",
3913                 {
3914                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3915                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3916                         { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
3917                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3918                 },
3919                 {
3920                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
3921                 });
3922
3923         verify_markers_test (
3924                 "build/test/verify_markers_incorrect_lfoc",
3925                 {
3926                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3927                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3928                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3929                         { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
3930                 },
3931                 {
3932                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
3933                 });
3934 }
3935
3936
3937 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
3938 {
3939         path dir = "build/test/verify_missing_cpl_metadata_version_number";
3940         prepare_directory (dir);
3941         auto dcp = make_simple (dir);
3942         auto cpl = dcp->cpls()[0];
3943         cpl->unset_version_number();
3944         dcp->write_xml();
3945
3946         check_verify_result(
3947                 {dir},
3948                 {},
3949                 {
3950                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
3951                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
3952                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3953                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3954                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3955                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3956                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3957                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
3958                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
3959                         dcp::VerificationNote(
3960                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->file().get()
3961                                 ).set_cpl_id(cpl->id())
3962                 });
3963 }
3964
3965
3966 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
3967 {
3968         path dir = "build/test/verify_missing_extension_metadata1";
3969         auto dcp = make_simple (dir);
3970         dcp->write_xml();
3971
3972         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3973         auto cpl = dcp->cpls()[0];
3974
3975         HashCalculator calc(cpl->file().get());
3976
3977         {
3978                 Editor e (cpl->file().get());
3979                 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
3980         }
3981
3982         check_verify_result (
3983                 {dir},
3984                 {},
3985                 {
3986                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3987                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
3988                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
3989                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
3990                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
3991                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
3992                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
3993                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
3994                         dcp::VerificationNote(
3995                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3996                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3997                         dcp::VerificationNote(
3998                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()
3999                                 ).set_cpl_id(cpl->id())
4000                 });
4001 }
4002
4003
4004 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
4005 {
4006         path dir = "build/test/verify_missing_extension_metadata2";
4007         auto dcp = make_simple (dir);
4008         dcp->write_xml();
4009
4010         auto cpl = dcp->cpls()[0];
4011
4012         HashCalculator calc(cpl->file().get());
4013
4014         {
4015                 Editor e (cpl->file().get());
4016                 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
4017         }
4018
4019         check_verify_result (
4020                 {dir},
4021                 {},
4022                 {
4023                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4024                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4025                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4026                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4027                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4028                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4029                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4030                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4031                         dcp::VerificationNote(
4032                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4033                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4034                         dcp::VerificationNote(
4035                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()
4036                                 ).set_cpl_id(cpl->id())
4037                 });
4038 }
4039
4040
4041 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
4042 {
4043         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
4044         auto dcp = make_simple (dir);
4045         dcp->write_xml();
4046
4047         auto const cpl = dcp->cpls()[0];
4048
4049         HashCalculator calc(cpl->file().get());
4050
4051         {
4052                 Editor e (cpl->file().get());
4053                 e.replace ("<meta:Name>A", "<meta:NameX>A");
4054                 e.replace ("n</meta:Name>", "n</meta:NameX>");
4055         }
4056
4057         check_verify_result (
4058                 {dir},
4059                 {},
4060                 {
4061                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4062                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4063                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4064                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4065                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4066                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4067                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4068                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4069                         dcp::VerificationNote(
4070                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70
4071                                 ).set_cpl_id(cpl->id()),
4072                         dcp::VerificationNote(
4073                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77).set_cpl_id(cpl->id()),
4074                         dcp::VerificationNote(
4075                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4076                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4077                 });
4078 }
4079
4080
4081 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
4082 {
4083         path dir = "build/test/verify_invalid_extension_metadata1";
4084         auto dcp = make_simple (dir);
4085         dcp->write_xml();
4086
4087         auto cpl = dcp->cpls()[0];
4088
4089         HashCalculator calc(cpl->file().get());
4090
4091         {
4092                 Editor e (cpl->file().get());
4093                 e.replace ("Application", "Fred");
4094         }
4095
4096         check_verify_result (
4097                 {dir},
4098                 {},
4099                 {
4100                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4101                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4102                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4103                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4104                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4105                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4106                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4107                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4108                         dcp::VerificationNote(
4109                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4110                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4111                         dcp::VerificationNote(
4112                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get()
4113                                 ).set_cpl_id(cpl->id())
4114                 });
4115 }
4116
4117
4118 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
4119 {
4120         path dir = "build/test/verify_invalid_extension_metadata2";
4121         auto dcp = make_simple (dir);
4122         dcp->write_xml();
4123
4124         auto cpl = dcp->cpls()[0];
4125
4126         HashCalculator calc(cpl->file().get());
4127
4128         {
4129                 Editor e (cpl->file().get());
4130                 e.replace ("DCP Constraints Profile", "Fred");
4131         }
4132
4133         check_verify_result (
4134                 {dir},
4135                 {},
4136                 {
4137                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4138                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4139                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4140                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4141                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4142                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4143                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4144                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4145                         dcp::VerificationNote(
4146                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4147                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4148                         dcp::VerificationNote(
4149                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get()
4150                                 ).set_cpl_id(cpl->id())
4151                 });
4152 }
4153
4154
4155 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
4156 {
4157         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
4158         auto dcp = make_simple (dir);
4159         dcp->write_xml();
4160
4161         auto const cpl = dcp->cpls()[0];
4162
4163         HashCalculator calc(cpl->file().get());
4164
4165         {
4166                 Editor e (cpl->file().get());
4167                 e.replace ("<meta:Value>", "<meta:ValueX>");
4168                 e.replace ("</meta:Value>", "</meta:ValueX>");
4169         }
4170
4171         check_verify_result (
4172                 {dir},
4173                 {},
4174                 {
4175                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4176                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4177                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4178                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4179                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4180                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4181                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4182                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4183                         dcp::VerificationNote(
4184                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74
4185                                 ).set_cpl_id(cpl->id()),
4186                         dcp::VerificationNote(
4187                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 75
4188                                 ).set_cpl_id(cpl->id()),
4189                         dcp::VerificationNote(
4190                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4191                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
4192                 });
4193 }
4194
4195
4196 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
4197 {
4198         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
4199         auto dcp = make_simple (dir);
4200         dcp->write_xml();
4201
4202         auto const cpl = dcp->cpls()[0];
4203
4204         HashCalculator calc(cpl->file().get());
4205
4206         {
4207                 Editor e (cpl->file().get());
4208                 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
4209         }
4210
4211         check_verify_result (
4212                 {dir},
4213                 {},
4214                 {
4215                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4216                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4217                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4218                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4219                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4220                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4221                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4222                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4223                         dcp::VerificationNote(
4224                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4225                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4226                         dcp::VerificationNote(
4227                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get()
4228                                 ).set_cpl_id(cpl->id())
4229                 });
4230 }
4231
4232
4233 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
4234 {
4235         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
4236         auto dcp = make_simple (dir);
4237         dcp->write_xml();
4238
4239         auto const cpl = dcp->cpls()[0];
4240
4241         HashCalculator calc(cpl->file().get());
4242
4243         {
4244                 Editor e (cpl->file().get());
4245                 e.replace ("<meta:Property>", "<meta:PropertyX>");
4246                 e.replace ("</meta:Property>", "</meta:PropertyX>");
4247         }
4248
4249         check_verify_result (
4250                 {dir},
4251                 {},
4252                 {
4253                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4254                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4255                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4256                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4257                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4258                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4259                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4260                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4261                         dcp::VerificationNote(
4262                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72
4263                                 ).set_cpl_id(cpl->id()),
4264                         dcp::VerificationNote(
4265                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76).set_cpl_id(cpl->id()),
4266                         dcp::VerificationNote(
4267                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4268                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4269                 });
4270 }
4271
4272
4273 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
4274 {
4275         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
4276         auto dcp = make_simple (dir);
4277         dcp->write_xml();
4278
4279         auto const cpl = dcp->cpls()[0];
4280
4281         HashCalculator calc(cpl->file().get());
4282
4283         {
4284                 Editor e (cpl->file().get());
4285                 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
4286                 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
4287         }
4288
4289         check_verify_result (
4290                 {dir},
4291                 {},
4292                 {
4293                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4294                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4295                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4296                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4297                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4298                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4299                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4300                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4301                         dcp::VerificationNote(
4302                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71
4303                                 ).set_cpl_id(cpl->id()),
4304                         dcp::VerificationNote(
4305                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77
4306                                 ).set_cpl_id(cpl->id()),
4307                         dcp::VerificationNote(
4308                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
4309                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4310                 });
4311 }
4312
4313
4314
4315 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
4316 {
4317         path const dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
4318         prepare_directory (dir);
4319         for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
4320                 copy_file (i.path(), dir / i.path().filename());
4321         }
4322
4323         path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml");
4324         path const cpl_path = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
4325
4326         HashCalculator calc(cpl_path);
4327
4328         {
4329                 Editor e(cpl_path);
4330                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
4331         }
4332
4333         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
4334
4335         check_verify_result (
4336                 {dir},
4337                 {},
4338                 {
4339                         ok(dcp::VerificationNote::Code::ALL_ENCRYPTED, cpl),
4340                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4341                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4342                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"feature"}, cpl),
4343                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4344                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4345                         dcp::VerificationNote(
4346                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
4347                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4348                         dcp::VerificationNote(
4349                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl)
4350                                 ).set_cpl_id(cpl->id()),
4351                         dcp::VerificationNote(
4352                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
4353                                 ).set_cpl_id(cpl->id()),
4354                         dcp::VerificationNote(
4355                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
4356                                 ).set_cpl_id(cpl->id()),
4357                         dcp::VerificationNote(
4358                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
4359                                 ).set_cpl_id(cpl->id()),
4360                         dcp::VerificationNote(
4361                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
4362                                 ).set_cpl_id(cpl->id()),
4363                         dcp::VerificationNote(
4364                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path)
4365                                 ).set_cpl_id(cpl->id()),
4366                         dcp::VerificationNote(
4367                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_path)
4368                                 ).set_cpl_id(cpl->id())
4369                 });
4370 }
4371
4372
4373 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
4374 {
4375         path dir = "build/test/unsigned_pkl_with_encrypted_content";
4376         prepare_directory (dir);
4377         for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
4378                 copy_file (i.path(), dir / i.path().filename());
4379         }
4380
4381         path const cpl_path = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
4382         path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
4383         {
4384                 Editor e (pkl);
4385                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
4386         }
4387
4388         auto cpl = std::make_shared<dcp::CPL>(cpl_path);
4389
4390         check_verify_result (
4391                 {dir},
4392                 {},
4393                 {
4394                         ok(dcp::VerificationNote::Code::ALL_ENCRYPTED, cpl),
4395                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4396                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4397                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"feature"}, cpl),
4398                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4399                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4400                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
4401                         dcp::VerificationNote(
4402                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl)
4403                                 ).set_cpl_id(cpl->id()),
4404                         dcp::VerificationNote(
4405                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
4406                                 ).set_cpl_id(cpl->id()),
4407                         dcp::VerificationNote(
4408                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
4409                                 ).set_cpl_id(cpl->id()),
4410                         dcp::VerificationNote(
4411                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
4412                                 ).set_cpl_id(cpl->id()),
4413                         dcp::VerificationNote(
4414                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
4415                                 ).set_cpl_id(cpl->id()),
4416                         dcp::VerificationNote(
4417                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path)
4418                                 ).set_cpl_id(cpl->id()),
4419                         dcp::VerificationNote(
4420                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl)
4421                                 )
4422                 });
4423 }
4424
4425
4426 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
4427 {
4428         path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
4429         prepare_directory (dir);
4430         for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
4431                 copy_file (i.path(), dir / i.path().filename());
4432         }
4433
4434         {
4435                 Editor e (dir / dcp_test1_pkl());
4436                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
4437         }
4438
4439         auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
4440
4441         check_verify_result(
4442                 {dir},
4443                 {},
4444                 {
4445                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4446                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
4447                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4448                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4449                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4450                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4451                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4452                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4453                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4454                 });
4455 }
4456
4457
4458 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
4459 {
4460         path dir ("build/test/verify_must_not_be_partially_encrypted");
4461         prepare_directory (dir);
4462
4463         dcp::DCP d (dir);
4464
4465         auto signer = make_shared<dcp::CertificateChain>();
4466         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
4467         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
4468         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
4469         signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
4470
4471         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4472
4473         dcp::Key key;
4474
4475         auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
4476         mp->set_key (key);
4477
4478         auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
4479         dcp::ArrayData j2c ("test/data/flat_red.j2c");
4480         for (int i = 0; i < 24; ++i) {
4481                 writer->write (j2c.data(), j2c.size());
4482         }
4483         writer->finalize ();
4484
4485         auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
4486
4487         auto reel = make_shared<dcp::Reel>(
4488                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4489                 make_shared<dcp::ReelSoundAsset>(ms, 0)
4490                 );
4491
4492         reel->add (simple_markers());
4493
4494         cpl->add (reel);
4495
4496         cpl->set_content_version (
4497                 {"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"}
4498                 );
4499         cpl->set_annotation_text ("A Test DCP");
4500         cpl->set_issuer ("OpenDCP 0.0.25");
4501         cpl->set_creator ("OpenDCP 0.0.25");
4502         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
4503         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
4504         cpl->set_main_sound_sample_rate (48000);
4505         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
4506         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
4507         cpl->set_version_number (1);
4508
4509         d.add (cpl);
4510
4511         d.set_issuer("OpenDCP 0.0.25");
4512         d.set_creator("OpenDCP 0.0.25");
4513         d.set_issue_date("2012-07-17T04:45:18+00:00");
4514         d.set_annotation_text("A Test DCP");
4515         d.write_xml(signer);
4516
4517         check_verify_result (
4518                 {dir},
4519                 {},
4520                 {
4521                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4522                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4523                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4524                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1440x1080"}, cpl),
4525                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
4526                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4527                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4528                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4529                         dcp::VerificationNote(
4530                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED
4531                                 ).set_cpl_id(cpl->id())
4532                 });
4533 }
4534
4535
4536 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
4537 {
4538         vector<dcp::VerificationNote> notes;
4539         dcp::MonoPictureAsset picture (find_file(private_test / "data" / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV", "j2c.mxf"));
4540         auto reader = picture.start_read ();
4541         auto frame = reader->get_frame (0);
4542         verify_j2k(frame, 0, 0, 24, notes);
4543         BOOST_CHECK(notes.empty());
4544 }
4545
4546
4547 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
4548 {
4549         vector<dcp::VerificationNote> notes;
4550         dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
4551         auto reader = picture.start_read ();
4552         auto frame = reader->get_frame (0);
4553         verify_j2k(frame, 0, 0, 24, notes);
4554         BOOST_CHECK(notes.empty());
4555 }
4556
4557
4558 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
4559 {
4560         boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
4561         prepare_directory (dir);
4562         auto dcp = make_simple (dir);
4563         dcp->write_xml ();
4564         vector<dcp::VerificationNote> notes;
4565         dcp::MonoPictureAsset picture (find_file(dir, "video"));
4566         auto reader = picture.start_read ();
4567         auto frame = reader->get_frame (0);
4568         verify_j2k(frame, 0, 0, 24, notes);
4569         BOOST_CHECK(notes.empty());
4570 }
4571
4572
4573 /** Check that ResourceID and the XML ID being different is spotted */
4574 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
4575 {
4576         boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
4577         prepare_directory (dir);
4578
4579         ASDCP::WriterInfo writer_info;
4580         writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
4581
4582         unsigned int c;
4583         auto mxf_id = dcp::make_uuid ();
4584         Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
4585         BOOST_REQUIRE (c == Kumu::UUID_Length);
4586
4587         auto resource_id = dcp::make_uuid ();
4588         ASDCP::TimedText::TimedTextDescriptor descriptor;
4589         Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
4590         DCP_ASSERT (c == Kumu::UUID_Length);
4591
4592         auto xml_id = dcp::make_uuid ();
4593         ASDCP::TimedText::MXFWriter writer;
4594         auto subs_mxf = dir / "subs.mxf";
4595         auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
4596         BOOST_REQUIRE (ASDCP_SUCCESS(r));
4597         writer.WriteTimedTextResource (dcp::String::compose(
4598                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
4599                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
4600                 "<Id>urn:uuid:%1</Id>"
4601                 "<ContentTitleText>Content</ContentTitleText>"
4602                 "<AnnotationText>Annotation</AnnotationText>"
4603                 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
4604                 "<ReelNumber>1</ReelNumber>"
4605                 "<Language>en-US</Language>"
4606                 "<EditRate>25 1</EditRate>"
4607                 "<TimeCodeRate>25</TimeCodeRate>"
4608                 "<StartTime>00:00:00:00</StartTime>"
4609                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
4610                 "<SubtitleList>"
4611                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
4612                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
4613                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
4614                 "</Subtitle>"
4615                 "</Font>"
4616                 "</SubtitleList>"
4617                 "</SubtitleReel>",
4618                 xml_id).c_str());
4619
4620         writer.Finalize();
4621
4622         auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
4623         auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
4624
4625         auto cpl = write_dcp_with_single_asset (dir, subs_reel);
4626
4627         check_verify_result (
4628                 { dir },
4629                 {},
4630                 {
4631                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4632                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
4633                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4634                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4635                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
4636                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4637                         dcp::VerificationNote(
4638                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
4639                                 ).set_cpl_id(cpl->id()),
4640                         dcp::VerificationNote(
4641                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID
4642                                 ).set_cpl_id(cpl->id()),
4643                         dcp::VerificationNote(
4644                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
4645                                 ).set_cpl_id(cpl->id()),
4646                         dcp::VerificationNote(
4647                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
4648                                 ).set_cpl_id(cpl->id())
4649                 });
4650 }
4651
4652
4653 /** Check that ResourceID and the MXF ID being the same is spotted */
4654 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
4655 {
4656         boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
4657         prepare_directory (dir);
4658
4659         ASDCP::WriterInfo writer_info;
4660         writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
4661
4662         unsigned int c;
4663         auto mxf_id = dcp::make_uuid ();
4664         Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
4665         BOOST_REQUIRE (c == Kumu::UUID_Length);
4666
4667         auto resource_id = mxf_id;
4668         ASDCP::TimedText::TimedTextDescriptor descriptor;
4669         Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
4670         DCP_ASSERT (c == Kumu::UUID_Length);
4671
4672         auto xml_id = resource_id;
4673         ASDCP::TimedText::MXFWriter writer;
4674         auto subs_mxf = dir / "subs.mxf";
4675         auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
4676         BOOST_REQUIRE (ASDCP_SUCCESS(r));
4677         writer.WriteTimedTextResource (dcp::String::compose(
4678                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
4679                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
4680                 "<Id>urn:uuid:%1</Id>"
4681                 "<ContentTitleText>Content</ContentTitleText>"
4682                 "<AnnotationText>Annotation</AnnotationText>"
4683                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
4684                 "<ReelNumber>1</ReelNumber>"
4685                 "<Language>en-US</Language>"
4686                 "<EditRate>25 1</EditRate>"
4687                 "<TimeCodeRate>25</TimeCodeRate>"
4688                 "<StartTime>00:00:00:00</StartTime>"
4689                 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
4690                 "<SubtitleList>"
4691                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
4692                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
4693                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
4694                 "</Subtitle>"
4695                 "</Font>"
4696                 "</SubtitleList>"
4697                 "</SubtitleReel>",
4698                 xml_id).c_str());
4699
4700         writer.Finalize();
4701
4702         auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
4703         auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
4704
4705         auto cpl = write_dcp_with_single_asset (dir, subs_reel);
4706
4707         check_verify_result (
4708                 { dir },
4709                 {},
4710                 {
4711                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4712                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
4713                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4714                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4715                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
4716                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4717                         dcp::VerificationNote(
4718                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
4719                                 ).set_cpl_id(cpl->id()),
4720                         dcp::VerificationNote(
4721                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID
4722                                 ).set_cpl_id(cpl->id()),
4723                         dcp::VerificationNote(
4724                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
4725                                 ).set_cpl_id(cpl->id()),
4726                         dcp::VerificationNote(
4727                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
4728                                 ).set_cpl_id(cpl->id()),
4729                         dcp::VerificationNote(
4730                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"}
4731                                 ).set_cpl_id(cpl->id())
4732                 });
4733 }
4734
4735
4736 /** Check a DCP with a 3D asset marked as 2D */
4737 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
4738 {
4739         auto const path = private_test / "data" / "xm";
4740
4741         auto cpl = std::make_shared<dcp::CPL>(find_prefix(path, "CPL_"));
4742         BOOST_REQUIRE(cpl);
4743
4744         check_verify_result (
4745                 { path },
4746                 {},
4747                 {
4748                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4749                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "0d6f57e6-adac-4e1d-bfbe-d162bf13e2cd_j2c.mxf"), cpl),
4750                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "0d6f57e6-adac-4e1d-bfbe-d162bf13e2cd_j2c.mxf"), cpl),
4751                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4752                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
4753                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4754                         dcp::VerificationNote(
4755                                 dcp::VerificationNote::Type::WARNING,
4756                                 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(path, "j2c"))
4757                                 ),
4758                         dcp::VerificationNote(
4759                                 dcp::VerificationNote::Type::BV21_ERROR,
4760                                 dcp::VerificationNote::Code::INVALID_STANDARD
4761                                 )
4762                 });
4763
4764 }
4765
4766
4767 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
4768 {
4769         path dir = "build/test/verify_unexpected_things_in_main_markers";
4770         prepare_directory (dir);
4771         auto dcp = make_simple (dir, 1, 24);
4772         dcp->write_xml();
4773
4774         HashCalculator calc(find_cpl(dir));
4775
4776         {
4777                 Editor e (find_cpl(dir));
4778                 e.insert(
4779                         "          <IntrinsicDuration>24</IntrinsicDuration>",
4780                         "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
4781                         );
4782         }
4783
4784         auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
4785
4786         check_verify_result (
4787                 { dir },
4788                 {},
4789                 {
4790                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4791                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4792                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4793                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4794                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4795                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4796                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4797                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4798                         dcp::VerificationNote(
4799                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4800                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4801                         dcp::VerificationNote(
4802                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT
4803                                 ).set_cpl_id(cpl->id()),
4804                         dcp::VerificationNote(
4805                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION
4806                                 ).set_cpl_id(cpl->id())
4807                 });
4808 }
4809
4810
4811 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
4812 {
4813         path dir = "build/test/verify_invalid_content_kind";
4814         prepare_directory (dir);
4815         auto dcp = make_simple (dir, 1, 24);
4816         dcp->write_xml();
4817
4818         HashCalculator calc(find_cpl(dir));
4819
4820         {
4821                 Editor e(find_cpl(dir));
4822                 e.replace("trailer", "trip");
4823         }
4824
4825         auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4826
4827         check_verify_result (
4828                 { dir },
4829                 {},
4830                 {
4831                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4832                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4833                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4834                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4835                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4836                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4837                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4838                         dcp::VerificationNote(
4839                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4840                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4841                         dcp::VerificationNote(
4842                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip")
4843                                 ).set_cpl_id(cpl->id()),
4844                 });
4845
4846 }
4847
4848
4849 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
4850 {
4851         path dir = "build/test/verify_valid_content_kind";
4852         prepare_directory (dir);
4853         auto dcp = make_simple (dir, 1, 24);
4854         dcp->write_xml();
4855
4856         HashCalculator calc(find_cpl(dir));
4857
4858         {
4859                 Editor e(find_cpl(dir));
4860                 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
4861         }
4862
4863         auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4864
4865         check_verify_result (
4866                 { dir },
4867                 {},
4868                 {
4869                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4870                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4871                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4872                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4873                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4874                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4875                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
4876                         dcp::VerificationNote(
4877                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4878                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4879                 });
4880 }
4881
4882
4883 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
4884 {
4885         path dir = "build/test/verify_invalid_main_picture_active_area_1";
4886         prepare_directory(dir);
4887         auto dcp = make_simple(dir, 1, 24);
4888         dcp->write_xml();
4889
4890         auto constexpr area = "<meta:MainPictureActiveArea>";
4891
4892         HashCalculator calc(find_cpl(dir));
4893
4894         {
4895                 Editor e(find_cpl(dir));
4896                 e.delete_lines_after(area, 2);
4897                 e.insert(area, "<meta:Height>4080</meta:Height>");
4898                 e.insert(area, "<meta:Width>1997</meta:Width>");
4899         }
4900
4901         dcp::PKL pkl(find_pkl(dir));
4902         auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4903
4904         check_verify_result(
4905                 { dir },
4906                 {},
4907                 {
4908                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4909                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4910                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4911                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4912                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4913                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4914                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4915                         dcp::VerificationNote(
4916                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4917                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4918                         dcp::VerificationNote(
4919                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir))
4920                                 ).set_cpl_id(cpl->id()),
4921                         dcp::VerificationNote(
4922                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 4080 is bigger than the asset height 1080", canonical(find_cpl(dir))
4923                                 ).set_cpl_id(cpl->id()),
4924                 });
4925 }
4926
4927
4928 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
4929 {
4930         path dir = "build/test/verify_invalid_main_picture_active_area_2";
4931         prepare_directory(dir);
4932         auto dcp = make_simple(dir, 1, 24);
4933         dcp->write_xml();
4934
4935         auto constexpr area = "<meta:MainPictureActiveArea>";
4936
4937         HashCalculator calc(find_cpl(dir));
4938
4939         {
4940                 Editor e(find_cpl(dir));
4941                 e.delete_lines_after(area, 2);
4942                 e.insert(area, "<meta:Height>5125</meta:Height>");
4943                 e.insert(area, "<meta:Width>9900</meta:Width>");
4944         }
4945
4946         dcp::PKL pkl(find_pkl(dir));
4947         auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4948
4949         check_verify_result(
4950                 { dir },
4951                 {},
4952                 {
4953                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4954                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
4955                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
4956                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4957                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
4958                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
4959                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
4960                         dcp::VerificationNote(
4961                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4962                                 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4963                         dcp::VerificationNote(
4964                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir))
4965                                 ).set_cpl_id(cpl->id()),
4966                         dcp::VerificationNote(
4967                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 9900 is bigger than the asset width 1998", canonical(find_cpl(dir))
4968                                 ).set_cpl_id(cpl->id()),
4969                         dcp::VerificationNote(
4970                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is bigger than the asset height 1080", canonical(find_cpl(dir))
4971                                 ).set_cpl_id(cpl->id())
4972                 });
4973 }
4974
4975
4976 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
4977 {
4978         RNGFixer rg;
4979
4980         path dir = "build/test/verify_duplicate_pkl_asset_ids";
4981         prepare_directory(dir);
4982         auto dcp = make_simple(dir, 1, 24);
4983         dcp->write_xml();
4984
4985         {
4986                 Editor e(find_pkl(dir));
4987                 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
4988         }
4989
4990         dcp::PKL pkl(find_pkl(dir));
4991         auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4992
4993         check_verify_result(
4994                 { dir },
4995                 {},
4996                 {
4997                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
4998                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
4999                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5000                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
5001                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5002                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5003                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5004                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
5005                 });
5006 }
5007
5008
5009 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
5010 {
5011         RNGFixer rg;
5012
5013         path dir = "build/test/verify_duplicate_assetmap_asset_ids";
5014         prepare_directory(dir);
5015         auto dcp = make_simple(dir, 1, 24);
5016         dcp->write_xml();
5017
5018         {
5019                 Editor e(find_asset_map(dir));
5020                 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
5021         }
5022
5023         dcp::PKL pkl(find_pkl(dir));
5024         dcp::AssetMap asset_map(find_asset_map(dir));
5025         auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
5026
5027         check_verify_result(
5028                 { dir },
5029                 {},
5030                 {
5031                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5032                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5033                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5034                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
5035                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5036                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5037                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5038                         dcp::VerificationNote(
5039                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir))
5040                                 ),
5041                         dcp::VerificationNote(
5042                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54")
5043                                 )
5044                 });
5045 }
5046
5047
5048 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
5049 {
5050         boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
5051
5052         dcp::MXFMetadata mxf_meta;
5053         mxf_meta.company_name = "OpenDCP";
5054         mxf_meta.product_name = "OpenDCP";
5055         mxf_meta.product_version = "0.0.25";
5056
5057         auto constexpr sample_rate = 48000;
5058         auto constexpr frames = 240;
5059
5060         boost::filesystem::remove_all(path);
5061         boost::filesystem::create_directories(path);
5062         auto dcp = make_shared<dcp::DCP>(path);
5063         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
5064         cpl->set_annotation_text("hello");
5065         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
5066         cpl->set_main_sound_sample_rate(sample_rate);
5067         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
5068         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
5069         cpl->set_version_number(1);
5070
5071         {
5072
5073                 /* Reel with 2 channels of audio */
5074
5075                 auto mp = simple_picture(path, "1", frames, {});
5076                 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
5077
5078                 auto reel = make_shared<dcp::Reel>(
5079                         std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
5080                         std::make_shared<dcp::ReelSoundAsset>(ms, 0)
5081                         );
5082
5083                 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
5084                 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
5085                 reel->add(markers);
5086
5087                 cpl->add(reel);
5088         }
5089
5090         {
5091                 /* Reel with 6 channels of audio */
5092
5093                 auto mp = simple_picture(path, "2", frames, {});
5094                 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
5095
5096                 auto reel = make_shared<dcp::Reel>(
5097                         std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
5098                         std::make_shared<dcp::ReelSoundAsset>(ms, 0)
5099                         );
5100
5101                 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
5102                 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
5103                 reel->add(markers);
5104
5105                 cpl->add(reel);
5106         }
5107
5108         dcp->add(cpl);
5109         dcp->set_annotation_text("hello");
5110         dcp->write_xml();
5111
5112         check_verify_result(
5113                 { path },
5114                 {},
5115                 {
5116                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5117                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5118                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5119                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5120                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
5121                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5122                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video1.mxf"), cpl),
5123                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "video1.mxf"), cpl),
5124                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video2.mxf"), cpl),
5125                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "video2.mxf"), cpl),
5126                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5127                         dcp::VerificationNote(
5128                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2"))
5129                                 ).set_cpl_id(cpl->id())
5130                 });
5131 }
5132
5133
5134 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
5135 {
5136         boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
5137
5138         dcp::MXFMetadata mxf_meta;
5139         mxf_meta.company_name = "OpenDCP";
5140         mxf_meta.product_name = "OpenDCP";
5141         mxf_meta.product_version = "0.0.25";
5142
5143         auto constexpr sample_rate = 48000;
5144         auto constexpr frames = 240;
5145
5146         boost::filesystem::remove_all(path);
5147         boost::filesystem::create_directories(path);
5148         auto dcp = make_shared<dcp::DCP>(path);
5149         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
5150         cpl->set_annotation_text("hello");
5151         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
5152         cpl->set_main_sound_sample_rate(sample_rate);
5153         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
5154         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
5155         cpl->set_version_number(1);
5156
5157         auto mp = simple_picture(path, "1", frames, {});
5158         auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
5159
5160         auto reel = make_shared<dcp::Reel>(
5161                 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
5162                 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
5163                 );
5164
5165         auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
5166         markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
5167         markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
5168         reel->add(markers);
5169
5170         cpl->add(reel);
5171
5172         dcp->add(cpl);
5173         dcp->set_annotation_text("hello");
5174         dcp->write_xml();
5175
5176         check_verify_result(
5177                 { path },
5178                 {},
5179                 {
5180                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5181                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5182                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5183                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5184                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
5185                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video1.mxf"), cpl),
5186                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(path / "video1.mxf"), cpl),
5187                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5188                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5189                         dcp::VerificationNote(
5190                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, std::string{"MainSoundConfiguration has 6 channels but sound assets have 2"}, canonical(find_cpl(path))
5191                                 ).set_cpl_id(cpl->id())
5192                 });
5193 }
5194
5195
5196 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
5197 {
5198         boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
5199         auto constexpr video_frames = 24;
5200         auto constexpr sample_rate = 48000;
5201
5202         boost::filesystem::remove_all(path);
5203         boost::filesystem::create_directories(path);
5204
5205         auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
5206         auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
5207
5208         dcp::Size const size(1998, 1080);
5209         auto image = make_shared<dcp::OpenJPEGImage>(size);
5210         boost::random::mt19937 rng(1);
5211         boost::random::uniform_int_distribution<> dist(0, 4095);
5212         for (int c = 0; c < 3; ++c) {
5213                 for (int p = 0; p < (1998 * 1080); ++p) {
5214                         image->data(c)[p] = dist(rng);
5215                 }
5216         }
5217         auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
5218         for (int i = 0; i < 24; ++i) {
5219                 picture_writer->write(j2c.data(), j2c.size());
5220         }
5221         picture_writer->finalize();
5222
5223         auto dcp = make_shared<dcp::DCP>(path);
5224         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
5225         cpl->set_content_version(
5226                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
5227                 );
5228         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
5229         cpl->set_main_sound_sample_rate(sample_rate);
5230         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
5231         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
5232         cpl->set_version_number(1);
5233
5234         auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
5235
5236         auto reel = make_shared<dcp::Reel>(
5237                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
5238                 make_shared<dcp::ReelSoundAsset>(ms, 0)
5239                 );
5240
5241         cpl->add(reel);
5242         dcp->add(cpl);
5243         dcp->set_annotation_text("A Test DCP");
5244         dcp->write_xml();
5245
5246         vector<dcp::VerificationNote> expected = {
5247                 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5248                 ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5249                 ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(path / "video.mxf"), cpl),
5250                 ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5251                 ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5252                 ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5253                 ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
5254                 ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5255                 dcp::VerificationNote(
5256                         dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
5257                         ).set_cpl_id(cpl->id()),
5258                 dcp::VerificationNote(
5259                         dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
5260                         ).set_cpl_id(cpl->id())
5261         };
5262
5263         for (auto frame = 0; frame < 24; frame++) {
5264                 expected.push_back(
5265                         dcp::VerificationNote(
5266                                 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf")
5267                         ).set_frame(frame).set_frame_rate(24).set_cpl_id(cpl->id())
5268                 );
5269         }
5270
5271         int component_sizes[] = {
5272                 1321816,
5273                 1294414,
5274                 1289881,
5275         };
5276
5277         for (auto frame = 0; frame < 24; frame++) {
5278                 for (auto component = 0; component < 3; component++) {
5279                         expected.push_back(
5280                                 dcp::VerificationNote(
5281                                         dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE
5282                                         ).set_frame(frame).set_component(component).set_size(component_sizes[component]).set_cpl_id(cpl->id())
5283                                 );
5284                 }
5285         }
5286
5287         check_verify_result({ path }, {}, expected);
5288 }
5289
5290
5291 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
5292 {
5293         boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
5294         dcp::DCP dcp(dir);
5295         dcp.read();
5296         BOOST_REQUIRE(!dcp.cpls().empty());
5297         auto cpl = dcp.cpls()[0];
5298
5299         check_verify_result(
5300                 { dir },
5301                 {},
5302                 {
5303                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5304                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5305                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5306                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"feature"}, cpl),
5307                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"Dcp_FTR-1_F_XX-XX_MOS_2K_20230407_SMPTE_OV"}, cpl),
5308                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "j2c_42b34dcd-caa5-4c7b-aa0f-66a590947ba1.mxf"), cpl),
5309                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "j2c_42b34dcd-caa5-4c7b-aa0f-66a590947ba1.mxf"), cpl),
5310                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5311                         dcp::VerificationNote(
5312                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
5313                                 ).set_cpl_id(cpl->id()),
5314                         dcp::VerificationNote(
5315                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
5316                                 ).set_cpl_id(cpl->id()),
5317                         dcp::VerificationNote(
5318                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
5319                                 ).set_cpl_id(cpl->id()),
5320                         dcp::VerificationNote(
5321                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_"))
5322                                 ).set_cpl_id(cpl->id()),
5323                         dcp::VerificationNote(
5324                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(find_file(dir, "cpl_"))
5325                                 ).set_cpl_id(cpl->id()),
5326                         dcp::VerificationNote(
5327                                 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"}
5328                                 ).set_cpl_id(cpl->id()),
5329                 });
5330 }
5331
5332
5333 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
5334 {
5335         path const dir("build/test/verify_missing_load_font");
5336         prepare_directory (dir);
5337         copy_file ("test/data/subs1.xml", dir / "subs.xml");
5338         {
5339                 Editor editor(dir / "subs.xml");
5340                 editor.delete_first_line_containing("LoadFont");
5341         }
5342         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
5343         auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
5344         auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
5345
5346         check_verify_result (
5347                 {dir},
5348                 {},
5349                 {
5350                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5351                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5352                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5353                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5354                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5355                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
5356                         dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId").set_cpl_id(cpl->id())
5357                 });
5358
5359 }
5360
5361
5362 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
5363 {
5364         boost::filesystem::path const dir = "build/test/verify_missing_load_font";
5365         prepare_directory(dir);
5366         auto dcp = make_simple (dir, 1, 202);
5367
5368         string const xml =
5369                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
5370                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
5371                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
5372                 "<ContentTitleText>Content</ContentTitleText>"
5373                 "<AnnotationText>Annotation</AnnotationText>"
5374                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
5375                 "<ReelNumber>1</ReelNumber>"
5376                 "<EditRate>24 1</EditRate>"
5377                 "<TimeCodeRate>24</TimeCodeRate>"
5378                 "<StartTime>00:00:00:00</StartTime>"
5379                 "<Language>de-DE</Language>"
5380                 "<SubtitleList>"
5381                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
5382                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
5383                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
5384                 "</Subtitle>"
5385                 "</Font>"
5386                 "</SubtitleList>"
5387                 "</SubtitleReel>";
5388
5389         dcp::File xml_file(dir / "subs.xml", "w");
5390         BOOST_REQUIRE(xml_file);
5391         xml_file.write(xml.c_str(), xml.size(), 1);
5392         xml_file.close();
5393         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
5394         subs->write(dir / "subs.mxf");
5395
5396         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
5397         auto cpl = dcp->cpls()[0];
5398         cpl->reels()[0]->add(reel_subs);
5399         dcp->write_xml();
5400
5401         check_verify_result (
5402                 { dir },
5403                 {},
5404                 {
5405                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5406                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5407                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5408                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5409                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
5410                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5411                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5412                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
5413                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
5414                         dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id()).set_cpl_id(cpl->id())
5415                 });
5416 }
5417
5418
5419 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
5420 {
5421         boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
5422         boost::filesystem::remove_all(dir);
5423
5424         auto dcp1 = make_simple(dir / "1");
5425         dcp1->write_xml();
5426         auto cpl = dcp1->cpls()[0];
5427
5428         auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
5429
5430         auto dcp2 = make_simple(dir / "2");
5431         dcp2->write_xml();
5432         auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
5433
5434         boost::filesystem::remove(dir / "1" / "video.mxf");
5435         boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
5436
5437         check_verify_result(
5438                 {dir / "1"},
5439                 {},
5440                 {
5441                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5442                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5443                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5444                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5445                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
5446                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5447                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5448                         dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
5449                 });
5450 }
5451
5452
5453 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
5454 {
5455         boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
5456         boost::filesystem::remove_all(dir);
5457
5458         auto dcp = make_simple(dir);
5459         BOOST_REQUIRE(dcp->cpls().size() == 1);
5460         auto cpl = dcp->cpls()[0];
5461         cpl->set_content_version(dcp::ContentVersion(""));
5462         dcp->write_xml();
5463
5464         check_verify_result(
5465                 {dir},
5466                 {},
5467                 {
5468                         ok(dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, string{"1998x1080"}, cpl),
5469                         ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
5470                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5471                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5472                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5473                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"A Test DCP"}, cpl),
5474                         ok(dcp::VerificationNote::Code::CORRECT_PICTURE_HASH, canonical(dir / "video.mxf"), cpl),
5475                         ok(dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, canonical(dir / "video.mxf"), cpl),
5476                         dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_cpl_id(cpl->id())
5477                 });
5478 }
5479
5480
5481 /** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */
5482 BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp)
5483 {
5484         auto const dir = path("build/test/verify_encrypted_smpte_dcp");
5485         dcp::Key key;
5486         auto key_id = dcp::make_uuid();
5487         auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
5488
5489         dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
5490         kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
5491
5492         path const pkl_file = find_file(dir, "pkl_");
5493         path const cpl_file = find_file(dir, "cpl_");
5494
5495         check_verify_result(
5496                 { dir },
5497                 { kdm },
5498                 {
5499                         ok(dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, cpl),
5500                         ok(dcp::VerificationNote::Code::MATCHING_CPL_HASHES, cpl),
5501                         ok(dcp::VerificationNote::Code::VALID_CONTENT_KIND, string{"trailer"}, cpl),
5502                         ok(dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, cpl->content_version()->label_text, cpl),
5503                         ok(dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, string{"hello"}, cpl),
5504                         ok(dcp::VerificationNote::Code::ALL_ENCRYPTED, cpl),
5505                         dcp::VerificationNote(
5506                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_file)
5507                                 ).set_cpl_id(cpl->id()),
5508                         dcp::VerificationNote(
5509                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_file)
5510                                 ).set_cpl_id(cpl->id()),
5511                         dcp::VerificationNote(
5512                                 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file)
5513                                 )
5514                 });
5515 }
5516
5517