2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
60 #include "verify_j2k.h"
61 #include <boost/algorithm/string.hpp>
62 #include <boost/random.hpp>
63 #include <boost/test/unit_test.hpp>
71 using std::make_shared;
73 using std::shared_ptr;
76 using boost::optional;
77 using namespace boost::filesystem;
80 static list<pair<string, optional<path>>> stages;
82 static string filename_to_id(boost::filesystem::path path)
84 return path.string().substr(4, path.string().length() - 8);
88 boost::filesystem::path
91 return find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
98 return filename_to_id(dcp_test1_pkl());
102 boost::filesystem::path
105 return find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
112 return filename_to_id(dcp_test1_cpl());
115 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
119 encryption_test_cpl_id()
121 return filename_to_id(find_file("test/ref/DCP/encryption_test", "cpl_").filename());
126 encryption_test_pkl_id()
128 return filename_to_id(find_file("test/ref/DCP/encryption_test", "pkl_").filename());
132 stage (string s, optional<path> p)
134 stages.push_back (make_pair (s, p));
144 prepare_directory (path path)
146 using namespace boost::filesystem;
148 create_directories (path);
154 find_prefix(path dir, string prefix)
156 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
157 return boost::starts_with(p.filename().string(), prefix);
160 BOOST_REQUIRE(iter != directory_iterator());
169 return find_prefix(dir, "cpl_");
177 return find_prefix(dir, "pkl_");
183 find_asset_map(path dir)
185 return find_prefix(dir, "ASSETMAP");
189 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
190 * to make a new sacrificial test DCP.
193 setup (int reference_number, string verify_test_suffix)
195 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
196 prepare_directory (dir);
197 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
198 copy_file (i.path(), dir / i.path().filename());
207 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
209 auto reel = make_shared<dcp::Reel>();
210 reel->add (reel_asset);
211 reel->add (simple_markers());
213 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
215 auto dcp = make_shared<dcp::DCP>(dir);
217 dcp->set_annotation_text("hello");
224 LIBDCP_DISABLE_WARNINGS
227 dump_notes (vector<dcp::VerificationNote> const & notes)
229 for (auto i: notes) {
230 std::cout << dcp::note_to_string(i) << "\n";
233 LIBDCP_ENABLE_WARNINGS
238 to_string(dcp::VerificationNote const& note)
240 string s = note_to_string(note) + dcp::String::compose(
241 "\n [%1 %2 %3 %4 %5 %6 ",
242 static_cast<int>(note.type()),
243 static_cast<int>(note.code()),
244 note.note().get_value_or("<none>"),
245 note.file().get_value_or("<none>"),
246 note.line().get_value_or(0),
247 note.frame().get_value_or(0)
250 s += dcp::String::compose(
252 note.id().get_value_or("<none>"),
253 note.other_id().get_value_or("<none>"),
254 note.cpl_id().get_value_or("<none>"),
255 note.reference_hash().get_value_or("<none>"),
256 note.calculated_hash().get_value_or("<none>")
265 check_verify_result(vector<dcp::VerificationNote> notes, vector<dcp::VerificationNote> test_notes)
267 std::sort(notes.begin(), notes.end());
268 std::sort(test_notes.begin(), test_notes.end());
270 string message = "\n";
272 vector<dcp::VerificationNote> not_expected;
273 for (auto note: notes) {
274 auto iter = std::find_if(test_notes.begin(), test_notes.end(), [note](dcp::VerificationNote const& n) { return note.type() == n.type() && note.code() == n.code(); });
275 if (iter != test_notes.end() && *iter != note) {
276 message += "Wrong details:\n --seen " + to_string(note) + " --expected " + to_string(*iter) + "\n";
277 } else if (iter == test_notes.end()) {
278 not_expected.push_back(note);
282 vector<dcp::VerificationNote> not_seen;
283 for (auto note: test_notes) {
284 auto iter = std::find_if(notes.begin(), notes.end(), [note](dcp::VerificationNote const& n) { return note.type() == n.type() && note.code() == n.code(); });
285 if (iter == notes.end()) {
286 not_seen.push_back(note);
290 for (auto note: not_expected) {
291 message += "Not expected:\n" + to_string(note) + "\n";
294 for (auto note: not_seen) {
295 message += "Not seen:\n" + to_string(note) + "\n";
298 BOOST_REQUIRE_MESSAGE(notes == test_notes, message);
304 check_verify_result(vector<path> dir, vector<dcp::DecryptedKDM> kdm, vector<dcp::VerificationNote> test_notes)
306 check_verify_result(dcp::verify({dir}, kdm, &stage, &progress, {}, xsd_test).notes, test_notes);
310 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
311 * replacing from with to.
315 replace(string suffix, boost::function<path (string)> file, string from, string to)
317 auto dir = setup (1, suffix);
320 Editor e (file(suffix));
321 e.replace (from, to);
328 add_font(shared_ptr<dcp::SubtitleAsset> asset)
330 dcp::ArrayData fake_font(1024);
331 asset->add_font("font", fake_font);
338 HashCalculator(boost::filesystem::path path)
340 , _old_hash(dcp::make_digest(path, [](int64_t, int64_t) {}))
343 std::string old_hash() const {
347 std::string new_hash() const {
348 return dcp::make_digest(_path, [](int64_t, int64_t) {});
352 boost::filesystem::path _path;
353 std::string _old_hash;
358 dcp::VerificationNote
359 ok(dcp::VerificationNote::Code code, shared_ptr<const dcp::CPL> cpl)
361 return dcp::VerificationNote(dcp::VerificationNote::Type::OK, code).set_cpl_id(cpl->id());
366 add(vector<dcp::VerificationNote>& notes, vector<dcp::VerificationNote> const& add)
374 BOOST_AUTO_TEST_CASE (verify_no_error)
377 auto dir = setup (1, "no_error");
378 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
380 path const cpl_file = dir / dcp_test1_cpl();
381 path const pkl_file = dir / dcp_test1_pkl();
382 path const assetmap_file = dir / "ASSETMAP.xml";
384 auto st = stages.begin();
385 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
386 BOOST_REQUIRE (st->second);
387 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
389 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
390 BOOST_REQUIRE (st->second);
391 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
393 BOOST_CHECK_EQUAL (st->first, "Checking reel");
394 BOOST_REQUIRE (!st->second);
396 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
397 BOOST_REQUIRE (st->second);
398 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
400 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
401 BOOST_REQUIRE (st->second);
402 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
404 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
405 BOOST_REQUIRE (st->second);
406 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
408 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
409 BOOST_REQUIRE (st->second);
410 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
412 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
413 BOOST_REQUIRE (st->second);
414 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
415 ++st; BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
416 BOOST_REQUIRE (st->second);
417 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
419 BOOST_REQUIRE (st == stages.end());
421 for (auto note: notes) {
422 BOOST_CHECK(note.type() == dcp::VerificationNote::Type::OK);
427 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
429 using namespace boost::filesystem;
431 auto dir = setup (1, "incorrect_picture_sound_hash");
432 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
434 auto video_path = path(dir / "video.mxf");
435 HashCalculator video_calc(video_path);
436 auto mod = fopen(video_path.string().c_str(), "r+b");
438 BOOST_REQUIRE_EQUAL(fseek(mod, -16, SEEK_END), 0);
440 BOOST_REQUIRE(fwrite(&x, sizeof(x), 1, mod) == 1);
443 auto audio_path = path(dir / "audio.mxf");
444 HashCalculator audio_calc(audio_path);
445 mod = fopen(audio_path.string().c_str(), "r+b");
447 BOOST_REQUIRE_EQUAL(fseek(mod, 0, SEEK_END), 0);
448 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
451 dcp::ASDCPErrorSuspender sus;
452 check_verify_result (
456 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
457 dcp::VerificationNote(
458 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path)
459 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(video_calc.old_hash()).set_calculated_hash(video_calc.new_hash()),
460 dcp::VerificationNote(
461 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path)
462 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(audio_calc.old_hash()).set_calculated_hash(audio_calc.new_hash()),
467 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
469 using namespace boost::filesystem;
471 auto dir = setup (1, "mismatched_picture_sound_hashes");
472 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
474 HashCalculator calc(dir / dcp_test1_cpl());
477 Editor e (dir / dcp_test1_pkl());
478 e.replace ("<Hash>", "<Hash>x");
481 check_verify_result (
485 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
486 dcp::VerificationNote(
487 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl())
488 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash("x" + calc.old_hash()).set_calculated_hash(calc.old_hash()),
489 dcp::VerificationNote(
490 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf")
491 ).set_cpl_id(dcp_test1_cpl_id()),
492 dcp::VerificationNote(
493 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf")
494 ).set_cpl_id(dcp_test1_cpl_id()),
495 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'x3M7YTgvFKXXMEGLkIbV4miC90FE=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 28 },
496 { 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 },
497 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xvsVjRV9vhTBPUWfE/TT1o2vdQsI=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 20 },
502 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
504 auto dir = setup (1, "failed_read_content_kind");
506 HashCalculator calc(dir / dcp_test1_cpl());
509 Editor e (dir / dcp_test1_cpl());
510 e.replace ("<ContentKind>", "<ContentKind>x");
513 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
515 check_verify_result (
519 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
520 dcp::VerificationNote(
521 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl())
522 ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
523 dcp::VerificationNote(
524 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer")
525 ).set_cpl_id(dcp_test1_cpl_id())
532 dcp_test1_cpl_path(string suffix)
534 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl());
540 dcp_test1_pkl_path(string suffix)
542 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl());
548 asset_map (string suffix)
550 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
554 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
556 auto const suffix = "invalid_picture_frame_rate";
558 replace(suffix, &dcp_test1_cpl_path, "<FrameRate>24 1", "<FrameRate>99 1");
560 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
561 auto const cpl_path = find_cpl(dir);
562 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
564 std::vector<dcp::VerificationNote> expected =
566 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
567 dcp::VerificationNote(
568 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
569 ).set_cpl_id(cpl->id()).set_calculated_hash("7n7GQ2TbxQbmHYuAR8ml7XDOep8=").set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI="),
570 dcp::VerificationNote(
571 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, string{"99/1"}
572 ).set_cpl_id(cpl->id())
575 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
578 BOOST_AUTO_TEST_CASE (verify_missing_asset)
580 auto dir = setup (1, "missing_asset");
581 remove (dir / "video.mxf");
583 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
585 check_verify_result (
589 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
590 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
595 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
597 auto const suffix = "empty_asset_path";
599 replace("empty_asset_path", &asset_map, "<Path>video.mxf</Path>", "<Path></Path>");
601 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
602 auto const cpl_path = find_cpl(dir);
603 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
605 std::vector<dcp::VerificationNote> expected = {
606 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
607 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
610 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
614 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
616 auto const suffix = "mismatched_standard";
618 replace(suffix, &dcp_test1_cpl_path, "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#");
620 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
621 auto const cpl_path = find_cpl(dir);
622 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
624 std::vector<dcp::VerificationNote> expected = {
625 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
626 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_STANDARD },
627 dcp::VerificationNote(
628 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "invalid character encountered", canonical(cpl_path), 42
629 ).set_cpl_id(cpl->id()),
630 dcp::VerificationNote(
631 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'Id'", canonical(cpl_path), 53
632 ).set_cpl_id(cpl->id()),
633 dcp::VerificationNote(
634 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'EditRate'", canonical(cpl_path), 54
635 ).set_cpl_id(cpl->id()),
636 dcp::VerificationNote(
637 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "no declaration found for element 'IntrinsicDuration'", canonical(cpl_path), 55
638 ).set_cpl_id(cpl->id()),
639 dcp::VerificationNote(
640 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
641 "element 'Id' is not allowed for content model '(Id,AnnotationText?,EditRate,IntrinsicDuration,"
642 "EntryPoint?,Duration?,FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
643 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,MainSoundSampleRate,"
644 "MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,ExtensionMetadataList?,)'",
645 canonical(cpl_path), 149
646 ).set_cpl_id(cpl->id()),
647 dcp::VerificationNote(
648 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
649 ).set_cpl_id(cpl->id()).set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI=").set_calculated_hash("FZ9E7L/pOuJ6aZfbiaANTv8BFOo=")
652 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
656 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
658 auto const suffix = "invalid_xml_cpl_id";
660 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
661 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");
663 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
664 auto const cpl_path = find_cpl(dir);
665 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
667 std::vector<dcp::VerificationNote> expected = {
668 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
669 dcp::VerificationNote(
670 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
671 "value 'urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a' does not match regular expression "
672 "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
673 ).set_cpl_id(cpl->id())
676 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
680 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
682 auto const suffix = "invalid_xml_issue_date";
684 replace("invalid_xml_issue_date", &dcp_test1_cpl_path, "<IssueDate>", "<IssueDate>x");
686 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
687 auto const cpl_path = find_cpl(dir);
688 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
690 std::vector<dcp::VerificationNote> expected = {
691 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
692 dcp::VerificationNote(
693 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
694 ).set_cpl_id(cpl->id()).set_reference_hash("skI+5b/9LA/y6h0mcyxysJYanxI=").set_calculated_hash("sz3BeIugJ567q3HMnA62JeRw4TE="),
695 dcp::VerificationNote(
696 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
697 "invalid character encountered",
698 canonical(cpl_path), 5
699 ).set_cpl_id(cpl->id()),
702 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
706 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
708 auto const suffix = "invalid_xml_pkl_id";
710 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));
712 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
713 auto const pkl_path = find_pkl(dir);
714 auto const cpl_path = find_cpl(dir);
715 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
717 std::vector<dcp::VerificationNote> expected = {
718 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
719 dcp::VerificationNote(
720 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
721 "value 'urn:uuid:x199d58b-5ef8-4d49-b270-07e590ccb280' does not match regular "
722 "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}'",
723 canonical(pkl_path), 3
727 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
731 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
733 auto const suffix = "invalid_xml_asset_map_id";
735 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));
737 auto const dir = dcp::String::compose("build/test/verify_test%1", suffix);
738 auto const cpl_path = find_cpl(dir);
739 auto const asset_map_path = find_asset_map(dir);
740 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
742 std::vector<dcp::VerificationNote> expected = {
743 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
744 dcp::VerificationNote(
745 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML,
746 "value 'urn:uuid:x17b3de4-6dda-408d-b19b-6711354b0bc3' does not match regular "
747 "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}'",
748 canonical(asset_map_path), 3
752 check_verify_result(dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes, expected);
756 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
759 auto dir = setup (3, "verify_invalid_standard");
760 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes;
762 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
763 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
764 path const assetmap_file = dir / "ASSETMAP";
765 auto cpl = std::make_shared<dcp::CPL>(cpl_file);
767 auto st = stages.begin();
768 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
769 BOOST_REQUIRE (st->second);
770 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
772 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
773 BOOST_REQUIRE (st->second);
774 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
776 BOOST_CHECK_EQUAL (st->first, "Checking reel");
777 BOOST_REQUIRE (!st->second);
779 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
780 BOOST_REQUIRE (st->second);
781 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
783 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
784 BOOST_REQUIRE (st->second);
785 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
787 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
788 BOOST_REQUIRE (st->second);
789 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
791 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
792 BOOST_REQUIRE (st->second);
793 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
795 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
796 BOOST_REQUIRE (st->second);
797 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
799 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
800 BOOST_REQUIRE (st->second);
801 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
803 BOOST_REQUIRE (st == stages.end());
805 vector<dcp::VerificationNote> expected = {
806 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
807 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
810 for (int j = 0; j < 24; ++j) {
812 dcp::VerificationNote(
813 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2")
814 ).set_cpl_id(cpl->id())
818 check_verify_result(notes, expected);
821 /* DCP with a short asset */
822 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
824 auto dir = setup (8, "invalid_duration");
828 BOOST_REQUIRE(dcp.cpls().size() == 1);
829 auto cpl = dcp.cpls()[0];
831 vector<dcp::VerificationNote> expected = {
832 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
833 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
834 dcp::VerificationNote(
835 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91")
836 ).set_cpl_id(cpl->id()),
837 dcp::VerificationNote(
838 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91")
839 ).set_cpl_id(cpl->id()),
840 dcp::VerificationNote(
841 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626")
842 ).set_cpl_id(cpl->id()),
843 dcp::VerificationNote(
844 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626")
845 ).set_cpl_id(cpl->id()),
846 dcp::VerificationNote(
847 dcp::VerificationNote::Type::WARNING,
848 dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT,
850 ).set_cpl_id(cpl->id())
853 for (int i = 0; i < 23; ++i) {
855 dcp::VerificationNote(
856 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2")
857 ).set_cpl_id(cpl->id())
861 check_verify_result({ dir }, {}, expected);
867 dcp_from_frame (dcp::ArrayData const& frame, path dir)
869 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
870 create_directories (dir);
871 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
872 for (int i = 0; i < 24; ++i) {
873 writer->write (frame.data(), frame.size());
877 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
878 return write_dcp_with_single_asset (dir, reel_asset);
882 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
884 int const too_big = 1302083 * 2;
886 /* Compress a black image */
887 auto image = black_image ();
888 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
889 BOOST_REQUIRE (frame.size() < too_big);
891 /* Place it in a bigger block with some zero padding at the end */
892 dcp::ArrayData oversized_frame(too_big);
893 memcpy (oversized_frame.data(), frame.data(), frame.size());
894 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
896 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
897 prepare_directory (dir);
898 auto cpl = dcp_from_frame (oversized_frame, dir);
900 vector<dcp::VerificationNote> expected = {
901 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
904 for (auto i = 0; i < 24; ++i) {
906 dcp::VerificationNote(
907 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
908 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
912 for (auto i = 0; i < 24; ++i) {
914 dcp::VerificationNote(
915 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
916 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
921 dcp::VerificationNote(
922 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
923 ).set_cpl_id(cpl->id())
926 check_verify_result({ dir }, {}, expected);
930 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
932 int const nearly_too_big = 1302083 * 0.98;
934 /* Compress a black image */
935 auto image = black_image ();
936 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
937 BOOST_REQUIRE (frame.size() < nearly_too_big);
939 /* Place it in a bigger block with some zero padding at the end */
940 dcp::ArrayData oversized_frame(nearly_too_big);
941 memcpy (oversized_frame.data(), frame.data(), frame.size());
942 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
944 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
945 prepare_directory (dir);
946 auto cpl = dcp_from_frame (oversized_frame, dir);
948 vector<dcp::VerificationNote> expected = {
949 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
952 for (auto i = 0; i < 24; ++i) {
954 dcp::VerificationNote(
955 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
956 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
960 for (auto i = 0; i < 24; ++i) {
962 dcp::VerificationNote(
963 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
964 ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id())
969 dcp::VerificationNote(
970 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
971 ).set_cpl_id(cpl->id())
974 check_verify_result ({ dir }, {}, expected);
978 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
980 /* Compress a black image */
981 auto image = black_image ();
982 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
983 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
985 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
986 prepare_directory (dir);
987 auto cpl = dcp_from_frame (frame, dir);
993 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
994 dcp::VerificationNote(dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()).set_cpl_id(cpl->id())
999 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
1001 path const dir("build/test/verify_valid_interop_subtitles");
1002 prepare_directory (dir);
1003 copy_file ("test/data/subs1.xml", dir / "subs.xml");
1004 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1005 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1006 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1008 check_verify_result (
1012 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1013 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1014 dcp::VerificationNote(
1015 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1016 ).set_cpl_id(cpl->id())
1021 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
1023 path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
1024 prepare_directory(dir);
1025 copy_file("test/data/subs1.xml", dir / "ccap.xml");
1026 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
1027 auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1028 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1030 check_verify_result (
1034 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1035 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1036 dcp::VerificationNote(
1037 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1038 ).set_cpl_id(cpl->id())
1043 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
1045 using namespace boost::filesystem;
1047 path const dir("build/test/verify_invalid_interop_subtitles");
1048 prepare_directory (dir);
1049 copy_file ("test/data/subs1.xml", dir / "subs.xml");
1050 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1051 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1052 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1055 Editor e (dir / "subs.xml");
1056 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
1059 check_verify_result (
1063 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1064 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1065 dcp::VerificationNote(
1066 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5
1067 ).set_cpl_id(cpl->id()),
1068 dcp::VerificationNote(
1069 dcp::VerificationNote::Type::ERROR,
1070 dcp::VerificationNote::Code::INVALID_XML,
1071 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
1074 ).set_cpl_id(cpl->id()),
1075 dcp::VerificationNote(
1076 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1077 ).set_cpl_id(cpl->id())
1082 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
1084 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
1085 prepare_directory(dir);
1086 copy_file("test/data/subs4.xml", dir / "subs.xml");
1087 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1088 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1089 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1091 check_verify_result (
1095 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1096 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1097 dcp::VerificationNote(
1098 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get())
1099 ).set_cpl_id(cpl->id()),
1100 dcp::VerificationNote(
1101 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"}
1102 ).set_cpl_id(cpl->id())
1108 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
1110 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
1111 prepare_directory(dir);
1112 copy_file("test/data/subs5.xml", dir / "subs.xml");
1113 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1114 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1115 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
1117 check_verify_result (
1121 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1122 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1123 dcp::VerificationNote(
1124 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"}
1125 ).set_cpl_id(cpl->id())
1131 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
1133 path const dir("build/test/verify_valid_smpte_subtitles");
1134 prepare_directory (dir);
1135 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1136 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1137 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1138 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1140 check_verify_result(
1144 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1145 dcp::VerificationNote(
1146 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1147 ).set_cpl_id(cpl->id()),
1148 dcp::VerificationNote(
1149 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"}
1150 ).set_cpl_id(cpl->id()),
1151 dcp::VerificationNote(
1152 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1153 ).set_cpl_id(cpl->id()),
1158 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
1160 using namespace boost::filesystem;
1162 path const dir("build/test/verify_invalid_smpte_subtitles");
1163 prepare_directory (dir);
1164 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
1165 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
1166 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1167 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1168 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1170 check_verify_result (
1174 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1175 dcp::VerificationNote(
1176 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2
1177 ).set_cpl_id(cpl->id()),
1178 dcp::VerificationNote(
1179 dcp::VerificationNote::Type::ERROR,
1180 dcp::VerificationNote::Code::INVALID_XML,
1181 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
1184 ).set_cpl_id(cpl->id()),
1185 dcp::VerificationNote(
1186 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1187 ).set_cpl_id(cpl->id()),
1188 dcp::VerificationNote(
1189 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1190 ).set_cpl_id(cpl->id()),
1191 dcp::VerificationNote(
1192 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"}
1193 ).set_cpl_id(cpl->id()),
1194 dcp::VerificationNote(
1195 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1196 ).set_cpl_id(cpl->id()),
1201 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
1203 path const dir("build/test/verify_empty_text_node_in_subtitles");
1204 prepare_directory (dir);
1205 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
1206 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1207 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1208 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1210 check_verify_result (
1214 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1215 dcp::VerificationNote(
1216 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT
1217 ).set_cpl_id(cpl->id()),
1218 dcp::VerificationNote(
1219 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1220 ).set_cpl_id(cpl->id()),
1221 dcp::VerificationNote(
1222 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf")
1223 ).set_cpl_id(cpl->id()),
1224 dcp::VerificationNote(
1225 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1226 ).set_cpl_id(cpl->id()),
1227 dcp::VerificationNote(
1228 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"}
1229 ).set_cpl_id(cpl->id()),
1230 dcp::VerificationNote(
1231 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()
1232 ).set_cpl_id(cpl->id())
1237 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
1238 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
1240 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
1241 prepare_directory (dir);
1242 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
1243 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1244 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1245 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
1247 check_verify_result (
1251 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1252 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1253 dcp::VerificationNote(
1254 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"}
1255 ).set_cpl_id(cpl->id())
1260 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
1261 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
1263 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
1264 prepare_directory (dir);
1265 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
1266 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
1267 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
1268 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
1270 check_verify_result (
1274 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1275 dcp::VerificationNote(
1276 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get())
1277 ).set_cpl_id(cpl->id()),
1278 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
1279 dcp::VerificationNote(
1280 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT
1281 ).set_cpl_id(cpl->id()),
1282 dcp::VerificationNote(
1283 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"}
1284 ).set_cpl_id(cpl->id())
1289 BOOST_AUTO_TEST_CASE (verify_external_asset)
1291 path const ov_dir("build/test/verify_external_asset");
1292 prepare_directory (ov_dir);
1294 auto image = black_image ();
1295 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
1296 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
1297 dcp_from_frame (frame, ov_dir);
1299 dcp::DCP ov (ov_dir);
1302 path const vf_dir("build/test/verify_external_asset_vf");
1303 prepare_directory (vf_dir);
1305 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
1306 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
1308 check_verify_result (
1312 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1313 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
1314 dcp::VerificationNote(
1315 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1316 ).set_cpl_id(cpl->id())
1321 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
1323 path const dir("build/test/verify_valid_cpl_metadata");
1324 prepare_directory (dir);
1326 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1327 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1328 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1330 auto reel = make_shared<dcp::Reel>();
1331 reel->add (reel_asset);
1333 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1334 reel->add (simple_markers(16 * 24));
1336 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1338 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1339 cpl->set_main_sound_sample_rate (48000);
1340 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1341 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1342 cpl->set_version_number (1);
1346 dcp.set_annotation_text("hello");
1351 /* DCP with invalid CompositionMetadataAsset */
1352 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1354 using namespace boost::filesystem;
1356 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1357 prepare_directory (dir);
1359 auto reel = make_shared<dcp::Reel>();
1360 reel->add (black_picture_asset(dir));
1361 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1363 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1364 cpl->set_main_sound_sample_rate (48000);
1365 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1366 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1367 cpl->set_version_number (1);
1369 reel->add (simple_markers());
1373 dcp.set_annotation_text("hello");
1376 HashCalculator calc(find_cpl(dir));
1379 Editor e (find_cpl(dir));
1380 e.replace ("MainSound", "MainSoundX");
1383 check_verify_result (
1387 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1388 dcp::VerificationNote(
1389 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50
1390 ).set_cpl_id(cpl->id()),
1391 dcp::VerificationNote(
1392 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51
1393 ).set_cpl_id(cpl->id()),
1394 dcp::VerificationNote(
1395 dcp::VerificationNote::Type::ERROR,
1396 dcp::VerificationNote::Code::INVALID_XML,
1397 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1398 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1399 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1400 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1401 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1402 "ExtensionMetadataList?,)'"),
1403 canonical(cpl->file().get()),
1404 71).set_cpl_id(cpl->id()),
1405 dcp::VerificationNote(
1406 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
1407 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
1412 /* DCP with invalid CompositionMetadataAsset */
1413 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1415 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1416 prepare_directory (dir);
1418 auto reel = make_shared<dcp::Reel>();
1419 reel->add (black_picture_asset(dir));
1420 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1422 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1423 cpl->set_main_sound_sample_rate (48000);
1424 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1425 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1429 dcp.set_annotation_text("hello");
1433 Editor e (find_cpl(dir));
1434 e.replace ("meta:Width", "meta:WidthX");
1437 check_verify_result (
1440 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1445 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1447 path const dir("build/test/verify_invalid_language1");
1448 prepare_directory (dir);
1449 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1450 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1451 asset->_language = "wrong-andbad";
1452 asset->write (dir / "subs.mxf");
1453 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1454 reel_asset->_language = "badlang";
1455 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1457 check_verify_result (
1461 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1462 dcp::VerificationNote(
1463 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang")
1464 ).set_cpl_id(cpl->id()),
1465 dcp::VerificationNote(
1466 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad")
1467 ).set_cpl_id(cpl->id()),
1468 dcp::VerificationNote(
1469 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1470 ).set_cpl_id(cpl->id())
1475 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1476 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1478 path const dir("build/test/verify_invalid_language2");
1479 prepare_directory (dir);
1480 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1481 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1482 asset->_language = "wrong-andbad";
1483 asset->write (dir / "subs.mxf");
1484 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1485 reel_asset->_language = "badlang";
1486 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1488 check_verify_result (
1492 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1493 dcp::VerificationNote(
1494 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang")
1495 ).set_cpl_id(cpl->id()),
1496 dcp::VerificationNote(
1497 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad")
1498 ).set_cpl_id(cpl->id()),
1499 dcp::VerificationNote(
1500 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1501 ).set_cpl_id(cpl->id())
1506 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1507 * the release territory.
1509 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1511 path const dir("build/test/verify_invalid_language3");
1512 prepare_directory (dir);
1514 auto picture = simple_picture (dir, "foo");
1515 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1516 auto reel = make_shared<dcp::Reel>();
1517 reel->add (reel_picture);
1518 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1519 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1520 reel->add (reel_sound);
1521 reel->add (simple_markers());
1523 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1525 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1526 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1527 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1528 cpl->set_main_sound_sample_rate (48000);
1529 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1530 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1531 cpl->set_version_number (1);
1532 cpl->_release_territory = "fred-jim";
1533 auto dcp = make_shared<dcp::DCP>(dir);
1535 dcp->set_annotation_text("hello");
1538 check_verify_result (
1542 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1543 dcp::VerificationNote(
1544 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong")
1545 ).set_cpl_id(cpl->id()),
1546 dcp::VerificationNote(
1547 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this")
1548 ).set_cpl_id(cpl->id()),
1549 dcp::VerificationNote(
1550 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim")
1551 ).set_cpl_id(cpl->id()),
1552 dcp::VerificationNote(
1553 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz")
1554 ).set_cpl_id(cpl->id()),
1560 std::tuple<vector<dcp::VerificationNote>, shared_ptr<dcp::CPL>, boost::filesystem::path>
1561 check_picture_size (int width, int height, int frame_rate, bool three_d)
1563 using namespace boost::filesystem;
1565 path dcp_path = "build/test/verify_picture_test";
1566 prepare_directory (dcp_path);
1568 shared_ptr<dcp::PictureAsset> mp;
1570 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1572 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1574 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1576 auto image = black_image (dcp::Size(width, height));
1577 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1578 int const length = three_d ? frame_rate * 2 : frame_rate;
1579 for (int i = 0; i < length; ++i) {
1580 picture_writer->write (j2c.data(), j2c.size());
1582 picture_writer->finalize ();
1584 auto d = make_shared<dcp::DCP>(dcp_path);
1585 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1586 cpl->set_annotation_text ("A Test DCP");
1587 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1588 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1589 cpl->set_main_sound_sample_rate (48000);
1590 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1591 cpl->set_main_picture_active_area(dcp::Size(width, height));
1592 cpl->set_version_number (1);
1594 auto reel = make_shared<dcp::Reel>();
1597 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1599 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1602 reel->add (simple_markers(frame_rate));
1607 d->set_annotation_text("A Test DCP");
1610 /* It seems that for the Ubuntu 16.04 compiler we can't use an initializer list here */
1611 return std::tuple<vector<dcp::VerificationNote>, shared_ptr<dcp::CPL>, boost::filesystem::path>{ dcp::verify({dcp_path}, {}, &stage, &progress, {}, xsd_test).notes, cpl, dcp_path };
1617 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1619 vector<dcp::VerificationNote> notes;
1620 shared_ptr<dcp::CPL> cpl;
1621 boost::filesystem::path dir;
1622 std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1624 std::vector<dcp::VerificationNote> expected = {
1625 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1627 check_verify_result(notes, expected);
1633 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1635 vector<dcp::VerificationNote> notes;
1636 shared_ptr<dcp::CPL> cpl;
1637 boost::filesystem::path dir;
1638 std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1640 std::vector<dcp::VerificationNote> expected = {
1641 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1642 dcp::VerificationNote(
1643 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS, dcp::String::compose("%1x%2", width, height), canonical(dir / "video.mxf")
1644 ).set_cpl_id(cpl->id())
1646 check_verify_result(notes, expected);
1652 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1654 vector<dcp::VerificationNote> notes;
1655 shared_ptr<dcp::CPL> cpl;
1656 boost::filesystem::path dir;
1657 std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1659 std::vector<dcp::VerificationNote> expected = {
1660 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1661 dcp::VerificationNote(
1662 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, dcp::String::compose("%1/1", frame_rate * (three_d ? 2 : 1))
1663 ).set_cpl_id(cpl->id()),
1664 dcp::VerificationNote(
1665 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K, dcp::String::compose("%1/1", frame_rate), canonical(dir / "video.mxf")
1666 ).set_cpl_id(cpl->id())
1669 check_verify_result(notes, expected);
1675 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1677 vector<dcp::VerificationNote> notes;
1678 shared_ptr<dcp::CPL> cpl;
1679 boost::filesystem::path dir;
1680 std::tie(notes, cpl, dir) = check_picture_size(width, height, frame_rate, three_d);
1682 std::vector<dcp::VerificationNote> expected = {
1683 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1684 dcp::VerificationNote(
1685 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K, dcp::String::compose("%1/1", frame_rate), canonical(dir / "video.mxf")
1686 ).set_cpl_id(cpl->id())
1689 check_verify_result(notes, expected);
1693 BOOST_AUTO_TEST_CASE (verify_picture_size)
1695 using namespace boost::filesystem;
1698 check_picture_size_ok (2048, 858, 24, false);
1699 check_picture_size_ok (2048, 858, 25, false);
1700 check_picture_size_ok (2048, 858, 48, false);
1701 check_picture_size_ok (2048, 858, 24, true);
1702 check_picture_size_ok (2048, 858, 25, true);
1703 check_picture_size_ok (2048, 858, 48, true);
1706 check_picture_size_ok (1998, 1080, 24, false);
1707 check_picture_size_ok (1998, 1080, 25, false);
1708 check_picture_size_ok (1998, 1080, 48, false);
1709 check_picture_size_ok (1998, 1080, 24, true);
1710 check_picture_size_ok (1998, 1080, 25, true);
1711 check_picture_size_ok (1998, 1080, 48, true);
1714 check_picture_size_ok (4096, 1716, 24, false);
1717 check_picture_size_ok (3996, 2160, 24, false);
1719 /* Bad frame size */
1720 check_picture_size_bad_frame_size (2050, 858, 24, false);
1721 check_picture_size_bad_frame_size (2048, 658, 25, false);
1722 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1723 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1725 /* Bad 2K frame rate */
1726 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1727 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1728 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1730 /* Bad 4K frame rate */
1731 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1732 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1735 vector<dcp::VerificationNote> notes;
1736 shared_ptr<dcp::CPL> cpl;
1737 boost::filesystem::path dir;
1738 std::tie(notes, cpl, dir) = check_picture_size(3996, 2160, 24, true);
1740 std::vector<dcp::VerificationNote> expected = {
1741 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1742 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D },
1749 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")
1752 std::make_shared<dcp::SubtitleString>(
1760 dcp::Time(start_frame, 24, 24),
1761 dcp::Time(end_frame, 24, 24),
1763 dcp::HAlign::CENTER,
1767 dcp::Direction::LTR,
1774 std::vector<dcp::Ruby>()
1780 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1782 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1783 prepare_directory (dir);
1785 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1786 for (int i = 0; i < 2048; ++i) {
1787 add_test_subtitle (asset, i * 24, i * 24 + 20);
1790 asset->set_language (dcp::LanguageTag("de-DE"));
1791 asset->write (dir / "subs.mxf");
1792 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1793 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1795 check_verify_result (
1799 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1800 dcp::VerificationNote(
1801 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1802 ).set_cpl_id(cpl->id()),
1803 dcp::VerificationNote(
1804 dcp::VerificationNote::Type::BV21_ERROR,
1805 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1807 canonical(dir / "subs.mxf")
1808 ).set_cpl_id(cpl->id()),
1809 dcp::VerificationNote(
1810 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1811 ).set_cpl_id(cpl->id()),
1812 dcp::VerificationNote(
1813 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1814 ).set_cpl_id(cpl->id())
1820 shared_ptr<dcp::SMPTESubtitleAsset>
1821 make_large_subtitle_asset (path font_file)
1823 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1824 dcp::ArrayData big_fake_font(1024 * 1024);
1825 big_fake_font.write (font_file);
1826 for (int i = 0; i < 116; ++i) {
1827 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1835 verify_timed_text_asset_too_large (string name)
1837 auto const dir = path("build/test") / name;
1838 prepare_directory (dir);
1839 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1840 add_test_subtitle (asset, 0, 240);
1841 asset->set_language (dcp::LanguageTag("de-DE"));
1842 asset->write (dir / "subs.mxf");
1844 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1845 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1847 check_verify_result (
1851 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1852 dcp::VerificationNote(
1853 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121698284"), canonical(dir / "subs.mxf")
1854 ).set_cpl_id(cpl->id()),
1855 dcp::VerificationNote(
1856 dcp::VerificationNote::Type::BV21_ERROR,
1857 dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES,
1858 dcp::raw_convert<string>(121634816),
1859 canonical(dir / "subs.mxf")
1860 ).set_cpl_id(cpl->id()),
1861 dcp::VerificationNote(
1862 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
1863 ).set_cpl_id(cpl->id()),
1864 dcp::VerificationNote(
1865 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1866 ).set_cpl_id(cpl->id()),
1867 dcp::VerificationNote(
1868 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
1869 ).set_cpl_id(cpl->id())
1874 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1876 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1877 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1881 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1883 path dir = "build/test/verify_missing_subtitle_language";
1884 prepare_directory (dir);
1885 auto dcp = make_simple (dir, 1, 106);
1888 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1889 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1890 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1891 "<ContentTitleText>Content</ContentTitleText>"
1892 "<AnnotationText>Annotation</AnnotationText>"
1893 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1894 "<ReelNumber>1</ReelNumber>"
1895 "<EditRate>24 1</EditRate>"
1896 "<TimeCodeRate>24</TimeCodeRate>"
1897 "<StartTime>00:00:00:00</StartTime>"
1898 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1900 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1901 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1902 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1908 dcp::File xml_file(dir / "subs.xml", "w");
1909 BOOST_REQUIRE (xml_file);
1910 xml_file.write(xml.c_str(), xml.size(), 1);
1912 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1913 subs->write (dir / "subs.mxf");
1915 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1916 auto cpl = dcp->cpls()[0];
1917 cpl->reels()[0]->add(reel_subs);
1920 check_verify_result (
1924 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1925 dcp::VerificationNote(
1926 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf")
1927 ).set_cpl_id(cpl->id()),
1928 dcp::VerificationNote(
1929 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
1930 ).set_cpl_id(cpl->id())
1935 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1937 path path ("build/test/verify_mismatched_subtitle_languages");
1938 auto constexpr reel_length = 192;
1939 auto dcp = make_simple (path, 2, reel_length);
1940 auto cpl = dcp->cpls()[0];
1943 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1944 subs->set_language (dcp::LanguageTag("de-DE"));
1945 subs->add (simple_subtitle());
1947 subs->write (path / "subs1.mxf");
1948 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1949 cpl->reels()[0]->add(reel_subs);
1953 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1954 subs->set_language (dcp::LanguageTag("en-US"));
1955 subs->add (simple_subtitle());
1957 subs->write (path / "subs2.mxf");
1958 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1959 cpl->reels()[1]->add(reel_subs);
1964 check_verify_result (
1968 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
1969 dcp::VerificationNote(
1970 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf")
1971 ).set_cpl_id(cpl->id()),
1972 dcp::VerificationNote(
1973 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf")
1974 ).set_cpl_id(cpl->id()),
1975 dcp::VerificationNote(
1976 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES
1977 ).set_cpl_id(cpl->id()),
1982 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1984 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1985 auto constexpr reel_length = 192;
1986 auto dcp = make_simple (path, 2, reel_length);
1987 auto cpl = dcp->cpls()[0];
1990 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1991 ccaps->set_language (dcp::LanguageTag("de-DE"));
1992 ccaps->add (simple_subtitle());
1994 ccaps->write (path / "subs1.mxf");
1995 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1996 cpl->reels()[0]->add(reel_ccaps);
2000 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
2001 ccaps->set_language (dcp::LanguageTag("en-US"));
2002 ccaps->add (simple_subtitle());
2004 ccaps->write (path / "subs2.mxf");
2005 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
2006 cpl->reels()[1]->add(reel_ccaps);
2011 check_verify_result (
2015 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2016 dcp::VerificationNote(
2017 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf")
2018 ).set_cpl_id(cpl->id()),
2019 dcp::VerificationNote(
2020 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf")
2021 ).set_cpl_id(cpl->id())
2026 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
2028 path dir = "build/test/verify_missing_subtitle_start_time";
2029 prepare_directory (dir);
2030 auto dcp = make_simple (dir, 1, 106);
2033 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2034 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
2035 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
2036 "<ContentTitleText>Content</ContentTitleText>"
2037 "<AnnotationText>Annotation</AnnotationText>"
2038 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2039 "<ReelNumber>1</ReelNumber>"
2040 "<Language>de-DE</Language>"
2041 "<EditRate>24 1</EditRate>"
2042 "<TimeCodeRate>24</TimeCodeRate>"
2043 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
2045 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2046 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2047 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2053 dcp::File xml_file(dir / "subs.xml", "w");
2054 BOOST_REQUIRE (xml_file);
2055 xml_file.write(xml.c_str(), xml.size(), 1);
2057 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2058 subs->write (dir / "subs.mxf");
2060 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
2061 auto cpl = dcp->cpls()[0];
2062 cpl->reels()[0]->add(reel_subs);
2065 check_verify_result (
2069 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2070 dcp::VerificationNote(
2071 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
2072 ).set_cpl_id(cpl->id()),
2073 dcp::VerificationNote(
2074 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2075 ).set_cpl_id(cpl->id())
2080 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
2082 path dir = "build/test/verify_invalid_subtitle_start_time";
2083 prepare_directory (dir);
2084 auto dcp = make_simple (dir, 1, 106);
2087 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2088 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
2089 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
2090 "<ContentTitleText>Content</ContentTitleText>"
2091 "<AnnotationText>Annotation</AnnotationText>"
2092 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2093 "<ReelNumber>1</ReelNumber>"
2094 "<Language>de-DE</Language>"
2095 "<EditRate>24 1</EditRate>"
2096 "<TimeCodeRate>24</TimeCodeRate>"
2097 "<StartTime>00:00:02:00</StartTime>"
2098 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
2100 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2101 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2102 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2108 dcp::File xml_file(dir / "subs.xml", "w");
2109 BOOST_REQUIRE (xml_file);
2110 xml_file.write(xml.c_str(), xml.size(), 1);
2112 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
2113 subs->write (dir / "subs.mxf");
2115 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
2116 auto cpl = dcp->cpls()[0];
2117 cpl->reels().front()->add(reel_subs);
2120 check_verify_result (
2124 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2125 dcp::VerificationNote(
2126 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf")
2127 ).set_cpl_id(cpl->id()),
2128 dcp::VerificationNote(
2129 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2130 ).set_cpl_id(cpl->id())
2138 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
2141 , v_position(v_position_)
2149 dcp::VAlign v_align;
2155 shared_ptr<dcp::CPL>
2156 dcp_with_text(path dir, vector<TestText> subs, optional<dcp::Key> key = boost::none, optional<string> key_id = boost::none)
2158 prepare_directory (dir);
2159 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2160 asset->set_start_time (dcp::Time());
2161 for (auto i: subs) {
2162 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
2164 asset->set_language (dcp::LanguageTag("de-DE"));
2165 if (key && key_id) {
2166 asset->set_key(*key);
2167 asset->set_key_id(*key_id);
2170 asset->write (dir / "subs.mxf");
2172 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
2173 return write_dcp_with_single_asset (dir, reel_asset);
2178 shared_ptr<dcp::CPL>
2179 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
2181 prepare_directory (dir);
2182 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
2183 asset->set_start_time (dcp::Time());
2184 asset->set_language (dcp::LanguageTag("de-DE"));
2186 auto subs_mxf = dir / "subs.mxf";
2187 asset->write (subs_mxf);
2189 /* The call to write() puts the asset into the DCP correctly but it will have
2190 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
2193 ASDCP::TimedText::MXFWriter writer;
2194 ASDCP::WriterInfo writer_info;
2195 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2197 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2198 DCP_ASSERT (c == Kumu::UUID_Length);
2199 ASDCP::TimedText::TimedTextDescriptor descriptor;
2200 descriptor.ContainerDuration = asset->intrinsic_duration();
2201 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
2202 DCP_ASSERT (c == Kumu::UUID_Length);
2203 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
2204 BOOST_REQUIRE (!ASDCP_FAILURE(r));
2205 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
2206 BOOST_REQUIRE (!ASDCP_FAILURE(r));
2209 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
2210 return write_dcp_with_single_asset (dir, reel_asset);
2214 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
2216 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
2217 /* Just too early */
2218 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
2219 check_verify_result (
2223 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2224 dcp::VerificationNote(
2225 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2226 ).set_cpl_id(cpl->id()),
2227 dcp::VerificationNote(
2228 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2229 ).set_cpl_id(cpl->id())
2235 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
2237 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
2238 /* Just late enough */
2239 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
2240 check_verify_result(
2244 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2245 dcp::VerificationNote(
2246 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2247 ).set_cpl_id(cpl->id())
2252 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
2254 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
2255 prepare_directory (dir);
2257 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
2258 asset1->set_start_time (dcp::Time());
2259 /* Just late enough */
2260 add_test_subtitle (asset1, 4 * 24, 5 * 24);
2261 asset1->set_language (dcp::LanguageTag("de-DE"));
2263 asset1->write (dir / "subs1.mxf");
2264 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
2265 auto reel1 = make_shared<dcp::Reel>();
2266 reel1->add (reel_asset1);
2267 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
2268 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2269 reel1->add (markers1);
2271 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
2272 asset2->set_start_time (dcp::Time());
2274 /* This would be too early on first reel but should be OK on the second */
2275 add_test_subtitle (asset2, 3, 4 * 24);
2276 asset2->set_language (dcp::LanguageTag("de-DE"));
2277 asset2->write (dir / "subs2.mxf");
2278 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
2279 auto reel2 = make_shared<dcp::Reel>();
2280 reel2->add (reel_asset2);
2281 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
2282 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
2283 reel2->add (markers2);
2285 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2288 auto dcp = make_shared<dcp::DCP>(dir);
2290 dcp->set_annotation_text("hello");
2293 check_verify_result(
2297 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2298 dcp::VerificationNote(
2299 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2300 ).set_cpl_id(cpl->id())
2305 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
2307 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
2308 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2312 { 5 * 24 + 1, 6 * 24 },
2314 check_verify_result (
2318 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2319 dcp::VerificationNote(
2320 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING
2321 ).set_cpl_id(cpl->id()),
2322 dcp::VerificationNote(
2323 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2324 ).set_cpl_id(cpl->id())
2329 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
2331 auto const dir = path("build/test/verify_valid_subtitle_spacing");
2332 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2336 { 5 * 24 + 16, 8 * 24 },
2339 check_verify_result(
2343 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2344 dcp::VerificationNote(
2345 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2346 ).set_cpl_id(cpl->id())
2351 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
2353 auto const dir = path("build/test/verify_invalid_subtitle_duration");
2354 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
2355 check_verify_result (
2359 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2360 dcp::VerificationNote(
2361 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION
2362 ).set_cpl_id(cpl->id()),
2363 dcp::VerificationNote(
2364 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2365 ).set_cpl_id(cpl->id())
2370 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
2372 auto const dir = path("build/test/verify_valid_subtitle_duration");
2373 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
2375 check_verify_result(
2379 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2380 dcp::VerificationNote(
2381 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2382 ).set_cpl_id(cpl->id())
2387 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
2389 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
2390 prepare_directory (dir);
2391 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
2392 asset->set_start_time (dcp::Time());
2393 add_test_subtitle (asset, 0, 4 * 24);
2395 asset->set_language (dcp::LanguageTag("de-DE"));
2396 asset->write (dir / "subs.mxf");
2398 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
2399 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
2400 check_verify_result (
2404 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2405 dcp::VerificationNote(
2406 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get())
2407 ).set_cpl_id(cpl->id()),
2408 dcp::VerificationNote(
2409 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
2410 ).set_cpl_id(cpl->id()),
2411 dcp::VerificationNote(
2412 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY
2413 ).set_cpl_id(cpl->id()),
2414 dcp::VerificationNote(
2415 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2416 ).set_cpl_id(cpl->id())
2422 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
2424 auto const dir = path ("build/test/invalid_subtitle_line_count1");
2425 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2428 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2429 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2430 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2431 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2433 check_verify_result (
2437 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2438 dcp::VerificationNote(
2439 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT
2440 ).set_cpl_id(cpl->id()),
2441 dcp::VerificationNote(
2442 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2443 ).set_cpl_id(cpl->id())
2448 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
2450 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
2451 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2454 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2455 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2456 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2459 check_verify_result(
2463 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2464 dcp::VerificationNote(
2465 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2466 ).set_cpl_id(cpl->id())
2471 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
2473 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
2474 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2477 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2478 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2479 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2480 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2482 check_verify_result (
2486 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2487 dcp::VerificationNote(
2488 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT
2489 ).set_cpl_id(cpl->id()),
2490 dcp::VerificationNote(
2491 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2492 ).set_cpl_id(cpl->id())
2497 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
2499 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
2500 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2503 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2504 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2505 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2506 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2509 check_verify_result(
2513 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2514 dcp::VerificationNote(
2515 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2516 ).set_cpl_id(cpl->id())
2521 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
2523 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
2524 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2527 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2529 check_verify_result (
2533 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2534 dcp::VerificationNote(
2535 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH
2536 ).set_cpl_id(cpl->id()),
2537 dcp::VerificationNote(
2538 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2539 ).set_cpl_id(cpl->id())
2544 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2546 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2547 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2550 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2552 check_verify_result (
2556 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2557 dcp::VerificationNote(
2558 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH
2559 ).set_cpl_id(cpl->id()),
2560 dcp::VerificationNote(
2561 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2562 ).set_cpl_id(cpl->id())
2567 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2569 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2570 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2573 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2574 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2575 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2576 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2578 check_verify_result (
2582 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2583 dcp::VerificationNote(
2584 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT
2585 ).set_cpl_id(cpl->id()),
2586 dcp::VerificationNote(
2587 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2588 ).set_cpl_id(cpl->id())
2593 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2595 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2596 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2599 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2600 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2601 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2604 check_verify_result(
2608 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2609 dcp::VerificationNote(
2610 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2611 ).set_cpl_id(cpl->id())
2616 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2618 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2619 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2622 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2623 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2624 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2625 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2627 check_verify_result (
2631 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2632 dcp::VerificationNote(
2633 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT
2634 ).set_cpl_id(cpl->id()),
2635 dcp::VerificationNote(
2636 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2637 ).set_cpl_id(cpl->id())
2642 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2644 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2645 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2648 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2649 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2650 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2651 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2654 check_verify_result(
2658 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2659 dcp::VerificationNote(
2660 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2661 ).set_cpl_id(cpl->id())
2666 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2668 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2669 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2672 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2675 check_verify_result (
2679 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2680 dcp::VerificationNote(
2681 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2682 ).set_cpl_id(cpl->id())
2687 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2689 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2690 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2693 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2695 check_verify_result (
2699 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2700 dcp::VerificationNote(
2701 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH
2702 ).set_cpl_id(cpl->id()),
2703 dcp::VerificationNote(
2704 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2705 ).set_cpl_id(cpl->id())
2710 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2712 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2713 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2716 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2717 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2718 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2720 check_verify_result (
2724 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2725 dcp::VerificationNote(
2726 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2727 ).set_cpl_id(cpl->id())
2732 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2734 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2735 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2738 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2739 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2740 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2742 check_verify_result (
2746 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2747 dcp::VerificationNote(
2748 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN
2749 ).set_cpl_id(cpl->id()),
2750 dcp::VerificationNote(
2751 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2752 ).set_cpl_id(cpl->id())
2757 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2759 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2760 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2763 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2764 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2765 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2768 check_verify_result(
2772 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2773 dcp::VerificationNote(
2774 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2775 ).set_cpl_id(cpl->id())
2780 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2782 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2783 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2786 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2787 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2788 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2791 check_verify_result(
2795 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2796 dcp::VerificationNote(
2797 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2798 ).set_cpl_id(cpl->id())
2803 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2805 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2806 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2807 check_verify_result (
2811 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2812 dcp::VerificationNote(
2813 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING
2814 ).set_cpl_id(cpl->id()),
2815 dcp::VerificationNote(
2816 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2817 ).set_cpl_id(cpl->id())
2822 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2824 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2825 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2827 check_verify_result(
2831 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2832 dcp::VerificationNote(
2833 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2834 ).set_cpl_id(cpl->id())
2840 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2842 path const dir("build/test/verify_invalid_sound_frame_rate");
2843 prepare_directory (dir);
2845 auto picture = simple_picture (dir, "foo");
2846 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2847 auto reel = make_shared<dcp::Reel>();
2848 reel->add (reel_picture);
2849 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2850 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2851 reel->add (reel_sound);
2852 reel->add (simple_markers());
2853 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2855 auto dcp = make_shared<dcp::DCP>(dir);
2857 dcp->set_annotation_text("hello");
2860 check_verify_result (
2864 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2865 dcp::VerificationNote(
2866 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf")
2867 ).set_cpl_id(cpl->id()),
2868 dcp::VerificationNote(
2869 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
2870 ).set_cpl_id(cpl->id())
2875 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2877 path const dir("build/test/verify_missing_cpl_annotation_text");
2878 auto dcp = make_simple (dir);
2881 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2883 auto const cpl = dcp->cpls()[0];
2885 HashCalculator calc(cpl->file().get());
2888 BOOST_REQUIRE (cpl->file());
2889 Editor e(cpl->file().get());
2890 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2893 check_verify_result (
2897 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2898 dcp::VerificationNote(
2899 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, canonical(cpl->file().get())
2900 ).set_cpl_id(cpl->id()),
2901 dcp::VerificationNote(
2902 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
2903 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
2908 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2910 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2911 auto dcp = make_simple (dir);
2914 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2915 auto const cpl = dcp->cpls()[0];
2917 HashCalculator calc(cpl->file().get());
2920 BOOST_REQUIRE (cpl->file());
2921 Editor e(cpl->file().get());
2922 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2925 check_verify_result (
2929 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2930 dcp::VerificationNote(
2931 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, canonical(cpl->file().get())
2932 ).set_cpl_id(cpl->id()),
2933 dcp::VerificationNote(
2934 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get())
2935 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()).set_cpl_id(cpl->id())
2940 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2942 path const dir("build/test/verify_mismatched_asset_duration");
2943 prepare_directory (dir);
2944 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2945 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2947 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2948 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2950 auto reel = make_shared<dcp::Reel>(
2951 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2952 make_shared<dcp::ReelSoundAsset>(ms, 0)
2955 reel->add (simple_markers());
2959 dcp->set_annotation_text("A Test DCP");
2962 check_verify_result (
2966 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
2967 dcp::VerificationNote(
2968 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION
2969 ).set_cpl_id(cpl->id()),
2970 dcp::VerificationNote(
2971 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl->file().get())
2972 ).set_cpl_id(cpl->id())
2979 shared_ptr<dcp::CPL>
2980 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2982 prepare_directory (dir);
2983 auto dcp = make_shared<dcp::DCP>(dir);
2984 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2986 auto constexpr reel_length = 192;
2988 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2989 subs->set_language (dcp::LanguageTag("de-DE"));
2990 subs->set_start_time (dcp::Time());
2991 subs->add (simple_subtitle());
2993 subs->write (dir / "subs.mxf");
2994 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2996 auto reel1 = make_shared<dcp::Reel>(
2997 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2998 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
3002 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3005 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3006 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
3007 reel1->add (markers1);
3011 auto reel2 = make_shared<dcp::Reel>(
3012 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
3013 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
3017 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3020 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3021 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
3022 reel2->add (markers2);
3027 dcp->set_annotation_text("A Test DCP");
3034 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
3037 path dir ("build/test/missing_main_subtitle_from_some_reels");
3038 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
3039 check_verify_result (
3043 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3044 dcp::VerificationNote(
3045 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS
3046 ).set_cpl_id(cpl->id()),
3047 dcp::VerificationNote(
3048 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3049 ).set_cpl_id(cpl->id())
3055 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
3056 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
3057 check_verify_result(
3061 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3062 dcp::VerificationNote(
3063 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3064 ).set_cpl_id(cpl->id())
3069 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
3070 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
3071 check_verify_result(
3075 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3076 dcp::VerificationNote(
3077 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3078 ).set_cpl_id(cpl->id())
3085 shared_ptr<dcp::CPL>
3086 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
3088 prepare_directory (dir);
3089 auto dcp = make_shared<dcp::DCP>(dir);
3090 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3092 auto constexpr reel_length = 192;
3094 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
3095 subs->set_language (dcp::LanguageTag("de-DE"));
3096 subs->set_start_time (dcp::Time());
3097 subs->add (simple_subtitle());
3099 subs->write (dir / "subs.mxf");
3101 auto reel1 = make_shared<dcp::Reel>(
3102 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
3103 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
3106 for (int i = 0; i < caps_in_reel1; ++i) {
3107 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3110 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3111 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
3112 reel1->add (markers1);
3116 auto reel2 = make_shared<dcp::Reel>(
3117 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
3118 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
3121 for (int i = 0; i < caps_in_reel2; ++i) {
3122 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
3125 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
3126 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
3127 reel2->add (markers2);
3132 dcp->set_annotation_text("A Test DCP");
3139 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
3142 path dir ("build/test/mismatched_closed_caption_asset_counts");
3143 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
3144 check_verify_result (
3148 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3149 dcp::VerificationNote(
3150 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS
3151 ).set_cpl_id(cpl->id()),
3152 dcp::VerificationNote(
3153 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3154 ).set_cpl_id(cpl->id())
3159 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
3160 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
3161 check_verify_result(
3165 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3166 dcp::VerificationNote(
3167 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3168 ).set_cpl_id(cpl->id())
3173 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
3174 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
3175 check_verify_result(
3179 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3180 dcp::VerificationNote(
3181 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3182 ).set_cpl_id(cpl->id())
3190 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
3192 prepare_directory (dir);
3193 auto dcp = make_shared<dcp::DCP>(dir);
3194 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3196 auto constexpr reel_length = 192;
3198 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
3199 subs->set_language (dcp::LanguageTag("de-DE"));
3200 subs->set_start_time (dcp::Time());
3201 subs->add (simple_subtitle());
3203 subs->write (dir / "subs.mxf");
3204 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
3207 auto reel = make_shared<dcp::Reel>(
3208 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
3209 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
3212 reel->add (reel_text);
3214 reel->add (simple_markers(reel_length));
3219 dcp->set_annotation_text("A Test DCP");
3222 check_verify_result (
3226 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3227 dcp::VerificationNote(
3228 dcp::VerificationNote::Type::BV21_ERROR, code, subs->id()
3229 ).set_cpl_id(cpl->id()),
3230 dcp::VerificationNote(
3231 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
3232 ).set_cpl_id(cpl->id())
3237 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
3239 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
3240 "build/test/verify_subtitle_entry_point_must_be_present",
3241 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
3242 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
3243 asset->unset_entry_point ();
3247 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
3248 "build/test/verify_subtitle_entry_point_must_be_zero",
3249 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
3250 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
3251 asset->set_entry_point (4);
3255 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
3256 "build/test/verify_closed_caption_entry_point_must_be_present",
3257 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
3258 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
3259 asset->unset_entry_point ();
3263 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
3264 "build/test/verify_closed_caption_entry_point_must_be_zero",
3265 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
3266 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
3267 asset->set_entry_point (9);
3273 BOOST_AUTO_TEST_CASE (verify_missing_hash)
3277 path const dir("build/test/verify_missing_hash");
3278 auto dcp = make_simple (dir);
3281 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3282 auto const cpl = dcp->cpls()[0];
3283 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
3284 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
3285 auto asset_id = cpl->reels()[0]->main_picture()->id();
3287 HashCalculator calc(cpl->file().get());
3290 BOOST_REQUIRE (cpl->file());
3291 Editor e(cpl->file().get());
3292 e.delete_first_line_containing("<Hash>");
3295 check_verify_result (
3299 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3300 dcp::VerificationNote(
3301 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3302 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3303 dcp::VerificationNote(
3304 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id
3305 ).set_cpl_id(cpl->id())
3312 verify_markers_test (
3314 vector<pair<dcp::Marker, dcp::Time>> markers,
3315 vector<dcp::VerificationNote> test_notes
3318 auto dcp = make_simple (dir);
3319 auto cpl = dcp->cpls()[0];
3320 cpl->set_content_kind(dcp::ContentKind::FEATURE);
3321 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
3322 for (auto const& i: markers) {
3323 markers_asset->set (i.first, i.second);
3325 cpl->reels()[0]->add(markers_asset);
3328 for (auto& note: test_notes) {
3329 note.set_cpl_id(cpl->id());
3332 test_notes.push_back(ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl));
3333 check_verify_result({dir}, {}, test_notes);
3337 BOOST_AUTO_TEST_CASE (verify_markers)
3339 verify_markers_test (
3340 "build/test/verify_markers_all_correct",
3342 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3343 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3344 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3345 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3350 verify_markers_test (
3351 "build/test/verify_markers_missing_ffec",
3353 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3354 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3355 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3358 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
3361 verify_markers_test (
3362 "build/test/verify_markers_missing_ffmc",
3364 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3365 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3366 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3369 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
3372 verify_markers_test (
3373 "build/test/verify_markers_missing_ffoc",
3375 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3376 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3377 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3380 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
3383 verify_markers_test (
3384 "build/test/verify_markers_missing_lfoc",
3386 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3387 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3388 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
3391 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
3394 verify_markers_test (
3395 "build/test/verify_markers_incorrect_ffoc",
3397 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3398 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3399 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
3400 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
3403 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
3406 verify_markers_test (
3407 "build/test/verify_markers_incorrect_lfoc",
3409 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
3410 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
3411 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
3412 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
3415 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
3420 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
3422 path dir = "build/test/verify_missing_cpl_metadata_version_number";
3423 prepare_directory (dir);
3424 auto dcp = make_simple (dir);
3425 auto cpl = dcp->cpls()[0];
3426 cpl->unset_version_number();
3429 check_verify_result(
3433 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3434 dcp::VerificationNote(
3435 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->file().get()
3436 ).set_cpl_id(cpl->id())
3441 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
3443 path dir = "build/test/verify_missing_extension_metadata1";
3444 auto dcp = make_simple (dir);
3447 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
3448 auto cpl = dcp->cpls()[0];
3450 HashCalculator calc(cpl->file().get());
3453 Editor e (cpl->file().get());
3454 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
3457 check_verify_result (
3461 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3462 dcp::VerificationNote(
3463 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3464 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3465 dcp::VerificationNote(
3466 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()
3467 ).set_cpl_id(cpl->id())
3472 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
3474 path dir = "build/test/verify_missing_extension_metadata2";
3475 auto dcp = make_simple (dir);
3478 auto cpl = dcp->cpls()[0];
3480 HashCalculator calc(cpl->file().get());
3483 Editor e (cpl->file().get());
3484 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
3487 check_verify_result (
3491 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3492 dcp::VerificationNote(
3493 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3494 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3495 dcp::VerificationNote(
3496 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()
3497 ).set_cpl_id(cpl->id())
3502 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
3504 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
3505 auto dcp = make_simple (dir);
3508 auto const cpl = dcp->cpls()[0];
3510 HashCalculator calc(cpl->file().get());
3513 Editor e (cpl->file().get());
3514 e.replace ("<meta:Name>A", "<meta:NameX>A");
3515 e.replace ("n</meta:Name>", "n</meta:NameX>");
3518 check_verify_result (
3522 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3523 dcp::VerificationNote(
3524 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70
3525 ).set_cpl_id(cpl->id()),
3526 dcp::VerificationNote(
3527 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()),
3528 dcp::VerificationNote(
3529 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3530 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3535 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
3537 path dir = "build/test/verify_invalid_extension_metadata1";
3538 auto dcp = make_simple (dir);
3541 auto cpl = dcp->cpls()[0];
3543 HashCalculator calc(cpl->file().get());
3546 Editor e (cpl->file().get());
3547 e.replace ("Application", "Fred");
3550 check_verify_result (
3554 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3555 dcp::VerificationNote(
3556 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3557 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3558 dcp::VerificationNote(
3559 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get()
3560 ).set_cpl_id(cpl->id())
3565 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
3567 path dir = "build/test/verify_invalid_extension_metadata2";
3568 auto dcp = make_simple (dir);
3571 auto cpl = dcp->cpls()[0];
3573 HashCalculator calc(cpl->file().get());
3576 Editor e (cpl->file().get());
3577 e.replace ("DCP Constraints Profile", "Fred");
3580 check_verify_result (
3584 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3585 dcp::VerificationNote(
3586 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3587 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3588 dcp::VerificationNote(
3589 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get()
3590 ).set_cpl_id(cpl->id())
3595 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
3597 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
3598 auto dcp = make_simple (dir);
3601 auto const cpl = dcp->cpls()[0];
3603 HashCalculator calc(cpl->file().get());
3606 Editor e (cpl->file().get());
3607 e.replace ("<meta:Value>", "<meta:ValueX>");
3608 e.replace ("</meta:Value>", "</meta:ValueX>");
3611 check_verify_result (
3615 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3616 dcp::VerificationNote(
3617 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74
3618 ).set_cpl_id(cpl->id()),
3619 dcp::VerificationNote(
3620 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
3621 ).set_cpl_id(cpl->id()),
3622 dcp::VerificationNote(
3623 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3624 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
3629 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
3631 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
3632 auto dcp = make_simple (dir);
3635 auto const cpl = dcp->cpls()[0];
3637 HashCalculator calc(cpl->file().get());
3640 Editor e (cpl->file().get());
3641 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
3644 check_verify_result (
3648 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3649 dcp::VerificationNote(
3650 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3651 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3652 dcp::VerificationNote(
3653 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()
3654 ).set_cpl_id(cpl->id())
3659 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
3661 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
3662 auto dcp = make_simple (dir);
3665 auto const cpl = dcp->cpls()[0];
3667 HashCalculator calc(cpl->file().get());
3670 Editor e (cpl->file().get());
3671 e.replace ("<meta:Property>", "<meta:PropertyX>");
3672 e.replace ("</meta:Property>", "</meta:PropertyX>");
3675 check_verify_result (
3679 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3680 dcp::VerificationNote(
3681 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72
3682 ).set_cpl_id(cpl->id()),
3683 dcp::VerificationNote(
3684 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()),
3685 dcp::VerificationNote(
3686 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3687 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3692 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
3694 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
3695 auto dcp = make_simple (dir);
3698 auto const cpl = dcp->cpls()[0];
3700 HashCalculator calc(cpl->file().get());
3703 Editor e (cpl->file().get());
3704 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
3705 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
3708 check_verify_result (
3712 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3713 dcp::VerificationNote(
3714 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71
3715 ).set_cpl_id(cpl->id()),
3716 dcp::VerificationNote(
3717 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
3718 ).set_cpl_id(cpl->id()),
3719 dcp::VerificationNote(
3720 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get()
3721 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3727 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
3729 path const dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
3730 prepare_directory (dir);
3731 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3732 copy_file (i.path(), dir / i.path().filename());
3735 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml");
3736 path const cpl_path = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
3738 HashCalculator calc(cpl_path);
3742 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3745 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
3747 check_verify_result (
3751 ok(dcp::VerificationNote::Code::ALL_ENCRYPTED, cpl),
3752 dcp::VerificationNote(
3753 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path)
3754 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3755 dcp::VerificationNote(
3756 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl)
3757 ).set_cpl_id(cpl->id()),
3758 dcp::VerificationNote(
3759 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
3760 ).set_cpl_id(cpl->id()),
3761 dcp::VerificationNote(
3762 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
3763 ).set_cpl_id(cpl->id()),
3764 dcp::VerificationNote(
3765 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
3766 ).set_cpl_id(cpl->id()),
3767 dcp::VerificationNote(
3768 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
3769 ).set_cpl_id(cpl->id()),
3770 dcp::VerificationNote(
3771 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path)
3772 ).set_cpl_id(cpl->id()),
3773 dcp::VerificationNote(
3774 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_path)
3775 ).set_cpl_id(cpl->id())
3780 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
3782 path dir = "build/test/unsigned_pkl_with_encrypted_content";
3783 prepare_directory (dir);
3784 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3785 copy_file (i.path(), dir / i.path().filename());
3788 path const cpl_path = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
3789 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
3792 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3795 auto cpl = std::make_shared<dcp::CPL>(cpl_path);
3797 check_verify_result (
3801 ok(dcp::VerificationNote::Code::ALL_ENCRYPTED, cpl),
3802 dcp::VerificationNote(
3803 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl)
3804 ).set_cpl_id(cpl->id()),
3805 dcp::VerificationNote(
3806 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
3807 ).set_cpl_id(cpl->id()),
3808 dcp::VerificationNote(
3809 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
3810 ).set_cpl_id(cpl->id()),
3811 dcp::VerificationNote(
3812 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
3813 ).set_cpl_id(cpl->id()),
3814 dcp::VerificationNote(
3815 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
3816 ).set_cpl_id(cpl->id()),
3817 dcp::VerificationNote(
3818 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path)
3819 ).set_cpl_id(cpl->id()),
3820 dcp::VerificationNote(
3821 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl)
3827 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3829 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3830 prepare_directory (dir);
3831 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3832 copy_file (i.path(), dir / i.path().filename());
3836 Editor e (dir / dcp_test1_pkl());
3837 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3840 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
3842 check_verify_result(
3846 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
3851 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3853 path dir ("build/test/verify_must_not_be_partially_encrypted");
3854 prepare_directory (dir);
3858 auto signer = make_shared<dcp::CertificateChain>();
3859 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3860 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3861 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3862 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3864 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3868 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3871 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3872 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3873 for (int i = 0; i < 24; ++i) {
3874 writer->write (j2c.data(), j2c.size());
3876 writer->finalize ();
3878 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3880 auto reel = make_shared<dcp::Reel>(
3881 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3882 make_shared<dcp::ReelSoundAsset>(ms, 0)
3885 reel->add (simple_markers());
3889 cpl->set_content_version (
3890 {"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"}
3892 cpl->set_annotation_text ("A Test DCP");
3893 cpl->set_issuer ("OpenDCP 0.0.25");
3894 cpl->set_creator ("OpenDCP 0.0.25");
3895 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3896 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3897 cpl->set_main_sound_sample_rate (48000);
3898 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3899 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3900 cpl->set_version_number (1);
3904 d.set_issuer("OpenDCP 0.0.25");
3905 d.set_creator("OpenDCP 0.0.25");
3906 d.set_issue_date("2012-07-17T04:45:18+00:00");
3907 d.set_annotation_text("A Test DCP");
3908 d.write_xml(signer);
3910 check_verify_result (
3914 dcp::VerificationNote(
3915 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED
3916 ).set_cpl_id(cpl->id())
3921 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3923 vector<dcp::VerificationNote> notes;
3924 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"));
3925 auto reader = picture.start_read ();
3926 auto frame = reader->get_frame (0);
3927 verify_j2k(frame, 0, 0, 24, notes);
3928 BOOST_CHECK(notes.empty());
3932 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3934 vector<dcp::VerificationNote> notes;
3935 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3936 auto reader = picture.start_read ();
3937 auto frame = reader->get_frame (0);
3938 verify_j2k(frame, 0, 0, 24, notes);
3939 BOOST_CHECK(notes.empty());
3943 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3945 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3946 prepare_directory (dir);
3947 auto dcp = make_simple (dir);
3949 vector<dcp::VerificationNote> notes;
3950 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3951 auto reader = picture.start_read ();
3952 auto frame = reader->get_frame (0);
3953 verify_j2k(frame, 0, 0, 24, notes);
3954 BOOST_CHECK(notes.empty());
3958 /** Check that ResourceID and the XML ID being different is spotted */
3959 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3961 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3962 prepare_directory (dir);
3964 ASDCP::WriterInfo writer_info;
3965 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3968 auto mxf_id = dcp::make_uuid ();
3969 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3970 BOOST_REQUIRE (c == Kumu::UUID_Length);
3972 auto resource_id = dcp::make_uuid ();
3973 ASDCP::TimedText::TimedTextDescriptor descriptor;
3974 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3975 DCP_ASSERT (c == Kumu::UUID_Length);
3977 auto xml_id = dcp::make_uuid ();
3978 ASDCP::TimedText::MXFWriter writer;
3979 auto subs_mxf = dir / "subs.mxf";
3980 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3981 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3982 writer.WriteTimedTextResource (dcp::String::compose(
3983 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3984 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3985 "<Id>urn:uuid:%1</Id>"
3986 "<ContentTitleText>Content</ContentTitleText>"
3987 "<AnnotationText>Annotation</AnnotationText>"
3988 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3989 "<ReelNumber>1</ReelNumber>"
3990 "<Language>en-US</Language>"
3991 "<EditRate>25 1</EditRate>"
3992 "<TimeCodeRate>25</TimeCodeRate>"
3993 "<StartTime>00:00:00:00</StartTime>"
3994 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3996 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3997 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3998 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
4007 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
4008 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
4010 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
4012 check_verify_result (
4016 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4017 dcp::VerificationNote(
4018 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
4019 ).set_cpl_id(cpl->id()),
4020 dcp::VerificationNote(
4021 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID
4022 ).set_cpl_id(cpl->id()),
4023 dcp::VerificationNote(
4024 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
4025 ).set_cpl_id(cpl->id()),
4026 dcp::VerificationNote(
4027 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
4028 ).set_cpl_id(cpl->id())
4033 /** Check that ResourceID and the MXF ID being the same is spotted */
4034 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
4036 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
4037 prepare_directory (dir);
4039 ASDCP::WriterInfo writer_info;
4040 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
4043 auto mxf_id = dcp::make_uuid ();
4044 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
4045 BOOST_REQUIRE (c == Kumu::UUID_Length);
4047 auto resource_id = mxf_id;
4048 ASDCP::TimedText::TimedTextDescriptor descriptor;
4049 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
4050 DCP_ASSERT (c == Kumu::UUID_Length);
4052 auto xml_id = resource_id;
4053 ASDCP::TimedText::MXFWriter writer;
4054 auto subs_mxf = dir / "subs.mxf";
4055 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
4056 BOOST_REQUIRE (ASDCP_SUCCESS(r));
4057 writer.WriteTimedTextResource (dcp::String::compose(
4058 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
4059 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
4060 "<Id>urn:uuid:%1</Id>"
4061 "<ContentTitleText>Content</ContentTitleText>"
4062 "<AnnotationText>Annotation</AnnotationText>"
4063 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
4064 "<ReelNumber>1</ReelNumber>"
4065 "<Language>en-US</Language>"
4066 "<EditRate>25 1</EditRate>"
4067 "<TimeCodeRate>25</TimeCodeRate>"
4068 "<StartTime>00:00:00:00</StartTime>"
4069 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
4071 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
4072 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
4073 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
4082 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
4083 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
4085 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
4087 check_verify_result (
4091 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4092 dcp::VerificationNote(
4093 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
4094 ).set_cpl_id(cpl->id()),
4095 dcp::VerificationNote(
4096 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID
4097 ).set_cpl_id(cpl->id()),
4098 dcp::VerificationNote(
4099 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
4100 ).set_cpl_id(cpl->id()),
4101 dcp::VerificationNote(
4102 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
4103 ).set_cpl_id(cpl->id()),
4104 dcp::VerificationNote(
4105 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"}
4106 ).set_cpl_id(cpl->id())
4111 /** Check a DCP with a 3D asset marked as 2D */
4112 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
4114 auto const path = private_test / "data" / "xm";
4116 auto cpl = std::make_shared<dcp::CPL>(find_prefix(path, "CPL_"));
4119 check_verify_result (
4123 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4124 dcp::VerificationNote(
4125 dcp::VerificationNote::Type::WARNING,
4126 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(path, "j2c"))
4128 dcp::VerificationNote(
4129 dcp::VerificationNote::Type::BV21_ERROR,
4130 dcp::VerificationNote::Code::INVALID_STANDARD
4137 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
4139 path dir = "build/test/verify_unexpected_things_in_main_markers";
4140 prepare_directory (dir);
4141 auto dcp = make_simple (dir, 1, 24);
4144 HashCalculator calc(find_cpl(dir));
4147 Editor e (find_cpl(dir));
4149 " <IntrinsicDuration>24</IntrinsicDuration>",
4150 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
4154 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
4156 check_verify_result (
4160 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4161 dcp::VerificationNote(
4162 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4163 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4164 dcp::VerificationNote(
4165 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT
4166 ).set_cpl_id(cpl->id()),
4167 dcp::VerificationNote(
4168 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION
4169 ).set_cpl_id(cpl->id())
4174 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
4176 path dir = "build/test/verify_invalid_content_kind";
4177 prepare_directory (dir);
4178 auto dcp = make_simple (dir, 1, 24);
4181 HashCalculator calc(find_cpl(dir));
4184 Editor e(find_cpl(dir));
4185 e.replace("trailer", "trip");
4188 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4190 check_verify_result (
4194 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4195 dcp::VerificationNote(
4196 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4197 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4198 dcp::VerificationNote(
4199 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip")
4200 ).set_cpl_id(cpl->id()),
4206 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
4208 path dir = "build/test/verify_valid_content_kind";
4209 prepare_directory (dir);
4210 auto dcp = make_simple (dir, 1, 24);
4213 HashCalculator calc(find_cpl(dir));
4216 Editor e(find_cpl(dir));
4217 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
4220 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4222 check_verify_result (
4226 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4227 dcp::VerificationNote(
4228 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4229 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4234 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
4236 path dir = "build/test/verify_invalid_main_picture_active_area_1";
4237 prepare_directory(dir);
4238 auto dcp = make_simple(dir, 1, 24);
4241 auto constexpr area = "<meta:MainPictureActiveArea>";
4243 HashCalculator calc(find_cpl(dir));
4246 Editor e(find_cpl(dir));
4247 e.delete_lines_after(area, 2);
4248 e.insert(area, "<meta:Height>4080</meta:Height>");
4249 e.insert(area, "<meta:Width>1997</meta:Width>");
4252 dcp::PKL pkl(find_pkl(dir));
4253 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4255 check_verify_result(
4259 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4260 dcp::VerificationNote(
4261 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4262 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4263 dcp::VerificationNote(
4264 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir))
4265 ).set_cpl_id(cpl->id()),
4266 dcp::VerificationNote(
4267 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))
4268 ).set_cpl_id(cpl->id()),
4273 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
4275 path dir = "build/test/verify_invalid_main_picture_active_area_2";
4276 prepare_directory(dir);
4277 auto dcp = make_simple(dir, 1, 24);
4280 auto constexpr area = "<meta:MainPictureActiveArea>";
4282 HashCalculator calc(find_cpl(dir));
4285 Editor e(find_cpl(dir));
4286 e.delete_lines_after(area, 2);
4287 e.insert(area, "<meta:Height>5125</meta:Height>");
4288 e.insert(area, "<meta:Width>9900</meta:Width>");
4291 dcp::PKL pkl(find_pkl(dir));
4292 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4294 check_verify_result(
4298 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4299 dcp::VerificationNote(
4300 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
4301 ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
4302 dcp::VerificationNote(
4303 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir))
4304 ).set_cpl_id(cpl->id()),
4305 dcp::VerificationNote(
4306 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))
4307 ).set_cpl_id(cpl->id()),
4308 dcp::VerificationNote(
4309 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))
4310 ).set_cpl_id(cpl->id())
4315 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
4319 path dir = "build/test/verify_duplicate_pkl_asset_ids";
4320 prepare_directory(dir);
4321 auto dcp = make_simple(dir, 1, 24);
4325 Editor e(find_pkl(dir));
4326 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
4329 dcp::PKL pkl(find_pkl(dir));
4330 auto cpl = std::make_shared<dcp::CPL>(find_cpl(dir));
4332 check_verify_result(
4336 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4337 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
4342 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
4346 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
4347 prepare_directory(dir);
4348 auto dcp = make_simple(dir, 1, 24);
4352 Editor e(find_asset_map(dir));
4353 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
4356 dcp::PKL pkl(find_pkl(dir));
4357 dcp::AssetMap asset_map(find_asset_map(dir));
4358 auto cpl = make_shared<dcp::CPL>(find_cpl(dir));
4360 check_verify_result(
4364 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4365 dcp::VerificationNote(
4366 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir))
4368 dcp::VerificationNote(
4369 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54")
4375 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
4377 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
4379 dcp::MXFMetadata mxf_meta;
4380 mxf_meta.company_name = "OpenDCP";
4381 mxf_meta.product_name = "OpenDCP";
4382 mxf_meta.product_version = "0.0.25";
4384 auto constexpr sample_rate = 48000;
4385 auto constexpr frames = 240;
4387 boost::filesystem::remove_all(path);
4388 boost::filesystem::create_directories(path);
4389 auto dcp = make_shared<dcp::DCP>(path);
4390 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4391 cpl->set_annotation_text("hello");
4392 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
4393 cpl->set_main_sound_sample_rate(sample_rate);
4394 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
4395 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
4396 cpl->set_version_number(1);
4400 /* Reel with 2 channels of audio */
4402 auto mp = simple_picture(path, "1", frames, {});
4403 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
4405 auto reel = make_shared<dcp::Reel>(
4406 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4407 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
4410 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
4411 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
4418 /* Reel with 6 channels of audio */
4420 auto mp = simple_picture(path, "2", frames, {});
4421 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
4423 auto reel = make_shared<dcp::Reel>(
4424 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4425 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
4428 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
4429 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
4436 dcp->set_annotation_text("hello");
4439 check_verify_result(
4443 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4444 dcp::VerificationNote(
4445 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2"))
4446 ).set_cpl_id(cpl->id())
4451 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
4453 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
4455 dcp::MXFMetadata mxf_meta;
4456 mxf_meta.company_name = "OpenDCP";
4457 mxf_meta.product_name = "OpenDCP";
4458 mxf_meta.product_version = "0.0.25";
4460 auto constexpr sample_rate = 48000;
4461 auto constexpr frames = 240;
4463 boost::filesystem::remove_all(path);
4464 boost::filesystem::create_directories(path);
4465 auto dcp = make_shared<dcp::DCP>(path);
4466 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4467 cpl->set_annotation_text("hello");
4468 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
4469 cpl->set_main_sound_sample_rate(sample_rate);
4470 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
4471 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
4472 cpl->set_version_number(1);
4474 auto mp = simple_picture(path, "1", frames, {});
4475 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
4477 auto reel = make_shared<dcp::Reel>(
4478 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4479 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
4482 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
4483 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
4484 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
4490 dcp->set_annotation_text("hello");
4493 check_verify_result(
4497 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4498 dcp::VerificationNote(
4499 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))
4500 ).set_cpl_id(cpl->id())
4505 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
4507 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
4508 auto constexpr video_frames = 24;
4509 auto constexpr sample_rate = 48000;
4511 boost::filesystem::remove_all(path);
4512 boost::filesystem::create_directories(path);
4514 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
4515 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
4517 dcp::Size const size(1998, 1080);
4518 auto image = make_shared<dcp::OpenJPEGImage>(size);
4519 boost::random::mt19937 rng(1);
4520 boost::random::uniform_int_distribution<> dist(0, 4095);
4521 for (int c = 0; c < 3; ++c) {
4522 for (int p = 0; p < (1998 * 1080); ++p) {
4523 image->data(c)[p] = dist(rng);
4526 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
4527 for (int i = 0; i < 24; ++i) {
4528 picture_writer->write(j2c.data(), j2c.size());
4530 picture_writer->finalize();
4532 auto dcp = make_shared<dcp::DCP>(path);
4533 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
4534 cpl->set_content_version(
4535 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
4537 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
4538 cpl->set_main_sound_sample_rate(sample_rate);
4539 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
4540 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
4541 cpl->set_version_number(1);
4543 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
4545 auto reel = make_shared<dcp::Reel>(
4546 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
4547 make_shared<dcp::ReelSoundAsset>(ms, 0)
4552 dcp->set_annotation_text("A Test DCP");
4555 vector<dcp::VerificationNote> expected = {
4556 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4557 dcp::VerificationNote(
4558 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
4559 ).set_cpl_id(cpl->id()),
4560 dcp::VerificationNote(
4561 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
4562 ).set_cpl_id(cpl->id())
4565 for (auto frame = 0; frame < 24; frame++) {
4567 dcp::VerificationNote(
4568 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf")
4569 ).set_frame(frame).set_frame_rate(24).set_cpl_id(cpl->id())
4573 int component_sizes[] = {
4579 for (auto frame = 0; frame < 24; frame++) {
4580 for (auto component = 0; component < 3; component++) {
4582 dcp::VerificationNote(
4583 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE
4584 ).set_frame(frame).set_component(component).set_size(component_sizes[component]).set_cpl_id(cpl->id())
4589 check_verify_result({ path }, {}, expected);
4593 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
4595 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
4598 BOOST_REQUIRE(!dcp.cpls().empty());
4599 auto cpl = dcp.cpls()[0];
4601 check_verify_result(
4605 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4606 dcp::VerificationNote(
4607 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
4608 ).set_cpl_id(cpl->id()),
4609 dcp::VerificationNote(
4610 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
4611 ).set_cpl_id(cpl->id()),
4612 dcp::VerificationNote(
4613 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
4614 ).set_cpl_id(cpl->id()),
4615 dcp::VerificationNote(
4616 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_"))
4617 ).set_cpl_id(cpl->id()),
4618 dcp::VerificationNote(
4619 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(find_file(dir, "cpl_"))
4620 ).set_cpl_id(cpl->id()),
4621 dcp::VerificationNote(
4622 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"}
4623 ).set_cpl_id(cpl->id()),
4628 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
4630 path const dir("build/test/verify_missing_load_font");
4631 prepare_directory (dir);
4632 copy_file ("test/data/subs1.xml", dir / "subs.xml");
4634 Editor editor(dir / "subs.xml");
4635 editor.delete_first_line_containing("LoadFont");
4637 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
4638 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
4639 auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
4641 check_verify_result (
4645 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4646 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
4647 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId").set_cpl_id(cpl->id())
4653 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
4655 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
4656 prepare_directory(dir);
4657 auto dcp = make_simple (dir, 1, 202);
4660 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
4661 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
4662 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
4663 "<ContentTitleText>Content</ContentTitleText>"
4664 "<AnnotationText>Annotation</AnnotationText>"
4665 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
4666 "<ReelNumber>1</ReelNumber>"
4667 "<EditRate>24 1</EditRate>"
4668 "<TimeCodeRate>24</TimeCodeRate>"
4669 "<StartTime>00:00:00:00</StartTime>"
4670 "<Language>de-DE</Language>"
4672 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
4673 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
4674 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
4680 dcp::File xml_file(dir / "subs.xml", "w");
4681 BOOST_REQUIRE(xml_file);
4682 xml_file.write(xml.c_str(), xml.size(), 1);
4684 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
4685 subs->write(dir / "subs.mxf");
4687 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
4688 auto cpl = dcp->cpls()[0];
4689 cpl->reels()[0]->add(reel_subs);
4692 check_verify_result (
4696 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4697 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id()).set_cpl_id(cpl->id())
4702 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
4704 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
4705 boost::filesystem::remove_all(dir);
4707 auto dcp1 = make_simple(dir / "1");
4709 auto cpl = dcp1->cpls()[0];
4711 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
4713 auto dcp2 = make_simple(dir / "2");
4715 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
4717 boost::filesystem::remove(dir / "1" / "video.mxf");
4718 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
4720 check_verify_result(
4724 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4725 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
4730 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
4732 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
4733 boost::filesystem::remove_all(dir);
4735 auto dcp = make_simple(dir);
4736 BOOST_REQUIRE(dcp->cpls().size() == 1);
4737 auto cpl = dcp->cpls()[0];
4738 cpl->set_content_version(dcp::ContentVersion(""));
4741 check_verify_result(
4745 ok(dcp::VerificationNote::Code::NONE_ENCRYPTED, cpl),
4746 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_cpl_id(cpl->id())
4751 /** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */
4752 BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp)
4754 auto const dir = path("build/test/verify_encrypted_smpte_dcp");
4756 auto key_id = dcp::make_uuid();
4757 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
4759 dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
4760 kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
4762 path const pkl_file = find_file(dir, "pkl_");
4763 path const cpl_file = find_file(dir, "cpl_");
4765 check_verify_result(
4769 ok(dcp::VerificationNote::Code::ALL_ENCRYPTED, cpl),
4770 dcp::VerificationNote(
4771 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_file)
4772 ).set_cpl_id(cpl->id()),
4773 dcp::VerificationNote(
4774 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_file)
4775 ).set_cpl_id(cpl->id()),
4776 dcp::VerificationNote(
4777 dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file)