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