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