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