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