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