2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
35 #include "compose.hpp"
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"
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"
60 #include "verify_j2k.h"
61 #include <boost/algorithm/string.hpp>
62 #include <boost/random.hpp>
63 #include <boost/test/unit_test.hpp>
71 using std::make_shared;
73 using std::shared_ptr;
76 using boost::optional;
77 using namespace boost::filesystem;
80 static list<pair<string, optional<path>>> stages;
82 static string filename_to_id(boost::filesystem::path path)
84 return path.string().substr(4, path.string().length() - 8);
88 boost::filesystem::path
91 return find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
98 return filename_to_id(dcp_test1_pkl());
102 boost::filesystem::path
105 return find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
112 return filename_to_id(dcp_test1_cpl());
115 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
119 encryption_test_cpl_id()
121 return filename_to_id(find_file("test/ref/DCP/encryption_test", "cpl_").filename());
126 encryption_test_pkl_id()
128 return filename_to_id(find_file("test/ref/DCP/encryption_test", "pkl_").filename());
132 stage (string s, optional<path> p)
134 stages.push_back (make_pair (s, p));
144 prepare_directory (path path)
146 using namespace boost::filesystem;
148 create_directories (path);
154 find_prefix(path dir, string prefix)
156 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
157 return boost::starts_with(p.filename().string(), prefix);
160 BOOST_REQUIRE(iter != directory_iterator());
169 return find_prefix(dir, "cpl_");
177 return find_prefix(dir, "pkl_");
183 find_asset_map(path dir)
185 return find_prefix(dir, "ASSETMAP");
189 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
190 * to make a new sacrificial test DCP.
193 setup (int reference_number, string verify_test_suffix)
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());
207 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
209 auto reel = make_shared<dcp::Reel>();
210 reel->add (reel_asset);
211 reel->add (simple_markers());
213 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
215 auto dcp = make_shared<dcp::DCP>(dir);
217 dcp->set_annotation_text("hello");
224 LIBDCP_DISABLE_WARNINGS
227 dump_notes (vector<dcp::VerificationNote> const & notes)
229 for (auto i: notes) {
230 std::cout << dcp::note_to_string(i) << "\n";
233 LIBDCP_ENABLE_WARNINGS
238 to_string(dcp::VerificationNote const& note)
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)
250 s += dcp::String::compose(
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>")
265 check_verify_result(vector<dcp::VerificationNote> notes, vector<dcp::VerificationNote> test_notes)
267 std::sort(notes.begin(), notes.end());
268 std::sort(test_notes.begin(), test_notes.end());
270 string message = "\n";
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);
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);
290 for (auto note: not_expected) {
291 message += "Not expected:\n" + to_string(note) + "\n";
294 for (auto note: not_seen) {
295 message += "Not seen:\n" + to_string(note) + "\n";
298 BOOST_REQUIRE_MESSAGE(notes == test_notes, message);
304 check_verify_result(vector<path> dir, vector<dcp::DecryptedKDM> kdm, vector<dcp::VerificationNote> test_notes)
306 check_verify_result(dcp::verify({dir}, kdm, &stage, &progress, {}, xsd_test).notes, test_notes);
310 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
311 * replacing from with to.
315 replace(string suffix, boost::function<path (string)> file, string from, string to)
317 auto dir = setup (1, suffix);
320 Editor e (file(suffix));
321 e.replace (from, to);
328 add_font(shared_ptr<dcp::SubtitleAsset> asset)
330 dcp::ArrayData fake_font(1024);
331 asset->add_font("font", fake_font);
338 HashCalculator(boost::filesystem::path path)
340 , _old_hash(dcp::make_digest(path, [](int64_t, int64_t) {}))
343 std::string old_hash() const {
347 std::string new_hash() const {
348 return dcp::make_digest(_path, [](int64_t, int64_t) {});
352 boost::filesystem::path _path;
353 std::string _old_hash;
358 dcp::VerificationNote
359 ok(dcp::VerificationNote::Code code, shared_ptr<const dcp::CPL> cpl)
361 return dcp::VerificationNote(dcp::VerificationNote::Type::OK, code).set_cpl_id(cpl->id());
366 dcp::VerificationNote
367 ok(dcp::VerificationNote::Code code, boost::filesystem::path path, shared_ptr<const dcp::CPL> cpl)
369 return dcp::VerificationNote(dcp::VerificationNote::Type::OK, code, path).set_cpl_id(cpl->id());
374 add(vector<dcp::VerificationNote>& notes, vector<dcp::VerificationNote> const& add)
382 BOOST_AUTO_TEST_CASE (verify_no_error)
385 auto dir = setup (1, "no_error");
386 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
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";
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));
397 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
398 BOOST_REQUIRE (st->second);
399 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
401 BOOST_CHECK_EQUAL (st->first, "Checking reel");
402 BOOST_REQUIRE (!st->second);
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"));
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"));
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"));
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"));
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));
427 BOOST_REQUIRE (st == stages.end());
429 for (auto note: notes) {
430 BOOST_CHECK(note.type() == dcp::VerificationNote::Type::OK);
435 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
437 using namespace boost::filesystem;
439 auto dir = setup (1, "incorrect_picture_sound_hash");
440 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
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");
446 BOOST_REQUIRE_EQUAL(fseek(mod, -16, SEEK_END), 0);
448 BOOST_REQUIRE(fwrite(&x, sizeof(x), 1, mod) == 1);
451 auto audio_path = path(dir / "audio.mxf");
452 HashCalculator audio_calc(audio_path);
453 mod = fopen(audio_path.string().c_str(), "r+b");
455 BOOST_REQUIRE_EQUAL(fseek(mod, 0, SEEK_END), 0);
456 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
459 dcp::ASDCPErrorSuspender sus;
460 check_verify_result (
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()),
477 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
479 using namespace boost::filesystem;
481 auto dir = setup (1, "mismatched_picture_sound_hashes");
482 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
484 HashCalculator calc(dir / dcp_test1_cpl());
487 Editor e (dir / dcp_test1_pkl());
488 e.replace ("<Hash>", "<Hash>x");
491 check_verify_result (
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 },
513 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
515 auto dir = setup (1, "failed_read_content_kind");
517 HashCalculator calc(dir / dcp_test1_cpl());
520 Editor e (dir / dcp_test1_cpl());
521 e.replace ("<ContentKind>", "<ContentKind>x");
524 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
526 check_verify_result (
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())
545 dcp_test1_cpl_path(string suffix)
547 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl());
553 dcp_test1_pkl_path(string suffix)
555 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl());
561 asset_map (string suffix)
563 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
567 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
569 auto const suffix = "invalid_picture_frame_rate";
571 replace(suffix, &dcp_test1_cpl_path, "<FrameRate>24 1", "<FrameRate>99 1");
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);
577 std::vector<dcp::VerificationNote> expected =
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())
590 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
593 BOOST_AUTO_TEST_CASE (verify_missing_asset)
595 auto dir = setup (1, "missing_asset");
596 remove (dir / "video.mxf");
598 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
600 check_verify_result (
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" }
611 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
613 auto const suffix = "empty_asset_path";
615 replace("empty_asset_path", &asset_map, "<Path>video.mxf</Path>", "<Path></Path>");
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);
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 }
627 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
631 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
633 auto const suffix = "mismatched_standard";
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#");
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);
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=")
671 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
675 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
677 auto const suffix = "invalid_xml_cpl_id";
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");
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);
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())
698 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
702 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
704 auto const suffix = "invalid_xml_issue_date";
706 replace("invalid_xml_issue_date", &dcp_test1_cpl_path, "<IssueDate>", "<IssueDate>x");
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);
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()),
726 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
730 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
732 auto const suffix = "invalid_xml_pkl_id";
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));
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);
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
754 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
758 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
760 auto const suffix = "invalid_xml_asset_map_id";
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));
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);
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
782 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
786 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
789 auto dir = setup (3, "verify_invalid_standard");
790 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
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);
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));
802 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
803 BOOST_REQUIRE (st->second);
804 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
806 BOOST_CHECK_EQUAL (st->first, "Checking reel");
807 BOOST_REQUIRE (!st->second);
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"));
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"));
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"));
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"));
825 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
826 BOOST_REQUIRE (st->second);
827 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
829 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
830 BOOST_REQUIRE (st->second);
831 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
833 BOOST_REQUIRE (st == stages.end());
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)
843 for (int j = 0; j < 24; ++j) {
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())
851 check_verify_result(notes, expected);
854 /* DCP with a short asset */
855 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
857 auto dir = setup (8, "invalid_duration");
861 BOOST_REQUIRE(dcp.cpls().size() == 1);
862 auto cpl = dcp.cpls()[0];
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,
886 ).set_cpl_id(cpl->id())
889 for (int i = 0; i < 23; ++i) {
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())
897 check_verify_result({ dir }, {}, expected);
903 dcp_from_frame (dcp::ArrayData const& frame, path dir)
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());
913 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
914 return write_dcp_with_single_asset (dir, reel_asset);
918 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
920 int const too_big = 1302083 * 2;
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);
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());
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);
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),
942 for (auto i = 0; i < 24; ++i) {
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())
950 for (auto i = 0; i < 24; ++i) {
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())
959 dcp::VerificationNote(
960 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
961 ).set_cpl_id(cpl->id())
964 check_verify_result({ dir }, {}, expected);
968 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
970 int const nearly_too_big = 1302083 * 0.98;
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);
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());
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);
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),
992 for (auto i = 0; i < 24; ++i) {
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())
1000 for (auto i = 0; i < 24; ++i) {
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())
1009 dcp::VerificationNote(
1010 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1011 ).set_cpl_id(cpl->id())
1014 check_verify_result ({ dir }, {}, expected);
1018 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
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));
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);
1029 check_verify_result(
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())
1042 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
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);
1051 check_verify_result (
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())
1065 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
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);
1074 check_verify_result (
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())
1088 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
1090 using namespace boost::filesystem;
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);
1100 Editor e (dir / "subs.xml");
1101 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
1104 check_verify_result (
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*)'"),
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())
1128 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
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);
1137 check_verify_result (
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())
1155 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
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);
1164 check_verify_result (
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())
1179 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
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);
1188 check_verify_result(
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()),
1207 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
1209 using namespace boost::filesystem;
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);
1219 check_verify_result (
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)'"),
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()),
1251 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
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);
1260 check_verify_result (
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())
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)
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);
1298 check_verify_result (
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())
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)
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);
1322 check_verify_result (
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())
1342 BOOST_AUTO_TEST_CASE (verify_external_asset)
1344 path const ov_dir("build/test/verify_external_asset");
1345 prepare_directory (ov_dir);
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);
1352 dcp::DCP ov (ov_dir);
1355 path const vf_dir("build/test/verify_external_asset_vf");
1356 prepare_directory (vf_dir);
1358 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
1359 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
1361 check_verify_result (
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())
1375 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
1377 path const dir("build/test/verify_valid_cpl_metadata");
1378 prepare_directory (dir);
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);
1384 auto reel = make_shared<dcp::Reel>();
1385 reel->add (reel_asset);
1387 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1388 reel->add (simple_markers(16 * 24));
1390 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
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);
1400 dcp.set_annotation_text("hello");
1405 /* DCP with invalid CompositionMetadataAsset */
1406 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1408 using namespace boost::filesystem;
1410 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1411 prepare_directory (dir);
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);
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);
1423 reel->add (simple_markers());
1427 dcp.set_annotation_text("hello");
1430 HashCalculator calc(find_cpl(dir));
1433 Editor e (find_cpl(dir));
1434 e.replace ("MainSound", "MainSoundX");
1437 check_verify_result (
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())
1468 /* DCP with invalid CompositionMetadataAsset */
1469 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1471 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1472 prepare_directory (dir);
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);
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));
1485 dcp.set_annotation_text("hello");
1489 Editor e (find_cpl(dir));
1490 e.replace ("meta:Width", "meta:WidthX");
1493 check_verify_result (
1496 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1501 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
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);
1513 check_verify_result (
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())
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)
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);
1545 check_verify_result (
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())
1564 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1565 * the release territory.
1567 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1569 path const dir("build/test/verify_invalid_language3");
1570 prepare_directory (dir);
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());
1581 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
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);
1593 dcp->set_annotation_text("hello");
1596 check_verify_result (
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()),
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)
1624 using namespace boost::filesystem;
1626 path dcp_path = "build/test/verify_picture_test";
1627 prepare_directory (dcp_path);
1629 shared_ptr<dcp::PictureAsset> mp;
1631 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1633 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1635 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
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());
1643 picture_writer->finalize ();
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);
1655 auto reel = make_shared<dcp::Reel>();
1658 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1660 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1663 reel->add (simple_markers(frame_rate));
1668 d->set_annotation_text("A Test DCP");
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 };
1678 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
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);
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)
1691 check_verify_result(notes, expected);
1697 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
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);
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())
1713 check_verify_result(notes, expected);
1719 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
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);
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())
1739 check_verify_result(notes, expected);
1745 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
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);
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())
1762 check_verify_result(notes, expected);
1766 BOOST_AUTO_TEST_CASE (verify_picture_size)
1768 using namespace boost::filesystem;
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);
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);
1787 check_picture_size_ok (4096, 1716, 24, false);
1790 check_picture_size_ok (3996, 2160, 24, false);
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);
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);
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);
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);
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 },
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")
1828 std::make_shared<dcp::SubtitleString>(
1836 dcp::Time(start_frame, 24, 24),
1837 dcp::Time(end_frame, 24, 24),
1839 dcp::HAlign::CENTER,
1843 dcp::Direction::LTR,
1850 std::vector<dcp::Ruby>()
1856 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1858 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1859 prepare_directory (dir);
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);
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);
1871 check_verify_result (
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,
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())
1897 shared_ptr<dcp::SMPTESubtitleAsset>
1898 make_large_subtitle_asset (path font_file)
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);
1912 verify_timed_text_asset_too_large (string name)
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");
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);
1924 check_verify_result (
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())
1952 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
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");
1959 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1961 path dir = "build/test/verify_missing_subtitle_language";
1962 prepare_directory (dir);
1963 auto dcp = make_simple (dir, 1, 106);
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>"
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>"
1986 dcp::File xml_file(dir / "subs.xml", "w");
1987 BOOST_REQUIRE (xml_file);
1988 xml_file.write(xml.c_str(), xml.size(), 1);
1990 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1991 subs->write (dir / "subs.mxf");
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);
1998 check_verify_result (
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())
2016 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
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];
2024 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2025 subs->set_language (dcp::LanguageTag("de-DE"));
2026 subs->add (simple_subtitle());
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);
2034 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2035 subs->set_language (dcp::LanguageTag("en-US"));
2036 subs->add (simple_subtitle());
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);
2045 check_verify_result (
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()),
2068 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
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];
2076 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
2077 ccaps->set_language (dcp::LanguageTag("de-DE"));
2078 ccaps->add (simple_subtitle());
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);
2086 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
2087 ccaps->set_language (dcp::LanguageTag("en-US"));
2088 ccaps->add (simple_subtitle());
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);
2097 check_verify_result (
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())
2117 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
2119 path dir = "build/test/verify_missing_subtitle_start_time";
2120 prepare_directory (dir);
2121 auto dcp = make_simple (dir, 1, 106);
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>"
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>"
2144 dcp::File xml_file(dir / "subs.xml", "w");
2145 BOOST_REQUIRE (xml_file);
2146 xml_file.write(xml.c_str(), xml.size(), 1);
2148 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2149 subs->write (dir / "subs.mxf");
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);
2156 check_verify_result (
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())
2174 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
2176 path dir = "build/test/verify_invalid_subtitle_start_time";
2177 prepare_directory (dir);
2178 auto dcp = make_simple (dir, 1, 106);
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>"
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>"
2202 dcp::File xml_file(dir / "subs.xml", "w");
2203 BOOST_REQUIRE (xml_file);
2204 xml_file.write(xml.c_str(), xml.size(), 1);
2206 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2207 subs->write (dir / "subs.mxf");
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);
2214 check_verify_result (
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())
2235 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
2238 , v_position(v_position_)
2246 dcp::VAlign v_align;
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)
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);
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);
2267 asset->write (dir / "subs.mxf");
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);
2275 shared_ptr<dcp::CPL>
2276 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
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"));
2283 auto subs_mxf = dir / "subs.mxf";
2284 asset->write (subs_mxf);
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
2290 ASDCP::TimedText::MXFWriter writer;
2291 ASDCP::WriterInfo writer_info;
2292 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
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));
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);
2311 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
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 (
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())
2333 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
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(
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())
2351 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
2353 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
2354 prepare_directory (dir);
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"));
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);
2370 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
2371 asset2->set_start_time (dcp::Time());
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);
2384 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2387 auto dcp = make_shared<dcp::DCP>(dir);
2389 dcp->set_annotation_text("hello");
2392 check_verify_result(
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())
2405 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
2407 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
2408 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2412 { 5 * 24 + 1, 6 * 24 },
2414 check_verify_result (
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())
2430 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
2432 auto const dir = path("build/test/verify_valid_subtitle_spacing");
2433 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2437 { 5 * 24 + 16, 8 * 24 },
2440 check_verify_result(
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())
2453 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
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 (
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())
2473 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
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 }});
2478 check_verify_result(
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())
2491 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
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);
2499 asset->set_language (dcp::LanguageTag("de-DE"));
2500 asset->write (dir / "subs.mxf");
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 (
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())
2527 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
2529 auto const dir = path ("build/test/invalid_subtitle_line_count1");
2530 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
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" }
2538 check_verify_result (
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())
2554 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
2556 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
2557 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
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" },
2565 check_verify_result(
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())
2578 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
2580 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
2581 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
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" }
2589 check_verify_result (
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())
2605 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
2607 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
2608 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
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" }
2617 check_verify_result(
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())
2630 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
2632 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
2633 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2636 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2638 check_verify_result (
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())
2654 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2656 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2657 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2660 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2662 check_verify_result (
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())
2678 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2680 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2681 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" }
2689 check_verify_result (
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())
2705 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2707 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2708 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" },
2716 check_verify_result(
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())
2729 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2731 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2732 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" }
2740 check_verify_result (
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())
2756 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2758 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2759 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" }
2768 check_verify_result(
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())
2781 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2783 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2784 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2787 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2790 check_verify_result (
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())
2803 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2805 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2806 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2809 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2811 check_verify_result (
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())
2827 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2829 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2830 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" },
2837 check_verify_result (
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())
2850 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2852 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2853 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" },
2860 check_verify_result (
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())
2876 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2878 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2879 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" },
2887 check_verify_result(
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())
2900 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2902 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2903 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
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" },
2911 check_verify_result(
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())
2924 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
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 (
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())
2944 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
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");
2949 check_verify_result(
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())
2963 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2965 path const dir("build/test/verify_invalid_sound_frame_rate");
2966 prepare_directory (dir);
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);
2978 auto dcp = make_shared<dcp::DCP>(dir);
2980 dcp->set_annotation_text("hello");
2983 check_verify_result (
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())
3001 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
3003 path const dir("build/test/verify_missing_cpl_annotation_text");
3004 auto dcp = make_simple (dir);
3007 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3009 auto const cpl = dcp->cpls()[0];
3011 HashCalculator calc(cpl->file().get());
3014 BOOST_REQUIRE (cpl->file());
3015 Editor e(cpl->file().get());
3016 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
3019 check_verify_result (
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())
3036 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
3038 path const dir("build/test/verify_mismatched_cpl_annotation_text");
3039 auto dcp = make_simple (dir);
3042 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3043 auto const cpl = dcp->cpls()[0];
3045 HashCalculator calc(cpl->file().get());
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>");
3053 check_verify_result (
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())
3070 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
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);
3077 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
3078 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
3080 auto reel = make_shared<dcp::Reel>(
3081 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3082 make_shared<dcp::ReelSoundAsset>(ms, 0)
3085 reel->add (simple_markers());
3089 dcp->set_annotation_text("A Test DCP");
3092 check_verify_result (
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())
3112 shared_ptr<dcp::CPL>
3113 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
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);
3119 auto constexpr reel_length = 192;
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());
3126 subs->write (dir / "subs.mxf");
3127 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
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)
3135 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
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);
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)
3150 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
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);
3160 dcp->set_annotation_text("A Test DCP");
3167 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
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 (
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())
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(
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())
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(
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())
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)
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);
3240 auto constexpr reel_length = 192;
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());
3247 subs->write (dir / "subs.mxf");
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)
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));
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);
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)
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));
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);
3280 dcp->set_annotation_text("A Test DCP");
3287 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
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 (
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())
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(
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())
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(
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())
3353 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
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);
3359 auto constexpr reel_length = 192;
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());
3366 subs->write (dir / "subs.mxf");
3367 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
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)
3375 reel->add (reel_text);
3377 reel->add (simple_markers(reel_length));
3382 dcp->set_annotation_text("A Test DCP");
3385 check_verify_result (
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())
3403 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
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 ();
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);
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 ();
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);
3439 BOOST_AUTO_TEST_CASE (verify_missing_hash)
3443 path const dir("build/test/verify_missing_hash");
3444 auto dcp = make_simple (dir);
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();
3453 HashCalculator calc(cpl->file().get());
3456 BOOST_REQUIRE (cpl->file());
3457 Editor e(cpl->file().get());
3458 e.delete_first_line_containing("<Hash>");
3461 check_verify_result (
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())
3480 verify_markers_test (
3482 vector<pair<dcp::Marker, dcp::Time>> markers,
3483 vector<dcp::VerificationNote> test_notes
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);
3493 cpl->reels()[0]->add(markers_asset);
3496 for (auto& note: test_notes) {
3497 note.set_cpl_id(cpl->id());
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);
3508 BOOST_AUTO_TEST_CASE (verify_markers)
3510 verify_markers_test (
3511 "build/test/verify_markers_all_correct",
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) }
3521 verify_markers_test (
3522 "build/test/verify_markers_missing_ffec",
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) }
3529 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
3532 verify_markers_test (
3533 "build/test/verify_markers_missing_ffmc",
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) }
3540 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
3543 verify_markers_test (
3544 "build/test/verify_markers_missing_ffoc",
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) }
3551 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
3554 verify_markers_test (
3555 "build/test/verify_markers_missing_lfoc",
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) }
3562 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
3565 verify_markers_test (
3566 "build/test/verify_markers_incorrect_ffoc",
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) }
3574 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
3577 verify_markers_test (
3578 "build/test/verify_markers_incorrect_lfoc",
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) }
3586 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
3591 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
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();
3600 check_verify_result(
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())
3615 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
3617 path dir = "build/test/verify_missing_extension_metadata1";
3618 auto dcp = make_simple (dir);
3621 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3622 auto cpl = dcp->cpls()[0];
3624 HashCalculator calc(cpl->file().get());
3627 Editor e (cpl->file().get());
3628 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
3631 check_verify_result (
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())
3648 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
3650 path dir = "build/test/verify_missing_extension_metadata2";
3651 auto dcp = make_simple (dir);
3654 auto cpl = dcp->cpls()[0];
3656 HashCalculator calc(cpl->file().get());
3659 Editor e (cpl->file().get());
3660 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
3663 check_verify_result (
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())
3680 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
3682 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
3683 auto dcp = make_simple (dir);
3686 auto const cpl = dcp->cpls()[0];
3688 HashCalculator calc(cpl->file().get());
3691 Editor e (cpl->file().get());
3692 e.replace ("<meta:Name>A", "<meta:NameX>A");
3693 e.replace ("n</meta:Name>", "n</meta:NameX>");
3696 check_verify_result (
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()),
3715 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
3717 path dir = "build/test/verify_invalid_extension_metadata1";
3718 auto dcp = make_simple (dir);
3721 auto cpl = dcp->cpls()[0];
3723 HashCalculator calc(cpl->file().get());
3726 Editor e (cpl->file().get());
3727 e.replace ("Application", "Fred");
3730 check_verify_result (
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())
3747 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
3749 path dir = "build/test/verify_invalid_extension_metadata2";
3750 auto dcp = make_simple (dir);
3753 auto cpl = dcp->cpls()[0];
3755 HashCalculator calc(cpl->file().get());
3758 Editor e (cpl->file().get());
3759 e.replace ("DCP Constraints Profile", "Fred");
3762 check_verify_result (
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())
3779 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
3781 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
3782 auto dcp = make_simple (dir);
3785 auto const cpl = dcp->cpls()[0];
3787 HashCalculator calc(cpl->file().get());
3790 Editor e (cpl->file().get());
3791 e.replace ("<meta:Value>", "<meta:ValueX>");
3792 e.replace ("</meta:Value>", "</meta:ValueX>");
3795 check_verify_result (
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())
3815 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
3817 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
3818 auto dcp = make_simple (dir);
3821 auto const cpl = dcp->cpls()[0];
3823 HashCalculator calc(cpl->file().get());
3826 Editor e (cpl->file().get());
3827 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
3830 check_verify_result (
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())
3847 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
3849 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
3850 auto dcp = make_simple (dir);
3853 auto const cpl = dcp->cpls()[0];
3855 HashCalculator calc(cpl->file().get());
3858 Editor e (cpl->file().get());
3859 e.replace ("<meta:Property>", "<meta:PropertyX>");
3860 e.replace ("</meta:Property>", "</meta:PropertyX>");
3863 check_verify_result (
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()),
3882 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
3884 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
3885 auto dcp = make_simple (dir);
3888 auto const cpl = dcp->cpls()[0];
3890 HashCalculator calc(cpl->file().get());
3893 Editor e (cpl->file().get());
3894 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
3895 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
3898 check_verify_result (
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()),
3919 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
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());
3927 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml");
3928 path const cpl_path = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
3930 HashCalculator calc(cpl_path);
3934 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3937 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
3939 check_verify_result (
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())
3974 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
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());
3982 path const cpl_path = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
3983 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
3986 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3989 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
3991 check_verify_result (
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)
4024 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
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());
4033 Editor e (dir / dcp_test1_pkl());
4034 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
4037 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
4039 check_verify_result(
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),
4051 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
4053 path dir ("build/test/verify_must_not_be_partially_encrypted");
4054 prepare_directory (dir);
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"));
4064 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4068 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
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());
4076 writer->finalize ();
4078 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
4080 auto reel = make_shared<dcp::Reel>(
4081 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4082 make_shared<dcp::ReelSoundAsset>(ms, 0)
4085 reel->add (simple_markers());
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"}
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);
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);
4110 check_verify_result (
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())
4124 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
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());
4135 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
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());
4146 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
4148 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
4149 prepare_directory (dir);
4150 auto dcp = make_simple (dir);
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());
4161 /** Check that ResourceID and the XML ID being different is spotted */
4162 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
4164 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
4165 prepare_directory (dir);
4167 ASDCP::WriterInfo writer_info;
4168 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
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);
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);
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>"
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>"
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);
4213 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
4215 check_verify_result (
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())
4237 /** Check that ResourceID and the MXF ID being the same is spotted */
4238 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
4240 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
4241 prepare_directory (dir);
4243 ASDCP::WriterInfo writer_info;
4244 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
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);
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);
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>"
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>"
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);
4289 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
4291 check_verify_result (
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())
4316 /** Check a DCP with a 3D asset marked as 2D */
4317 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
4319 auto const path = private_test / "data" / "xm";
4321 auto cpl = std::make_shared<dcp::CPL>(find_prefix(path, "CPL_"));
4324 check_verify_result (
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"))
4336 dcp::VerificationNote(
4337 dcp::VerificationNote::Type::BV21_ERROR,
4338 dcp::VerificationNote::Code::INVALID_STANDARD
4345 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
4347 path dir = "build/test/verify_unexpected_things_in_main_markers";
4348 prepare_directory (dir);
4349 auto dcp = make_simple (dir, 1, 24);
4352 HashCalculator calc(find_cpl(dir));
4355 Editor e (find_cpl(dir));
4357 " <IntrinsicDuration>24</IntrinsicDuration>",
4358 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
4362 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
4364 check_verify_result (
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())
4384 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
4386 path dir = "build/test/verify_invalid_content_kind";
4387 prepare_directory (dir);
4388 auto dcp = make_simple (dir, 1, 24);
4391 HashCalculator calc(find_cpl(dir));
4394 Editor e(find_cpl(dir));
4395 e.replace("trailer", "trip");
4398 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4400 check_verify_result (
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()),
4418 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
4420 path dir = "build/test/verify_valid_content_kind";
4421 prepare_directory (dir);
4422 auto dcp = make_simple (dir, 1, 24);
4425 HashCalculator calc(find_cpl(dir));
4428 Editor e(find_cpl(dir));
4429 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
4432 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4434 check_verify_result (
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()),
4448 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
4450 path dir = "build/test/verify_invalid_main_picture_active_area_1";
4451 prepare_directory(dir);
4452 auto dcp = make_simple(dir, 1, 24);
4455 auto constexpr area = "<meta:MainPictureActiveArea>";
4457 HashCalculator calc(find_cpl(dir));
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>");
4466 dcp::PKL pkl(find_pkl(dir));
4467 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4469 check_verify_result(
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()),
4489 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
4491 path dir = "build/test/verify_invalid_main_picture_active_area_2";
4492 prepare_directory(dir);
4493 auto dcp = make_simple(dir, 1, 24);
4496 auto constexpr area = "<meta:MainPictureActiveArea>";
4498 HashCalculator calc(find_cpl(dir));
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>");
4507 dcp::PKL pkl(find_pkl(dir));
4508 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4510 check_verify_result(
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())
4533 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
4537 path dir = "build/test/verify_duplicate_pkl_asset_ids";
4538 prepare_directory(dir);
4539 auto dcp = make_simple(dir, 1, 24);
4543 Editor e(find_pkl(dir));
4544 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
4547 dcp::PKL pkl(find_pkl(dir));
4548 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4550 check_verify_result(
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)) },
4561 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
4565 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
4566 prepare_directory(dir);
4567 auto dcp = make_simple(dir, 1, 24);
4571 Editor e(find_asset_map(dir));
4572 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
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));
4579 check_verify_result(
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))
4588 dcp::VerificationNote(
4589 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54")
4595 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
4597 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
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";
4604 auto constexpr sample_rate = 48000;
4605 auto constexpr frames = 240;
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);
4620 /* Reel with 2 channels of audio */
4622 auto mp = simple_picture(path, "1", frames, {});
4623 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
4625 auto reel = make_shared<dcp::Reel>(
4626 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4627 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
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));
4638 /* Reel with 6 channels of audio */
4640 auto mp = simple_picture(path, "2", frames, {});
4641 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
4643 auto reel = make_shared<dcp::Reel>(
4644 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4645 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
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));
4656 dcp->set_annotation_text("hello");
4659 check_verify_result(
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())
4676 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
4678 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
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";
4685 auto constexpr sample_rate = 48000;
4686 auto constexpr frames = 240;
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);
4699 auto mp = simple_picture(path, "1", frames, {});
4700 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
4702 auto reel = make_shared<dcp::Reel>(
4703 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4704 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
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));
4715 dcp->set_annotation_text("hello");
4718 check_verify_result(
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())
4733 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
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;
4739 boost::filesystem::remove_all(path);
4740 boost::filesystem::create_directories(path);
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);
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);
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());
4758 picture_writer->finalize();
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")
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);
4771 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
4773 auto reel = make_shared<dcp::Reel>(
4774 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4775 make_shared<dcp::ReelSoundAsset>(ms, 0)
4780 dcp->set_annotation_text("A Test DCP");
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())
4795 for (auto frame = 0; frame < 24; frame++) {
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())
4803 int component_sizes[] = {
4809 for (auto frame = 0; frame < 24; frame++) {
4810 for (auto component = 0; component < 3; component++) {
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())
4819 check_verify_result({ path }, {}, expected);
4823 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
4825 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
4828 BOOST_REQUIRE(!dcp.cpls().empty());
4829 auto cpl = dcp.cpls()[0];
4831 check_verify_result(
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()),
4861 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
4863 path const dir("build/test/verify_missing_load_font");
4864 prepare_directory (dir);
4865 copy_file ("test/data/subs1.xml", dir / "subs.xml");
4867 Editor editor(dir / "subs.xml");
4868 editor.delete_first_line_containing("LoadFont");
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);
4874 check_verify_result (
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())
4887 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
4889 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
4890 prepare_directory(dir);
4891 auto dcp = make_simple (dir, 1, 202);
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>"
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>"
4914 dcp::File xml_file(dir / "subs.xml", "w");
4915 BOOST_REQUIRE(xml_file);
4916 xml_file.write(xml.c_str(), xml.size(), 1);
4918 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
4919 subs->write(dir / "subs.mxf");
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);
4926 check_verify_result (
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())
4939 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
4941 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
4942 boost::filesystem::remove_all(dir);
4944 auto dcp1 = make_simple(dir / "1");
4946 auto cpl = dcp1->cpls()[0];
4948 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
4950 auto dcp2 = make_simple(dir / "2");
4952 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
4954 boost::filesystem::remove(dir / "1" / "video.mxf");
4955 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
4957 check_verify_result(
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)
4968 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
4970 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
4971 boost::filesystem::remove_all(dir);
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(""));
4979 check_verify_result(
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())
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)
4995 auto const dir = path("build/test/verify_encrypted_smpte_dcp");
4997 auto key_id = dcp::make_uuid();
4998 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
5000 dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
5001 kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
5003 path const pkl_file = find_file(dir, "pkl_");
5004 path const cpl_file = find_file(dir, "cpl_");
5006 check_verify_result(
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)