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>
70 using std::make_shared;
72 using std::shared_ptr;
75 using boost::optional;
76 using namespace boost::filesystem;
79 static list<pair<string, optional<path>>> stages;
81 static string filename_to_id(boost::filesystem::path path)
83 return path.string().substr(4, path.string().length() - 8);
87 boost::filesystem::path
90 return find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
97 return filename_to_id(dcp_test1_pkl());
101 boost::filesystem::path
104 return find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
111 return filename_to_id(dcp_test1_cpl());
114 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
118 encryption_test_cpl_id()
120 return filename_to_id(find_file("test/ref/DCP/encryption_test", "cpl_").filename());
125 encryption_test_pkl_id()
127 return filename_to_id(find_file("test/ref/DCP/encryption_test", "pkl_").filename());
131 stage (string s, optional<path> p)
133 stages.push_back (make_pair (s, p));
143 prepare_directory (path path)
145 using namespace boost::filesystem;
147 create_directories (path);
153 find_prefix(path dir, string prefix)
155 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
156 return boost::starts_with(p.filename().string(), prefix);
159 BOOST_REQUIRE(iter != directory_iterator());
168 return find_prefix(dir, "cpl_");
176 return find_prefix(dir, "pkl_");
182 find_asset_map(path dir)
184 return find_prefix(dir, "ASSETMAP");
188 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
189 * to make a new sacrificial test DCP.
192 setup (int reference_number, string verify_test_suffix)
194 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
195 prepare_directory (dir);
196 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
197 copy_file (i.path(), dir / i.path().filename());
206 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
208 auto reel = make_shared<dcp::Reel>();
209 reel->add (reel_asset);
210 reel->add (simple_markers());
212 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
214 auto dcp = make_shared<dcp::DCP>(dir);
216 dcp->set_annotation_text("hello");
223 LIBDCP_DISABLE_WARNINGS
226 dump_notes (vector<dcp::VerificationNote> const & notes)
228 for (auto i: notes) {
229 std::cout << dcp::note_to_string(i) << "\n";
232 LIBDCP_ENABLE_WARNINGS
237 to_string(dcp::VerificationNote const& note)
239 string s = note_to_string(note) + dcp::String::compose(
240 "\n [%1 %2 %3 %4 %5 %6 ",
241 static_cast<int>(note.type()),
242 static_cast<int>(note.code()),
243 note.note().get_value_or("<none>"),
244 note.file().get_value_or("<none>"),
245 note.line().get_value_or(0),
246 note.frame().get_value_or(0)
249 s += dcp::String::compose(
251 note.id().get_value_or("<none>"),
252 note.other_id().get_value_or("<none>"),
253 note.cpl_id().get_value_or("<none>"),
254 note.reference_hash().get_value_or("<none>"),
255 note.calculated_hash().get_value_or("<none>")
264 check_verify_result(vector<dcp::VerificationNote> notes, vector<dcp::VerificationNote> test_notes)
266 std::sort(notes.begin(), notes.end());
267 std::sort(test_notes.begin(), test_notes.end());
269 string message = "\n";
271 vector<dcp::VerificationNote> not_expected;
272 for (auto note: notes) {
273 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(); });
274 if (iter != test_notes.end() && *iter != note) {
275 message += "Wrong details:\n --seen " + to_string(note) + " --expected " + to_string(*iter) + "\n";
276 } else if (iter == test_notes.end()) {
277 not_expected.push_back(note);
281 vector<dcp::VerificationNote> not_seen;
282 for (auto note: test_notes) {
283 auto iter = std::find_if(notes.begin(), notes.end(), [note](dcp::VerificationNote const& n) { return note.type() == n.type() && note.code() == n.code(); });
284 if (iter == notes.end()) {
285 not_seen.push_back(note);
289 for (auto note: not_expected) {
290 message += "Not expected:\n" + to_string(note) + "\n";
293 for (auto note: not_seen) {
294 message += "Not seen:\n" + to_string(note) + "\n";
297 BOOST_REQUIRE_MESSAGE(notes == test_notes, message);
303 check_verify_result(vector<path> dir, vector<dcp::DecryptedKDM> kdm, vector<dcp::VerificationNote> test_notes)
305 check_verify_result(dcp::verify({dir}, kdm, &stage, &progress, {}, xsd_test).notes, test_notes);
309 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
310 * replacing from with to.
314 replace(string suffix, boost::function<path (string)> file, string from, string to)
316 auto dir = setup (1, suffix);
319 Editor e (file(suffix));
320 e.replace (from, to);
327 add_font(shared_ptr<dcp::SubtitleAsset> asset)
329 dcp::ArrayData fake_font(1024);
330 asset->add_font("font", fake_font);
337 HashCalculator(boost::filesystem::path path)
339 , _old_hash(dcp::make_digest(path, [](int64_t, int64_t) {}))
342 std::string old_hash() const {
346 std::string new_hash() const {
347 return dcp::make_digest(_path, [](int64_t, int64_t) {});
351 boost::filesystem::path _path;
352 std::string _old_hash;
357 dcp::VerificationNote
358 ok(dcp::VerificationNote::Code code, shared_ptr<const dcp::CPL> cpl)
360 return dcp::VerificationNote(dcp::VerificationNote::Type::OK, code).set_cpl_id(cpl->id());
365 add(vector<dcp::VerificationNote>& notes, vector<dcp::VerificationNote> const& add)
373 BOOST_AUTO_TEST_CASE (verify_no_error)
376 auto dir = setup (1, "no_error");
377 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
379 path const cpl_file = dir / dcp_test1_cpl();
380 path const pkl_file = dir / dcp_test1_pkl();
381 path const assetmap_file = dir / "ASSETMAP.xml";
383 auto st = stages.begin();
384 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
385 BOOST_REQUIRE (st->second);
386 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
388 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
389 BOOST_REQUIRE (st->second);
390 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
392 BOOST_CHECK_EQUAL (st->first, "Checking reel");
393 BOOST_REQUIRE (!st->second);
395 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
396 BOOST_REQUIRE (st->second);
397 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
399 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
400 BOOST_REQUIRE (st->second);
401 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
403 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
404 BOOST_REQUIRE (st->second);
405 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
407 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
408 BOOST_REQUIRE (st->second);
409 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
411 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
412 BOOST_REQUIRE (st->second);
413 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
414 ++st; BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
415 BOOST_REQUIRE (st->second);
416 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
418 BOOST_REQUIRE (st == stages.end());
420 BOOST_CHECK_EQUAL (notes.size(), 0U);
424 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
426 using namespace boost::filesystem;
428 auto dir = setup (1, "incorrect_picture_sound_hash");
430 auto video_path = path(dir / "video.mxf");
431 HashCalculator video_calc(video_path);
432 auto mod = fopen(video_path.string().c_str(), "r+b");
434 BOOST_REQUIRE_EQUAL(fseek(mod, -16, SEEK_END), 0);
436 BOOST_REQUIRE(fwrite(&x, sizeof(x), 1, mod) == 1);
439 auto audio_path = path(dir / "audio.mxf");
440 HashCalculator audio_calc(audio_path);
441 mod = fopen(audio_path.string().c_str(), "r+b");
443 BOOST_REQUIRE_EQUAL(fseek(mod, 0, SEEK_END), 0);
444 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
447 dcp::ASDCPErrorSuspender sus;
448 check_verify_result (
452 dcp::VerificationNote(
453 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path)
454 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(video_calc.old_hash()).set_calculated_hash(video_calc.new_hash()),
455 dcp::VerificationNote(
456 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path)
457 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(audio_calc.old_hash()).set_calculated_hash(audio_calc.new_hash()),
462 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
464 using namespace boost::filesystem;
466 auto dir = setup (1, "mismatched_picture_sound_hashes");
468 HashCalculator calc(dir / dcp_test1_cpl());
471 Editor e (dir / dcp_test1_pkl());
472 e.replace ("<Hash>", "<Hash>x");
475 check_verify_result (
479 dcp::VerificationNote(
480 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl())
481 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash("x" + calc.old_hash()).set_calculated_hash(calc.old_hash()),
482 dcp::VerificationNote(
483 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf")
484 ).set_cpl_id(dcp_test1_cpl_id()),
485 dcp::VerificationNote(
486 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf")
487 ).set_cpl_id(dcp_test1_cpl_id()),
488 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'x3M7YTgvFKXXMEGLkIbV4miC90FE=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 28 },
489 { 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 },
490 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xvsVjRV9vhTBPUWfE/TT1o2vdQsI=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 20 },
495 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
497 auto dir = setup (1, "failed_read_content_kind");
499 HashCalculator calc(dir / dcp_test1_cpl());
502 Editor e (dir / dcp_test1_cpl());
503 e.replace ("<ContentKind>", "<ContentKind>x");
506 check_verify_result (
510 dcp::VerificationNote(
511 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl())
512 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
513 dcp::VerificationNote(
514 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer")
515 ).set_cpl_id(dcp_test1_cpl_id())
522 dcp_test1_cpl_path(string suffix)
524 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl());
530 dcp_test1_pkl_path(string suffix)
532 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl());
538 asset_map (string suffix)
540 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
544 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
546 auto const suffix = "invalid_picture_frame_rate";
548 replace(suffix, &dcp_test1_cpl_path, "<FrameRate>24 1", "<FrameRate>99 1");
550 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
551 auto const cpl_path = find_cpl(dir);
552 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
554 std::vector<dcp::VerificationNote> expected =
556 dcp::VerificationNote(
557 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
558 ).set_cpl_id(cpl->id()).set_calculated_hash("7n7GQ2TbxQbmHYuAR8ml7XDOep8=").set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI="),
559 dcp::VerificationNote(
560 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, string{"99/1"}
561 ).set_cpl_id(cpl->id())
564 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
567 BOOST_AUTO_TEST_CASE (verify_missing_asset)
569 auto dir = setup (1, "missing_asset");
570 remove (dir / "video.mxf");
572 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
574 check_verify_result (
578 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
583 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
585 auto const suffix = "empty_asset_path";
587 replace("empty_asset_path", &asset_map, "<Path>video.mxf</Path>", "<Path></Path>");
589 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
590 auto const cpl_path = find_cpl(dir);
591 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
593 std::vector<dcp::VerificationNote> expected = {
594 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
597 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
601 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
603 auto const suffix = "mismatched_standard";
605 replace(suffix, &dcp_test1_cpl_path, "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#");
607 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
608 auto const cpl_path = find_cpl(dir);
609 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
611 std::vector<dcp::VerificationNote> expected = {
612 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_STANDARD },
613 dcp::VerificationNote(
614 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "invalid character encountered", canonical(cpl_path), 42
615 ).set_cpl_id(cpl->id()),
616 dcp::VerificationNote(
617 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'Id'", canonical(cpl_path), 53
618 ).set_cpl_id(cpl->id()),
619 dcp::VerificationNote(
620 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'EditRate'", canonical(cpl_path), 54
621 ).set_cpl_id(cpl->id()),
622 dcp::VerificationNote(
623 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'IntrinsicDuration'", canonical(cpl_path), 55
624 ).set_cpl_id(cpl->id()),
625 dcp::VerificationNote(
626 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
627 "element 'Id' is not allowed for content model '(Id,AnnotationText?,EditRate,IntrinsicDuration,"
628 "EntryPoint?,Duration?,FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
629 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,MainSoundSampleRate,"
630 "MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,ExtensionMetadataList?,)'",
631 canonical(cpl_path), 149
632 ).set_cpl_id(cpl->id()),
633 dcp::VerificationNote(
634 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
635 ).set_cpl_id(cpl->id()).set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI=").set_calculated_hash("FZ9E7L/pOuJ6aZfbiaANTv8BFOo=")
638 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
642 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
644 auto const suffix = "invalid_xml_cpl_id";
646 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
647 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");
649 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
650 auto const cpl_path = find_cpl(dir);
651 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
653 std::vector<dcp::VerificationNote> expected = {
654 dcp::VerificationNote(
655 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
656 "value 'urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a' does not match regular expression "
657 "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
658 ).set_cpl_id(cpl->id())
661 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
665 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
667 auto const suffix = "invalid_xml_issue_date";
669 replace("invalid_xml_issue_date", &dcp_test1_cpl_path, "<IssueDate>", "<IssueDate>x");
671 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
672 auto const cpl_path = find_cpl(dir);
673 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
675 std::vector<dcp::VerificationNote> expected = {
676 dcp::VerificationNote(
677 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
678 ).set_cpl_id(cpl->id()).set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI=").set_calculated_hash("sz3BeIugJ567q3HMnA62JeRw4TE="),
679 dcp::VerificationNote(
680 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
681 "invalid character encountered",
682 canonical(cpl_path), 5
683 ).set_cpl_id(cpl->id()),
686 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
690 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
692 auto const suffix = "invalid_xml_pkl_id";
694 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));
696 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
697 auto const pkl_path = find_pkl(dir);
698 auto const cpl_path = find_cpl(dir);
699 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
701 std::vector<dcp::VerificationNote> expected = {
702 dcp::VerificationNote(
703 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
704 "value 'urn:uuid:x199d58b-5ef8-4d49-b270-07e590ccb280' does not match regular "
705 "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}'",
706 canonical(pkl_path), 3
710 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
714 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
716 auto const suffix = "invalid_xml_asset_map_id";
718 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));
720 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
721 auto const cpl_path = find_cpl(dir);
722 auto const asset_map_path = find_asset_map(dir);
723 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
725 std::vector<dcp::VerificationNote> expected = {
726 dcp::VerificationNote(
727 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
728 "value 'urn:uuid:x17b3de4-6dda-408d-b19b-6711354b0bc3' does not match regular "
729 "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}'",
730 canonical(asset_map_path), 3
734 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
738 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
741 auto dir = setup (3, "verify_invalid_standard");
742 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
744 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
745 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
746 path const assetmap_file = dir / "ASSETMAP";
748 auto st = stages.begin();
749 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
750 BOOST_REQUIRE (st->second);
751 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
753 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
754 BOOST_REQUIRE (st->second);
755 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
757 BOOST_CHECK_EQUAL (st->first, "Checking reel");
758 BOOST_REQUIRE (!st->second);
760 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
761 BOOST_REQUIRE (st->second);
762 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
764 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
765 BOOST_REQUIRE (st->second);
766 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
768 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
769 BOOST_REQUIRE (st->second);
770 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
772 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
773 BOOST_REQUIRE (st->second);
774 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
776 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
777 BOOST_REQUIRE (st->second);
778 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
780 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
781 BOOST_REQUIRE (st->second);
782 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
784 BOOST_REQUIRE (st == stages.end());
786 BOOST_REQUIRE_EQUAL(notes.size(), 25U);
787 auto i = notes.begin ();
788 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
789 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
791 for (int j = 0; j < 24; ++j) {
792 BOOST_CHECK_EQUAL(i->type(), dcp::VerificationNote::Type::BV21_ERROR);
793 BOOST_CHECK_EQUAL(i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
798 /* DCP with a short asset */
799 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
801 auto dir = setup (8, "invalid_duration");
805 BOOST_REQUIRE(dcp.cpls().size() == 1);
806 auto cpl = dcp.cpls()[0];
808 vector<dcp::VerificationNote> expected = {
809 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
810 dcp::VerificationNote(
811 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91")
812 ).set_cpl_id(cpl->id()),
813 dcp::VerificationNote(
814 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91")
815 ).set_cpl_id(cpl->id()),
816 dcp::VerificationNote(
817 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626")
818 ).set_cpl_id(cpl->id()),
819 dcp::VerificationNote(
820 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626")
821 ).set_cpl_id(cpl->id()),
822 dcp::VerificationNote(
823 dcp::VerificationNote::Type::WARNING,
824 dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT,
826 ).set_cpl_id(cpl->id())
829 for (int i = 0; i < 23; ++i) {
831 dcp::VerificationNote(
832 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2")
833 ).set_cpl_id(cpl->id())
837 check_verify_result({ dir }, {}, expected);
843 dcp_from_frame (dcp::ArrayData const& frame, path dir)
845 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
846 create_directories (dir);
847 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
848 for (int i = 0; i < 24; ++i) {
849 writer->write (frame.data(), frame.size());
853 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
854 return write_dcp_with_single_asset (dir, reel_asset);
858 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
860 int const too_big = 1302083 * 2;
862 /* Compress a black image */
863 auto image = black_image ();
864 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
865 BOOST_REQUIRE (frame.size() < too_big);
867 /* Place it in a bigger block with some zero padding at the end */
868 dcp::ArrayData oversized_frame(too_big);
869 memcpy (oversized_frame.data(), frame.data(), frame.size());
870 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
872 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
873 prepare_directory (dir);
874 auto cpl = dcp_from_frame (oversized_frame, dir);
876 vector<dcp::VerificationNote> expected;
877 for (auto i = 0; i < 24; ++i) {
879 dcp::VerificationNote(
880 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
881 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
885 for (auto i = 0; i < 24; ++i) {
887 dcp::VerificationNote(
888 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
889 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
894 dcp::VerificationNote(
895 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
896 ).set_cpl_id(cpl->id())
899 check_verify_result({ dir }, {}, expected);
903 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
905 int const nearly_too_big = 1302083 * 0.98;
907 /* Compress a black image */
908 auto image = black_image ();
909 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
910 BOOST_REQUIRE (frame.size() < nearly_too_big);
912 /* Place it in a bigger block with some zero padding at the end */
913 dcp::ArrayData oversized_frame(nearly_too_big);
914 memcpy (oversized_frame.data(), frame.data(), frame.size());
915 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
917 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
918 prepare_directory (dir);
919 auto cpl = dcp_from_frame (oversized_frame, dir);
921 vector<dcp::VerificationNote> expected;
923 for (auto i = 0; i < 24; ++i) {
925 dcp::VerificationNote(
926 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
927 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
931 for (auto i = 0; i < 24; ++i) {
933 dcp::VerificationNote(
934 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
935 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
940 dcp::VerificationNote(
941 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
942 ).set_cpl_id(cpl->id())
945 check_verify_result ({ dir }, {}, expected);
949 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
951 /* Compress a black image */
952 auto image = black_image ();
953 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
954 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
956 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
957 prepare_directory (dir);
958 auto cpl = dcp_from_frame (frame, dir);
963 { dcp::VerificationNote(dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()).set_cpl_id(cpl->id()) }
968 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
970 path const dir("build/test/verify_valid_interop_subtitles");
971 prepare_directory (dir);
972 copy_file ("test/data/subs1.xml", dir / "subs.xml");
973 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
974 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
975 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
977 check_verify_result (
981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
982 dcp::VerificationNote(
983 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
984 ).set_cpl_id(cpl->id())
989 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
991 path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
992 prepare_directory(dir);
993 copy_file("test/data/subs1.xml", dir / "ccap.xml");
994 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
995 auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
996 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
998 check_verify_result (
1002 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1003 dcp::VerificationNote(
1004 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1005 ).set_cpl_id(cpl->id())
1010 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
1012 using namespace boost::filesystem;
1014 path const dir("build/test/verify_invalid_interop_subtitles");
1015 prepare_directory (dir);
1016 copy_file ("test/data/subs1.xml", dir / "subs.xml");
1017 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1018 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1019 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1022 Editor e (dir / "subs.xml");
1023 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
1026 check_verify_result (
1030 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1031 dcp::VerificationNote(
1032 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5
1033 ).set_cpl_id(cpl->id()),
1034 dcp::VerificationNote(
1035 dcp::VerificationNote::Type::ERROR,
1036 dcp::VerificationNote::Code::INVALID_XML,
1037 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
1040 ).set_cpl_id(cpl->id()),
1041 dcp::VerificationNote(
1042 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1043 ).set_cpl_id(cpl->id())
1048 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
1050 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
1051 prepare_directory(dir);
1052 copy_file("test/data/subs4.xml", dir / "subs.xml");
1053 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1054 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1055 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1057 check_verify_result (
1061 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1062 dcp::VerificationNote(
1063 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get())
1064 ).set_cpl_id(cpl->id()),
1065 dcp::VerificationNote(
1066 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1067 ).set_cpl_id(cpl->id())
1073 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
1075 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
1076 prepare_directory(dir);
1077 copy_file("test/data/subs5.xml", dir / "subs.xml");
1078 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1079 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1080 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1082 check_verify_result (
1086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1087 dcp::VerificationNote(
1088 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"}
1089 ).set_cpl_id(cpl->id())
1095 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
1097 path const dir("build/test/verify_valid_smpte_subtitles");
1098 prepare_directory (dir);
1099 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1100 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1101 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1102 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1104 check_verify_result(
1108 dcp::VerificationNote(
1109 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1110 ).set_cpl_id(cpl->id()),
1111 dcp::VerificationNote(
1112 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"}
1113 ).set_cpl_id(cpl->id()),
1114 dcp::VerificationNote(
1115 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1116 ).set_cpl_id(cpl->id()),
1121 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
1123 using namespace boost::filesystem;
1125 path const dir("build/test/verify_invalid_smpte_subtitles");
1126 prepare_directory (dir);
1127 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
1128 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
1129 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1130 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1131 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1133 check_verify_result (
1137 dcp::VerificationNote(
1138 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2
1139 ).set_cpl_id(cpl->id()),
1140 dcp::VerificationNote(
1141 dcp::VerificationNote::Type::ERROR,
1142 dcp::VerificationNote::Code::INVALID_XML,
1143 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
1146 ).set_cpl_id(cpl->id()),
1147 dcp::VerificationNote(
1148 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1149 ).set_cpl_id(cpl->id()),
1150 dcp::VerificationNote(
1151 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1152 ).set_cpl_id(cpl->id()),
1153 dcp::VerificationNote(
1154 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"}
1155 ).set_cpl_id(cpl->id()),
1156 dcp::VerificationNote(
1157 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1158 ).set_cpl_id(cpl->id()),
1163 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
1165 path const dir("build/test/verify_empty_text_node_in_subtitles");
1166 prepare_directory (dir);
1167 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
1168 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1169 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1170 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1172 check_verify_result (
1176 dcp::VerificationNote(
1177 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT
1178 ).set_cpl_id(cpl->id()),
1179 dcp::VerificationNote(
1180 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1181 ).set_cpl_id(cpl->id()),
1182 dcp::VerificationNote(
1183 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf")
1184 ).set_cpl_id(cpl->id()),
1185 dcp::VerificationNote(
1186 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1187 ).set_cpl_id(cpl->id()),
1188 dcp::VerificationNote(
1189 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"}
1190 ).set_cpl_id(cpl->id()),
1191 dcp::VerificationNote(
1192 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1193 ).set_cpl_id(cpl->id())
1198 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
1199 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
1201 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
1202 prepare_directory (dir);
1203 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
1204 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1205 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1206 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
1208 check_verify_result (
1212 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1213 dcp::VerificationNote(
1214 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"}
1215 ).set_cpl_id(cpl->id())
1220 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
1221 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
1223 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
1224 prepare_directory (dir);
1225 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
1226 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1227 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1228 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
1230 check_verify_result (
1234 dcp::VerificationNote(
1235 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get())
1236 ).set_cpl_id(cpl->id()),
1237 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1238 dcp::VerificationNote(
1239 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT
1240 ).set_cpl_id(cpl->id()),
1241 dcp::VerificationNote(
1242 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"}
1243 ).set_cpl_id(cpl->id())
1248 BOOST_AUTO_TEST_CASE (verify_external_asset)
1250 path const ov_dir("build/test/verify_external_asset");
1251 prepare_directory (ov_dir);
1253 auto image = black_image ();
1254 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
1255 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
1256 dcp_from_frame (frame, ov_dir);
1258 dcp::DCP ov (ov_dir);
1261 path const vf_dir("build/test/verify_external_asset_vf");
1262 prepare_directory (vf_dir);
1264 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
1265 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
1267 check_verify_result (
1271 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
1272 dcp::VerificationNote(
1273 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1274 ).set_cpl_id(cpl->id())
1279 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
1281 path const dir("build/test/verify_valid_cpl_metadata");
1282 prepare_directory (dir);
1284 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1285 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1286 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1288 auto reel = make_shared<dcp::Reel>();
1289 reel->add (reel_asset);
1291 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1292 reel->add (simple_markers(16 * 24));
1294 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1296 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1297 cpl->set_main_sound_sample_rate (48000);
1298 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1299 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1300 cpl->set_version_number (1);
1304 dcp.set_annotation_text("hello");
1309 /* DCP with invalid CompositionMetadataAsset */
1310 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1312 using namespace boost::filesystem;
1314 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1315 prepare_directory (dir);
1317 auto reel = make_shared<dcp::Reel>();
1318 reel->add (black_picture_asset(dir));
1319 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1321 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1322 cpl->set_main_sound_sample_rate (48000);
1323 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1324 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1325 cpl->set_version_number (1);
1327 reel->add (simple_markers());
1331 dcp.set_annotation_text("hello");
1334 HashCalculator calc(find_cpl(dir));
1337 Editor e (find_cpl(dir));
1338 e.replace ("MainSound", "MainSoundX");
1341 check_verify_result (
1345 dcp::VerificationNote(
1346 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50
1347 ).set_cpl_id(cpl->id()),
1348 dcp::VerificationNote(
1349 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51
1350 ).set_cpl_id(cpl->id()),
1351 dcp::VerificationNote(
1352 dcp::VerificationNote::Type::ERROR,
1353 dcp::VerificationNote::Code::INVALID_XML,
1354 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1355 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1356 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1357 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1358 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1359 "ExtensionMetadataList?,)'"),
1360 canonical(cpl->file().get()),
1361 71).set_cpl_id(cpl->id()),
1362 dcp::VerificationNote(
1363 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
1364 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
1369 /* DCP with invalid CompositionMetadataAsset */
1370 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1372 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1373 prepare_directory (dir);
1375 auto reel = make_shared<dcp::Reel>();
1376 reel->add (black_picture_asset(dir));
1377 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1379 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1380 cpl->set_main_sound_sample_rate (48000);
1381 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1382 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1386 dcp.set_annotation_text("hello");
1390 Editor e (find_cpl(dir));
1391 e.replace ("meta:Width", "meta:WidthX");
1394 check_verify_result (
1397 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1402 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1404 path const dir("build/test/verify_invalid_language1");
1405 prepare_directory (dir);
1406 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1407 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1408 asset->_language = "wrong-andbad";
1409 asset->write (dir / "subs.mxf");
1410 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1411 reel_asset->_language = "badlang";
1412 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1414 check_verify_result (
1418 dcp::VerificationNote(
1419 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang")
1420 ).set_cpl_id(cpl->id()),
1421 dcp::VerificationNote(
1422 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad")
1423 ).set_cpl_id(cpl->id()),
1424 dcp::VerificationNote(
1425 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1426 ).set_cpl_id(cpl->id())
1431 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1432 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1434 path const dir("build/test/verify_invalid_language2");
1435 prepare_directory (dir);
1436 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1437 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1438 asset->_language = "wrong-andbad";
1439 asset->write (dir / "subs.mxf");
1440 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1441 reel_asset->_language = "badlang";
1442 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1444 check_verify_result (
1448 dcp::VerificationNote(
1449 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang")
1450 ).set_cpl_id(cpl->id()),
1451 dcp::VerificationNote(
1452 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad")
1453 ).set_cpl_id(cpl->id()),
1454 dcp::VerificationNote(
1455 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1456 ).set_cpl_id(cpl->id())
1461 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1462 * the release territory.
1464 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1466 path const dir("build/test/verify_invalid_language3");
1467 prepare_directory (dir);
1469 auto picture = simple_picture (dir, "foo");
1470 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1471 auto reel = make_shared<dcp::Reel>();
1472 reel->add (reel_picture);
1473 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1474 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1475 reel->add (reel_sound);
1476 reel->add (simple_markers());
1478 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1480 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1481 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1482 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1483 cpl->set_main_sound_sample_rate (48000);
1484 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1485 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1486 cpl->set_version_number (1);
1487 cpl->_release_territory = "fred-jim";
1488 auto dcp = make_shared<dcp::DCP>(dir);
1490 dcp->set_annotation_text("hello");
1493 check_verify_result (
1497 dcp::VerificationNote(
1498 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong")
1499 ).set_cpl_id(cpl->id()),
1500 dcp::VerificationNote(
1501 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this")
1502 ).set_cpl_id(cpl->id()),
1503 dcp::VerificationNote(
1504 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim")
1505 ).set_cpl_id(cpl->id()),
1506 dcp::VerificationNote(
1507 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz")
1508 ).set_cpl_id(cpl->id()),
1514 vector<dcp::VerificationNote>
1515 check_picture_size (int width, int height, int frame_rate, bool three_d)
1517 using namespace boost::filesystem;
1519 path dcp_path = "build/test/verify_picture_test";
1520 prepare_directory (dcp_path);
1522 shared_ptr<dcp::PictureAsset> mp;
1524 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1526 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1528 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1530 auto image = black_image (dcp::Size(width, height));
1531 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1532 int const length = three_d ? frame_rate * 2 : frame_rate;
1533 for (int i = 0; i < length; ++i) {
1534 picture_writer->write (j2c.data(), j2c.size());
1536 picture_writer->finalize ();
1538 auto d = make_shared<dcp::DCP>(dcp_path);
1539 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1540 cpl->set_annotation_text ("A Test DCP");
1541 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1542 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1543 cpl->set_main_sound_sample_rate (48000);
1544 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1545 cpl->set_main_picture_active_area(dcp::Size(width, height));
1546 cpl->set_version_number (1);
1548 auto reel = make_shared<dcp::Reel>();
1551 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1553 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1556 reel->add (simple_markers(frame_rate));
1561 d->set_annotation_text("A Test DCP");
1564 return dcp::verify({dcp_path}, {}, &stage, &progress, {}, xsd_test).notes;
1570 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1572 auto notes = check_picture_size(width, height, frame_rate, three_d);
1573 BOOST_CHECK_EQUAL (notes.size(), 0U);
1579 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1581 auto notes = check_picture_size(width, height, frame_rate, three_d);
1582 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1583 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1584 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1590 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1592 auto notes = check_picture_size(width, height, frame_rate, three_d);
1593 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1594 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1595 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1601 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1603 auto notes = check_picture_size(width, height, frame_rate, three_d);
1604 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1605 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1606 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1610 BOOST_AUTO_TEST_CASE (verify_picture_size)
1612 using namespace boost::filesystem;
1615 check_picture_size_ok (2048, 858, 24, false);
1616 check_picture_size_ok (2048, 858, 25, false);
1617 check_picture_size_ok (2048, 858, 48, false);
1618 check_picture_size_ok (2048, 858, 24, true);
1619 check_picture_size_ok (2048, 858, 25, true);
1620 check_picture_size_ok (2048, 858, 48, true);
1623 check_picture_size_ok (1998, 1080, 24, false);
1624 check_picture_size_ok (1998, 1080, 25, false);
1625 check_picture_size_ok (1998, 1080, 48, false);
1626 check_picture_size_ok (1998, 1080, 24, true);
1627 check_picture_size_ok (1998, 1080, 25, true);
1628 check_picture_size_ok (1998, 1080, 48, true);
1631 check_picture_size_ok (4096, 1716, 24, false);
1634 check_picture_size_ok (3996, 2160, 24, false);
1636 /* Bad frame size */
1637 check_picture_size_bad_frame_size (2050, 858, 24, false);
1638 check_picture_size_bad_frame_size (2048, 658, 25, false);
1639 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1640 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1642 /* Bad 2K frame rate */
1643 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1644 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1645 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1647 /* Bad 4K frame rate */
1648 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1649 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1652 auto notes = check_picture_size(3996, 2160, 24, true);
1653 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1654 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1655 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1661 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")
1664 std::make_shared<dcp::SubtitleString>(
1672 dcp::Time(start_frame, 24, 24),
1673 dcp::Time(end_frame, 24, 24),
1675 dcp::HAlign::CENTER,
1679 dcp::Direction::LTR,
1686 std::vector<dcp::Ruby>()
1692 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1694 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1695 prepare_directory (dir);
1697 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1698 for (int i = 0; i < 2048; ++i) {
1699 add_test_subtitle (asset, i * 24, i * 24 + 20);
1702 asset->set_language (dcp::LanguageTag("de-DE"));
1703 asset->write (dir / "subs.mxf");
1704 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1705 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1707 check_verify_result (
1711 dcp::VerificationNote(
1712 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1713 ).set_cpl_id(cpl->id()),
1714 dcp::VerificationNote(
1715 dcp::VerificationNote::Type::BV21_ERROR,
1716 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1718 canonical(dir / "subs.mxf")
1719 ).set_cpl_id(cpl->id()),
1720 dcp::VerificationNote(
1721 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1722 ).set_cpl_id(cpl->id()),
1723 dcp::VerificationNote(
1724 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1725 ).set_cpl_id(cpl->id())
1731 shared_ptr<dcp::SMPTESubtitleAsset>
1732 make_large_subtitle_asset (path font_file)
1734 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1735 dcp::ArrayData big_fake_font(1024 * 1024);
1736 big_fake_font.write (font_file);
1737 for (int i = 0; i < 116; ++i) {
1738 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1746 verify_timed_text_asset_too_large (string name)
1748 auto const dir = path("build/test") / name;
1749 prepare_directory (dir);
1750 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1751 add_test_subtitle (asset, 0, 240);
1752 asset->set_language (dcp::LanguageTag("de-DE"));
1753 asset->write (dir / "subs.mxf");
1755 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1756 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1758 check_verify_result (
1762 dcp::VerificationNote(
1763 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121698284"), canonical(dir / "subs.mxf")
1764 ).set_cpl_id(cpl->id()),
1765 dcp::VerificationNote(
1766 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf")
1767 ).set_cpl_id(cpl->id()),
1768 dcp::VerificationNote(
1769 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1770 ).set_cpl_id(cpl->id()),
1771 dcp::VerificationNote(
1772 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1773 ).set_cpl_id(cpl->id()),
1774 dcp::VerificationNote(
1775 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1776 ).set_cpl_id(cpl->id())
1781 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1783 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1784 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1788 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1790 path dir = "build/test/verify_missing_subtitle_language";
1791 prepare_directory (dir);
1792 auto dcp = make_simple (dir, 1, 106);
1795 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1796 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1797 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1798 "<ContentTitleText>Content</ContentTitleText>"
1799 "<AnnotationText>Annotation</AnnotationText>"
1800 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1801 "<ReelNumber>1</ReelNumber>"
1802 "<EditRate>24 1</EditRate>"
1803 "<TimeCodeRate>24</TimeCodeRate>"
1804 "<StartTime>00:00:00:00</StartTime>"
1805 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1807 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1808 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1809 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1815 dcp::File xml_file(dir / "subs.xml", "w");
1816 BOOST_REQUIRE (xml_file);
1817 xml_file.write(xml.c_str(), xml.size(), 1);
1819 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1820 subs->write (dir / "subs.mxf");
1822 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1823 auto cpl = dcp->cpls()[0];
1824 cpl->reels()[0]->add(reel_subs);
1827 check_verify_result (
1831 dcp::VerificationNote(
1832 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf")
1833 ).set_cpl_id(cpl->id()),
1834 dcp::VerificationNote(
1835 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1836 ).set_cpl_id(cpl->id())
1841 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1843 path path ("build/test/verify_mismatched_subtitle_languages");
1844 auto constexpr reel_length = 192;
1845 auto dcp = make_simple (path, 2, reel_length);
1846 auto cpl = dcp->cpls()[0];
1849 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1850 subs->set_language (dcp::LanguageTag("de-DE"));
1851 subs->add (simple_subtitle());
1853 subs->write (path / "subs1.mxf");
1854 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1855 cpl->reels()[0]->add(reel_subs);
1859 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1860 subs->set_language (dcp::LanguageTag("en-US"));
1861 subs->add (simple_subtitle());
1863 subs->write (path / "subs2.mxf");
1864 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1865 cpl->reels()[1]->add(reel_subs);
1870 check_verify_result (
1874 dcp::VerificationNote(
1875 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf")
1876 ).set_cpl_id(cpl->id()),
1877 dcp::VerificationNote(
1878 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf")
1879 ).set_cpl_id(cpl->id()),
1880 dcp::VerificationNote(
1881 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES
1882 ).set_cpl_id(cpl->id()),
1887 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1889 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1890 auto constexpr reel_length = 192;
1891 auto dcp = make_simple (path, 2, reel_length);
1892 auto cpl = dcp->cpls()[0];
1895 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1896 ccaps->set_language (dcp::LanguageTag("de-DE"));
1897 ccaps->add (simple_subtitle());
1899 ccaps->write (path / "subs1.mxf");
1900 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1901 cpl->reels()[0]->add(reel_ccaps);
1905 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1906 ccaps->set_language (dcp::LanguageTag("en-US"));
1907 ccaps->add (simple_subtitle());
1909 ccaps->write (path / "subs2.mxf");
1910 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1911 cpl->reels()[1]->add(reel_ccaps);
1916 check_verify_result (
1920 dcp::VerificationNote(
1921 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf")
1922 ).set_cpl_id(cpl->id()),
1923 dcp::VerificationNote(
1924 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf")
1925 ).set_cpl_id(cpl->id())
1930 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1932 path dir = "build/test/verify_missing_subtitle_start_time";
1933 prepare_directory (dir);
1934 auto dcp = make_simple (dir, 1, 106);
1937 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1938 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1939 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1940 "<ContentTitleText>Content</ContentTitleText>"
1941 "<AnnotationText>Annotation</AnnotationText>"
1942 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1943 "<ReelNumber>1</ReelNumber>"
1944 "<Language>de-DE</Language>"
1945 "<EditRate>24 1</EditRate>"
1946 "<TimeCodeRate>24</TimeCodeRate>"
1947 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1949 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1950 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1951 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1957 dcp::File xml_file(dir / "subs.xml", "w");
1958 BOOST_REQUIRE (xml_file);
1959 xml_file.write(xml.c_str(), xml.size(), 1);
1961 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1962 subs->write (dir / "subs.mxf");
1964 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1965 auto cpl = dcp->cpls()[0];
1966 cpl->reels()[0]->add(reel_subs);
1969 check_verify_result (
1973 dcp::VerificationNote(
1974 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1975 ).set_cpl_id(cpl->id()),
1976 dcp::VerificationNote(
1977 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1978 ).set_cpl_id(cpl->id())
1983 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1985 path dir = "build/test/verify_invalid_subtitle_start_time";
1986 prepare_directory (dir);
1987 auto dcp = make_simple (dir, 1, 106);
1990 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1991 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1992 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1993 "<ContentTitleText>Content</ContentTitleText>"
1994 "<AnnotationText>Annotation</AnnotationText>"
1995 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1996 "<ReelNumber>1</ReelNumber>"
1997 "<Language>de-DE</Language>"
1998 "<EditRate>24 1</EditRate>"
1999 "<TimeCodeRate>24</TimeCodeRate>"
2000 "<StartTime>00:00:02:00</StartTime>"
2001 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
2003 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2004 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2005 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2011 dcp::File xml_file(dir / "subs.xml", "w");
2012 BOOST_REQUIRE (xml_file);
2013 xml_file.write(xml.c_str(), xml.size(), 1);
2015 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2016 subs->write (dir / "subs.mxf");
2018 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
2019 auto cpl = dcp->cpls()[0];
2020 cpl->reels().front()->add(reel_subs);
2023 check_verify_result (
2027 dcp::VerificationNote(
2028 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
2029 ).set_cpl_id(cpl->id()),
2030 dcp::VerificationNote(
2031 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2032 ).set_cpl_id(cpl->id())
2040 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
2043 , v_position(v_position_)
2051 dcp::VAlign v_align;
2057 shared_ptr<dcp::CPL>
2058 dcp_with_text(path dir, vector<TestText> subs, optional<dcp::Key> key = boost::none, optional<string> key_id = boost::none)
2060 prepare_directory (dir);
2061 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2062 asset->set_start_time (dcp::Time());
2063 for (auto i: subs) {
2064 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
2066 asset->set_language (dcp::LanguageTag("de-DE"));
2067 if (key && key_id) {
2068 asset->set_key(*key);
2069 asset->set_key_id(*key_id);
2072 asset->write (dir / "subs.mxf");
2074 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
2075 return write_dcp_with_single_asset (dir, reel_asset);
2080 shared_ptr<dcp::CPL>
2081 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
2083 prepare_directory (dir);
2084 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
2085 asset->set_start_time (dcp::Time());
2086 asset->set_language (dcp::LanguageTag("de-DE"));
2088 auto subs_mxf = dir / "subs.mxf";
2089 asset->write (subs_mxf);
2091 /* The call to write() puts the asset into the DCP correctly but it will have
2092 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
2095 ASDCP::TimedText::MXFWriter writer;
2096 ASDCP::WriterInfo writer_info;
2097 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2099 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2100 DCP_ASSERT (c == Kumu::UUID_Length);
2101 ASDCP::TimedText::TimedTextDescriptor descriptor;
2102 descriptor.ContainerDuration = asset->intrinsic_duration();
2103 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
2104 DCP_ASSERT (c == Kumu::UUID_Length);
2105 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
2106 BOOST_REQUIRE (!ASDCP_FAILURE(r));
2107 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
2108 BOOST_REQUIRE (!ASDCP_FAILURE(r));
2111 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
2112 return write_dcp_with_single_asset (dir, reel_asset);
2116 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
2118 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
2119 /* Just too early */
2120 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
2121 check_verify_result (
2125 dcp::VerificationNote(
2126 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2127 ).set_cpl_id(cpl->id()),
2128 dcp::VerificationNote(
2129 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2130 ).set_cpl_id(cpl->id())
2136 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
2138 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
2139 /* Just late enough */
2140 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
2141 check_verify_result(
2145 dcp::VerificationNote(
2146 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2147 ).set_cpl_id(cpl->id())
2152 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
2154 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
2155 prepare_directory (dir);
2157 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
2158 asset1->set_start_time (dcp::Time());
2159 /* Just late enough */
2160 add_test_subtitle (asset1, 4 * 24, 5 * 24);
2161 asset1->set_language (dcp::LanguageTag("de-DE"));
2163 asset1->write (dir / "subs1.mxf");
2164 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
2165 auto reel1 = make_shared<dcp::Reel>();
2166 reel1->add (reel_asset1);
2167 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
2168 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2169 reel1->add (markers1);
2171 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
2172 asset2->set_start_time (dcp::Time());
2174 /* This would be too early on first reel but should be OK on the second */
2175 add_test_subtitle (asset2, 3, 4 * 24);
2176 asset2->set_language (dcp::LanguageTag("de-DE"));
2177 asset2->write (dir / "subs2.mxf");
2178 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
2179 auto reel2 = make_shared<dcp::Reel>();
2180 reel2->add (reel_asset2);
2181 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
2182 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
2183 reel2->add (markers2);
2185 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2188 auto dcp = make_shared<dcp::DCP>(dir);
2190 dcp->set_annotation_text("hello");
2193 check_verify_result(
2197 dcp::VerificationNote(
2198 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2199 ).set_cpl_id(cpl->id())
2204 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
2206 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
2207 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2211 { 5 * 24 + 1, 6 * 24 },
2213 check_verify_result (
2217 dcp::VerificationNote(
2218 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING
2219 ).set_cpl_id(cpl->id()),
2220 dcp::VerificationNote(
2221 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2222 ).set_cpl_id(cpl->id())
2227 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
2229 auto const dir = path("build/test/verify_valid_subtitle_spacing");
2230 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2234 { 5 * 24 + 16, 8 * 24 },
2237 check_verify_result(
2241 dcp::VerificationNote(
2242 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2243 ).set_cpl_id(cpl->id())
2248 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
2250 auto const dir = path("build/test/verify_invalid_subtitle_duration");
2251 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
2252 check_verify_result (
2256 dcp::VerificationNote(
2257 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION
2258 ).set_cpl_id(cpl->id()),
2259 dcp::VerificationNote(
2260 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2261 ).set_cpl_id(cpl->id())
2266 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
2268 auto const dir = path("build/test/verify_valid_subtitle_duration");
2269 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
2271 check_verify_result(
2275 dcp::VerificationNote(
2276 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2277 ).set_cpl_id(cpl->id())
2282 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
2284 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
2285 prepare_directory (dir);
2286 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2287 asset->set_start_time (dcp::Time());
2288 add_test_subtitle (asset, 0, 4 * 24);
2290 asset->set_language (dcp::LanguageTag("de-DE"));
2291 asset->write (dir / "subs.mxf");
2293 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
2294 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
2295 check_verify_result (
2299 dcp::VerificationNote(
2300 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get())
2301 ).set_cpl_id(cpl->id()),
2302 dcp::VerificationNote(
2303 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2304 ).set_cpl_id(cpl->id()),
2305 dcp::VerificationNote(
2306 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY
2307 ).set_cpl_id(cpl->id()),
2308 dcp::VerificationNote(
2309 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2310 ).set_cpl_id(cpl->id())
2316 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
2318 auto const dir = path ("build/test/invalid_subtitle_line_count1");
2319 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2322 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2323 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2324 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2325 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2327 check_verify_result (
2331 dcp::VerificationNote(
2332 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT
2333 ).set_cpl_id(cpl->id()),
2334 dcp::VerificationNote(
2335 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2336 ).set_cpl_id(cpl->id())
2341 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
2343 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
2344 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2347 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2348 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2349 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2352 check_verify_result(
2356 dcp::VerificationNote(
2357 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2358 ).set_cpl_id(cpl->id())
2363 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
2365 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
2366 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2369 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2370 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2371 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2372 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2374 check_verify_result (
2378 dcp::VerificationNote(
2379 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT
2380 ).set_cpl_id(cpl->id()),
2381 dcp::VerificationNote(
2382 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2383 ).set_cpl_id(cpl->id())
2388 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
2390 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
2391 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2394 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2395 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2396 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2397 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2400 check_verify_result(
2404 dcp::VerificationNote(
2405 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2406 ).set_cpl_id(cpl->id())
2411 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
2413 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
2414 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2417 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2419 check_verify_result (
2423 dcp::VerificationNote(
2424 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH
2425 ).set_cpl_id(cpl->id()),
2426 dcp::VerificationNote(
2427 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2428 ).set_cpl_id(cpl->id())
2433 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2435 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2436 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2439 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2441 check_verify_result (
2445 dcp::VerificationNote(
2446 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH
2447 ).set_cpl_id(cpl->id()),
2448 dcp::VerificationNote(
2449 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2450 ).set_cpl_id(cpl->id())
2455 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2457 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2458 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2461 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2462 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2463 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2464 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2466 check_verify_result (
2470 dcp::VerificationNote(
2471 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT
2472 ).set_cpl_id(cpl->id()),
2473 dcp::VerificationNote(
2474 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2475 ).set_cpl_id(cpl->id())
2480 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2482 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2483 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2486 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2487 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2488 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2491 check_verify_result(
2495 dcp::VerificationNote(
2496 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2497 ).set_cpl_id(cpl->id())
2502 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2504 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2505 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2508 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2509 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2510 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2511 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2513 check_verify_result (
2517 dcp::VerificationNote(
2518 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT
2519 ).set_cpl_id(cpl->id()),
2520 dcp::VerificationNote(
2521 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2522 ).set_cpl_id(cpl->id())
2527 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2529 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2530 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2533 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2534 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2535 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2536 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2539 check_verify_result(
2543 dcp::VerificationNote(
2544 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2545 ).set_cpl_id(cpl->id())
2550 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2552 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2553 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2556 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2559 check_verify_result (
2563 dcp::VerificationNote(
2564 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2565 ).set_cpl_id(cpl->id())
2570 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2572 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2573 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2576 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2578 check_verify_result (
2582 dcp::VerificationNote(
2583 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH
2584 ).set_cpl_id(cpl->id()),
2585 dcp::VerificationNote(
2586 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2587 ).set_cpl_id(cpl->id())
2592 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2594 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2595 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2598 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2599 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2600 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2602 check_verify_result (
2606 dcp::VerificationNote(
2607 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2608 ).set_cpl_id(cpl->id())
2613 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2615 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2616 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2619 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2620 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2621 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2623 check_verify_result (
2627 dcp::VerificationNote(
2628 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN
2629 ).set_cpl_id(cpl->id()),
2630 dcp::VerificationNote(
2631 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2632 ).set_cpl_id(cpl->id())
2637 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2639 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2640 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2643 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2644 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2645 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2648 check_verify_result(
2652 dcp::VerificationNote(
2653 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2654 ).set_cpl_id(cpl->id())
2659 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2661 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2662 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2665 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2666 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2667 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2670 check_verify_result(
2674 dcp::VerificationNote(
2675 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2676 ).set_cpl_id(cpl->id())
2681 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2683 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2684 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2685 check_verify_result (
2689 dcp::VerificationNote(
2690 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING
2691 ).set_cpl_id(cpl->id()),
2692 dcp::VerificationNote(
2693 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2694 ).set_cpl_id(cpl->id())
2699 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2701 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2702 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2704 check_verify_result(
2708 dcp::VerificationNote(
2709 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2710 ).set_cpl_id(cpl->id())
2716 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2718 path const dir("build/test/verify_invalid_sound_frame_rate");
2719 prepare_directory (dir);
2721 auto picture = simple_picture (dir, "foo");
2722 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2723 auto reel = make_shared<dcp::Reel>();
2724 reel->add (reel_picture);
2725 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2726 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2727 reel->add (reel_sound);
2728 reel->add (simple_markers());
2729 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2731 auto dcp = make_shared<dcp::DCP>(dir);
2733 dcp->set_annotation_text("hello");
2736 check_verify_result (
2740 dcp::VerificationNote(
2741 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf")
2742 ).set_cpl_id(cpl->id()),
2743 dcp::VerificationNote(
2744 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2745 ).set_cpl_id(cpl->id())
2750 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2752 path const dir("build/test/verify_missing_cpl_annotation_text");
2753 auto dcp = make_simple (dir);
2756 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2758 auto const cpl = dcp->cpls()[0];
2760 HashCalculator calc(cpl->file().get());
2763 BOOST_REQUIRE (cpl->file());
2764 Editor e(cpl->file().get());
2765 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2768 check_verify_result (
2772 dcp::VerificationNote(
2773 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, canonical(cpl->file().get())
2774 ).set_cpl_id(cpl->id()),
2775 dcp::VerificationNote(
2776 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
2777 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
2782 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2784 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2785 auto dcp = make_simple (dir);
2788 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2789 auto const cpl = dcp->cpls()[0];
2791 HashCalculator calc(cpl->file().get());
2794 BOOST_REQUIRE (cpl->file());
2795 Editor e(cpl->file().get());
2796 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2799 check_verify_result (
2803 dcp::VerificationNote(
2804 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, canonical(cpl->file().get())
2805 ).set_cpl_id(cpl->id()),
2806 dcp::VerificationNote(
2807 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
2808 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()).set_cpl_id(cpl->id())
2813 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2815 path const dir("build/test/verify_mismatched_asset_duration");
2816 prepare_directory (dir);
2817 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2818 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2820 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2821 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2823 auto reel = make_shared<dcp::Reel>(
2824 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2825 make_shared<dcp::ReelSoundAsset>(ms, 0)
2828 reel->add (simple_markers());
2832 dcp->set_annotation_text("A Test DCP");
2835 check_verify_result (
2839 dcp::VerificationNote(
2840 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION
2841 ).set_cpl_id(cpl->id()),
2842 dcp::VerificationNote(
2843 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl->file().get())
2844 ).set_cpl_id(cpl->id())
2851 shared_ptr<dcp::CPL>
2852 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2854 prepare_directory (dir);
2855 auto dcp = make_shared<dcp::DCP>(dir);
2856 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2858 auto constexpr reel_length = 192;
2860 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2861 subs->set_language (dcp::LanguageTag("de-DE"));
2862 subs->set_start_time (dcp::Time());
2863 subs->add (simple_subtitle());
2865 subs->write (dir / "subs.mxf");
2866 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2868 auto reel1 = make_shared<dcp::Reel>(
2869 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2870 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2874 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2877 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2878 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2879 reel1->add (markers1);
2883 auto reel2 = make_shared<dcp::Reel>(
2884 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2885 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2889 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2892 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2893 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2894 reel2->add (markers2);
2899 dcp->set_annotation_text("A Test DCP");
2906 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2909 path dir ("build/test/missing_main_subtitle_from_some_reels");
2910 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2911 check_verify_result (
2915 dcp::VerificationNote(
2916 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS
2917 ).set_cpl_id(cpl->id()),
2918 dcp::VerificationNote(
2919 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2920 ).set_cpl_id(cpl->id())
2926 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2927 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2928 check_verify_result(
2932 dcp::VerificationNote(
2933 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2934 ).set_cpl_id(cpl->id())
2939 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2940 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2941 check_verify_result(
2945 dcp::VerificationNote(
2946 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2947 ).set_cpl_id(cpl->id())
2954 shared_ptr<dcp::CPL>
2955 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2957 prepare_directory (dir);
2958 auto dcp = make_shared<dcp::DCP>(dir);
2959 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2961 auto constexpr reel_length = 192;
2963 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2964 subs->set_language (dcp::LanguageTag("de-DE"));
2965 subs->set_start_time (dcp::Time());
2966 subs->add (simple_subtitle());
2968 subs->write (dir / "subs.mxf");
2970 auto reel1 = make_shared<dcp::Reel>(
2971 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2972 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2975 for (int i = 0; i < caps_in_reel1; ++i) {
2976 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2979 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2980 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2981 reel1->add (markers1);
2985 auto reel2 = make_shared<dcp::Reel>(
2986 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2987 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2990 for (int i = 0; i < caps_in_reel2; ++i) {
2991 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2994 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2995 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2996 reel2->add (markers2);
3001 dcp->set_annotation_text("A Test DCP");
3008 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
3011 path dir ("build/test/mismatched_closed_caption_asset_counts");
3012 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
3013 check_verify_result (
3017 dcp::VerificationNote(
3018 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS
3019 ).set_cpl_id(cpl->id()),
3020 dcp::VerificationNote(
3021 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3022 ).set_cpl_id(cpl->id())
3027 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
3028 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
3029 check_verify_result(
3033 dcp::VerificationNote(
3034 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3035 ).set_cpl_id(cpl->id())
3040 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
3041 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
3042 check_verify_result(
3046 dcp::VerificationNote(
3047 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3048 ).set_cpl_id(cpl->id())
3056 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
3058 prepare_directory (dir);
3059 auto dcp = make_shared<dcp::DCP>(dir);
3060 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3062 auto constexpr reel_length = 192;
3064 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
3065 subs->set_language (dcp::LanguageTag("de-DE"));
3066 subs->set_start_time (dcp::Time());
3067 subs->add (simple_subtitle());
3069 subs->write (dir / "subs.mxf");
3070 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
3073 auto reel = make_shared<dcp::Reel>(
3074 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
3075 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
3078 reel->add (reel_text);
3080 reel->add (simple_markers(reel_length));
3085 dcp->set_annotation_text("A Test DCP");
3088 check_verify_result (
3092 dcp::VerificationNote(
3093 dcp::VerificationNote::Type::BV21_ERROR, code, subs->id()
3094 ).set_cpl_id(cpl->id()),
3095 dcp::VerificationNote(
3096 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3097 ).set_cpl_id(cpl->id())
3102 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
3104 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
3105 "build/test/verify_subtitle_entry_point_must_be_present",
3106 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
3107 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
3108 asset->unset_entry_point ();
3112 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
3113 "build/test/verify_subtitle_entry_point_must_be_zero",
3114 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
3115 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
3116 asset->set_entry_point (4);
3120 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
3121 "build/test/verify_closed_caption_entry_point_must_be_present",
3122 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
3123 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
3124 asset->unset_entry_point ();
3128 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
3129 "build/test/verify_closed_caption_entry_point_must_be_zero",
3130 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
3131 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
3132 asset->set_entry_point (9);
3138 BOOST_AUTO_TEST_CASE (verify_missing_hash)
3142 path const dir("build/test/verify_missing_hash");
3143 auto dcp = make_simple (dir);
3146 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3147 auto const cpl = dcp->cpls()[0];
3148 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
3149 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
3150 auto asset_id = cpl->reels()[0]->main_picture()->id();
3152 HashCalculator calc(cpl->file().get());
3155 BOOST_REQUIRE (cpl->file());
3156 Editor e(cpl->file().get());
3157 e.delete_first_line_containing("<Hash>");
3160 check_verify_result (
3164 dcp::VerificationNote(
3165 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3166 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3167 dcp::VerificationNote(
3168 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id
3169 ).set_cpl_id(cpl->id())
3176 verify_markers_test (
3178 vector<pair<dcp::Marker, dcp::Time>> markers,
3179 vector<dcp::VerificationNote> test_notes
3182 auto dcp = make_simple (dir);
3183 auto cpl = dcp->cpls()[0];
3184 cpl->set_content_kind(dcp::ContentKind::FEATURE);
3185 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
3186 for (auto const& i: markers) {
3187 markers_asset->set (i.first, i.second);
3189 cpl->reels()[0]->add(markers_asset);
3192 for (auto& note: test_notes) {
3193 note.set_cpl_id(cpl->id());
3196 check_verify_result({dir}, {}, test_notes);
3200 BOOST_AUTO_TEST_CASE (verify_markers)
3202 verify_markers_test (
3203 "build/test/verify_markers_all_correct",
3205 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3206 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3207 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3208 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3213 verify_markers_test (
3214 "build/test/verify_markers_missing_ffec",
3216 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3217 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3218 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3221 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
3224 verify_markers_test (
3225 "build/test/verify_markers_missing_ffmc",
3227 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3228 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3229 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
3235 verify_markers_test (
3236 "build/test/verify_markers_missing_ffoc",
3238 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3239 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3240 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3243 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
3246 verify_markers_test (
3247 "build/test/verify_markers_missing_lfoc",
3249 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3250 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3251 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
3254 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
3257 verify_markers_test (
3258 "build/test/verify_markers_incorrect_ffoc",
3260 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3261 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3262 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
3263 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3266 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
3269 verify_markers_test (
3270 "build/test/verify_markers_incorrect_lfoc",
3272 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3273 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3274 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3275 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
3278 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
3283 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
3285 path dir = "build/test/verify_missing_cpl_metadata_version_number";
3286 prepare_directory (dir);
3287 auto dcp = make_simple (dir);
3288 auto cpl = dcp->cpls()[0];
3289 cpl->unset_version_number();
3292 check_verify_result(
3296 dcp::VerificationNote(
3297 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->file().get()
3298 ).set_cpl_id(cpl->id())
3303 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
3305 path dir = "build/test/verify_missing_extension_metadata1";
3306 auto dcp = make_simple (dir);
3309 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3310 auto cpl = dcp->cpls()[0];
3312 HashCalculator calc(cpl->file().get());
3315 Editor e (cpl->file().get());
3316 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
3319 check_verify_result (
3323 dcp::VerificationNote(
3324 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3325 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3326 dcp::VerificationNote(
3327 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()
3328 ).set_cpl_id(cpl->id())
3333 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
3335 path dir = "build/test/verify_missing_extension_metadata2";
3336 auto dcp = make_simple (dir);
3339 auto cpl = dcp->cpls()[0];
3341 HashCalculator calc(cpl->file().get());
3344 Editor e (cpl->file().get());
3345 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
3348 check_verify_result (
3352 dcp::VerificationNote(
3353 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3354 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3355 dcp::VerificationNote(
3356 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()
3357 ).set_cpl_id(cpl->id())
3362 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
3364 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
3365 auto dcp = make_simple (dir);
3368 auto const cpl = dcp->cpls()[0];
3370 HashCalculator calc(cpl->file().get());
3373 Editor e (cpl->file().get());
3374 e.replace ("<meta:Name>A", "<meta:NameX>A");
3375 e.replace ("n</meta:Name>", "n</meta:NameX>");
3378 check_verify_result (
3382 dcp::VerificationNote(
3383 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70
3384 ).set_cpl_id(cpl->id()),
3385 dcp::VerificationNote(
3386 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()),
3387 dcp::VerificationNote(
3388 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3389 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3394 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
3396 path dir = "build/test/verify_invalid_extension_metadata1";
3397 auto dcp = make_simple (dir);
3400 auto cpl = dcp->cpls()[0];
3402 HashCalculator calc(cpl->file().get());
3405 Editor e (cpl->file().get());
3406 e.replace ("Application", "Fred");
3409 check_verify_result (
3413 dcp::VerificationNote(
3414 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3415 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3416 dcp::VerificationNote(
3417 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get()
3418 ).set_cpl_id(cpl->id())
3423 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
3425 path dir = "build/test/verify_invalid_extension_metadata2";
3426 auto dcp = make_simple (dir);
3429 auto cpl = dcp->cpls()[0];
3431 HashCalculator calc(cpl->file().get());
3434 Editor e (cpl->file().get());
3435 e.replace ("DCP Constraints Profile", "Fred");
3438 check_verify_result (
3442 dcp::VerificationNote(
3443 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3444 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3445 dcp::VerificationNote(
3446 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get()
3447 ).set_cpl_id(cpl->id())
3452 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
3454 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
3455 auto dcp = make_simple (dir);
3458 auto const cpl = dcp->cpls()[0];
3460 HashCalculator calc(cpl->file().get());
3463 Editor e (cpl->file().get());
3464 e.replace ("<meta:Value>", "<meta:ValueX>");
3465 e.replace ("</meta:Value>", "</meta:ValueX>");
3468 check_verify_result (
3472 dcp::VerificationNote(
3473 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74
3474 ).set_cpl_id(cpl->id()),
3475 dcp::VerificationNote(
3476 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
3477 ).set_cpl_id(cpl->id()),
3478 dcp::VerificationNote(
3479 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3480 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
3485 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
3487 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
3488 auto dcp = make_simple (dir);
3491 auto const cpl = dcp->cpls()[0];
3493 HashCalculator calc(cpl->file().get());
3496 Editor e (cpl->file().get());
3497 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
3500 check_verify_result (
3504 dcp::VerificationNote(
3505 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3506 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3507 dcp::VerificationNote(
3508 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()
3509 ).set_cpl_id(cpl->id())
3514 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
3516 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
3517 auto dcp = make_simple (dir);
3520 auto const cpl = dcp->cpls()[0];
3522 HashCalculator calc(cpl->file().get());
3525 Editor e (cpl->file().get());
3526 e.replace ("<meta:Property>", "<meta:PropertyX>");
3527 e.replace ("</meta:Property>", "</meta:PropertyX>");
3530 check_verify_result (
3534 dcp::VerificationNote(
3535 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72
3536 ).set_cpl_id(cpl->id()),
3537 dcp::VerificationNote(
3538 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()),
3539 dcp::VerificationNote(
3540 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3541 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3546 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
3548 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
3549 auto dcp = make_simple (dir);
3552 auto const cpl = dcp->cpls()[0];
3554 HashCalculator calc(cpl->file().get());
3557 Editor e (cpl->file().get());
3558 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
3559 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
3562 check_verify_result (
3566 dcp::VerificationNote(
3567 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71
3568 ).set_cpl_id(cpl->id()),
3569 dcp::VerificationNote(
3570 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
3571 ).set_cpl_id(cpl->id()),
3572 dcp::VerificationNote(
3573 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3574 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3580 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
3582 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
3583 prepare_directory (dir);
3584 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3585 copy_file (i.path(), dir / i.path().filename());
3588 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml");
3589 path const cpl_path = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
3591 HashCalculator calc(cpl_path);
3595 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3598 dcp::CPL cpl(cpl_path);
3600 check_verify_result (
3604 dcp::VerificationNote(
3605 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
3606 ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3607 dcp::VerificationNote(
3608 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl)
3609 ).set_cpl_id(cpl.id()),
3610 dcp::VerificationNote(
3611 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
3612 ).set_cpl_id(cpl.id()),
3613 dcp::VerificationNote(
3614 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
3615 ).set_cpl_id(cpl.id()),
3616 dcp::VerificationNote(
3617 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
3618 ).set_cpl_id(cpl.id()),
3619 dcp::VerificationNote(
3620 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
3621 ).set_cpl_id(cpl.id()),
3622 dcp::VerificationNote(
3623 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path)
3624 ).set_cpl_id(cpl.id()),
3625 dcp::VerificationNote(
3626 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_path)
3627 ).set_cpl_id(cpl.id())
3632 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
3634 path dir = "build/test/unsigned_pkl_with_encrypted_content";
3635 prepare_directory (dir);
3636 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3637 copy_file (i.path(), dir / i.path().filename());
3640 path const cpl_path = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
3641 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
3644 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3647 dcp::CPL cpl(cpl_path);
3649 check_verify_result (
3653 dcp::VerificationNote(
3654 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl)
3655 ).set_cpl_id(cpl.id()),
3656 dcp::VerificationNote(
3657 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
3658 ).set_cpl_id(cpl.id()),
3659 dcp::VerificationNote(
3660 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
3661 ).set_cpl_id(cpl.id()),
3662 dcp::VerificationNote(
3663 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
3664 ).set_cpl_id(cpl.id()),
3665 dcp::VerificationNote(
3666 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
3667 ).set_cpl_id(cpl.id()),
3668 dcp::VerificationNote(
3669 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path)
3670 ).set_cpl_id(cpl.id()),
3671 dcp::VerificationNote(
3672 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl)
3678 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3680 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3681 prepare_directory (dir);
3682 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3683 copy_file (i.path(), dir / i.path().filename());
3687 Editor e (dir / dcp_test1_pkl());
3688 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3691 check_verify_result({dir}, {}, {});
3695 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3697 path dir ("build/test/verify_must_not_be_partially_encrypted");
3698 prepare_directory (dir);
3702 auto signer = make_shared<dcp::CertificateChain>();
3703 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3704 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3705 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3706 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3708 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3712 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3715 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3716 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3717 for (int i = 0; i < 24; ++i) {
3718 writer->write (j2c.data(), j2c.size());
3720 writer->finalize ();
3722 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3724 auto reel = make_shared<dcp::Reel>(
3725 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3726 make_shared<dcp::ReelSoundAsset>(ms, 0)
3729 reel->add (simple_markers());
3733 cpl->set_content_version (
3734 {"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"}
3736 cpl->set_annotation_text ("A Test DCP");
3737 cpl->set_issuer ("OpenDCP 0.0.25");
3738 cpl->set_creator ("OpenDCP 0.0.25");
3739 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3740 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3741 cpl->set_main_sound_sample_rate (48000);
3742 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3743 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3744 cpl->set_version_number (1);
3748 d.set_issuer("OpenDCP 0.0.25");
3749 d.set_creator("OpenDCP 0.0.25");
3750 d.set_issue_date("2012-07-17T04:45:18+00:00");
3751 d.set_annotation_text("A Test DCP");
3752 d.write_xml(signer);
3754 check_verify_result (
3758 dcp::VerificationNote(
3759 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED
3760 ).set_cpl_id(cpl->id())
3765 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3767 vector<dcp::VerificationNote> notes;
3768 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"));
3769 auto reader = picture.start_read ();
3770 auto frame = reader->get_frame (0);
3771 verify_j2k(frame, 0, 0, 24, notes);
3772 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3776 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3778 vector<dcp::VerificationNote> notes;
3779 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3780 auto reader = picture.start_read ();
3781 auto frame = reader->get_frame (0);
3782 verify_j2k(frame, 0, 0, 24, notes);
3783 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3787 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3789 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3790 prepare_directory (dir);
3791 auto dcp = make_simple (dir);
3793 vector<dcp::VerificationNote> notes;
3794 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3795 auto reader = picture.start_read ();
3796 auto frame = reader->get_frame (0);
3797 verify_j2k(frame, 0, 0, 24, notes);
3798 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3802 /** Check that ResourceID and the XML ID being different is spotted */
3803 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3805 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3806 prepare_directory (dir);
3808 ASDCP::WriterInfo writer_info;
3809 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3812 auto mxf_id = dcp::make_uuid ();
3813 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3814 BOOST_REQUIRE (c == Kumu::UUID_Length);
3816 auto resource_id = dcp::make_uuid ();
3817 ASDCP::TimedText::TimedTextDescriptor descriptor;
3818 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3819 DCP_ASSERT (c == Kumu::UUID_Length);
3821 auto xml_id = dcp::make_uuid ();
3822 ASDCP::TimedText::MXFWriter writer;
3823 auto subs_mxf = dir / "subs.mxf";
3824 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3825 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3826 writer.WriteTimedTextResource (dcp::String::compose(
3827 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3828 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3829 "<Id>urn:uuid:%1</Id>"
3830 "<ContentTitleText>Content</ContentTitleText>"
3831 "<AnnotationText>Annotation</AnnotationText>"
3832 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3833 "<ReelNumber>1</ReelNumber>"
3834 "<Language>en-US</Language>"
3835 "<EditRate>25 1</EditRate>"
3836 "<TimeCodeRate>25</TimeCodeRate>"
3837 "<StartTime>00:00:00:00</StartTime>"
3838 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3840 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3841 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3842 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3851 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3852 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3854 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3856 check_verify_result (
3860 dcp::VerificationNote(
3861 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
3862 ).set_cpl_id(cpl->id()),
3863 dcp::VerificationNote(
3864 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID
3865 ).set_cpl_id(cpl->id()),
3866 dcp::VerificationNote(
3867 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
3868 ).set_cpl_id(cpl->id()),
3869 dcp::VerificationNote(
3870 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3871 ).set_cpl_id(cpl->id())
3876 /** Check that ResourceID and the MXF ID being the same is spotted */
3877 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3879 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3880 prepare_directory (dir);
3882 ASDCP::WriterInfo writer_info;
3883 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3886 auto mxf_id = dcp::make_uuid ();
3887 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3888 BOOST_REQUIRE (c == Kumu::UUID_Length);
3890 auto resource_id = mxf_id;
3891 ASDCP::TimedText::TimedTextDescriptor descriptor;
3892 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3893 DCP_ASSERT (c == Kumu::UUID_Length);
3895 auto xml_id = resource_id;
3896 ASDCP::TimedText::MXFWriter writer;
3897 auto subs_mxf = dir / "subs.mxf";
3898 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3899 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3900 writer.WriteTimedTextResource (dcp::String::compose(
3901 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3902 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3903 "<Id>urn:uuid:%1</Id>"
3904 "<ContentTitleText>Content</ContentTitleText>"
3905 "<AnnotationText>Annotation</AnnotationText>"
3906 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3907 "<ReelNumber>1</ReelNumber>"
3908 "<Language>en-US</Language>"
3909 "<EditRate>25 1</EditRate>"
3910 "<TimeCodeRate>25</TimeCodeRate>"
3911 "<StartTime>00:00:00:00</StartTime>"
3912 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3914 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3915 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3916 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3925 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3926 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3928 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3930 check_verify_result (
3934 dcp::VerificationNote(
3935 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
3936 ).set_cpl_id(cpl->id()),
3937 dcp::VerificationNote(
3938 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID
3939 ).set_cpl_id(cpl->id()),
3940 dcp::VerificationNote(
3941 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
3942 ).set_cpl_id(cpl->id()),
3943 dcp::VerificationNote(
3944 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3945 ).set_cpl_id(cpl->id()),
3946 dcp::VerificationNote(
3947 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"}
3948 ).set_cpl_id(cpl->id())
3953 /** Check a DCP with a 3D asset marked as 2D */
3954 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3956 auto const path = private_test / "data" / "xm";
3958 check_verify_result (
3962 dcp::VerificationNote(
3963 dcp::VerificationNote::Type::WARNING,
3964 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(path, "j2c"))
3966 dcp::VerificationNote(
3967 dcp::VerificationNote::Type::BV21_ERROR,
3968 dcp::VerificationNote::Code::INVALID_STANDARD
3975 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3977 path dir = "build/test/verify_unexpected_things_in_main_markers";
3978 prepare_directory (dir);
3979 auto dcp = make_simple (dir, 1, 24);
3982 HashCalculator calc(find_cpl(dir));
3985 Editor e (find_cpl(dir));
3987 " <IntrinsicDuration>24</IntrinsicDuration>",
3988 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3992 dcp::CPL cpl (find_cpl(dir));
3994 check_verify_result (
3998 dcp::VerificationNote(
3999 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4000 ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4001 dcp::VerificationNote(
4002 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT
4003 ).set_cpl_id(cpl.id()),
4004 dcp::VerificationNote(
4005 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION
4006 ).set_cpl_id(cpl.id())
4011 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
4013 path dir = "build/test/verify_invalid_content_kind";
4014 prepare_directory (dir);
4015 auto dcp = make_simple (dir, 1, 24);
4018 HashCalculator calc(find_cpl(dir));
4021 Editor e(find_cpl(dir));
4022 e.replace("trailer", "trip");
4025 dcp::CPL cpl (find_cpl(dir));
4027 check_verify_result (
4031 dcp::VerificationNote(
4032 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4033 ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4034 dcp::VerificationNote(
4035 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip")
4036 ).set_cpl_id(cpl.id()),
4042 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
4044 path dir = "build/test/verify_valid_content_kind";
4045 prepare_directory (dir);
4046 auto dcp = make_simple (dir, 1, 24);
4049 HashCalculator calc(find_cpl(dir));
4052 Editor e(find_cpl(dir));
4053 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
4056 dcp::CPL cpl (find_cpl(dir));
4058 check_verify_result (
4062 dcp::VerificationNote(
4063 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4064 ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4069 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
4071 path dir = "build/test/verify_invalid_main_picture_active_area_1";
4072 prepare_directory(dir);
4073 auto dcp = make_simple(dir, 1, 24);
4076 auto constexpr area = "<meta:MainPictureActiveArea>";
4078 HashCalculator calc(find_cpl(dir));
4081 Editor e(find_cpl(dir));
4082 e.delete_lines_after(area, 2);
4083 e.insert(area, "<meta:Height>4080</meta:Height>");
4084 e.insert(area, "<meta:Width>1997</meta:Width>");
4087 dcp::PKL pkl(find_pkl(dir));
4088 dcp::CPL cpl(find_cpl(dir));
4090 check_verify_result(
4094 dcp::VerificationNote(
4095 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4096 ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4097 dcp::VerificationNote(
4098 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir))
4099 ).set_cpl_id(cpl.id()),
4100 dcp::VerificationNote(
4101 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))
4102 ).set_cpl_id(cpl.id()),
4107 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
4109 path dir = "build/test/verify_invalid_main_picture_active_area_2";
4110 prepare_directory(dir);
4111 auto dcp = make_simple(dir, 1, 24);
4114 auto constexpr area = "<meta:MainPictureActiveArea>";
4116 HashCalculator calc(find_cpl(dir));
4119 Editor e(find_cpl(dir));
4120 e.delete_lines_after(area, 2);
4121 e.insert(area, "<meta:Height>5125</meta:Height>");
4122 e.insert(area, "<meta:Width>9900</meta:Width>");
4125 dcp::PKL pkl(find_pkl(dir));
4126 dcp::CPL cpl(find_cpl(dir));
4128 check_verify_result(
4132 dcp::VerificationNote(
4133 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4134 ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4135 dcp::VerificationNote(
4136 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir))
4137 ).set_cpl_id(cpl.id()),
4138 dcp::VerificationNote(
4139 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))
4140 ).set_cpl_id(cpl.id()),
4141 dcp::VerificationNote(
4142 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))
4143 ).set_cpl_id(cpl.id())
4148 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
4152 path dir = "build/test/verify_duplicate_pkl_asset_ids";
4153 prepare_directory(dir);
4154 auto dcp = make_simple(dir, 1, 24);
4158 Editor e(find_pkl(dir));
4159 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
4162 dcp::PKL pkl(find_pkl(dir));
4164 check_verify_result(
4168 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
4173 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
4177 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
4178 prepare_directory(dir);
4179 auto dcp = make_simple(dir, 1, 24);
4183 Editor e(find_asset_map(dir));
4184 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
4187 dcp::PKL pkl(find_pkl(dir));
4188 dcp::AssetMap asset_map(find_asset_map(dir));
4189 dcp::CPL cpl(find_cpl(dir));
4191 check_verify_result(
4195 dcp::VerificationNote(
4196 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir))
4198 dcp::VerificationNote(
4199 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54")
4205 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
4207 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
4209 dcp::MXFMetadata mxf_meta;
4210 mxf_meta.company_name = "OpenDCP";
4211 mxf_meta.product_name = "OpenDCP";
4212 mxf_meta.product_version = "0.0.25";
4214 auto constexpr sample_rate = 48000;
4215 auto constexpr frames = 240;
4217 boost::filesystem::remove_all(path);
4218 boost::filesystem::create_directories(path);
4219 auto dcp = make_shared<dcp::DCP>(path);
4220 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4221 cpl->set_annotation_text("hello");
4222 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
4223 cpl->set_main_sound_sample_rate(sample_rate);
4224 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
4225 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
4226 cpl->set_version_number(1);
4230 /* Reel with 2 channels of audio */
4232 auto mp = simple_picture(path, "1", frames, {});
4233 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
4235 auto reel = make_shared<dcp::Reel>(
4236 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4237 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
4240 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
4241 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
4248 /* Reel with 6 channels of audio */
4250 auto mp = simple_picture(path, "2", frames, {});
4251 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
4253 auto reel = make_shared<dcp::Reel>(
4254 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4255 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
4258 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
4259 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
4266 dcp->set_annotation_text("hello");
4269 check_verify_result(
4273 dcp::VerificationNote(
4274 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2"))
4275 ).set_cpl_id(cpl->id())
4280 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
4282 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
4284 dcp::MXFMetadata mxf_meta;
4285 mxf_meta.company_name = "OpenDCP";
4286 mxf_meta.product_name = "OpenDCP";
4287 mxf_meta.product_version = "0.0.25";
4289 auto constexpr sample_rate = 48000;
4290 auto constexpr frames = 240;
4292 boost::filesystem::remove_all(path);
4293 boost::filesystem::create_directories(path);
4294 auto dcp = make_shared<dcp::DCP>(path);
4295 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4296 cpl->set_annotation_text("hello");
4297 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
4298 cpl->set_main_sound_sample_rate(sample_rate);
4299 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
4300 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
4301 cpl->set_version_number(1);
4303 auto mp = simple_picture(path, "1", frames, {});
4304 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
4306 auto reel = make_shared<dcp::Reel>(
4307 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4308 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
4311 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
4312 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
4313 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
4319 dcp->set_annotation_text("hello");
4322 check_verify_result(
4326 dcp::VerificationNote(
4327 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))
4328 ).set_cpl_id(cpl->id())
4333 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
4335 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
4336 auto constexpr video_frames = 24;
4337 auto constexpr sample_rate = 48000;
4339 boost::filesystem::remove_all(path);
4340 boost::filesystem::create_directories(path);
4342 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
4343 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
4345 dcp::Size const size(1998, 1080);
4346 auto image = make_shared<dcp::OpenJPEGImage>(size);
4347 boost::random::mt19937 rng(1);
4348 boost::random::uniform_int_distribution<> dist(0, 4095);
4349 for (int c = 0; c < 3; ++c) {
4350 for (int p = 0; p < (1998 * 1080); ++p) {
4351 image->data(c)[p] = dist(rng);
4354 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
4355 for (int i = 0; i < 24; ++i) {
4356 picture_writer->write(j2c.data(), j2c.size());
4358 picture_writer->finalize();
4360 auto dcp = make_shared<dcp::DCP>(path);
4361 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4362 cpl->set_content_version(
4363 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
4365 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
4366 cpl->set_main_sound_sample_rate(sample_rate);
4367 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
4368 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
4369 cpl->set_version_number(1);
4371 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
4373 auto reel = make_shared<dcp::Reel>(
4374 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4375 make_shared<dcp::ReelSoundAsset>(ms, 0)
4380 dcp->set_annotation_text("A Test DCP");
4383 vector<dcp::VerificationNote> expected;
4385 for (auto frame = 0; frame < 24; frame++) {
4387 dcp::VerificationNote(
4388 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf")
4389 ).set_frame(frame).set_frame_rate(24).set_cpl_id(cpl->id())
4393 int component_sizes[] = {
4399 for (auto frame = 0; frame < 24; frame++) {
4400 for (auto component = 0; component < 3; component++) {
4402 dcp::VerificationNote(
4403 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE
4404 ).set_frame(frame).set_component(component).set_size(component_sizes[component]).set_cpl_id(cpl->id())
4410 dcp::VerificationNote(
4411 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
4412 ).set_cpl_id(cpl->id())
4416 dcp::VerificationNote(
4417 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
4418 ).set_cpl_id(cpl->id())
4421 check_verify_result({ path }, {}, expected);
4425 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
4427 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
4430 BOOST_REQUIRE(!dcp.cpls().empty());
4431 auto cpl = dcp.cpls()[0];
4433 check_verify_result(
4437 dcp::VerificationNote(
4438 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
4439 ).set_cpl_id(cpl->id()),
4440 dcp::VerificationNote(
4441 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
4442 ).set_cpl_id(cpl->id()),
4443 dcp::VerificationNote(
4444 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
4445 ).set_cpl_id(cpl->id()),
4446 dcp::VerificationNote(
4447 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_"))
4448 ).set_cpl_id(cpl->id()),
4449 dcp::VerificationNote(
4450 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(find_file(dir, "cpl_"))
4451 ).set_cpl_id(cpl->id()),
4452 dcp::VerificationNote(
4453 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"}
4454 ).set_cpl_id(cpl->id())
4459 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
4461 path const dir("build/test/verify_missing_load_font");
4462 prepare_directory (dir);
4463 copy_file ("test/data/subs1.xml", dir / "subs.xml");
4465 Editor editor(dir / "subs.xml");
4466 editor.delete_first_line_containing("LoadFont");
4468 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
4469 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
4470 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
4472 check_verify_result (
4476 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
4477 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId").set_cpl_id(cpl->id())
4483 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
4485 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
4486 prepare_directory(dir);
4487 auto dcp = make_simple (dir, 1, 202);
4490 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
4491 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
4492 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
4493 "<ContentTitleText>Content</ContentTitleText>"
4494 "<AnnotationText>Annotation</AnnotationText>"
4495 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
4496 "<ReelNumber>1</ReelNumber>"
4497 "<EditRate>24 1</EditRate>"
4498 "<TimeCodeRate>24</TimeCodeRate>"
4499 "<StartTime>00:00:00:00</StartTime>"
4500 "<Language>de-DE</Language>"
4502 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
4503 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
4504 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
4510 dcp::File xml_file(dir / "subs.xml", "w");
4511 BOOST_REQUIRE(xml_file);
4512 xml_file.write(xml.c_str(), xml.size(), 1);
4514 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
4515 subs->write(dir / "subs.mxf");
4517 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
4518 auto cpl = dcp->cpls()[0];
4519 cpl->reels()[0]->add(reel_subs);
4522 check_verify_result (
4526 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id()).set_cpl_id(cpl->id())
4531 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
4533 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
4534 boost::filesystem::remove_all(dir);
4536 auto dcp1 = make_simple(dir / "1");
4539 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
4541 auto dcp2 = make_simple(dir / "2");
4543 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
4545 boost::filesystem::remove(dir / "1" / "video.mxf");
4546 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
4548 check_verify_result(
4552 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
4557 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
4559 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
4560 boost::filesystem::remove_all(dir);
4562 auto dcp = make_simple(dir);
4563 BOOST_REQUIRE(dcp->cpls().size() == 1);
4564 auto cpl = dcp->cpls()[0];
4565 cpl->set_content_version(dcp::ContentVersion(""));
4568 check_verify_result(
4572 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_cpl_id(cpl->id())
4577 /** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */
4578 BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp)
4580 auto const dir = path("build/test/verify_encrypted_smpte_dcp");
4582 auto key_id = dcp::make_uuid();
4583 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
4585 dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
4586 kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
4588 path const pkl_file = find_file(dir, "pkl_");
4589 path const cpl_file = find_file(dir, "cpl_");
4591 check_verify_result(
4595 dcp::VerificationNote(
4596 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_file)
4597 ).set_cpl_id(cpl->id()),
4598 dcp::VerificationNote(
4599 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_file)
4600 ).set_cpl_id(cpl->id()),
4601 dcp::VerificationNote(
4602 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file)