X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=test%2Fverify_test.cc;h=e1ab50ce0851d20635cd3643a756a5273a18c7f7;hb=d4f883f7e88b93d1ec2d451036f2b85434baff13;hp=0a9852ee47d52727696d98f3930e5584fa8dd29a;hpb=9eda188e03afb8cc4b7767a3dc073069b9669230;p=libdcp.git diff --git a/test/verify_test.cc b/test/verify_test.cc index 0a9852ee..e1ab50ce 100644 --- a/test/verify_test.cc +++ b/test/verify_test.cc @@ -31,27 +31,30 @@ 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 "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 "openjpeg_image.h" +#include "raw_convert.h" +#include "reel.h" #include "reel_closed_caption_asset.h" +#include "reel_markers_asset.h" +#include "reel_mono_picture_asset.h" +#include "reel_sound_asset.h" #include "reel_stereo_picture_asset.h" #include "reel_subtitle_asset.h" -#include "compose.hpp" +#include "smpte_subtitle_asset.h" +#include "stereo_picture_asset.h" +#include "stream_operators.h" #include "test.h" +#include "util.h" +#include "verify.h" +#include "verify_j2k.h" #include -#include #include #include #include @@ -63,13 +66,21 @@ using std::vector; using std::make_pair; using std::make_shared; using boost::optional; +using namespace boost::filesystem; using std::shared_ptr; -static list > > stages; +static list>> stages; +static string const dcp_test1_pkl_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f"; +static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml"; +static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b"; +static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml"; +static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440"; +static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b"; +static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8"; static void -stage (string s, optional p) +stage (string s, optional p) { stages.push_back (make_pair (s, p)); } @@ -81,7 +92,7 @@ progress (float) } static void -prepare_directory (boost::filesystem::path path) +prepare_directory (path path) { using namespace boost::filesystem; remove_all (path); @@ -89,30 +100,40 @@ prepare_directory (boost::filesystem::path path) } -static vector -setup (int reference_number, int verify_test_number) +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); - auto cpl = make_shared("hello", dcp::FEATURE); + reel->add (simple_markers()); + + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); cpl->add (reel); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml (standard); + dcp->write_xml ( + standard, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "hello" + ); + + return cpl; } @@ -122,7 +143,7 @@ write_dcp_with_single_asset (boost::filesystem::path dir, shared_ptr 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); + } + private: - boost::filesystem::path _path; + path _path; std::string _content; }; +#if 0 static void dump_notes (vector const & notes) @@ -157,38 +200,33 @@ dump_notes (vector const & notes) std::cout << dcp::note_to_string(i) << "\n"; } } +#endif static void -check_verify_result (vector dir, vector> types_and_codes) +check_verify_result (vector dir, vector test_notes) { auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - dump_notes (notes); - BOOST_REQUIRE_EQUAL (notes.size(), types_and_codes.size()); - auto i = notes.begin(); - auto j = types_and_codes.begin(); - while (i != notes.end()) { - BOOST_CHECK_EQUAL (i->type(), j->first); - BOOST_CHECK_EQUAL (i->code(), j->second); - ++i; - ++j; + BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size()); + for (auto i = 0U; i < notes.size(); ++i) { + BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]); } } 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); BOOST_REQUIRE_EQUAL (notes.size(), codes.size()); auto i = notes.begin(); @@ -201,71 +239,74 @@ check_verify_result_after_replace (int n, boost::functionfirst, "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)); + 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); } -/* 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 dir = setup (1, "incorrect_picture_sound_hash"); - auto mod = fopen("build/test/verify_test2/video.mxf", "r+b"); + auto video_path = path(dir / "video.mxf"); + auto mod = fopen(video_path.string().c_str(), "r+b"); BOOST_REQUIRE (mod); fseek (mod, 4096, SEEK_SET); int x = 42; fwrite (&x, sizeof(x), 1, mod); fclose (mod); - mod = fopen("build/test/verify_test2/audio.mxf", "r+b"); + auto audio_path = path(dir / "audio.mxf"); + mod = fopen(audio_path.string().c_str(), "r+b"); BOOST_REQUIRE (mod); BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0); BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1); @@ -273,242 +314,250 @@ BOOST_AUTO_TEST_CASE (verify_test2) 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::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) }, }); } -/* 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"); { - Editor e ("build/test/verify_test3/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml"); + 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::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xWU0/u1wM17y7Kriq06+65/ViX1o=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 } }); } -/* 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"); { - Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml"); + Editor e (dir / dcp_test1_cpl); e.replace ("", "x"); } - auto notes = dcp::verify (directories, &stage, &progress, xsd_test); - - BOOST_REQUIRE_EQUAL (notes.size(), 1); - BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ); - BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'"); + check_verify_result ( + { dir }, + {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}} + ); } + static -boost::filesystem::path -cpl (int n) +path +cpl (string suffix) { - return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n); + 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/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml", n); + 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::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, + "invalid_xml_cpl_id", &cpl, "urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375", - { dcp::VerificationNote::XML_VALIDATION_ERROR } + { 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:cd4", "urn:uuid:xd4", - { 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:63c", "urn:uuid:x3c", - { dcp::VerificationNote::XML_VALIDATION_ERROR } + "invalix_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); - boost::filesystem::path const cpl_file = "build/test/verify_test13/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml"; - boost::filesystem::path const pkl_file = "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(), 2U); 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; + BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR); + BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K); } /* DCP with a short asset */ -BOOST_AUTO_TEST_CASE (verify_test14) +BOOST_AUTO_TEST_CASE (verify_invalid_duration) { - auto directories = setup (8, 14); + auto dir = setup (8, "invalid_duration"); check_verify_result ( - directories, + { dir }, { - { 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 } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") } }); } 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 asset = make_shared(dcp::Fraction(24, 1), dcp::Standard::SMPTE); + create_directories (dir); auto writer = asset->start_write (dir / "pic.mxf", true); for (int i = 0; i < 24; ++i) { writer->write (frame.data(), frame.size()); @@ -516,12 +565,11 @@ dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir) 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; @@ -535,19 +583,21 @@ 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"); - boost::filesystem::remove_all (dir); - dcp_from_frame (oversized_frame, dir); + path const dir("build/test/verify_invalid_picture_frame_size_in_bytes"); + prepare_directory (dir); + auto cpl = dcp_from_frame (oversized_frame, dir); check_verify_result ( { dir }, - {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES }} - ); + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -/* 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; @@ -561,57 +611,58 @@ 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"); - boost::filesystem::remove_all (dir); - dcp_from_frame (oversized_frame, dir); + path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes"); + prepare_directory (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::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -/* 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"); - boost::filesystem::remove_all (dir); - dcp_from_frame (frame, dir); + path const dir("build/test/verify_valid_picture_frame_size_in_bytes"); + prepare_directory (dir); + auto cpl = dcp_from_frame (frame, dir); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE_EQUAL (notes.size(), 0); + check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -/* 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); + 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 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); + write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP); { Editor e (dir / "subs.xml"); @@ -621,52 +672,63 @@ 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::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 }, + { + 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 + } }); } -/* DCP with valid SMPTE subtitles */ -BOOST_AUTO_TEST_CASE (verify_test20) +BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles) { - boost::filesystem::path const dir("build/test/verify_test20"); + path const dir("build/test/verify_valid_smpte_subtitles"); 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"); - 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(25, 1), 300 * 24, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE_EQUAL (notes.size(), 0); + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -/* DCP with broken SMPTE subtitles */ -BOOST_AUTO_TEST_CASE (verify_test21) +BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles) { - boost::filesystem::path const dir("build/test/verify_test21"); + using namespace boost::filesystem; + + path const dir("build/test/verify_invalid_smpte_subtitles"); prepare_directory (dir); - boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf"); + 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); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 300 * 24, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); 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::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 }, + { + 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 + }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, }); } -/* VF */ -BOOST_AUTO_TEST_CASE (verify_test22) +BOOST_AUTO_TEST_CASE (verify_external_asset) { - boost::filesystem::path const ov_dir("build/test/verify_test22_ov"); + path const ov_dir("build/test/verify_external_asset"); prepare_directory (ov_dir); auto image = black_image (); @@ -677,48 +739,59 @@ BOOST_AUTO_TEST_CASE (verify_test22) dcp::DCP ov (ov_dir); ov.read (); - boost::filesystem::path const vf_dir("build/test/verify_test22_vf"); + path const vf_dir("build/test/verify_external_asset_vf"); prepare_directory (vf_dir); - write_dcp_with_single_asset (vf_dir, ov.cpls().front()->reels().front()->main_picture()); + 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::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET }}); + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -/* DCP with valid CompositionMetadataAsset */ -BOOST_AUTO_TEST_CASE (verify_test23) +BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata) { - boost::filesystem::path const dir("build/test/verify_test23"); + path const dir("build/test/verify_valid_cpl_metadata"); 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"); auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); auto reel = make_shared(); reel->add (reel_asset); - auto cpl = make_shared("hello", dcp::FEATURE); + + reel->add (make_shared(simple_picture(dir, "", 16 * 24), 0)); + reel->add (simple_markers(16 * 24)); + + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); cpl->add (reel); cpl->set_main_sound_configuration ("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); - - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_CHECK (notes.empty()); + dcp.write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "hello" + ); } -boost::filesystem::path find_cpl (boost::filesystem::path dir) +path find_cpl (path dir) { - for (auto i: boost::filesystem::directory_iterator(dir)) { + for (auto i: directory_iterator(dir)) { if (boost::starts_with(i.path().filename().string(), "cpl_")) { return i.path(); } @@ -730,49 +803,71 @@ boost::filesystem::path find_cpl (boost::filesystem::path dir) /* 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::FEATURE); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); cpl->add (reel); cpl->set_main_sound_configuration ("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); + + reel->add (simple_markers()); dcp::DCP dcp (dir); dcp.add (cpl); - dcp.write_xml (dcp::SMPTE); + dcp.write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "hello" + ); { - 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::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 }, + { + 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()), + 75 + }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }, }); } /* 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::FEATURE); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); cpl->add (reel); cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); cpl->set_main_sound_sample_rate (48000); @@ -781,78 +876,77 @@ BOOST_AUTO_TEST_CASE (verify_test25) dcp::DCP dcp (dir); dcp.add (cpl); - dcp.write_xml (dcp::SMPTE); + dcp.write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "hello" + ); { - 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), 300 * 24, 0); reel_asset->_language = "badlang"; - write_dcp_with_single_asset (dir, reel_asset); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE_EQUAL (notes.size(), 2U); - auto i = notes.begin(); - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "badlang"); - i++; - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad"); + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, + }); } /* 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), 300 * 24, 0); reel_asset->_language = "badlang"; - write_dcp_with_single_asset (dir, reel_asset); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE_EQUAL (notes.size(), 2U); - auto i = notes.begin (); - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "badlang"); - i++; - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad"); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } /* 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"); @@ -862,7 +956,9 @@ BOOST_AUTO_TEST_CASE (verify_various_invalid_languages) auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz"); auto reel_sound = make_shared(sound, 0); reel->add (reel_sound); - auto cpl = make_shared("hello", dcp::FEATURE); + reel->add (simple_markers()); + + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); cpl->add (reel); cpl->_additional_subtitle_languages.push_back("this-is-wrong"); cpl->_additional_subtitle_languages.push_back("andso-is-this"); @@ -870,30 +966,26 @@ BOOST_AUTO_TEST_CASE (verify_various_invalid_languages) 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); cpl->_release_territory = "fred-jim"; auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "hello" + ); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE_EQUAL (notes.size(), 4U); - auto i = notes.begin (); - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "this-is-wrong"); - ++i; - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "andso-is-this"); - ++i; - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "fred-jim"); - ++i; - BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE); - BOOST_REQUIRE (i->note()); - BOOST_CHECK_EQUAL (*i->note(), "frobozz"); - ++i; + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") }, + }); } @@ -904,14 +996,13 @@ check_picture_size (int width, int height, int frame_rate, bool three_d) using namespace boost::filesystem; path dcp_path = "build/test/verify_picture_test"; - remove_all (dcp_path); - create_directories (dcp_path); + prepare_directory (dcp_path); 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); @@ -924,9 +1015,14 @@ 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::FEATURE); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); 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_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_version_number (1); auto reel = make_shared(); @@ -936,10 +1032,18 @@ check_picture_size (int width, int height, int frame_rate, bool three_d) reel->add (make_shared(std::dynamic_pointer_cast(mp), 0)); } + reel->add (simple_markers(frame_rate)); + cpl->add (reel); d->add (cpl); - d->write_xml (dcp::SMPTE); + d->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); return dcp::verify ({dcp_path}, &stage, &progress, xsd_test); } @@ -950,7 +1054,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); } @@ -961,8 +1064,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); } @@ -972,8 +1075,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); } @@ -983,8 +1086,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); } @@ -1018,7 +1121,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); @@ -1032,8 +1135,8 @@ 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); } @@ -1053,12 +1156,12 @@ 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, + dcp::VAlign::CENTER, + dcp::Direction::LTR, text, - dcp::NONE, + dcp::Effect::NONE, dcp::Colour(), dcp::Time(), dcp::Time() @@ -1067,9 +1170,9 @@ add_test_subtitle (shared_ptr asset, int start_frame, int en } -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(); @@ -1078,22 +1181,28 @@ BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large) } 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), 2049 * 24, 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") }, + { + dcp::VerificationNote::Type::BV21_ERROR, + dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, + string("413262"), + canonical(dir / "subs.mxf") + }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, }); } 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); @@ -1109,7 +1218,7 @@ 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); @@ -1117,15 +1226,16 @@ verify_timed_text_asset_too_large (string name) 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 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::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, }); } @@ -1137,9 +1247,9 @@ BOOST_AUTO_TEST_CASE (verify_subtitle_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); @@ -1173,20 +1283,26 @@ BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml) 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->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME } }); } -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"); + path path ("build/test/verify_mismatched_subtitle_languages"); auto dcp = make_simple (path, 2, 240); auto cpl = dcp->cpls()[0]; @@ -1208,21 +1324,68 @@ BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages) cpl->reels()[1]->add(reel_subs); } - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + check_verify_result ( + { path }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed) +{ + path path ("build/test/verify_multiple_closed_caption_languages_allowed"); + auto dcp = make_simple (path, 2, 240); + auto cpl = dcp->cpls()[0]; + + { + auto ccaps = make_shared(); + ccaps->set_language (dcp::LanguageTag("de-DE")); + ccaps->add (simple_subtitle()); + ccaps->write (path / "subs1.mxf"); + auto reel_ccaps = make_shared(ccaps, dcp::Fraction(24, 1), 240, 0); + cpl->reels()[0]->add(reel_ccaps); + } + + { + auto ccaps = make_shared(); + ccaps->set_language (dcp::LanguageTag("en-US")); + ccaps->add (simple_subtitle()); + ccaps->write (path / "subs2.mxf"); + auto reel_ccaps = make_shared(ccaps, dcp::Fraction(24, 1), 240, 0); + cpl->reels()[1]->add(reel_ccaps); + } + + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") } }); } -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); @@ -1256,20 +1419,26 @@ BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml) 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->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME } }); } -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); @@ -1304,13 +1473,19 @@ BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml) 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->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); 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::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME } }); } @@ -1333,8 +1508,8 @@ public: template -void -dcp_with_text (boost::filesystem::path dir, vector subs) +shared_ptr +dcp_with_text (path dir, vector subs) { prepare_directory (dir); auto asset = make_shared(); @@ -1346,34 +1521,37 @@ dcp_with_text (boost::filesystem::path dir, vector subs) 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); + 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::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); + } -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 }}); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + auto cpl = dcp_with_text (dir, {{ 4 * 24, 5 * 24 }}); + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -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(); @@ -1385,6 +1563,9 @@ BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel) auto reel_asset1 = make_shared(asset1, dcp::Fraction(24, 1), 16 * 24, 0); auto reel1 = make_shared(); reel1->add (reel_asset1); + auto markers1 = make_shared(dcp::Fraction(24, 1), 16 * 24, 0); + markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); + reel1->add (markers1); auto asset2 = make_shared(); asset2->set_start_time (dcp::Time()); @@ -1395,67 +1576,107 @@ BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel) auto reel_asset2 = make_shared(asset2, dcp::Fraction(24, 1), 16 * 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)); + reel2->add (markers2); - auto cpl = make_shared("hello", dcp::FEATURE); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); cpl->add (reel1); cpl->add (reel2); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "hello" + ); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -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 }, { 5 * 24 + 1, 6 * 24 }, }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_CLOSE }}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -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 }, }); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); +} + + +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::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -BOOST_AUTO_TEST_CASE (verify_text_too_short) +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration) { - auto const dir = boost::filesystem::path("build/test/verify_text_too_short"); - dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 1 }}); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_SHORT }}); + 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -BOOST_AUTO_TEST_CASE (verify_text_not_too_short) +BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary) { - auto const dir = boost::filesystem::path("build/test/verify_text_not_too_short"); - dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 17 }}); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + 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); + 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::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); + } -BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines1"); - dcp_with_text ( + auto const dir = path ("build/test/invalid_subtitle_line_count1"); + auto cpl = dcp_with_text ( dir, { { 96, 200, 0.0, "We" }, @@ -1463,29 +1684,33 @@ BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1) { 96, 200, 0.2, "four" }, { 96, 200, 0.3, "lines" } }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1) +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1) { - auto const dir = boost::filesystem::path ("build/test/verify_not_too_many_subtitle_lines1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_valid_subtitle_line_count1"); + auto cpl = dcp_with_text ( dir, { { 96, 200, 0.0, "We" }, { 96, 200, 0.1, "have" }, { 96, 200, 0.2, "four" }, }); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_subtitle_lines2"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_subtitle_line_count2"); + auto cpl = dcp_with_text ( dir, { { 96, 300, 0.0, "We" }, @@ -1493,14 +1718,19 @@ BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2) { 150, 180, 0.2, "four" }, { 150, 180, 0.3, "lines" } }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2) +BOOST_AUTO_TEST_CASE (verify_valid_subtitle_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_subtitle_line_count2"); + auto cpl = dcp_with_text ( dir, { { 96, 300, 0.0, "We" }, @@ -1508,39 +1738,48 @@ BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2) { 150, 180, 0.2, "four" }, { 190, 250, 0.3, "lines" } }); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1) { - auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_subtitle_line_length1"); + auto cpl = dcp_with_text ( dir, { { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" } }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_LONGER_THAN_RECOMMENDED }}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2) +BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2) { - auto const dir = boost::filesystem::path ("build/test/verify_subtitle_lines_too_long2"); - dcp_with_text ( + auto const dir = path ("build/test/verify_invalid_subtitle_line_length2"); + auto cpl = dcp_with_text ( dir, { { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" } }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_TOO_LONG }}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1) +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines1"); - dcp_with_text ( + auto const dir = path ("build/test/verify_valid_closed_caption_line_count1"); + auto cpl = dcp_with_text ( dir, { { 96, 200, 0.0, "We" }, @@ -1548,29 +1787,33 @@ BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1) { 96, 200, 0.2, "four" }, { 96, 200, 0.3, "lines" } }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT}, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1) +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2) { - 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_valid_closed_caption_line_count2"); + auto cpl = dcp_with_text ( dir, { { 96, 200, 0.0, "We" }, { 96, 200, 0.1, "have" }, { 96, 200, 0.2, "four" }, }); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2) +BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3) { - auto const dir = boost::filesystem::path ("build/test/verify_too_many_closed_caption_lines2"); - 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, "We" }, @@ -1578,14 +1821,19 @@ BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2) { 150, 180, 0.2, "four" }, { 150, 180, 0.3, "lines" } }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT}, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2) +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4) { - 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_valid_closed_caption_line_count4"); + auto cpl = dcp_with_text ( dir, { { 96, 300, 0.0, "We" }, @@ -1593,26 +1841,30 @@ BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2) { 150, 180, 0.2, "four" }, { 190, 250, 0.3, "lines" } }); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } -BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1) +BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length) { - 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_closed_caption_line_length"); + auto cpl = dcp_with_text ( dir, { { 96, 300, 0.0, "0123456789012345678901234567890123" } }); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_LINE_TOO_LONG }}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } -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"); @@ -1622,94 +1874,133 @@ BOOST_AUTO_TEST_CASE (verify_sound_sampling_rate_must_be_48k) auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000); auto reel_sound = make_shared(sound, 0); reel->add (reel_sound); - auto cpl = make_shared("hello", dcp::FEATURE); + reel->add (simple_markers()); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); cpl->add (reel); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "hello" + ); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE }}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, + }); } -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->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); + auto const cpl = dcp->cpls()[0]; + { - 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) } }); } -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->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); + auto const cpl = dcp->cpls()[0]; { - 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::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) } }); } -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"); - boost::filesystem::remove_all (dir); - boost::filesystem::create_directories (dir); + 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::FEATURE)); + shared_ptr cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER)); shared_ptr mp = simple_picture (dir, "", 24); shared_ptr ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25); - cpl->add ( - make_shared( - make_shared(mp, 0), - make_shared(ms, 0) - ) - ); + auto reel = make_shared( + make_shared(mp, 0), + make_shared(ms, 0) + ); + + reel->add (simple_markers()); + cpl->add (reel); dcp->add (cpl); - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION }}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) } + }); } 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) { - boost::filesystem::remove_all (dir); - boost::filesystem::create_directories (dir); + prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::FEATURE); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); @@ -1727,8 +2018,11 @@ verify_subtitles_must_be_in_all_reels_check (boost::filesystem::path dir, bool a reel1->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); } - cpl->add (reel1); + auto markers1 = make_shared(dcp::Fraction(24, 1), 240, 0); + 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), @@ -1739,45 +2033,60 @@ verify_subtitles_must_be_in_all_reels_check (boost::filesystem::path dir, bool a reel2->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); } + auto markers2 = make_shared(dcp::Fraction(24, 1), 240, 0); + markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24)); + reel2->add (markers2); + cpl->add (reel2); dcp->add (cpl); - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + 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); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MAIN_SUBTITLE_NOT_IN_ALL_REELS}}); + 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); + } { - boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels2"); - verify_subtitles_must_be_in_all_reels_check (dir, true, true); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } { - boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1"); - verify_subtitles_must_be_in_all_reels_check (dir, false, false); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } } 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) { - boost::filesystem::remove_all (dir); - boost::filesystem::create_directories (dir); + prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::FEATURE); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); @@ -1794,6 +2103,10 @@ verify_closed_captions_must_be_in_all_reels_check (boost::filesystem::path dir, reel1->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); } + auto markers1 = make_shared(dcp::Fraction(24, 1), 240, 0); + markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); + reel1->add (markers1); + cpl->add (reel1); auto reel2 = make_shared( @@ -1805,46 +2118,59 @@ verify_closed_captions_must_be_in_all_reels_check (boost::filesystem::path dir, reel2->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); } + auto markers2 = make_shared(dcp::Fraction(24, 1), 240, 0); + markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24)); + reel2->add (markers2); + cpl->add (reel2); dcp->add (cpl); - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + 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); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_ASSET_COUNTS_DIFFER }}); + 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } { - 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); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } { - 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); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE (notes.empty()); + 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::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } } 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) { - boost::filesystem::remove_all (dir); - boost::filesystem::create_directories (dir); + prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::FEATURE); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); @@ -1861,12 +2187,25 @@ verify_text_entry_point_check (boost::filesystem::path dir, dcp::VerificationNot reel->add (reel_text); + reel->add (simple_markers(240)); + cpl->add (reel); dcp->add (cpl); - dcp->write_xml (dcp::SMPTE); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); - check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, code }}); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); } @@ -1874,7 +2213,7 @@ BOOST_AUTO_TEST_CASE (verify_text_entry_point) { verify_text_entry_point_check ( "build/test/verify_subtitle_entry_point_must_be_present", - dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT, + dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT, [](shared_ptr asset) { asset->unset_entry_point (); } @@ -1882,7 +2221,7 @@ BOOST_AUTO_TEST_CASE (verify_text_entry_point) verify_text_entry_point_check ( "build/test/verify_subtitle_entry_point_must_be_zero", - dcp::VerificationNote::SUBTITLE_ENTRY_POINT_NON_ZERO, + dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT, [](shared_ptr asset) { asset->set_entry_point (4); } @@ -1890,7 +2229,7 @@ BOOST_AUTO_TEST_CASE (verify_text_entry_point) verify_text_entry_point_check ( "build/test/verify_closed_caption_entry_point_must_be_present", - dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT, + dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT, [](shared_ptr asset) { asset->unset_entry_point (); } @@ -1898,7 +2237,7 @@ BOOST_AUTO_TEST_CASE (verify_text_entry_point) verify_text_entry_point_check ( "build/test/verify_closed_caption_entry_point_must_be_zero", - dcp::VerificationNote::CLOSED_CAPTION_ENTRY_POINT_NON_ZERO, + dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT, [](shared_ptr asset) { asset->set_entry_point (9); } @@ -1906,26 +2245,607 @@ BOOST_AUTO_TEST_CASE (verify_text_entry_point) } -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->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); + auto const cpl = dcp->cpls()[0]; + + { + BOOST_REQUIRE (cpl->file()); + Editor e(cpl->file().get()); + e.replace("addO7je2lZSNQp55qjCWo5DLKFQ=", ""); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") } + }); +} + + +static +void +verify_markers_test ( + path dir, + vector> markers, + vector test_notes + ) +{ + auto dcp = make_simple (dir); + dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE); + auto markers_asset = make_shared(dcp::Fraction(24, 1), 24, 0); + for (auto const& i: markers) { + markers_asset->set (i.first, i.second); + } + dcp->cpls()[0]->reels()[0]->add(markers_asset); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + check_verify_result ({dir}, test_notes); +} + + +BOOST_AUTO_TEST_CASE (verify_markers) +{ + verify_markers_test ( + "build/test/verify_markers_all_correct", + { + { dcp::Marker::FFEC, dcp::Time(12, 24, 24) }, + { dcp::Marker::FFMC, dcp::Time(13, 24, 24) }, + { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }, + { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } + }, + {} + ); + + verify_markers_test ( + "build/test/verify_markers_missing_ffec", + { + { dcp::Marker::FFMC, dcp::Time(13, 24, 24) }, + { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }, + { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } + }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE } + }); + + verify_markers_test ( + "build/test/verify_markers_missing_ffmc", + { + { dcp::Marker::FFEC, dcp::Time(12, 24, 24) }, + { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }, + { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } + }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE } + }); + + verify_markers_test ( + "build/test/verify_markers_missing_ffoc", + { + { dcp::Marker::FFEC, dcp::Time(12, 24, 24) }, + { dcp::Marker::FFMC, dcp::Time(13, 24, 24) }, + { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } + }, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC} + }); + + verify_markers_test ( + "build/test/verify_markers_missing_lfoc", + { + { dcp::Marker::FFEC, dcp::Time(12, 24, 24) }, + { dcp::Marker::FFMC, dcp::Time(13, 24, 24) }, + { dcp::Marker::FFOC, dcp::Time(1, 24, 24) } + }, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC } + }); + + verify_markers_test ( + "build/test/verify_markers_incorrect_ffoc", + { + { dcp::Marker::FFEC, dcp::Time(12, 24, 24) }, + { dcp::Marker::FFMC, dcp::Time(13, 24, 24) }, + { dcp::Marker::FFOC, dcp::Time(3, 24, 24) }, + { dcp::Marker::LFOC, dcp::Time(23, 24, 24) } + }, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") } + }); + + verify_markers_test ( + "build/test/verify_markers_incorrect_lfoc", + { + { dcp::Marker::FFEC, dcp::Time(12, 24, 24) }, + { dcp::Marker::FFMC, dcp::Time(13, 24, 24) }, + { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }, + { dcp::Marker::LFOC, dcp::Time(18, 24, 24) } + }, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number) +{ + path dir = "build/test/verify_missing_cpl_metadata_version_number"; + prepare_directory (dir); + auto dcp = make_simple (dir); + auto cpl = dcp->cpls()[0]; + cpl->unset_version_number(); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }}); +} + + +BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1) +{ + path dir = "build/test/verify_missing_extension_metadata1"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::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]; { - BOOST_REQUIRE (dcp->cpls()[0]->file()); - Editor e(dcp->cpls()[0]->file().get()); - e.replace("cb1OLhgHG9svy7G8hoTSPpltzhw=", ""); + 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_HASH } + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() } }); } + +BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2) +{ + path dir = "build/test/verify_missing_extension_metadata2"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::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]; + + { + Editor e (cpl->file().get()); + e.delete_lines ("", ""); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3) +{ + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + auto const cpl = dcp->cpls()[0]; + + { + Editor e (cpl->file().get()); + e.replace ("A", "A"); + e.replace ("n", "n"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1) +{ + path dir = "build/test/verify_invalid_extension_metadata1"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::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]; + + { + Editor e (cpl->file().get()); + e.replace ("Application", "Fred"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string(" should be 'Application'"), cpl->file().get() }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2) +{ + path dir = "build/test/verify_invalid_extension_metadata2"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::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]; + + { + Editor e (cpl->file().get()); + e.replace ("DCP Constraints Profile", "Fred"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string(" property should be 'DCP Constraints Profile'"), cpl->file().get() }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6) +{ + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + auto const cpl = dcp->cpls()[0]; + + { + Editor e (cpl->file().get()); + e.replace ("", ""); + e.replace ("", ""); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 80 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7) +{ + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + auto const cpl = dcp->cpls()[0]; + + { + Editor e (cpl->file().get()); + e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string(" property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8) +{ + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + auto const cpl = dcp->cpls()[0]; + + { + Editor e (cpl->file().get()); + e.replace ("", ""); + e.replace ("", ""); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9) +{ + path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9"; + auto dcp = make_simple (dir); + dcp->write_xml ( + dcp::Standard::SMPTE, + dcp::String::compose("libdcp %1", dcp::version), + dcp::String::compose("libdcp %1", dcp::version), + dcp::LocalTime().as_string(), + "A Test DCP" + ); + + auto const cpl = dcp->cpls()[0]; + + { + Editor e (cpl->file().get()); + e.replace ("", ""); + e.replace ("", ""); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, + }); +} + + + +BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content) +{ + path dir = "build/test/verify_unsigned_cpl_with_encrypted_content"; + prepare_directory (dir); + 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 = dir / ( "cpl_" + encryption_test_cpl_id + ".xml"); + + { + Editor e (cpl); + e.delete_lines (""); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), }, + /* It's encrypted so the J2K validity checks will fail */ + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content) +{ + path dir = "build/test/unsigned_pkl_with_encrypted_content"; + prepare_directory (dir); + for (auto i: directory_iterator("test/ref/DCP/encryption_test")) { + copy_file (i.path(), dir / i.path().filename()); + } + + path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml"); + path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml"); + { + Editor e (pkl); + e.delete_lines (""); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) }, + /* It's encrypted so the J2K validity checks will fail */ + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content) +{ + path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content"; + prepare_directory (dir); + for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) { + copy_file (i.path(), dir / i.path().filename()); + } + + { + Editor e (dir / dcp_test1_pkl); + e.delete_lines (""); + } + + check_verify_result ({dir}, {}); +} + + +BOOST_AUTO_TEST_CASE (verify_partially_encrypted) +{ + path dir ("build/test/verify_must_not_be_partially_encrypted"); + prepare_directory (dir); + + dcp::DCP d (dir); + + auto signer = make_shared(); + signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem"))); + signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem"))); + 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::ContentKind::TRAILER); + + dcp::Key key; + + auto mp = make_shared(dcp::Fraction (24, 1), dcp::Standard::SMPTE); + mp->set_key (key); + + auto writer = mp->start_write (dir / "video.mxf", false); + dcp::ArrayData j2c ("test/data/flat_red.j2c"); + for (int i = 0; i < 24; ++i) { + writer->write (j2c.data(), j2c.size()); + } + writer->finalize (); + + auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE"); + + auto reel = make_shared( + make_shared(mp, 0), + make_shared(ms, 0) + ); + + reel->add (simple_markers()); + + cpl->add (reel); + + cpl->set_content_version ( + {"urn:uri:81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00", "81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00"} + ); + cpl->set_annotation_text ("A Test DCP"); + 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_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); + + d.add (cpl); + + d.write_xml (dcp::Standard::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer); + + check_verify_result ( + {dir}, + { + {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}, + /* It's encrypted so the J2K validity checks will fail */ + {dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")} + }); +} + + +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, 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, 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 (dcp::Standard::SMPTE); + vector notes; + dcp::MonoPictureAsset picture (find_file(dir, "video")); + auto reader = picture.start_read (); + auto frame = reader->get_frame (0); + verify_j2k (frame, notes); + BOOST_REQUIRE_EQUAL (notes.size(), 0U); +} +