X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=test%2Fverify_test.cc;h=b4019acd9f7191957531079c1e5d5586e577a889;hb=16a42e43922f3ae40dac6e0e4a8474439401b2c8;hp=ab5b625f2028389bc7c544d908fa6ad11cfd183b;hpb=58f998e8a1453ae80ca6b612f81f32488031f028;p=libdcp.git diff --git a/test/verify_test.cc b/test/verify_test.cc index ab5b625f..b4019acd 100644 --- a/test/verify_test.cc +++ b/test/verify_test.cc @@ -31,48 +31,104 @@ files in the program, then also delete it here. */ -#include "verify.h" -#include "util.h" -#include "j2k.h" -#include "reel.h" -#include "reel_mono_picture_asset.h" -#include "reel_sound_asset.h" + +#include "compose.hpp" #include "cpl.h" #include "dcp.h" -#include "openjpeg_image.h" +#include "file.h" +#include "interop_subtitle_asset.h" +#include "j2k_transcode.h" #include "mono_picture_asset.h" -#include "stereo_picture_asset.h" #include "mono_picture_asset_writer.h" -#include "interop_subtitle_asset.h" -#include "smpte_subtitle_asset.h" -#include "reel_closed_caption_asset.h" -#include "reel_stereo_picture_asset.h" -#include "reel_subtitle_asset.h" +#include "openjpeg_image.h" +#include "raw_convert.h" +#include "reel.h" +#include "reel_interop_closed_caption_asset.h" +#include "reel_interop_subtitle_asset.h" #include "reel_markers_asset.h" -#include "compose.hpp" +#include "reel_mono_picture_asset.h" +#include "reel_sound_asset.h" +#include "reel_stereo_picture_asset.h" +#include "reel_smpte_closed_caption_asset.h" +#include "reel_smpte_subtitle_asset.h" +#include "smpte_subtitle_asset.h" +#include "stereo_picture_asset.h" +#include "stream_operators.h" #include "test.h" -#include -#include +#include "util.h" +#include "verify.h" +#include "verify_j2k.h" #include +#include +#include #include #include + using std::list; +using std::make_pair; +using std::make_shared; using std::pair; +using std::shared_ptr; using std::string; using std::vector; -using std::make_pair; -using std::make_shared; using boost::optional; -using std::shared_ptr; +using namespace boost::filesystem; -static list>> stages; -static string const dcp_test1_pkl = "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml"; -static string const dcp_test1_cpl = "cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml"; +static list>> stages; + +static string filename_to_id(boost::filesystem::path path) +{ + return path.string().substr(4, path.string().length() - 8); +} + +static +boost::filesystem::path +dcp_test1_pkl() +{ + return find_file("test/ref/DCP/dcp_test1", "pkl_").filename(); +} + +static +string +dcp_test1_pkl_id() +{ + return filename_to_id(dcp_test1_pkl()); +} + +static +boost::filesystem::path +dcp_test1_cpl() +{ + return find_file("test/ref/DCP/dcp_test1", "cpl_").filename(); +} + +static +string +dcp_test1_cpl_id() +{ + return filename_to_id(dcp_test1_cpl()); +} + +static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3"; + +static +string +encryption_test_cpl_id() +{ + return filename_to_id(find_file("test/ref/DCP/encryption_test", "cpl_").filename()); +} + +static +string +encryption_test_pkl_id() +{ + return filename_to_id(find_file("test/ref/DCP/encryption_test", "pkl_").filename()); +} static void -stage (string s, optional p) +stage (string s, optional p) { stages.push_back (make_pair (s, p)); } @@ -84,7 +140,7 @@ progress (float) } static void -prepare_directory (boost::filesystem::path path) +prepare_directory (path path) { using namespace boost::filesystem; remove_all (path); @@ -92,127 +148,143 @@ prepare_directory (boost::filesystem::path path) } -static vector -setup (int reference_number, int verify_test_number) +/** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix} + * to make a new sacrificial test DCP. + */ +static path +setup (int reference_number, string verify_test_suffix) { - prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number)); - for (auto i: boost::filesystem::directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) { - boost::filesystem::copy_file (i.path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i.path().filename()); + auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix); + prepare_directory (dir); + for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) { + copy_file (i.path(), dir / i.path().filename()); } - return { dcp::String::compose("build/test/verify_test%1", verify_test_number) }; - + return dir; } static -void -write_dcp_with_single_asset (boost::filesystem::path dir, shared_ptr reel_asset, dcp::Standard standard = dcp::SMPTE) +shared_ptr +write_dcp_with_single_asset (path dir, shared_ptr reel_asset, dcp::Standard standard = dcp::Standard::SMPTE) { auto reel = make_shared(); reel->add (reel_asset); reel->add (simple_markers()); - auto cpl = make_shared("hello", dcp::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, standard); cpl->add (reel); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml ( - standard, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp->set_annotation_text("hello"); + dcp->write_xml (); + + return cpl; } -/** Class that can alter a file by searching and replacing strings within it. - * On destruction modifies the file whose name was given to the constructor. - */ -class Editor +LIBDCP_DISABLE_WARNINGS +static +void +dump_notes (vector const & notes) { -public: - Editor (boost::filesystem::path path) - : _path(path) - { - _content = dcp::file_to_string (_path); + for (auto i: notes) { + std::cout << dcp::note_to_string(i) << "\n"; } +} +LIBDCP_ENABLE_WARNINGS - ~Editor () - { - auto f = fopen(_path.string().c_str(), "w"); - BOOST_REQUIRE (f); - fwrite (_content.c_str(), _content.length(), 1, f); - fclose (f); - } - void replace (string a, string b) - { - auto old_content = _content; - boost::algorithm::replace_all (_content, a, b); - BOOST_REQUIRE (_content != old_content); - } +static +string +to_string(dcp::VerificationNote const& note) +{ + string s = note_to_string(note) + dcp::String::compose( + "\n [%1 %2 %3 %4 %5 %6 ", + static_cast(note.type()), + static_cast(note.code()), + note.note().get_value_or(""), + note.file().get_value_or(""), + note.line().get_value_or(0), + note.frame().get_value_or(0) + ); - void delete_lines (string from, string to) - { - vector lines; - boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on); - bool deleting = false; - auto old_content = _content; - _content = ""; - for (auto i: lines) { - if (i.find(from) != string::npos) { - deleting = true; - } - if (!deleting) { - _content += i + "\n"; - } - if (deleting && i.find(to) != string::npos) { - deleting = false; - } - } - BOOST_REQUIRE (_content != old_content); - } + s += dcp::String::compose( + "%1 %2 %3 %4 %5]\n", + note.id().get_value_or(""), + note.other_id().get_value_or(""), + note.cpl_id().get_value_or(""), + note.reference_hash().get_value_or(""), + note.calculated_hash().get_value_or("") + ); -private: - boost::filesystem::path _path; - std::string _content; -}; + return s; +} static void -dump_notes (vector const & notes) +check_verify_result(vector notes, vector test_notes) { - for (auto i: notes) { - std::cout << dcp::note_to_string(i) << "\n"; + std::sort(notes.begin(), notes.end()); + std::sort(test_notes.begin(), test_notes.end()); + + string message = "\n"; + + vector not_expected; + for (auto note: notes) { + 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(); }); + if (iter != test_notes.end() && *iter != note) { + message += "Wrong details:\n --seen " + to_string(note) + " --expected " + to_string(*iter) + "\n"; + } else if (iter == test_notes.end()) { + not_expected.push_back(note); + } + } + + vector not_seen; + for (auto note: test_notes) { + auto iter = std::find_if(notes.begin(), notes.end(), [note](dcp::VerificationNote const& n) { return note.type() == n.type() && note.code() == n.code(); }); + if (iter == notes.end()) { + not_seen.push_back(note); + } + } + + for (auto note: not_expected) { + message += "Not expected:\n" + to_string(note) + "\n"; + } + + for (auto note: not_seen) { + message += "Not seen:\n" + to_string(note) + "\n"; } + + BOOST_REQUIRE_MESSAGE(notes == test_notes, message); } static void -check_verify_result (vector dir, vector test_notes) +check_verify_result(vector dir, vector kdm, vector test_notes) { - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - dump_notes (notes); - BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size()); + check_verify_result(dcp::verify({dir}, kdm, &stage, &progress, {}, xsd_test).notes, test_notes); } +/* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file', + * replacing from with to. Verify the resulting DCP and check that the results match the given + * list of codes. + */ static void -check_verify_result_after_replace (int n, boost::function file, string from, string to, vector codes) +check_verify_result_after_replace (string suffix, boost::function file, string from, string to, vector codes) { - auto directories = setup (1, n); + auto dir = setup (1, suffix); { - Editor e (file(n)); + Editor e (file(suffix)); e.replace (from, to); } - auto notes = dcp::verify (directories, &stage, &progress, xsd_test); + auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes; BOOST_REQUIRE_EQUAL (notes.size(), codes.size()); auto i = notes.begin(); @@ -225,330 +297,422 @@ check_verify_result_after_replace (int n, boost::function asset) +{ + dcp::ArrayData fake_font(1024); + asset->add_font("font", fake_font); +} + + +class HashCalculator +{ +public: + HashCalculator(boost::filesystem::path path) + : _path(path) + , _old_hash(dcp::make_digest(path, [](int64_t, int64_t) {})) + {} + + std::string old_hash() const { + return _old_hash; + } + + std::string new_hash() const { + return dcp::make_digest(_path, [](int64_t, int64_t) {}); + } + +private: + boost::filesystem::path _path; + std::string _old_hash; +}; + + +BOOST_AUTO_TEST_CASE (verify_no_error) { stages.clear (); - auto directories = setup (1, 1); - auto notes = dcp::verify (directories, &stage, &progress, xsd_test); + auto dir = setup (1, "no_error"); + auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes; - boost::filesystem::path const cpl_file = boost::filesystem::path("build") / "test" / "verify_test1" / dcp_test1_cpl; - boost::filesystem::path const pkl_file = boost::filesystem::path("build") / "test" / "verify_test1" / dcp_test1_pkl; - boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml"; + path const cpl_file = dir / dcp_test1_cpl(); + path const pkl_file = dir / dcp_test1_pkl(); + path const assetmap_file = dir / "ASSETMAP.xml"; auto st = stages.begin(); BOOST_CHECK_EQUAL (st->first, "Checking DCP"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir)); ++st; BOOST_CHECK_EQUAL (st->first, "Checking CPL"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file)); + BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file)); ++st; BOOST_CHECK_EQUAL (st->first, "Checking reel"); BOOST_REQUIRE (!st->second); ++st; BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking PKL"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file)); - ++st; - BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP"); + BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file)); + ++st; BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file)); + BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file)); ++st; BOOST_REQUIRE (st == stages.end()); - BOOST_CHECK_EQUAL (notes.size(), 0); + BOOST_CHECK_EQUAL (notes.size(), 0U); } -/* Corrupt the MXFs and check that this is spotted */ -BOOST_AUTO_TEST_CASE (verify_test2) + +BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash) { - auto directories = setup (1, 2); + using namespace boost::filesystem; - auto mod = fopen("build/test/verify_test2/video.mxf", "r+b"); + auto dir = setup (1, "incorrect_picture_sound_hash"); + + auto video_path = path(dir / "video.mxf"); + HashCalculator video_calc(video_path); + auto mod = fopen(video_path.string().c_str(), "r+b"); BOOST_REQUIRE (mod); - fseek (mod, 4096, SEEK_SET); + BOOST_REQUIRE_EQUAL(fseek(mod, -16, SEEK_END), 0); int x = 42; - fwrite (&x, sizeof(x), 1, mod); + BOOST_REQUIRE(fwrite(&x, sizeof(x), 1, mod) == 1); fclose (mod); - mod = fopen("build/test/verify_test2/audio.mxf", "r+b"); + auto audio_path = path(dir / "audio.mxf"); + HashCalculator audio_calc(audio_path); + mod = fopen(audio_path.string().c_str(), "r+b"); BOOST_REQUIRE (mod); - BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0); + BOOST_REQUIRE_EQUAL(fseek(mod, 0, SEEK_END), 0); BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1); fclose (mod); dcp::ASDCPErrorSuspender sus; check_verify_result ( - directories, + { dir }, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::SOUND_HASH_INCORRECT } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) + ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(video_calc.old_hash()).set_calculated_hash(video_calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) + ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(audio_calc.old_hash()).set_calculated_hash(audio_calc.new_hash()), }); } -/* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */ -BOOST_AUTO_TEST_CASE (verify_test3) + +BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes) { - auto directories = setup (1, 3); + using namespace boost::filesystem; + + auto dir = setup (1, "mismatched_picture_sound_hashes"); + + HashCalculator calc(dir / dcp_test1_cpl()); { - Editor e (boost::filesystem::path("build") / "test" / "verify_test3" / dcp_test1_pkl); + Editor e (dir / dcp_test1_pkl()); e.replace ("", "x"); } check_verify_result ( - directories, + { dir }, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DIFFER }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl()) + ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash("x" + calc.old_hash()).set_calculated_hash(calc.old_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") + ).set_cpl_id(dcp_test1_cpl_id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") + ).set_cpl_id(dcp_test1_cpl_id()), + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'x3M7YTgvFKXXMEGLkIbV4miC90FE=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 28 }, + { 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 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xvsVjRV9vhTBPUWfE/TT1o2vdQsI=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 20 }, }); } -/* Corrupt the ContentKind in the CPL */ -BOOST_AUTO_TEST_CASE (verify_test4) + +BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind) { - auto directories = setup (1, 4); + auto dir = setup (1, "failed_read_content_kind"); + + HashCalculator calc(dir / dcp_test1_cpl()); { - Editor e (boost::filesystem::path("build") / "test" / "verify_test4" / dcp_test1_cpl); + Editor e (dir / dcp_test1_cpl()); e.replace ("", "x"); } check_verify_result ( - directories, - {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::GENERAL_READ, string("Bad content kind 'xtrailer'")}} - ); + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(dir / dcp_test1_cpl()) + ).set_cpl_id(dcp_test1_cpl_id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") + ).set_cpl_id(dcp_test1_cpl_id()) + }); } + static -boost::filesystem::path -cpl (int n) +path +cpl (string suffix) { - return dcp::String::compose("build/test/verify_test%1/%2", n, dcp_test1_cpl); + return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl()); } + static -boost::filesystem::path -pkl (int n) +path +pkl (string suffix) { - return dcp::String::compose("build/test/verify_test%1/%2", n, dcp_test1_pkl); + return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl()); } + static -boost::filesystem::path -asset_map (int n) +path +asset_map (string suffix) { - return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n); + return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix); } -/* FrameRate */ -BOOST_AUTO_TEST_CASE (verify_test5) +BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate) { check_verify_result_after_replace ( - 5, &cpl, + "invalid_picture_frame_rate", &cpl, "24 1", "99 1", - { dcp::VerificationNote::CPL_HASH_INCORRECT, - dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE } + { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, + dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE } ); } -/* Missing asset */ -BOOST_AUTO_TEST_CASE (verify_test6) +BOOST_AUTO_TEST_CASE (verify_missing_asset) { - auto directories = setup (1, 6); - - boost::filesystem::remove ("build/test/verify_test6/video.mxf"); - check_verify_result (directories, {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET }}); + auto dir = setup (1, "missing_asset"); + remove (dir / "video.mxf"); + check_verify_result ( + { dir }, + {}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" } + }); } -static -boost::filesystem::path -assetmap (int n) -{ - return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n); -} -/* Empty asset filename in ASSETMAP */ -BOOST_AUTO_TEST_CASE (verify_test7) +BOOST_AUTO_TEST_CASE (verify_empty_asset_path) { check_verify_result_after_replace ( - 7, &assetmap, + "empty_asset_path", &asset_map, "video.mxf", "", - { dcp::VerificationNote::EMPTY_ASSET_PATH } + { dcp::VerificationNote::Code::EMPTY_ASSET_PATH } ); } -/* Mismatched standard */ -BOOST_AUTO_TEST_CASE (verify_test8) + +BOOST_AUTO_TEST_CASE (verify_mismatched_standard) { check_verify_result_after_replace ( - 8, &cpl, + "mismatched_standard", &cpl, "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#", - { dcp::VerificationNote::MISMATCHED_STANDARD, - dcp::VerificationNote::XML_VALIDATION_ERROR, - dcp::VerificationNote::XML_VALIDATION_ERROR, - dcp::VerificationNote::XML_VALIDATION_ERROR, - dcp::VerificationNote::XML_VALIDATION_ERROR, - dcp::VerificationNote::XML_VALIDATION_ERROR, - dcp::VerificationNote::CPL_HASH_INCORRECT } + { dcp::VerificationNote::Code::MISMATCHED_STANDARD, + dcp::VerificationNote::Code::INVALID_XML, + dcp::VerificationNote::Code::INVALID_XML, + dcp::VerificationNote::Code::INVALID_XML, + dcp::VerificationNote::Code::INVALID_XML, + dcp::VerificationNote::Code::INVALID_XML, + dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES } ); } -/* Badly formatted in CPL */ -BOOST_AUTO_TEST_CASE (verify_test9) + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id) { - /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */ + /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */ check_verify_result_after_replace ( - 9, &cpl, - "urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375", - { dcp::VerificationNote::XML_VALIDATION_ERROR } + "invalid_xml_cpl_id", &cpl, + "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a", + { dcp::VerificationNote::Code::INVALID_XML } ); } -/* Badly formatted in CPL */ -BOOST_AUTO_TEST_CASE (verify_test10) + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date) { check_verify_result_after_replace ( - 10, &cpl, + "invalid_xml_issue_date", &cpl, "", "x", - { dcp::VerificationNote::XML_VALIDATION_ERROR, - dcp::VerificationNote::CPL_HASH_INCORRECT } + { dcp::VerificationNote::Code::INVALID_XML, + dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES } ); } -/* Badly-formatted in PKL */ -BOOST_AUTO_TEST_CASE (verify_test11) + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id) { check_verify_result_after_replace ( - 11, &pkl, - "urn:uuid:2b9", "urn:uuid:xb9", - { dcp::VerificationNote::XML_VALIDATION_ERROR } + "invalid_xml_pkl_id", &pkl, + "urn:uuid:" + dcp_test1_pkl_id().substr(0, 3), + "urn:uuid:x" + dcp_test1_pkl_id().substr(1, 2), + { dcp::VerificationNote::Code::INVALID_XML } ); } -/* Badly-formatted in ASSETMAP */ -BOOST_AUTO_TEST_CASE (verify_test12) + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id) { check_verify_result_after_replace ( - 12, &asset_map, - "urn:uuid:07e", "urn:uuid:x7e", - { dcp::VerificationNote::XML_VALIDATION_ERROR } + "invalid_xml_asset_map_id", &asset_map, + "urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3), + "urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2), + { dcp::VerificationNote::Code::INVALID_XML } ); } -/* Basic test of an Interop DCP */ -BOOST_AUTO_TEST_CASE (verify_test13) + +BOOST_AUTO_TEST_CASE (verify_invalid_standard) { stages.clear (); - auto directories = setup (3, 13); - auto notes = dcp::verify (directories, &stage, &progress, xsd_test); + auto dir = setup (3, "verify_invalid_standard"); + auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test).notes; - boost::filesystem::path const cpl_file = boost::filesystem::path("build") / "test" / "verify_test13" / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml"; - boost::filesystem::path const pkl_file = boost::filesystem::path("build") / "test" / "verify_test13" / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml"; - boost::filesystem::path const assetmap_file = "build/test/verify_test13/ASSETMAP"; + path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml"; + path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml"; + path const assetmap_file = dir / "ASSETMAP"; auto st = stages.begin(); BOOST_CHECK_EQUAL (st->first, "Checking DCP"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir)); ++st; BOOST_CHECK_EQUAL (st->first, "Checking CPL"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file)); + BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file)); ++st; BOOST_CHECK_EQUAL (st->first, "Checking reel"); BOOST_REQUIRE (!st->second); ++st; BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf")); + BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf")); ++st; BOOST_CHECK_EQUAL (st->first, "Checking PKL"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file)); + BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file)); ++st; BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP"); BOOST_REQUIRE (st->second); - BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file)); + BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file)); ++st; BOOST_REQUIRE (st == stages.end()); - BOOST_REQUIRE_EQUAL (notes.size(), 1U); + BOOST_REQUIRE_EQUAL(notes.size(), 25U); auto i = notes.begin (); - BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR); - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE); + BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR); + BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD); + ++i; + for (int j = 0; j < 24; ++j) { + BOOST_CHECK_EQUAL(i->type(), dcp::VerificationNote::Type::BV21_ERROR); + BOOST_CHECK_EQUAL(i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K); + ++i; + } } /* DCP with a short asset */ -BOOST_AUTO_TEST_CASE (verify_test14) +BOOST_AUTO_TEST_CASE (verify_invalid_duration) { - auto directories = setup (8, 14); - check_verify_result ( - directories, - { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL } - }); + auto dir = setup (8, "invalid_duration"); + + dcp::DCP dcp(dir); + dcp.read(); + BOOST_REQUIRE(dcp.cpls().size() == 1); + auto cpl = dcp.cpls()[0]; + + vector expected = { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, + dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, + cpl->file().get() + ).set_cpl_id(cpl->id()) + }; + + for (int i = 0; i < 23; ++i) { + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") + ).set_cpl_id(cpl->id()) + ); + } + + check_verify_result({ dir }, {}, expected); } static -void -dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir) +shared_ptr +dcp_from_frame (dcp::ArrayData const& frame, path dir) { - auto asset = make_shared(dcp::Fraction(24, 1), dcp::SMPTE); - boost::filesystem::create_directories (dir); - auto writer = asset->start_write (dir / "pic.mxf", true); + auto asset = make_shared(dcp::Fraction(24, 1), dcp::Standard::SMPTE); + create_directories (dir); + auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW); for (int i = 0; i < 24; ++i) { writer->write (frame.data(), frame.size()); } writer->finalize (); auto reel_asset = make_shared(asset, 0); - write_dcp_with_single_asset (dir, reel_asset); + return write_dcp_with_single_asset (dir, reel_asset); } -/* DCP with an over-sized JPEG2000 frame */ -BOOST_AUTO_TEST_CASE (verify_test15) +BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes) { int const too_big = 1302083 * 2; @@ -562,21 +726,38 @@ BOOST_AUTO_TEST_CASE (verify_test15) memcpy (oversized_frame.data(), frame.data(), frame.size()); memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size()); - boost::filesystem::path const dir("build/test/verify_test15"); + path const dir("build/test/verify_invalid_picture_frame_size_in_bytes"); prepare_directory (dir); - dcp_from_frame (oversized_frame, dir); + auto cpl = dcp_from_frame (oversized_frame, dir); + + vector expected; + for (auto i = 0; i < 24; ++i) { + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") + ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id()) + ); + } - check_verify_result ( - { dir }, - { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } - }); + for (auto i = 0; i < 24; ++i) { + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") + ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id()) + ); + } + + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + ); + + check_verify_result({ dir }, {}, expected); } -/* DCP with a nearly over-sized JPEG2000 frame */ -BOOST_AUTO_TEST_CASE (verify_test16) +BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes) { int const nearly_too_big = 1302083 * 0.98; @@ -590,58 +771,109 @@ BOOST_AUTO_TEST_CASE (verify_test16) memcpy (oversized_frame.data(), frame.data(), frame.size()); memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size()); - boost::filesystem::path const dir("build/test/verify_test16"); + path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes"); prepare_directory (dir); - dcp_from_frame (oversized_frame, dir); + auto cpl = dcp_from_frame (oversized_frame, dir); - check_verify_result ( - { dir }, - { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } - }); + vector expected; + + for (auto i = 0; i < 24; ++i) { + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") + ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id()) + ); + } + + for (auto i = 0; i < 24; ++i) { + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") + ).set_frame(i).set_frame_rate(24).set_cpl_id(cpl->id()) + ); + } + + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + ); + + check_verify_result ({ dir }, {}, expected); } -/* DCP with a within-range JPEG2000 frame */ -BOOST_AUTO_TEST_CASE (verify_test17) +BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes) { /* Compress a black image */ auto image = black_image (); auto frame = dcp::compress_j2k (image, 100000000, 24, false, false); BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8)); - boost::filesystem::path const dir("build/test/verify_test17"); + path const dir("build/test/verify_valid_picture_frame_size_in_bytes"); prepare_directory (dir); - dcp_from_frame (frame, dir); + auto cpl = dcp_from_frame (frame, dir); - check_verify_result ({ dir }, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + check_verify_result( + { dir }, + {}, + { dcp::VerificationNote(dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()).set_cpl_id(cpl->id()) } + ); } -/* DCP with valid Interop subtitles */ -BOOST_AUTO_TEST_CASE (verify_test18) +BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles) { - boost::filesystem::path const dir("build/test/verify_test18"); + path const dir("build/test/verify_valid_interop_subtitles"); prepare_directory (dir); - boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml"); + copy_file ("test/data/subs1.xml", dir / "subs.xml"); auto asset = make_shared(dir / "subs.xml"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + {dir}, + {}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap) +{ + path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap"); + prepare_directory(dir); + copy_file("test/data/subs1.xml", dir / "ccap.xml"); + auto asset = make_shared(dir / "ccap.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE }}); + check_verify_result ( + {dir}, + {}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} + ).set_cpl_id(cpl->id()) + }); } -/* DCP with broken Interop subtitles */ -BOOST_AUTO_TEST_CASE (verify_test19) +BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles) { - boost::filesystem::path const dir("build/test/verify_test19"); + using namespace boost::filesystem; + + path const dir("build/test/verify_invalid_interop_subtitles"); prepare_directory (dir); - boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml"); + copy_file ("test/data/subs1.xml", dir / "subs.xml"); auto asset = make_shared(dir / "subs.xml"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP); { Editor e (dir / "subs.xml"); @@ -650,137 +882,332 @@ BOOST_AUTO_TEST_CASE (verify_test19) check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, + dcp::VerificationNote::Code::INVALID_XML, + string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"), + path(), + 29 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} + ).set_cpl_id(cpl->id()) }); } -/* DCP with valid SMPTE subtitles */ -BOOST_AUTO_TEST_CASE (verify_test20) +BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles) { - boost::filesystem::path const dir("build/test/verify_test20"); - prepare_directory (dir); - boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf"); - auto asset = make_shared(dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - write_dcp_with_single_asset (dir, reel_asset); + path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles"); + prepare_directory(dir); + copy_file("test/data/subs4.xml", dir / "subs.xml"); + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + { dir }, + {}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} + ).set_cpl_id(cpl->id()) + }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); } -/* DCP with broken SMPTE subtitles */ -BOOST_AUTO_TEST_CASE (verify_test21) +BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle) { - boost::filesystem::path const dir("build/test/verify_test21"); - prepare_directory (dir); - boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf"); - auto asset = make_shared(dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - write_dcp_with_single_asset (dir, reel_asset); + path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle"); + prepare_directory(dir); + copy_file("test/data/subs5.xml", dir / "subs.xml"); + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} + ).set_cpl_id(cpl->id()) }); + } -/* VF */ -BOOST_AUTO_TEST_CASE (verify_test22) +BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles) { - boost::filesystem::path const ov_dir("build/test/verify_test22_ov"); - prepare_directory (ov_dir); + path const dir("build/test/verify_valid_smpte_subtitles"); + prepare_directory (dir); + copy_file ("test/data/subs.mxf", dir / "subs.mxf"); + auto asset = make_shared(dir / "subs.mxf"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); - auto image = black_image (); - auto frame = dcp::compress_j2k (image, 100000000, 24, false, false); - BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8)); - dcp_from_frame (frame, ov_dir); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() + ).set_cpl_id(cpl->id()), + }); +} - dcp::DCP ov (ov_dir); - ov.read (); - boost::filesystem::path const vf_dir("build/test/verify_test22_vf"); - prepare_directory (vf_dir); +BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles) +{ + using namespace boost::filesystem; - write_dcp_with_single_asset (vf_dir, ov.cpls().front()->reels().front()->main_picture()); + path const dir("build/test/verify_invalid_smpte_subtitles"); + prepare_directory (dir); + /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */ + copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf"); + auto asset = make_shared(dir / "subs.mxf"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( - { vf_dir }, + { dir }, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, + dcp::VerificationNote::Code::INVALID_XML, + string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"), + path(), + 2 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() + ).set_cpl_id(cpl->id()), }); } -/* DCP with valid CompositionMetadataAsset */ -BOOST_AUTO_TEST_CASE (verify_test23) +BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles) { - boost::filesystem::path const dir("build/test/verify_test23"); + path const dir("build/test/verify_empty_text_node_in_subtitles"); prepare_directory (dir); - - boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf"); + copy_file ("test/data/empty_text.mxf", dir / "subs.mxf"); auto asset = make_shared(dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - - auto reel = make_shared(); - reel->add (reel_asset); - - reel->add (simple_markers(16 * 24 - 1)); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 192, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); - auto cpl = make_shared("hello", dcp::TRAILER); + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() + ).set_cpl_id(cpl->id()) + }); +} + + +/** A node with no content except some nodes, which themselves do have content */ +BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes) +{ + path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes"); + prepare_directory (dir); + copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml"); + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 192, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + { dir }, + {}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} + ).set_cpl_id(cpl->id()) + }); +} + + +/** A node with no content except some nodes, which themselves also have no content */ +BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes) +{ + path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes"); + prepare_directory (dir); + copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml"); + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 192, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) + ).set_cpl_id(cpl->id()), + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_external_asset) +{ + path const ov_dir("build/test/verify_external_asset"); + prepare_directory (ov_dir); + + auto image = black_image (); + auto frame = dcp::compress_j2k (image, 100000000, 24, false, false); + BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8)); + dcp_from_frame (frame, ov_dir); + + dcp::DCP ov (ov_dir); + ov.read (); + + path const vf_dir("build/test/verify_external_asset_vf"); + prepare_directory (vf_dir); + + auto picture = ov.cpls()[0]->reels()[0]->main_picture(); + auto cpl = write_dcp_with_single_asset (vf_dir, picture); + + check_verify_result ( + { vf_dir }, + {}, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() }, + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata) +{ + path const dir("build/test/verify_valid_cpl_metadata"); + prepare_directory (dir); + + copy_file ("test/data/subs.mxf", dir / "subs.mxf"); + auto asset = make_shared(dir / "subs.mxf"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + + auto reel = make_shared(); + reel->add (reel_asset); + + reel->add (make_shared(simple_picture(dir, "", 16 * 24), 0)); + reel->add (simple_markers(16 * 24)); + + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); - cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-")); cpl->set_main_sound_sample_rate (48000); cpl->set_main_picture_stored_area (dcp::Size(1998, 1080)); cpl->set_main_picture_active_area (dcp::Size(1440, 1080)); + cpl->set_version_number (1); dcp::DCP dcp (dir); dcp.add (cpl); - dcp.write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp.set_annotation_text("hello"); + dcp.write_xml (); +} + + +path +find_prefix(path dir, string prefix) +{ + auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) { + return boost::starts_with(p.filename().string(), prefix); + }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + BOOST_REQUIRE(iter != directory_iterator()); + return iter->path(); } -boost::filesystem::path find_cpl (boost::filesystem::path dir) +path find_cpl (path dir) { - for (auto i: boost::filesystem::directory_iterator(dir)) { - if (boost::starts_with(i.path().filename().string(), "cpl_")) { - return i.path(); - } - } + return find_prefix(dir, "cpl_"); +} + + +path +find_pkl(path dir) +{ + return find_prefix(dir, "pkl_"); +} - BOOST_REQUIRE (false); - return {}; + +path +find_asset_map(path dir) +{ + return find_prefix(dir, "ASSETMAP"); } /* DCP with invalid CompositionMetadataAsset */ -BOOST_AUTO_TEST_CASE (verify_test24) +BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag) { - boost::filesystem::path const dir("build/test/verify_test24"); + using namespace boost::filesystem; + + path const dir("build/test/verify_invalid_cpl_metadata_bad_tag"); prepare_directory (dir); auto reel = make_shared(); reel->add (black_picture_asset(dir)); - auto cpl = make_shared("hello", dcp::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); - cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-")); cpl->set_main_sound_sample_rate (48000); cpl->set_main_picture_stored_area (dcp::Size(1998, 1080)); cpl->set_main_picture_active_area (dcp::Size(1440, 1080)); @@ -790,109 +1217,132 @@ BOOST_AUTO_TEST_CASE (verify_test24) dcp::DCP dcp (dir); dcp.add (cpl); - dcp.write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp.set_annotation_text("hello"); + dcp.write_xml(); + + HashCalculator calc(find_cpl(dir)); { - Editor e (find_cpl("build/test/verify_test24")); + Editor e (find_cpl(dir)); e.replace ("MainSound", "MainSoundX"); } check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, + dcp::VerificationNote::Code::INVALID_XML, + string("element 'meta:MainSoundXConfiguration' is not allowed for content model " + "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?," + "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?," + "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration," + "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?," + "ExtensionMetadataList?,)'"), + canonical(cpl->file().get()), + 71).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get()) + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()) }); } /* DCP with invalid CompositionMetadataAsset */ -BOOST_AUTO_TEST_CASE (verify_test25) +BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag) { - boost::filesystem::path const dir("build/test/verify_test25"); + path const dir("build/test/verify_invalid_cpl_metadata_missing_tag"); prepare_directory (dir); auto reel = make_shared(); reel->add (black_picture_asset(dir)); - auto cpl = make_shared("hello", dcp::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); - cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-")); cpl->set_main_sound_sample_rate (48000); cpl->set_main_picture_stored_area (dcp::Size(1998, 1080)); cpl->set_main_picture_active_area (dcp::Size(1440, 1080)); dcp::DCP dcp (dir); dcp.add (cpl); - dcp.write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp.set_annotation_text("hello"); + dcp.write_xml(); { - Editor e (find_cpl("build/test/verify_test25")); + Editor e (find_cpl(dir)); e.replace ("meta:Width", "meta:WidthX"); } check_verify_result ( { dir }, - {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::GENERAL_READ }} + {}, + {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }} ); } -/* SMPTE DCP with invalid in the MainSubtitle reel and also in the XML within the MXF */ -BOOST_AUTO_TEST_CASE (verify_test26) +BOOST_AUTO_TEST_CASE (verify_invalid_language1) { - boost::filesystem::path const dir("build/test/verify_test26"); + path const dir("build/test/verify_invalid_language1"); prepare_directory (dir); - boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf"); + copy_file ("test/data/subs.mxf", dir / "subs.mxf"); auto asset = make_shared(dir / "subs.mxf"); asset->_language = "wrong-andbad"; asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); reel_asset->_language = "badlang"; - write_dcp_with_single_asset (dir, reel_asset); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("badlang") }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("wrong-andbad") }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }, + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } /* SMPTE DCP with invalid in the MainClosedCaption reel and also in the XML within the MXF */ -BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages) +BOOST_AUTO_TEST_CASE (verify_invalid_language2) { - boost::filesystem::path const dir("build/test/verify_invalid_closed_caption_languages"); + path const dir("build/test/verify_invalid_language2"); prepare_directory (dir); - boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf"); + copy_file ("test/data/subs.mxf", dir / "subs.mxf"); auto asset = make_shared(dir / "subs.mxf"); asset->_language = "wrong-andbad"; asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); reel_asset->_language = "badlang"; - write_dcp_with_single_asset (dir, reel_asset); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("badlang") }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("wrong-andbad") }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } @@ -900,9 +1350,9 @@ BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages) /* SMPTE DCP with invalid in the MainSound reel, the CPL additional subtitles languages and * the release territory. */ -BOOST_AUTO_TEST_CASE (verify_various_invalid_languages) +BOOST_AUTO_TEST_CASE (verify_invalid_language3) { - boost::filesystem::path const dir("build/test/verify_various_invalid_languages"); + path const dir("build/test/verify_invalid_language3"); prepare_directory (dir); auto picture = simple_picture (dir, "foo"); @@ -914,11 +1364,11 @@ BOOST_AUTO_TEST_CASE (verify_various_invalid_languages) reel->add (reel_sound); reel->add (simple_markers()); - auto cpl = make_shared("hello", dcp::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); cpl->_additional_subtitle_languages.push_back("this-is-wrong"); cpl->_additional_subtitle_languages.push_back("andso-is-this"); - cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-")); cpl->set_main_sound_sample_rate (48000); cpl->set_main_picture_stored_area (dcp::Size(1998, 1080)); cpl->set_main_picture_active_area (dcp::Size(1440, 1080)); @@ -926,21 +1376,25 @@ BOOST_AUTO_TEST_CASE (verify_various_invalid_languages) cpl->_release_territory = "fred-jim"; auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp->set_annotation_text("hello"); + dcp->write_xml(); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("this-is-wrong") }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("andso-is-this") }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("fred-jim") }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::BAD_LANGUAGE, string("frobozz") }, + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") + ).set_cpl_id(cpl->id()), }); } @@ -956,11 +1410,11 @@ check_picture_size (int width, int height, int frame_rate, bool three_d) shared_ptr mp; if (three_d) { - mp = make_shared(dcp::Fraction(frame_rate, 1), dcp::SMPTE); + mp = make_shared(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE); } else { - mp = make_shared(dcp::Fraction(frame_rate, 1), dcp::SMPTE); + mp = make_shared(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE); } - auto picture_writer = mp->start_write (dcp_path / "video.mxf", false); + auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW); auto image = black_image (dcp::Size(width, height)); auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048); @@ -971,13 +1425,13 @@ check_picture_size (int width, int height, int frame_rate, bool three_d) picture_writer->finalize (); auto d = make_shared(dcp_path); - auto cpl = make_shared("A Test DCP", dcp::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->set_annotation_text ("A Test DCP"); cpl->set_issue_date ("2012-07-17T04:45:18+00:00"); - cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-")); cpl->set_main_sound_sample_rate (48000); - cpl->set_main_picture_stored_area (dcp::Size(1998, 1080)); - cpl->set_main_picture_active_area (dcp::Size(1998, 1080)); + cpl->set_main_picture_stored_area(dcp::Size(width, height)); + cpl->set_main_picture_active_area(dcp::Size(width, height)); cpl->set_version_number (1); auto reel = make_shared(); @@ -993,15 +1447,10 @@ check_picture_size (int width, int height, int frame_rate, bool three_d) cpl->add (reel); d->add (cpl); - d->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + d->set_annotation_text("A Test DCP"); + d->write_xml(); - return dcp::verify ({dcp_path}, &stage, &progress, xsd_test); + return dcp::verify({dcp_path}, {}, &stage, &progress, {}, xsd_test).notes; } @@ -1010,7 +1459,6 @@ void check_picture_size_ok (int width, int height, int frame_rate, bool three_d) { auto notes = check_picture_size(width, height, frame_rate, three_d); - dump_notes (notes); BOOST_CHECK_EQUAL (notes.size(), 0U); } @@ -1021,8 +1469,8 @@ check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool t { auto notes = check_picture_size(width, height, frame_rate, three_d); BOOST_REQUIRE_EQUAL (notes.size(), 1U); - BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR); - BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS); + BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR); + BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS); } @@ -1032,8 +1480,8 @@ check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, boo { auto notes = check_picture_size(width, height, frame_rate, three_d); BOOST_REQUIRE_EQUAL (notes.size(), 2U); - BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR); - BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K); + BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR); + BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K); } @@ -1043,8 +1491,8 @@ check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, boo { auto notes = check_picture_size(width, height, frame_rate, three_d); BOOST_REQUIRE_EQUAL (notes.size(), 1U); - BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR); - BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K); + BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR); + BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K); } @@ -1078,7 +1526,7 @@ BOOST_AUTO_TEST_CASE (verify_picture_size) check_picture_size_bad_frame_size (2050, 858, 24, false); check_picture_size_bad_frame_size (2048, 658, 25, false); check_picture_size_bad_frame_size (1920, 1080, 48, true); - check_picture_size_bad_frame_size (4000, 3000, 24, true); + check_picture_size_bad_frame_size (4000, 2000, 24, true); /* Bad 2K frame rate */ check_picture_size_bad_2k_frame_rate (2048, 858, 26, false); @@ -1092,17 +1540,17 @@ BOOST_AUTO_TEST_CASE (verify_picture_size) /* No 4K 3D */ auto notes = check_picture_size(3996, 2160, 24, true); BOOST_REQUIRE_EQUAL (notes.size(), 1U); - BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR); - BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_4K_3D); + BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR); + BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D); } static void -add_test_subtitle (shared_ptr asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello") +add_test_subtitle (shared_ptr asset, int start_frame, int end_frame, float v_position = 0, dcp::VAlign v_align = dcp::VAlign::CENTER, string text = "Hello") { asset->add ( - make_shared( + std::make_shared( optional(), false, false, @@ -1113,48 +1561,64 @@ add_test_subtitle (shared_ptr asset, int start_frame, int en dcp::Time(start_frame, 24, 24), dcp::Time(end_frame, 24, 24), 0, - dcp::HALIGN_CENTER, + dcp::HAlign::CENTER, v_position, - dcp::VALIGN_CENTER, - dcp::DIRECTION_LTR, + v_align, + 0, + dcp::Direction::LTR, text, - dcp::NONE, + dcp::Effect::NONE, dcp::Colour(), dcp::Time(), - dcp::Time() + dcp::Time(), + 0, + std::vector() ) ); } -BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large) +BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes) { - boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large"); + path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes"); prepare_directory (dir); auto asset = make_shared(); for (int i = 0; i < 2048; ++i) { add_test_subtitle (asset, i * 24, i * 24 + 20); } + add_font(asset); asset->set_language (dcp::LanguageTag("de-DE")); asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - write_dcp_with_single_asset (dir, reel_asset); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 49148, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }, + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, + dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, + string("419371"), + canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } static shared_ptr -make_large_subtitle_asset (boost::filesystem::path font_file) +make_large_subtitle_asset (path font_file) { auto asset = make_shared(); dcp::ArrayData big_fake_font(1024 * 1024); @@ -1170,51 +1634,62 @@ template void verify_timed_text_asset_too_large (string name) { - auto const dir = boost::filesystem::path("build/test") / name; + auto const dir = path("build/test") / name; prepare_directory (dir); auto asset = make_large_subtitle_asset (dir / "font.ttf"); - add_test_subtitle (asset, 0, 20); + add_test_subtitle (asset, 0, 240); asset->set_language (dcp::LanguageTag("de-DE")); asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - write_dcp_with_single_asset (dir, reel_asset); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 240, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }, + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121698284"), canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large) { - verify_timed_text_asset_too_large("verify_subtitle_asset_too_large"); - verify_timed_text_asset_too_large("verify_closed_caption_asset_too_large"); + verify_timed_text_asset_too_large("verify_subtitle_asset_too_large"); + verify_timed_text_asset_too_large("verify_closed_caption_asset_too_large"); } -BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml) +BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language) { - boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml"; + path dir = "build/test/verify_missing_subtitle_language"; prepare_directory (dir); - auto dcp = make_simple (dir, 1, 240); + auto dcp = make_simple (dir, 1, 106); string const xml = "" - "" + "" "urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a" "Content" "Annotation" "2018-10-02T12:25:14+02:00" "1" - "25 1" - "25" + "24 1" + "24" "00:00:00:00" "urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6" "" @@ -1226,44 +1701,46 @@ BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml) "" ""; - auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w"); + dcp::File xml_file(dir / "subs.xml", "w"); BOOST_REQUIRE (xml_file); - fwrite (xml.c_str(), xml.size(), 1, xml_file); - fclose (xml_file); + xml_file.write(xml.c_str(), xml.size(), 1); + xml_file.close(); auto subs = make_shared(dir / "subs.xml"); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); - dcp->cpls().front()->reels().front()->add(reel_subs); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 106, 0); + auto cpl = dcp->cpls()[0]; + cpl->reels()[0]->add(reel_subs); + dcp->write_xml(); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages) +BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages) { - boost::filesystem::path path ("build/test/verify_inconsistent_subtitle_languages"); - auto dcp = make_simple (path, 2, 240); + path path ("build/test/verify_mismatched_subtitle_languages"); + auto constexpr reel_length = 192; + auto dcp = make_simple (path, 2, reel_length); auto cpl = dcp->cpls()[0]; { auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); subs->add (simple_subtitle()); + add_font(subs); subs->write (path / "subs1.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); cpl->reels()[0]->add(reel_subs); } @@ -1271,46 +1748,91 @@ BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages) auto subs = make_shared(); subs->set_language (dcp::LanguageTag("en-US")); subs->add (simple_subtitle()); + add_font(subs); subs->write (path / "subs2.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); cpl->reels()[1]->add(reel_subs); } - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + check_verify_result ( + { path }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES + ).set_cpl_id(cpl->id()), + }); +} + + +BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed) +{ + path path ("build/test/verify_multiple_closed_caption_languages_allowed"); + auto constexpr reel_length = 192; + auto dcp = make_simple (path, 2, reel_length); + auto cpl = dcp->cpls()[0]; + + { + auto ccaps = make_shared(); + ccaps->set_language (dcp::LanguageTag("de-DE")); + ccaps->add (simple_subtitle()); + add_font(ccaps); + ccaps->write (path / "subs1.mxf"); + auto reel_ccaps = make_shared(ccaps, dcp::Fraction(24, 1), reel_length, 0); + cpl->reels()[0]->add(reel_ccaps); + } + + { + auto ccaps = make_shared(); + ccaps->set_language (dcp::LanguageTag("en-US")); + ccaps->add (simple_subtitle()); + add_font(ccaps); + ccaps->write (path / "subs2.mxf"); + auto reel_ccaps = make_shared(ccaps, dcp::Fraction(24, 1), reel_length, 0); + cpl->reels()[1]->add(reel_ccaps); + } + + dcp->write_xml(); check_verify_result ( { path }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_LANGUAGES_DIFFER }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml) +BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time) { - boost::filesystem::path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml"; + path dir = "build/test/verify_missing_subtitle_start_time"; prepare_directory (dir); - auto dcp = make_simple (dir, 1, 240); + auto dcp = make_simple (dir, 1, 106); string const xml = "" - "" + "" "urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a" "Content" "Annotation" "2018-10-02T12:25:14+02:00" "1" "de-DE" - "25 1" - "25" + "24 1" + "24" "urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6" "" "" @@ -1321,49 +1843,49 @@ BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml) "" ""; - auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w"); + dcp::File xml_file(dir / "subs.xml", "w"); BOOST_REQUIRE (xml_file); - fwrite (xml.c_str(), xml.size(), 1, xml_file); - fclose (xml_file); + xml_file.write(xml.c_str(), xml.size(), 1); + xml_file.close(); auto subs = make_shared(dir / "subs.xml"); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); - dcp->cpls().front()->reels().front()->add(reel_subs); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 106, 0); + auto cpl = dcp->cpls()[0]; + cpl->reels()[0]->add(reel_subs); + dcp->write_xml(); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time) { - boost::filesystem::path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml"; + path dir = "build/test/verify_invalid_subtitle_start_time"; prepare_directory (dir); - auto dcp = make_simple (dir, 1, 240); + auto dcp = make_simple (dir, 1, 106); string const xml = "" - "" + "" "urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a" "Content" "Annotation" "2018-10-02T12:25:14+02:00" "1" "de-DE" - "25 1" - "25" + "24 1" + "24" "00:00:02:00" "urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6" "" @@ -1375,28 +1897,28 @@ BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml) "" ""; - auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w"); + dcp::File xml_file(dir / "subs.xml", "w"); BOOST_REQUIRE (xml_file); - fwrite (xml.c_str(), xml.size(), 1, xml_file); - fclose (xml_file); + xml_file.write(xml.c_str(), xml.size(), 1); + xml_file.close(); auto subs = make_shared(dir / "subs.xml"); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); - dcp->cpls().front()->reels().front()->add(reel_subs); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 106, 0); + auto cpl = dcp->cpls()[0]; + cpl->reels().front()->add(reel_subs); + dcp->write_xml(); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()) }); } @@ -1404,65 +1926,121 @@ BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml) class TestText { public: - TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello") + TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello") : in(in_) , out(out_) , v_position(v_position_) + , v_align(v_align_) , text(text_) {} int in; int out; float v_position; + dcp::VAlign v_align; string text; }; template -void -dcp_with_text (boost::filesystem::path dir, vector subs) +shared_ptr +dcp_with_text(path dir, vector subs, optional key = boost::none, optional key_id = boost::none) { prepare_directory (dir); auto asset = make_shared(); asset->set_start_time (dcp::Time()); for (auto i: subs) { - add_test_subtitle (asset, i.in, i.out, i.v_position, i.text); + add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text); } asset->set_language (dcp::LanguageTag("de-DE")); + if (key && key_id) { + asset->set_key(*key); + asset->set_key_id(*key_id); + } + add_font(asset); asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); - write_dcp_with_single_asset (dir, reel_asset); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0); + return write_dcp_with_single_asset (dir, reel_asset); +} + + +template +shared_ptr +dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml) +{ + prepare_directory (dir); + auto asset = make_shared(subs_xml); + asset->set_start_time (dcp::Time()); + asset->set_language (dcp::LanguageTag("de-DE")); + + auto subs_mxf = dir / "subs.mxf"; + asset->write (subs_mxf); + + /* The call to write() puts the asset into the DCP correctly but it will have + * XML re-written by our parser. Overwrite the MXF using the given file's verbatim + * contents. + */ + ASDCP::TimedText::MXFWriter writer; + ASDCP::WriterInfo writer_info; + writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE; + unsigned int c; + Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + ASDCP::TimedText::TimedTextDescriptor descriptor; + descriptor.ContainerDuration = asset->intrinsic_duration(); + Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384); + BOOST_REQUIRE (!ASDCP_FAILURE(r)); + r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml)); + BOOST_REQUIRE (!ASDCP_FAILURE(r)); + writer.Finalize (); + + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0); + return write_dcp_with_single_asset (dir, reel_asset); } -BOOST_AUTO_TEST_CASE (verify_text_too_early) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time) { - auto const dir = boost::filesystem::path("build/test/verify_text_too_early"); + auto const dir = path("build/test/verify_invalid_subtitle_first_text_time"); /* Just too early */ - dcp_with_text (dir, {{ 4 * 24 - 1, 5 * 24 }}); + auto cpl = dcp_with_text (dir, {{ 4 * 24 - 1, 5 * 24 }}); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_text_not_too_early) +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time) { - auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early"); + auto const dir = path("build/test/verify_valid_subtitle_first_text_time"); /* Just late enough */ - dcp_with_text (dir, {{ 4 * 24, 5 * 24 }}); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + auto cpl = dcp_with_text (dir, {{ 4 * 24, 5 * 24 }}); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } -BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel) +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel) { - auto const dir = boost::filesystem::path("build/test/verify_text_early_on_second_reel"); + auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel"); prepare_directory (dir); auto asset1 = make_shared(); @@ -1470,49 +2048,52 @@ BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel) /* Just late enough */ add_test_subtitle (asset1, 4 * 24, 5 * 24); asset1->set_language (dcp::LanguageTag("de-DE")); + add_font(asset1); asset1->write (dir / "subs1.mxf"); - auto reel_asset1 = make_shared(asset1, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset1 = make_shared(asset1, dcp::Fraction(24, 1), 5 * 24, 0); auto reel1 = make_shared(); reel1->add (reel_asset1); - auto markers1 = make_shared(dcp::Fraction(24, 1), 16 * 24, 0); + auto markers1 = make_shared(dcp::Fraction(24, 1), 5 * 24); markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); reel1->add (markers1); auto asset2 = make_shared(); asset2->set_start_time (dcp::Time()); + add_font(asset2); /* This would be too early on first reel but should be OK on the second */ - add_test_subtitle (asset2, 0, 4 * 24); + add_test_subtitle (asset2, 3, 4 * 24); asset2->set_language (dcp::LanguageTag("de-DE")); asset2->write (dir / "subs2.mxf"); - auto reel_asset2 = make_shared(asset2, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset2 = make_shared(asset2, dcp::Fraction(24, 1), 4 * 24, 0); auto reel2 = make_shared(); reel2->add (reel_asset2); - auto markers2 = make_shared(dcp::Fraction(24, 1), 16 * 24, 0); - markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24)); + auto markers2 = make_shared(dcp::Fraction(24, 1), 4 * 24); + markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24)); reel2->add (markers2); - auto cpl = make_shared("hello", dcp::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel1); cpl->add (reel2); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp->set_annotation_text("hello"); + dcp->write_xml(); - - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } -BOOST_AUTO_TEST_CASE (verify_text_too_close) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing) { - auto const dir = boost::filesystem::path("build/test/verify_text_too_close"); - dcp_with_text ( + auto const dir = path("build/test/verify_invalid_subtitle_spacing"); + auto cpl = dcp_with_text ( dir, { { 4 * 24, 5 * 24 }, @@ -1520,334 +2101,610 @@ BOOST_AUTO_TEST_CASE (verify_text_too_close) }); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_CLOSE }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_text_not_too_close) +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing) { - auto const dir = boost::filesystem::path("build/test/verify_text_not_too_close"); - dcp_with_text ( + auto const dir = path("build/test/verify_valid_subtitle_spacing"); + auto cpl = dcp_with_text ( dir, { { 4 * 24, 5 * 24 }, { 5 * 24 + 16, 8 * 24 }, }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration) +{ + auto const dir = path("build/test/verify_invalid_subtitle_duration"); + auto cpl = dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 1 }}); + check_verify_result ( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration) +{ + auto const dir = path("build/test/verify_valid_subtitle_duration"); + auto cpl = dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 17 }}); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary) +{ + auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary"); + prepare_directory (dir); + auto asset = make_shared(); + asset->set_start_time (dcp::Time()); + add_test_subtitle (asset, 0, 4 * 24); + add_font(asset); + asset->set_language (dcp::LanguageTag("de-DE")); + asset->write (dir / "subs.mxf"); + + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 3 * 24, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); + check_verify_result ( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); + +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1) +{ + auto const dir = path ("build/test/invalid_subtitle_line_count1"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, + { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" } + }); + check_verify_result ( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1) +{ + auto const dir = path ("build/test/verify_valid_subtitle_line_count1"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, + }); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } -BOOST_AUTO_TEST_CASE (verify_text_too_short) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2) { - auto const dir = boost::filesystem::path("build/test/verify_text_too_short"); - dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 1 }}); + auto const dir = path ("build/test/verify_invalid_subtitle_line_count2"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" } + }); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_SHORT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_text_not_too_short) +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2) { - auto const dir = boost::filesystem::path("build/test/verify_text_not_too_short"); - dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 17 }}); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + auto const dir = path ("build/test/verify_valid_subtitle_line_count2"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" } + }); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } -BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_subtitle_line_length1"); + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, - { 96, 200, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" } }); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2) { - auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_subtitle_lines1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_subtitle_line_length2"); + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, + { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" } + }); + check_verify_result ( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); } -BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2) +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines2"); - dcp_with_text ( + auto const dir = path ("build/test/verify_valid_closed_caption_line_count1"); + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 150, 180, 0.3, "lines" } + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, + { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2) +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2) { - auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_subtitle_lines2"); - dcp_with_text ( + auto const dir = path ("build/test/verify_valid_closed_caption_line_count2"); + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 190, 250, 0.3, "lines" } + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, + }); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); } -BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1) +BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3) { - auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3"); + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ( {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4) +{ + auto const dir = path ("build/test/verify_valid_closed_caption_line_count4"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" } + }); + + check_verify_result( + {dir}, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_LONGER_THAN_RECOMMENDED }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2) +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length) { - auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long2"); - dcp_with_text ( + auto const dir = path ("build/test/verify_valid_closed_caption_line_length"); + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" } }); + check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_TOO_LONG }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1) +BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_closed_caption_line_length"); + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, - { 96, 200, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" } }); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1) +BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1) { - auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_closed_caption_lines1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1"); + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, + { 96, 300, 0.0, dcp::VAlign::TOP, "This" }, + { 96, 300, 0.1, dcp::VAlign::TOP, "is" }, + { 96, 300, 0.2, dcp::VAlign::TOP, "fine" }, + }); + check_verify_result ( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); } -BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2) +BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines2"); - dcp_with_text ( + auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2"); + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 150, 180, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::TOP, "This" }, + { 96, 300, 0.1, dcp::VAlign::TOP, "is" }, + { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" }, }); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2) +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1) { - auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_closed_caption_lines2"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1"); + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 190, 250, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::TOP, "This" }, + { 96, 300, 0.1, dcp::VAlign::TOP, "is" }, + { 96, 300, 0.2, dcp::VAlign::TOP, "fine" }, + }); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); } -BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1) +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2) { - auto const dir = boost::filesystem::path ("build/test/verify_closed_caption_lines_too_long1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2"); + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "0123456789012345678901234567890123" } + { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" }, + { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" }, + { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" }, + }); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); +} + + +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3) +{ + auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3"); + auto cpl = dcp_with_text_from_file (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml"); check_verify_result ( {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4) +{ + auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4"); + auto cpl = dcp_with_text_from_file (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml"); + + check_verify_result( + {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_LINE_TOO_LONG }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_sound_sampling_rate_must_be_48k) + +BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate) { - boost::filesystem::path const dir("build/test/verify_sound_sampling_rate_must_be_48k"); + path const dir("build/test/verify_invalid_sound_frame_rate"); prepare_directory (dir); auto picture = simple_picture (dir, "foo"); auto reel_picture = make_shared(picture, 0); auto reel = make_shared(); reel->add (reel_picture); - auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000); + auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none); auto reel_sound = make_shared(sound, 0); reel->add (reel_sound); reel->add (simple_markers()); - auto cpl = make_shared("hello", dcp::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp->set_annotation_text("hello"); + dcp->write_xml(); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_must_have_annotation_text) +BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text) { - boost::filesystem::path const dir("build/test/verify_cpl_must_have_annotation_text"); + path const dir("build/test/verify_missing_cpl_annotation_text"); auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); + auto const cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); + { - BOOST_REQUIRE (dcp->cpls()[0]->file()); - Editor e(dcp->cpls()[0]->file().get()); + BOOST_REQUIRE (cpl->file()); + Editor e(cpl->file().get()); e.replace("A Test DCP", ""); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_ANNOTATION_TEXT_IN_CPL }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, canonical(cpl->file().get()) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get()) + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_annotation_text_should_be_same_as_content_title_text) +BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text) { - boost::filesystem::path const dir("build/test/verify_cpl_annotation_text_should_be_same_as_content_title_text"); + path const dir("build/test/verify_mismatched_cpl_annotation_text"); auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); + auto const cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); { - BOOST_REQUIRE (dcp->cpls()[0]->file()); - Editor e(dcp->cpls()[0]->file().get()); + BOOST_REQUIRE (cpl->file()); + Editor e(cpl->file().get()); e.replace("A Test DCP", "A Test DCP 1"); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::CPL_ANNOTATION_TEXT_DIFFERS_FROM_CONTENT_TITLE_TEXT }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT } + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, canonical(cpl->file().get()) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl->file().get()) + ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_reel_assets_durations_must_match) +BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration) { - boost::filesystem::path const dir("build/test/verify_reel_assets_durations_must_match"); + path const dir("build/test/verify_mismatched_asset_duration"); prepare_directory (dir); shared_ptr dcp (new dcp::DCP(dir)); - shared_ptr cpl (new dcp::CPL("A Test DCP", dcp::TRAILER)); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); shared_ptr mp = simple_picture (dir, "", 24); shared_ptr ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25); @@ -1861,298 +2718,344 @@ BOOST_AUTO_TEST_CASE (verify_reel_assets_durations_must_match) cpl->add (reel); dcp->add (cpl); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl->file().get()) + ).set_cpl_id(cpl->id()) }); } static -void -verify_subtitles_must_be_in_all_reels_check (boost::filesystem::path dir, bool add_to_reel1, bool add_to_reel2) +shared_ptr +verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2) { prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + + auto constexpr reel_length = 192; auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); subs->set_start_time (dcp::Time()); subs->add (simple_subtitle()); + add_font(subs); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); auto reel1 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "1", reel_length), 0), + make_shared(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0) ); if (add_to_reel1) { - reel1->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel1->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers1 = make_shared(dcp::Fraction(24, 1), 240, 0); + auto markers1 = make_shared(dcp::Fraction(24, 1), reel_length); markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); reel1->add (markers1); cpl->add (reel1); auto reel2 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "2", reel_length), 0), + make_shared(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0) ); if (add_to_reel2) { - reel2->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel2->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers2 = make_shared(dcp::Fraction(24, 1), 240, 0); - markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24)); + auto markers2 = make_shared(dcp::Fraction(24, 1), reel_length); + markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24)); reel2->add (markers2); cpl->add (reel2); dcp->add (cpl); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + + return cpl; } -BOOST_AUTO_TEST_CASE (verify_subtitles_must_be_in_all_reels) +BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels) { { - boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1"); - verify_subtitles_must_be_in_all_reels_check (dir, true, false); + path dir ("build/test/missing_main_subtitle_from_some_reels"); + auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false); check_verify_result ( { dir }, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MAIN_SUBTITLE_NOT_IN_ALL_REELS }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } { - boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels2"); - verify_subtitles_must_be_in_all_reels_check (dir, true, true); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + path dir ("build/test/verify_subtitles_must_be_in_all_reels2"); + auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } { - boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1"); - verify_subtitles_must_be_in_all_reels_check (dir, false, false); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + path dir ("build/test/verify_subtitles_must_be_in_all_reels1"); + auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } } static -void -verify_closed_captions_must_be_in_all_reels_check (boost::filesystem::path dir, int caps_in_reel1, int caps_in_reel2) +shared_ptr +verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2) { prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + + auto constexpr reel_length = 192; auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); subs->set_start_time (dcp::Time()); subs->add (simple_subtitle()); + add_font(subs); subs->write (dir / "subs.mxf"); auto reel1 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "1", reel_length), 0), + make_shared(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0) ); for (int i = 0; i < caps_in_reel1; ++i) { - reel1->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel1->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers1 = make_shared(dcp::Fraction(24, 1), 240, 0); + auto markers1 = make_shared(dcp::Fraction(24, 1), reel_length); markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); reel1->add (markers1); cpl->add (reel1); auto reel2 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "2", reel_length), 0), + make_shared(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0) ); for (int i = 0; i < caps_in_reel2; ++i) { - reel2->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel2->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers2 = make_shared(dcp::Fraction(24, 1), 240, 0); - markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24)); + auto markers2 = make_shared(dcp::Fraction(24, 1), reel_length); + markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24)); reel2->add (markers2); cpl->add (reel2); dcp->add (cpl); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + + return cpl; } -BOOST_AUTO_TEST_CASE (verify_closed_captions_must_be_in_all_reels) +BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts) { { - boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels1"); - verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4); + path dir ("build/test/mismatched_closed_caption_asset_counts"); + auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_ASSET_COUNTS_DIFFER }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } { - boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels2"); - verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + path dir ("build/test/verify_closed_captions_must_be_in_all_reels2"); + auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } { - boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels3"); - verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }}); + path dir ("build/test/verify_closed_captions_must_be_in_all_reels3"); + auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } } template void -verify_text_entry_point_check (boost::filesystem::path dir, dcp::VerificationNote::Code code, boost::function)> adjust) +verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function)> adjust) { prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + + auto constexpr reel_length = 192; auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); subs->set_start_time (dcp::Time()); subs->add (simple_subtitle()); + add_font(subs); subs->write (dir / "subs.mxf"); - auto reel_text = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_text = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); adjust (reel_text); auto reel = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "", reel_length), 0), + make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0) ); reel->add (reel_text); - reel->add (simple_markers(240)); + reel->add (simple_markers(reel_length)); cpl->add (reel); dcp->add (cpl); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, code }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } BOOST_AUTO_TEST_CASE (verify_text_entry_point) { - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_subtitle_entry_point_must_be_present", - dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT, - [](shared_ptr asset) { + dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT, + [](shared_ptr asset) { asset->unset_entry_point (); } ); - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_subtitle_entry_point_must_be_zero", - dcp::VerificationNote::SUBTITLE_ENTRY_POINT_NON_ZERO, - [](shared_ptr asset) { + dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT, + [](shared_ptr asset) { asset->set_entry_point (4); } ); - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_closed_caption_entry_point_must_be_present", - dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT, - [](shared_ptr asset) { + dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT, + [](shared_ptr asset) { asset->unset_entry_point (); } ); - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_closed_caption_entry_point_must_be_zero", - dcp::VerificationNote::CLOSED_CAPTION_ENTRY_POINT_NON_ZERO, - [](shared_ptr asset) { + dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT, + [](shared_ptr asset) { asset->set_entry_point (9); } ); } -BOOST_AUTO_TEST_CASE (verify_assets_must_have_hashes) +BOOST_AUTO_TEST_CASE (verify_missing_hash) { RNGFixer fix; - boost::filesystem::path const dir("build/test/verify_assets_must_have_hashes"); + path const dir("build/test/verify_missing_hash"); auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); + auto const cpl = dcp->cpls()[0]; + BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U); + BOOST_REQUIRE (cpl->reels()[0]->main_picture()); + auto asset_id = cpl->reels()[0]->main_picture()->id(); + + HashCalculator calc(cpl->file().get()); { - BOOST_REQUIRE (dcp->cpls()[0]->file()); - Editor e(dcp->cpls()[0]->file().get()); - e.replace("XGhFVrqZqapOJx5Fh2SLjj48Yjg=", ""); + BOOST_REQUIRE (cpl->file()); + Editor e(cpl->file().get()); + e.delete_first_line_containing(""); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_HASH } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id + ).set_cpl_id(cpl->id()) }); } @@ -2160,27 +3063,26 @@ BOOST_AUTO_TEST_CASE (verify_assets_must_have_hashes) static void verify_markers_test ( - boost::filesystem::path dir, + path dir, vector> markers, vector test_notes ) { auto dcp = make_simple (dir); - dcp->cpls()[0]->set_content_kind (dcp::FEATURE); - auto markers_asset = make_shared(dcp::Fraction(24, 1), 24, 0); + auto cpl = dcp->cpls()[0]; + cpl->set_content_kind(dcp::ContentKind::FEATURE); + auto markers_asset = make_shared(dcp::Fraction(24, 1), 24); for (auto const& i: markers) { markers_asset->set (i.first, i.second); } - dcp->cpls()[0]->reels()[0]->add(markers_asset); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + cpl->reels()[0]->add(markers_asset); + dcp->write_xml(); - check_verify_result ({dir}, test_notes); + for (auto& note: test_notes) { + note.set_cpl_id(cpl->id()); + } + + check_verify_result({dir}, {}, test_notes); } @@ -2205,7 +3107,7 @@ BOOST_AUTO_TEST_CASE (verify_markers) { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } }, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE } }); verify_markers_test ( @@ -2216,7 +3118,7 @@ BOOST_AUTO_TEST_CASE (verify_markers) { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } }, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE } }); verify_markers_test ( @@ -2227,7 +3129,7 @@ BOOST_AUTO_TEST_CASE (verify_markers) { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } }, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC} + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC} }); verify_markers_test ( @@ -2238,7 +3140,7 @@ BOOST_AUTO_TEST_CASE (verify_markers) { dcp::Marker::FFOC, dcp::Time(1, 24, 24) } }, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC } + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC } }); verify_markers_test ( @@ -2250,7 +3152,7 @@ BOOST_AUTO_TEST_CASE (verify_markers) { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } }, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_FFOC } + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") } }); verify_markers_test ( @@ -2262,343 +3164,426 @@ BOOST_AUTO_TEST_CASE (verify_markers) { dcp::Marker::LFOC, dcp::Time(18, 24, 24) } }, { - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_LFOC } + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") } }); } -BOOST_AUTO_TEST_CASE (verify_cpl_metadata_version) +BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number) { - boost::filesystem::path dir = "build/test/verify_cpl_metadata_version"; + path dir = "build/test/verify_missing_cpl_metadata_version_number"; prepare_directory (dir); auto dcp = make_simple (dir); - dcp->cpls()[0]->unset_version_number(); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + auto cpl = dcp->cpls()[0]; + cpl->unset_version_number(); + dcp->write_xml(); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER }}); + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata1) +BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata1"; + path dir = "build/test/verify_missing_extension_metadata1"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); + auto cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.delete_lines ("", ""); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata2) +BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata2"; + path dir = "build/test/verify_missing_extension_metadata2"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.delete_lines ("", ""); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata3) +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata3"; + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto const cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.replace ("A", "A"); e.replace ("n", "n"); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + 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()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata4) +BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata4"; + path dir = "build/test/verify_invalid_extension_metadata1"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.replace ("Application", "Fred"); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string(" property should be 'Application'") }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string(" should be 'Application'"), cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata5) +BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata5"; + path dir = "build/test/verify_invalid_extension_metadata2"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); + { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.replace ("DCP Constraints Profile", "Fred"); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string(" property should be 'DCP Constraints Profile'") }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string(" property should be 'DCP Constraints Profile'"), cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata6) +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata6"; + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto const cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.replace ("", ""); e.replace ("", ""); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + 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 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata7) +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata7"; + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto const cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); + { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred"); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string(" property should be 'SMPTE-RDD-52:2020-Bv2.1'") }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string(" property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() + ).set_cpl_id(cpl->id()) }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata8) +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata8"; + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto const cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); + { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.replace ("", ""); e.replace ("", ""); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + 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()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), }); } -BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata9) +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9) { - boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata9"; + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9"; auto dcp = make_simple (dir); - dcp->write_xml ( - dcp::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->write_xml(); + + auto const cpl = dcp->cpls()[0]; + + HashCalculator calc(cpl->file().get()); + { - Editor e (dcp->cpls()[0]->file().get()); + Editor e (cpl->file().get()); e.replace ("", ""); e.replace ("", ""); } check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + 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 + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->file().get() + ).set_cpl_id(cpl->id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), }); } -BOOST_AUTO_TEST_CASE (verify_encrypted_cpl_is_signed) +BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content) { - boost::filesystem::path dir = "build/test/verify_encrypted_cpl_is_signed"; + path dir = "build/test/verify_unsigned_cpl_with_encrypted_content"; prepare_directory (dir); - for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/encryption_test")) { - boost::filesystem::copy_file (i.path(), dir / i.path().filename()); + for (auto i: directory_iterator("test/ref/DCP/encryption_test")) { + copy_file (i.path(), dir / i.path().filename()); } + path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml"); + path const cpl_path = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml"); + + HashCalculator calc(cpl_path); + { - Editor e (dir / "cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml"); + Editor e(cpl_path); e.delete_lines (""); } + dcp::CPL cpl(cpl_path); + check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PKL_ANNOTATION_TEXT_DOES_NOT_MATCH_CPL_CONTENT_TITLE_TEXT }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_CPL_METADATA }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CPL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED } + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(cpl_path) + ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl) + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path) + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_path) + ).set_cpl_id(cpl.id()) }); } -BOOST_AUTO_TEST_CASE (verify_encrypted_pkl_is_signed) +BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content) { - boost::filesystem::path dir = "build/test/verify_encrypted_pkl_is_signed"; + path dir = "build/test/unsigned_pkl_with_encrypted_content"; prepare_directory (dir); - for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/encryption_test")) { - boost::filesystem::copy_file (i.path(), dir / i.path().filename()); + for (auto i: directory_iterator("test/ref/DCP/encryption_test")) { + copy_file (i.path(), dir / i.path().filename()); } + path const cpl_path = dir / ("cpl_" + encryption_test_cpl_id() + ".xml"); + path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml"); { - Editor e (dir / "pkl_93182bd2-b1e8-41a3-b5c8-6e6564273bff.xml"); + Editor e (pkl); e.delete_lines (""); } + dcp::CPL cpl(cpl_path); + check_verify_result ( {dir}, + {}, { - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PKL_ANNOTATION_TEXT_DOES_NOT_MATCH_CPL_CONTENT_TITLE_TEXT }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE }, - { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC }, - { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_CPL_METADATA }, - { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PKL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED } + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl) + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_path) + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl) + ) }); } -BOOST_AUTO_TEST_CASE (verify_unencrypted_pkl_can_be_unsigned) +BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content) { - boost::filesystem::path dir = "build/test/verify_unencrypted_pkl_can_be_unsigned"; + path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content"; prepare_directory (dir); - for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/dcp_test1")) { - boost::filesystem::copy_file (i.path(), dir / i.path().filename()); + for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) { + copy_file (i.path(), dir / i.path().filename()); } { - Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml"); + Editor e (dir / dcp_test1_pkl()); e.delete_lines (""); } - check_verify_result ({dir}, {}); + check_verify_result({dir}, {}, {}); } -BOOST_AUTO_TEST_CASE (verify_must_not_be_partially_encrypted) +BOOST_AUTO_TEST_CASE (verify_partially_encrypted) { - boost::filesystem::path dir ("build/test/verify_must_not_be_partially_encrypted"); + path dir ("build/test/verify_must_not_be_partially_encrypted"); prepare_directory (dir); dcp::DCP d (dir); @@ -2609,14 +3594,14 @@ BOOST_AUTO_TEST_CASE (verify_must_not_be_partially_encrypted) signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem"))); signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key")); - auto cpl = make_shared("A Test DCP", dcp::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); dcp::Key key; - auto mp = make_shared(dcp::Fraction (24, 1), dcp::SMPTE); + auto mp = make_shared(dcp::Fraction (24, 1), dcp::Standard::SMPTE); mp->set_key (key); - auto writer = mp->start_write (dir / "video.mxf", false); + auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW); dcp::ArrayData j2c ("test/data/flat_red.j2c"); for (int i = 0; i < 24; ++i) { writer->write (j2c.data(), j2c.size()); @@ -2641,7 +3626,7 @@ BOOST_AUTO_TEST_CASE (verify_must_not_be_partially_encrypted) cpl->set_issuer ("OpenDCP 0.0.25"); cpl->set_creator ("OpenDCP 0.0.25"); cpl->set_issue_date ("2012-07-17T04:45:18+00:00"); - cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-")); cpl->set_main_sound_sample_rate (48000); cpl->set_main_picture_stored_area (dcp::Size(1998, 1080)); cpl->set_main_picture_active_area (dcp::Size(1440, 1080)); @@ -2649,8 +3634,863 @@ BOOST_AUTO_TEST_CASE (verify_must_not_be_partially_encrypted) d.add (cpl); - d.write_xml (dcp::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer); + d.set_issuer("OpenDCP 0.0.25"); + d.set_creator("OpenDCP 0.0.25"); + d.set_issue_date("2012-07-17T04:45:18+00:00"); + d.set_annotation_text("A Test DCP"); + d.write_xml(signer); + + check_verify_result ( + {dir}, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k) +{ + vector notes; + 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")); + auto reader = picture.start_read (); + auto frame = reader->get_frame (0); + verify_j2k(frame, 0, 0, 24, notes); + BOOST_REQUIRE_EQUAL (notes.size(), 0U); +} + + +BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k) +{ + vector notes; + dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR")); + auto reader = picture.start_read (); + auto frame = reader->get_frame (0); + verify_j2k(frame, 0, 0, 24, notes); + BOOST_REQUIRE_EQUAL (notes.size(), 0U); +} + + +BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp) +{ + boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp"; + prepare_directory (dir); + auto dcp = make_simple (dir); + dcp->write_xml (); + vector notes; + dcp::MonoPictureAsset picture (find_file(dir, "video")); + auto reader = picture.start_read (); + auto frame = reader->get_frame (0); + verify_j2k(frame, 0, 0, 24, notes); + BOOST_REQUIRE_EQUAL (notes.size(), 0U); +} + + +/** Check that ResourceID and the XML ID being different is spotted */ +BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id) +{ + boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id"; + prepare_directory (dir); + + ASDCP::WriterInfo writer_info; + writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE; + + unsigned int c; + auto mxf_id = dcp::make_uuid (); + Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c); + BOOST_REQUIRE (c == Kumu::UUID_Length); + + auto resource_id = dcp::make_uuid (); + ASDCP::TimedText::TimedTextDescriptor descriptor; + Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + + auto xml_id = dcp::make_uuid (); + ASDCP::TimedText::MXFWriter writer; + auto subs_mxf = dir / "subs.mxf"; + auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096); + BOOST_REQUIRE (ASDCP_SUCCESS(r)); + writer.WriteTimedTextResource (dcp::String::compose( + "" + "" + "urn:uuid:%1" + "Content" + "Annotation" + "2018-10-02T12:25:14" + "1" + "en-US" + "25 1" + "25" + "00:00:00:00" + "urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6" + "" + "" + "" + "Hello world" + "" + "" + "" + "", + xml_id).c_str()); + + writer.Finalize(); + + auto subs_asset = make_shared(subs_mxf); + auto subs_reel = make_shared(subs_asset, dcp::Fraction(24, 1), 240, 0); + + auto cpl = write_dcp_with_single_asset (dir, subs_reel); + + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()) + }); +} + + +/** Check that ResourceID and the MXF ID being the same is spotted */ +BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id) +{ + boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id"; + prepare_directory (dir); + + ASDCP::WriterInfo writer_info; + writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE; + + unsigned int c; + auto mxf_id = dcp::make_uuid (); + Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c); + BOOST_REQUIRE (c == Kumu::UUID_Length); + + auto resource_id = mxf_id; + ASDCP::TimedText::TimedTextDescriptor descriptor; + Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + + auto xml_id = resource_id; + ASDCP::TimedText::MXFWriter writer; + auto subs_mxf = dir / "subs.mxf"; + auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096); + BOOST_REQUIRE (ASDCP_SUCCESS(r)); + writer.WriteTimedTextResource (dcp::String::compose( + "" + "" + "urn:uuid:%1" + "Content" + "Annotation" + "2018-10-02T12:25:14+02:00" + "1" + "en-US" + "25 1" + "25" + "00:00:00:00" + "urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55" + "" + "" + "" + "Hello world" + "" + "" + "" + "", + xml_id).c_str()); + + writer.Finalize(); + + auto subs_asset = make_shared(subs_mxf); + auto subs_reel = make_shared(subs_asset, dcp::Fraction(24, 1), 240, 0); + + auto cpl = write_dcp_with_single_asset (dir, subs_reel); - check_verify_result ({dir}, {{dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PARTIALLY_ENCRYPTED}}); + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get() + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} + ).set_cpl_id(cpl->id()) + }); } + +/** Check a DCP with a 3D asset marked as 2D */ +BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod) +{ + auto const path = private_test / "data" / "xm"; + + check_verify_result ( + { path }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, + dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(path, "j2c")) + ), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, + dcp::VerificationNote::Code::INVALID_STANDARD + ) + }); + +} + + +BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers) +{ + path dir = "build/test/verify_unexpected_things_in_main_markers"; + prepare_directory (dir); + auto dcp = make_simple (dir, 1, 24); + dcp->write_xml(); + + HashCalculator calc(find_cpl(dir)); + + { + Editor e (find_cpl(dir)); + e.insert( + " 24", + "024" + ); + } + + dcp::CPL cpl (find_cpl(dir)); + + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir)) + ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION + ).set_cpl_id(cpl.id()) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_content_kind) +{ + path dir = "build/test/verify_invalid_content_kind"; + prepare_directory (dir); + auto dcp = make_simple (dir, 1, 24); + dcp->write_xml(); + + HashCalculator calc(find_cpl(dir)); + + { + Editor e(find_cpl(dir)); + e.replace("trailer", "trip"); + } + + dcp::CPL cpl (find_cpl(dir)); + + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir)) + ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") + ).set_cpl_id(cpl.id()), + }); + +} + + +BOOST_AUTO_TEST_CASE(verify_valid_content_kind) +{ + path dir = "build/test/verify_valid_content_kind"; + prepare_directory (dir); + auto dcp = make_simple (dir, 1, 24); + dcp->write_xml(); + + HashCalculator calc(find_cpl(dir)); + + { + Editor e(find_cpl(dir)); + e.replace("trailer", "trip"); + } + + dcp::CPL cpl (find_cpl(dir)); + + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir)) + ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + }); +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1) +{ + path dir = "build/test/verify_invalid_main_picture_active_area_1"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + auto constexpr area = ""; + + HashCalculator calc(find_cpl(dir)); + + { + Editor e(find_cpl(dir)); + e.delete_lines_after(area, 2); + e.insert(area, "4080"); + e.insert(area, "1997"); + } + + dcp::PKL pkl(find_pkl(dir)); + dcp::CPL cpl(find_cpl(dir)); + + check_verify_result( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir)) + ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + 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)) + ).set_cpl_id(cpl.id()), + }); +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2) +{ + path dir = "build/test/verify_invalid_main_picture_active_area_2"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + auto constexpr area = ""; + + HashCalculator calc(find_cpl(dir)); + + { + Editor e(find_cpl(dir)); + e.delete_lines_after(area, 2); + e.insert(area, "5125"); + e.insert(area, "9900"); + } + + dcp::PKL pkl(find_pkl(dir)); + dcp::CPL cpl(find_cpl(dir)); + + check_verify_result( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir)) + ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()), + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + 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)) + ).set_cpl_id(cpl.id()), + dcp::VerificationNote( + 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)) + ).set_cpl_id(cpl.id()) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids) +{ + RNGFixer rg; + + path dir = "build/test/verify_duplicate_pkl_asset_ids"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + { + Editor e(find_pkl(dir)); + e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab"); + } + + dcp::PKL pkl(find_pkl(dir)); + + check_verify_result( + { dir }, + {}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) }, + }); +} + + +BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids) +{ + RNGFixer rg; + + path dir = "build/test/verify_duplicate_assetmap_asset_ids"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + { + Editor e(find_asset_map(dir)); + e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa"); + } + + dcp::PKL pkl(find_pkl(dir)); + dcp::AssetMap asset_map(find_asset_map(dir)); + dcp::CPL cpl(find_cpl(dir)); + + check_verify_result( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) + ), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") + ) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts) +{ + boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts"; + + dcp::MXFMetadata mxf_meta; + mxf_meta.company_name = "OpenDCP"; + mxf_meta.product_name = "OpenDCP"; + mxf_meta.product_version = "0.0.25"; + + auto constexpr sample_rate = 48000; + auto constexpr frames = 240; + + boost::filesystem::remove_all(path); + boost::filesystem::create_directories(path); + auto dcp = make_shared(path); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + cpl->set_annotation_text("hello"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R")); + cpl->set_main_sound_sample_rate(sample_rate); + cpl->set_main_picture_stored_area(dcp::Size(1998, 1080)); + cpl->set_main_picture_active_area(dcp::Size(1998, 1080)); + cpl->set_version_number(1); + + { + + /* Reel with 2 channels of audio */ + + auto mp = simple_picture(path, "1", frames, {}); + auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2); + + auto reel = make_shared( + std::make_shared(mp, 0), + std::make_shared(ms, 0) + ); + + auto markers = make_shared(dcp::Fraction(24, 1), frames); + markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24)); + reel->add(markers); + + cpl->add(reel); + } + + { + /* Reel with 6 channels of audio */ + + auto mp = simple_picture(path, "2", frames, {}); + auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6); + + auto reel = make_shared( + std::make_shared(mp, 0), + std::make_shared(ms, 0) + ); + + auto markers = make_shared(dcp::Fraction(24, 1), frames); + markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24)); + reel->add(markers); + + cpl->add(reel); + } + + dcp->add(cpl); + dcp->set_annotation_text("hello"); + dcp->write_xml(); + + check_verify_result( + { path }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration) +{ + boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration"; + + dcp::MXFMetadata mxf_meta; + mxf_meta.company_name = "OpenDCP"; + mxf_meta.product_name = "OpenDCP"; + mxf_meta.product_version = "0.0.25"; + + auto constexpr sample_rate = 48000; + auto constexpr frames = 240; + + boost::filesystem::remove_all(path); + boost::filesystem::create_directories(path); + auto dcp = make_shared(path); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + cpl->set_annotation_text("hello"); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs")); + cpl->set_main_sound_sample_rate(sample_rate); + cpl->set_main_picture_stored_area(dcp::Size(1998, 1080)); + cpl->set_main_picture_active_area(dcp::Size(1998, 1080)); + cpl->set_version_number(1); + + auto mp = simple_picture(path, "1", frames, {}); + auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2); + + auto reel = make_shared( + std::make_shared(mp, 0), + std::make_shared(ms, 0) + ); + + auto markers = make_shared(dcp::Fraction(24, 1), frames); + markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24)); + markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24)); + reel->add(markers); + + cpl->add(reel); + + dcp->add(cpl); + dcp->set_annotation_text("hello"); + dcp->write_xml(); + + check_verify_result( + { path }, + {}, + { + dcp::VerificationNote( + 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)) + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size) +{ + boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size"; + auto constexpr video_frames = 24; + auto constexpr sample_rate = 48000; + + boost::filesystem::remove_all(path); + boost::filesystem::create_directories(path); + + auto mp = make_shared(dcp::Fraction(24, 1), dcp::Standard::SMPTE); + auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW); + + dcp::Size const size(1998, 1080); + auto image = make_shared(size); + boost::random::mt19937 rng(1); + boost::random::uniform_int_distribution<> dist(0, 4095); + for (int c = 0; c < 3; ++c) { + for (int p = 0; p < (1998 * 1080); ++p) { + image->data(c)[p] = dist(rng); + } + } + auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false); + for (int i = 0; i < 24; ++i) { + picture_writer->write(j2c.data(), j2c.size()); + } + picture_writer->finalize(); + + auto dcp = make_shared(path); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + cpl->set_content_version( + dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text") + ); + cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs")); + cpl->set_main_sound_sample_rate(sample_rate); + cpl->set_main_picture_stored_area(dcp::Size(1998, 1080)); + cpl->set_main_picture_active_area(dcp::Size(1998, 1080)); + cpl->set_version_number(1); + + auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {}); + + auto reel = make_shared( + make_shared(mp, 0), + make_shared(ms, 0) + ); + + cpl->add(reel); + dcp->add(cpl); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + + vector expected; + + for (auto frame = 0; frame < 24; frame++) { + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") + ).set_frame(frame).set_frame_rate(24).set_cpl_id(cpl->id()) + ); + } + + int component_sizes[] = { + 1321816, + 1294414, + 1289881, + }; + + for (auto frame = 0; frame < 24; frame++) { + for (auto component = 0; component < 3; component++) { + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE + ).set_frame(frame).set_component(component).set_size(component_sizes[component]).set_cpl_id(cpl->id()) + ); + } + } + + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC + ).set_cpl_id(cpl->id()) + ); + + expected.push_back( + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC + ).set_cpl_id(cpl->id()) + ); + + check_verify_result({ path }, {}, expected); +} + + +BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces) +{ + boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test"; + dcp::DCP dcp(dir); + dcp.read(); + BOOST_REQUIRE(!dcp.cpls().empty()); + auto cpl = dcp.cpls()[0]; + + check_verify_result( + { dir }, + {}, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(find_file(dir, "cpl_")) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} + ).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font) +{ + path const dir("build/test/verify_missing_load_font"); + prepare_directory (dir); + copy_file ("test/data/subs1.xml", dir / "subs.xml"); + { + Editor editor(dir / "subs.xml"); + editor.delete_first_line_containing("LoadFont"); + } + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + {dir}, + {}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId").set_cpl_id(cpl->id()) + }); + +} + + +BOOST_AUTO_TEST_CASE(verify_missing_load_font) +{ + boost::filesystem::path const dir = "build/test/verify_missing_load_font"; + prepare_directory(dir); + auto dcp = make_simple (dir, 1, 202); + + string const xml = + "" + "" + "urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a" + "Content" + "Annotation" + "2018-10-02T12:25:14+02:00" + "1" + "24 1" + "24" + "00:00:00:00" + "de-DE" + "" + "" + "" + "Hello world" + "" + "" + "" + ""; + + dcp::File xml_file(dir / "subs.xml", "w"); + BOOST_REQUIRE(xml_file); + xml_file.write(xml.c_str(), xml.size(), 1); + xml_file.close(); + auto subs = make_shared(dir / "subs.xml"); + subs->write(dir / "subs.mxf"); + + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 202, 0); + auto cpl = dcp->cpls()[0]; + cpl->reels()[0]->add(reel_subs); + dcp->write_xml(); + + check_verify_result ( + { dir }, + {}, + { + dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id()).set_cpl_id(cpl->id()) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset) +{ + boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset"; + boost::filesystem::remove_all(dir); + + auto dcp1 = make_simple(dir / "1"); + dcp1->write_xml(); + + auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id(); + + auto dcp2 = make_simple(dir / "2"); + dcp2->write_xml(); + auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id(); + + boost::filesystem::remove(dir / "1" / "video.mxf"); + boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf"); + + check_verify_result( + {dir / "1"}, + {}, + { + dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2) + }); +} + + +BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty) +{ + boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty"; + boost::filesystem::remove_all(dir); + + auto dcp = make_simple(dir); + BOOST_REQUIRE(dcp->cpls().size() == 1); + auto cpl = dcp->cpls()[0]; + cpl->set_content_version(dcp::ContentVersion("")); + dcp->write_xml(); + + check_verify_result( + {dir}, + {}, + { + dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_cpl_id(cpl->id()) + }); +} + + +/** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */ +BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp) +{ + auto const dir = path("build/test/verify_encrypted_smpte_dcp"); + dcp::Key key; + auto key_id = dcp::make_uuid(); + auto cpl = dcp_with_text(dir, {{ 4 * 24, 5 * 24 }}, key, key_id); + + dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", ""); + kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE)); + + path const pkl_file = find_file(dir, "pkl_"); + path const cpl_file = find_file(dir, "cpl_"); + + check_verify_result( + { dir }, + { kdm }, + { + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_file) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_file) + ).set_cpl_id(cpl->id()), + dcp::VerificationNote( + dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file) + ) + }); +} + +