X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=test%2Fverify_test.cc;h=cde498309fe7f52776611dd67f1cf9ea905ffb22;hb=83c3a53c4b44b48823ecdece80bd6c246929ae0b;hp=e3efb57f7c1104e70974034d6ea2ec873b377e46;hpb=14d1f9d76f289a6447a58f03813c771c86c7d8af;p=libdcp.git diff --git a/test/verify_test.cc b/test/verify_test.cc index e3efb57f..cde49830 100644 --- a/test/verify_test.cc +++ b/test/verify_test.cc @@ -31,9 +31,11 @@ files in the program, then also delete it here. */ + #include "compose.hpp" #include "cpl.h" #include "dcp.h" +#include "file.h" #include "interop_subtitle_asset.h" #include "j2k_transcode.h" #include "mono_picture_asset.h" @@ -41,12 +43,14 @@ #include "openjpeg_image.h" #include "raw_convert.h" #include "reel.h" -#include "reel_closed_caption_asset.h" +#include "reel_interop_closed_caption_asset.h" +#include "reel_interop_subtitle_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 "reel_smpte_closed_caption_asset.h" +#include "reel_smpte_subtitle_asset.h" #include "smpte_subtitle_asset.h" #include "stereo_picture_asset.h" #include "stream_operators.h" @@ -59,21 +63,38 @@ #include #include + using std::list; +using std::make_pair; +using std::make_shared; using std::pair; +using std::shared_ptr; using std::string; using std::vector; -using std::make_pair; -using std::make_shared; using boost::optional; using namespace boost::filesystem; -using std::shared_ptr; static list>> stages; -static string const dcp_test1_pkl = "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.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 filename_to_id(boost::filesystem::path path) +{ + return path.string().substr(4, path.string().length() - 8); +} + +static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename(); +static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl); + +static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename(); +static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl); + +static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3"; + +static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename(); +static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl); + +static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename(); +static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl); static void stage (string s, optional p) @@ -96,6 +117,9 @@ prepare_directory (path path) } +/** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix} + * to make a new sacrifical test DCP. + */ static path setup (int reference_number, string verify_test_suffix) { @@ -117,17 +141,12 @@ write_dcp_with_single_asset (path dir, shared_ptr reel_asset, dc reel->add (reel_asset); reel->add (simple_markers()); - auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, standard); cpl->add (reel); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml ( - standard, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp->set_annotation_text("hello"); + dcp->write_xml (); return cpl; } @@ -153,19 +172,50 @@ public: fclose (f); } + class ChangeChecker + { + public: + ChangeChecker(Editor* editor) + : _editor(editor) + { + _old_content = _editor->_content; + } + + ~ChangeChecker() + { + BOOST_REQUIRE(_old_content != _editor->_content); + } + private: + Editor* _editor; + std::string _old_content; + }; + void replace (string a, string b) { - auto old_content = _content; + ChangeChecker cc(this); boost::algorithm::replace_all (_content, a, b); - BOOST_REQUIRE (_content != old_content); + } + + void delete_first_line_containing (string s) + { + ChangeChecker cc(this); + auto lines = as_lines(); + _content = ""; + bool done = false; + for (auto i: lines) { + if (i.find(s) == string::npos || done) { + _content += i + "\n"; + } else { + done = true; + } + } } void delete_lines (string from, string to) { - vector lines; - boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on); + ChangeChecker cc(this); + auto lines = as_lines(); bool deleting = false; - auto old_content = _content; _content = ""; for (auto i: lines) { if (i.find(from) != string::npos) { @@ -178,15 +228,60 @@ public: deleting = false; } } - BOOST_REQUIRE (_content != old_content); + } + + void insert (string after, string line) + { + ChangeChecker cc(this); + auto lines = as_lines(); + _content = ""; + bool replaced = false; + for (auto i: lines) { + _content += i + "\n"; + if (!replaced && i.find(after) != string::npos) { + _content += line + "\n"; + replaced = true; + } + } + } + + void delete_lines_after(string after, int lines_to_delete) + { + ChangeChecker cc(this); + auto lines = as_lines(); + _content = ""; + auto iter = std::find_if(lines.begin(), lines.end(), [after](string const& line) { + return line.find(after) != string::npos; + }); + int to_delete = 0; + for (auto i = lines.begin(); i != lines.end(); ++i) { + if (i == iter) { + to_delete = lines_to_delete; + _content += *i + "\n"; + } else if (to_delete == 0) { + _content += *i + "\n"; + } else { + --to_delete; + } + } } private: + friend class ChangeChecker; + + vector as_lines() const + { + vector lines; + boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on); + return lines; + } + path _path; std::string _content; }; +LIBDCP_DISABLE_WARNINGS static void dump_notes (vector const & notes) @@ -195,20 +290,50 @@ dump_notes (vector const & notes) std::cout << dcp::note_to_string(i) << "\n"; } } +LIBDCP_ENABLE_WARNINGS static void check_verify_result (vector dir, vector test_notes) { - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size()); - for (auto i = 0U; i < notes.size(); ++i) { - BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]); + auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test); + std::sort (notes.begin(), notes.end()); + std::sort (test_notes.begin(), test_notes.end()); + + string message = "\nVerification notes from test:\n"; + for (auto i: notes) { + message += " " + note_to_string(i) + "\n"; + message += dcp::String::compose( + " [%1 %2 %3 %4 %5]\n", + static_cast(i.type()), + static_cast(i.code()), + i.note().get_value_or(""), + i.file().get_value_or(""), + i.line().get_value_or(0) + ); } + message += "Expected:\n"; + for (auto i: test_notes) { + message += " " + note_to_string(i) + "\n"; + message += dcp::String::compose( + " [%1 %2 %3 %4 %5]\n", + static_cast(i.type()), + static_cast(i.code()), + i.note().get_value_or(""), + i.file().get_value_or(""), + i.line().get_value_or(0) + ); + } + + BOOST_REQUIRE_MESSAGE (notes == test_notes, message); } +/* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file', + * replacing from with to. Verify the resulting DCP and check that the results match the given + * list of codes. + */ static void check_verify_result_after_replace (string suffix, boost::function file, string from, string to, vector codes) @@ -220,7 +345,7 @@ check_verify_result_after_replace (string suffix, boost::function e.replace (from, to); } - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); + auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test); BOOST_REQUIRE_EQUAL (notes.size(), codes.size()); auto i = notes.begin(); @@ -237,7 +362,7 @@ BOOST_AUTO_TEST_CASE (verify_no_error) { stages.clear (); auto dir = setup (1, "no_error"); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); + auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test); path const cpl_file = dir / dcp_test1_cpl; path const pkl_file = dir / dcp_test1_pkl; @@ -281,7 +406,7 @@ BOOST_AUTO_TEST_CASE (verify_no_error) ++st; BOOST_REQUIRE (st == stages.end()); - BOOST_CHECK_EQUAL (notes.size(), 0); + BOOST_CHECK_EQUAL (notes.size(), 0U); } @@ -333,9 +458,9 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes) { 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 'xX3bMCBdXEOYEpYmsConNWrWUAGs=' 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 'xqtXbkcwhUj/yqquVLmV+wbzbxQ8=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 } + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 28 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 20 }, }); } @@ -351,8 +476,10 @@ BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind) check_verify_result ( { dir }, - {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}} - ); + { + { 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::INVALID_CONTENT_KIND, string("xtrailer") } + }); } @@ -433,7 +560,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id) /* 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 ( "invalid_xml_cpl_id", &cpl, - "urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375", + "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a", { dcp::VerificationNote::Code::INVALID_XML } ); } @@ -454,7 +581,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id) { check_verify_result_after_replace ( "invalid_xml_pkl_id", &pkl, - "urn:uuid:2b9", "urn:uuid:xb9", + "urn:uuid:" + dcp_test1_pkl_id.substr(0, 3), + "urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2), { dcp::VerificationNote::Code::INVALID_XML } ); } @@ -463,8 +591,9 @@ BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id) BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id) { check_verify_result_after_replace ( - "invalix_xml_asset_map_id", &asset_map, - "urn:uuid:07e", "urn:uuid:x7e", + "invalid_xml_asset_map_id", &asset_map, + "urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3), + "urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2), { dcp::VerificationNote::Code::INVALID_XML } ); } @@ -474,7 +603,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_standard) { stages.clear (); auto dir = setup (3, "verify_invalid_standard"); - auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); + auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test); path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml"; path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml"; @@ -518,10 +647,13 @@ BOOST_AUTO_TEST_CASE (verify_invalid_standard) ++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::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 */ @@ -535,7 +667,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_duration) { 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::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") } }); } @@ -578,6 +711,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes) check_verify_result ( { dir }, { + { 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() } }); @@ -605,6 +739,7 @@ BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes) check_verify_result ( { dir }, { + { 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() } }); @@ -632,7 +767,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles) prepare_directory (dir); 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); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP); check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }}); @@ -647,7 +782,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles) prepare_directory (dir); 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); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP); { @@ -671,16 +806,40 @@ BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles) } +BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles) +{ + path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles"); + prepare_directory(dir); + copy_file("test/data/subs4.xml", dir / "subs.xml"); + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) }, + }); + +} + + BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles) { path const dir("build/test/verify_valid_smpte_subtitles"); prepare_directory (dir); copy_file ("test/data/subs.mxf", dir / "subs.mxf"); auto asset = make_shared(dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); auto cpl = write_dcp_with_single_asset (dir, reel_asset); - check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); + check_verify_result( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} } + }); } @@ -690,9 +849,10 @@ BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles) path const dir("build/test/verify_invalid_smpte_subtitles"); prepare_directory (dir); + /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */ copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf"); auto asset = make_shared(dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( @@ -708,6 +868,66 @@ BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles) }, { 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() }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles) +{ + path const dir("build/test/verify_empty_text_node_in_subtitles"); + prepare_directory (dir); + copy_file ("test/data/empty_text.mxf", dir / "subs.mxf"); + auto asset = make_shared(dir / "subs.mxf"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 192, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} } + }); +} + + +/** A node with no content except some nodes, which themselves do have content */ +BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes) +{ + path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes"); + prepare_directory (dir); + copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml"); + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 192, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + }); +} + + +/** A node with no content except some nodes, which themselves also have no content */ +BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes) +{ + path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes"); + prepare_directory (dir); + copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml"); + auto asset = make_shared(dir / "subs.xml"); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 192, 0); + auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT }, }); } @@ -747,7 +967,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata) 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_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); auto reel = make_shared(); reel->add (reel_asset); @@ -755,7 +975,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata) reel->add (make_shared(simple_picture(dir, "", 16 * 24), 0)); reel->add (simple_markers(16 * 24)); - auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); cpl->set_main_sound_sample_rate (48000); @@ -765,26 +985,40 @@ BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata) dcp::DCP dcp (dir); dcp.add (cpl); - 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" - ); + dcp.set_annotation_text("hello"); + dcp.write_xml (); +} + + +path +find_prefix(path dir, string prefix) +{ + auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) { + return boost::starts_with(p.filename().string(), prefix); + }); + + BOOST_REQUIRE(iter != directory_iterator()); + return iter->path(); } path find_cpl (path dir) { - for (auto i: directory_iterator(dir)) { - if (boost::starts_with(i.path().filename().string(), "cpl_")) { - return i.path(); - } - } + return find_prefix(dir, "cpl_"); +} + - BOOST_REQUIRE (false); - return {}; +path +find_pkl(path dir) +{ + return find_prefix(dir, "pkl_"); +} + + +path +find_asset_map(path dir) +{ + return find_prefix(dir, "ASSETMAP"); } @@ -798,7 +1032,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag) auto reel = make_shared(); reel->add (black_picture_asset(dir)); - auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); cpl->set_main_sound_sample_rate (48000); @@ -810,13 +1044,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag) dcp::DCP dcp (dir); dcp.add (cpl); - 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" - ); + dcp.set_annotation_text("hello"); + dcp.write_xml(); { Editor e (find_cpl(dir)); @@ -826,8 +1055,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag) check_verify_result ( { dir }, { - { 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("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 }, { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, @@ -838,7 +1067,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag) "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?," "ExtensionMetadataList?,)'"), canonical(cpl->file().get()), - 75 + 71 }, { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }, }); @@ -853,7 +1082,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag) auto reel = make_shared(); reel->add (black_picture_asset(dir)); - auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); cpl->set_main_sound_sample_rate (48000); @@ -862,13 +1091,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag) dcp::DCP dcp (dir); dcp.add (cpl); - 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" - ); + dcp.set_annotation_text("hello"); + dcp.write_xml(); { Editor e (find_cpl(dir)); @@ -890,7 +1114,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_language1) auto asset = make_shared(dir / "subs.mxf"); asset->_language = "wrong-andbad"; asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); reel_asset->_language = "badlang"; auto cpl = write_dcp_with_single_asset (dir, reel_asset); @@ -913,7 +1137,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_language2) auto asset = make_shared(dir / "subs.mxf"); asset->_language = "wrong-andbad"; asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 6046, 0); reel_asset->_language = "badlang"; auto cpl = write_dcp_with_single_asset (dir, reel_asset); @@ -944,7 +1168,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_language3) reel->add (reel_sound); reel->add (simple_markers()); - auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); cpl->_additional_subtitle_languages.push_back("this-is-wrong"); cpl->_additional_subtitle_languages.push_back("andso-is-this"); @@ -956,13 +1180,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_language3) cpl->_release_territory = "fred-jim"; auto dcp = make_shared(dir); dcp->add (cpl); - 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" - ); + dcp->set_annotation_text("hello"); + dcp->write_xml(); check_verify_result ( { dir }, @@ -1001,13 +1220,13 @@ check_picture_size (int width, int height, int frame_rate, bool three_d) picture_writer->finalize (); auto d = make_shared(dcp_path); - auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->set_annotation_text ("A Test DCP"); cpl->set_issue_date ("2012-07-17T04:45:18+00:00"); cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-"); cpl->set_main_sound_sample_rate (48000); - cpl->set_main_picture_stored_area (dcp::Size(1998, 1080)); - cpl->set_main_picture_active_area (dcp::Size(1998, 1080)); + cpl->set_main_picture_stored_area(dcp::Size(width, height)); + cpl->set_main_picture_active_area(dcp::Size(width, height)); cpl->set_version_number (1); auto reel = make_shared(); @@ -1023,15 +1242,10 @@ check_picture_size (int width, int height, int frame_rate, bool three_d) cpl->add (reel); d->add (cpl); - 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" - ); + d->set_annotation_text("A Test DCP"); + d->write_xml(); - return dcp::verify ({dcp_path}, &stage, &progress, xsd_test); + return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test); } @@ -1040,7 +1254,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); } @@ -1108,7 +1321,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); @@ -1129,10 +1342,10 @@ BOOST_AUTO_TEST_CASE (verify_picture_size) static void -add_test_subtitle (shared_ptr asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello") +add_test_subtitle (shared_ptr asset, int start_frame, int end_frame, float v_position = 0, dcp::VAlign v_align = dcp::VAlign::CENTER, string text = "Hello") { asset->add ( - make_shared( + std::make_shared( optional(), false, false, @@ -1145,13 +1358,15 @@ add_test_subtitle (shared_ptr asset, int start_frame, int en 0, dcp::HAlign::CENTER, v_position, - dcp::VAlign::CENTER, + v_align, + 0, dcp::Direction::LTR, text, dcp::Effect::NONE, dcp::Colour(), dcp::Time(), - dcp::Time() + dcp::Time(), + 0 ) ); } @@ -1168,7 +1383,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes) } 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); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 49148, 0); auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( @@ -1178,7 +1393,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes) { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, - string("413262"), + string("419336"), canonical(dir / "subs.mxf") }, { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }, @@ -1208,17 +1423,17 @@ verify_timed_text_asset_too_large (string name) auto const dir = path("build/test") / name; prepare_directory (dir); auto asset = make_large_subtitle_asset (dir / "font.ttf"); - add_test_subtitle (asset, 0, 20); + add_test_subtitle (asset, 0, 240); asset->set_language (dcp::LanguageTag("de-DE")); asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), 240, 0); auto cpl = write_dcp_with_single_asset (dir, reel_asset); check_verify_result ( { dir }, { - { dcp::VerificationNote::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_SIZE_IN_BYTES, string("121695532"), 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 }, @@ -1229,8 +1444,8 @@ verify_timed_text_asset_too_large (string name) BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large) { - verify_timed_text_asset_too_large("verify_subtitle_asset_too_large"); - verify_timed_text_asset_too_large("verify_closed_caption_asset_too_large"); + verify_timed_text_asset_too_large("verify_subtitle_asset_too_large"); + verify_timed_text_asset_too_large("verify_closed_caption_asset_too_large"); } @@ -1238,7 +1453,7 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language) { path dir = "build/test/verify_missing_subtitle_language"; prepare_directory (dir); - auto dcp = make_simple (dir, 1, 240); + auto dcp = make_simple (dir, 1, 106); string const xml = "" @@ -1248,8 +1463,8 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language) "Annotation" "2018-10-02T12:25:14+02:00" "1" - "25 1" - "25" + "24 1" + "24" "00:00:00:00" "urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6" "" @@ -1261,22 +1476,17 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language) "" ""; - auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w"); + dcp::File xml_file(dir / "subs.xml", "w"); BOOST_REQUIRE (xml_file); - fwrite (xml.c_str(), xml.size(), 1, xml_file); - fclose (xml_file); + xml_file.write(xml.c_str(), xml.size(), 1); + xml_file.close(); auto subs = make_shared(dir / "subs.xml"); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); - dcp->cpls().front()->reels().front()->add(reel_subs); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 106, 0); + dcp->cpls()[0]->reels()[0]->add(reel_subs); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ( { dir }, @@ -1290,7 +1500,8 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language) BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages) { path path ("build/test/verify_mismatched_subtitle_languages"); - auto dcp = make_simple (path, 2, 240); + auto constexpr reel_length = 192; + auto dcp = make_simple (path, 2, reel_length); auto cpl = dcp->cpls()[0]; { @@ -1298,7 +1509,7 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages) subs->set_language (dcp::LanguageTag("de-DE")); subs->add (simple_subtitle()); subs->write (path / "subs1.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); cpl->reels()[0]->add(reel_subs); } @@ -1307,23 +1518,55 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages) subs->set_language (dcp::LanguageTag("en-US")); subs->add (simple_subtitle()); subs->write (path / "subs2.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); cpl->reels()[1]->add(reel_subs); } - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + + check_verify_result ( + { 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 constexpr reel_length = 192; + auto dcp = make_simple (path, 2, reel_length); + auto cpl = dcp->cpls()[0]; + + { + auto ccaps = make_shared(); + ccaps->set_language (dcp::LanguageTag("de-DE")); + ccaps->add (simple_subtitle()); + ccaps->write (path / "subs1.mxf"); + auto reel_ccaps = make_shared(ccaps, dcp::Fraction(24, 1), reel_length, 0); + cpl->reels()[0]->add(reel_ccaps); + } + + { + auto ccaps = make_shared(); + ccaps->set_language (dcp::LanguageTag("en-US")); + ccaps->add (simple_subtitle()); + ccaps->write (path / "subs2.mxf"); + auto reel_ccaps = make_shared(ccaps, dcp::Fraction(24, 1), reel_length, 0); + cpl->reels()[1]->add(reel_ccaps); + } + + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); 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::MISMATCHED_SUBTITLE_LANGUAGES }, { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") } }); } @@ -1333,7 +1576,7 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time) { path dir = "build/test/verify_missing_subtitle_start_time"; prepare_directory (dir); - auto dcp = make_simple (dir, 1, 240); + auto dcp = make_simple (dir, 1, 106); string const xml = "" @@ -1344,8 +1587,8 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time) "2018-10-02T12:25:14+02:00" "1" "de-DE" - "25 1" - "25" + "24 1" + "24" "urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6" "" "" @@ -1356,22 +1599,17 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time) "" ""; - auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w"); + dcp::File xml_file(dir / "subs.xml", "w"); BOOST_REQUIRE (xml_file); - fwrite (xml.c_str(), xml.size(), 1, xml_file); - fclose (xml_file); + xml_file.write(xml.c_str(), xml.size(), 1); + xml_file.close(); auto subs = make_shared(dir / "subs.xml"); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); - dcp->cpls().front()->reels().front()->add(reel_subs); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 106, 0); + dcp->cpls()[0]->reels()[0]->add(reel_subs); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ( { dir }, @@ -1386,7 +1624,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time) { path dir = "build/test/verify_invalid_subtitle_start_time"; prepare_directory (dir); - auto dcp = make_simple (dir, 1, 240); + auto dcp = make_simple (dir, 1, 106); string const xml = "" @@ -1397,8 +1635,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time) "2018-10-02T12:25:14+02:00" "1" "de-DE" - "25 1" - "25" + "24 1" + "24" "00:00:02:00" "urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6" "" @@ -1410,22 +1648,17 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time) "" ""; - auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w"); + dcp::File xml_file(dir / "subs.xml", "w"); BOOST_REQUIRE (xml_file); - fwrite (xml.c_str(), xml.size(), 1, xml_file); - fclose (xml_file); + xml_file.write(xml.c_str(), xml.size(), 1); + xml_file.close(); auto subs = make_shared(dir / "subs.xml"); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 106, 0); dcp->cpls().front()->reels().front()->add(reel_subs); - 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ( { dir }, @@ -1439,16 +1672,18 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time) class TestText { public: - TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello") + TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello") : in(in_) , out(out_) , v_position(v_position_) + , v_align(v_align_) , text(text_) {} int in; int out; float v_position; + dcp::VAlign v_align; string text; }; @@ -1461,12 +1696,49 @@ dcp_with_text (path dir, vector subs) auto asset = make_shared(); asset->set_start_time (dcp::Time()); for (auto i: subs) { - add_test_subtitle (asset, i.in, i.out, i.v_position, i.text); + add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text); } asset->set_language (dcp::LanguageTag("de-DE")); 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), asset->intrinsic_duration(), 0); + return write_dcp_with_single_asset (dir, reel_asset); +} + + +template +shared_ptr +dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml) +{ + prepare_directory (dir); + auto asset = make_shared(subs_xml); + asset->set_start_time (dcp::Time()); + asset->set_language (dcp::LanguageTag("de-DE")); + + auto subs_mxf = dir / "subs.mxf"; + asset->write (subs_mxf); + + /* The call to write() puts the asset into the DCP correctly but it will have + * XML re-written by our parser. Overwrite the MXF using the given file's verbatim + * contents. + */ + ASDCP::TimedText::MXFWriter writer; + ASDCP::WriterInfo writer_info; + writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE; + unsigned int c; + Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + ASDCP::TimedText::TimedTextDescriptor descriptor; + descriptor.ContainerDuration = asset->intrinsic_duration(); + Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384); + BOOST_REQUIRE (!ASDCP_FAILURE(r)); + r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml)); + BOOST_REQUIRE (!ASDCP_FAILURE(r)); + writer.Finalize (); + + auto reel_asset = make_shared(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0); return write_dcp_with_single_asset (dir, reel_asset); } @@ -1475,7 +1747,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time) { auto const dir = path("build/test/verify_invalid_subtitle_first_text_time"); /* Just too early */ - auto cpl = dcp_with_text (dir, {{ 4 * 24 - 1, 5 * 24 }}); + auto cpl = dcp_with_text (dir, {{ 4 * 24 - 1, 5 * 24 }}); check_verify_result ( { dir }, { @@ -1490,7 +1762,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time) { auto const dir = path("build/test/verify_valid_subtitle_first_text_time"); /* Just late enough */ - auto cpl = dcp_with_text (dir, {{ 4 * 24, 5 * 24 }}); + 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() }}); } @@ -1506,39 +1778,33 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel) add_test_subtitle (asset1, 4 * 24, 5 * 24); asset1->set_language (dcp::LanguageTag("de-DE")); asset1->write (dir / "subs1.mxf"); - auto reel_asset1 = make_shared(asset1, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset1 = make_shared(asset1, dcp::Fraction(24, 1), 5 * 24, 0); auto reel1 = make_shared(); reel1->add (reel_asset1); - auto markers1 = make_shared(dcp::Fraction(24, 1), 16 * 24, 0); + auto markers1 = make_shared(dcp::Fraction(24, 1), 5 * 24); markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); reel1->add (markers1); auto asset2 = make_shared(); asset2->set_start_time (dcp::Time()); /* This would be too early on first reel but should be OK on the second */ - add_test_subtitle (asset2, 0, 4 * 24); + add_test_subtitle (asset2, 3, 4 * 24); asset2->set_language (dcp::LanguageTag("de-DE")); asset2->write (dir / "subs2.mxf"); - auto reel_asset2 = make_shared(asset2, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset2 = make_shared(asset2, dcp::Fraction(24, 1), 4 * 24, 0); auto reel2 = make_shared(); reel2->add (reel_asset2); - auto markers2 = make_shared(dcp::Fraction(24, 1), 16 * 24, 0); - markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24)); + auto markers2 = make_shared(dcp::Fraction(24, 1), 4 * 24); + markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24)); reel2->add (markers2); - auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel1); cpl->add (reel2); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); - + dcp->set_annotation_text("hello"); + dcp->write_xml(); check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } @@ -1547,7 +1813,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel) BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing) { auto const dir = path("build/test/verify_invalid_subtitle_spacing"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { { 4 * 24, 5 * 24 }, @@ -1565,7 +1831,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing) BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing) { auto const dir = path("build/test/verify_valid_subtitle_spacing"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { { 4 * 24, 5 * 24 }, @@ -1578,7 +1844,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing) 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 }}); + auto cpl = dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 1 }}); check_verify_result ( {dir}, { @@ -1591,21 +1857,45 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration) BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration) { auto const dir = path("build/test/verify_valid_subtitle_duration"); - auto cpl = dcp_with_text (dir, {{ 4 * 24, 4 * 24 + 17 }}); + 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_subtitle_overlapping_reel_boundary) +{ + auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary"); + prepare_directory (dir); + auto asset = make_shared(); + asset->set_start_time (dcp::Time()); + add_test_subtitle (asset, 0, 4 * 24); + 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::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) }, + { 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_invalid_subtitle_line_count1) { auto const dir = path ("build/test/invalid_subtitle_line_count1"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, - { 96, 200, 0.3, "lines" } + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, + { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ( {dir}, @@ -1619,12 +1909,12 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1) BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1) { auto const dir = path ("build/test/verify_valid_subtitle_line_count1"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, }); check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } @@ -1633,13 +1923,13 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1) BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2) { auto const dir = path ("build/test/verify_invalid_subtitle_line_count2"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 150, 180, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ( {dir}, @@ -1653,13 +1943,13 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2) BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2) { auto const dir = path ("build/test/verify_valid_subtitle_line_count2"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 190, 250, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } @@ -1668,10 +1958,10 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2) BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1) { auto const dir = path ("build/test/verify_invalid_subtitle_line_length1"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" } }); check_verify_result ( {dir}, @@ -1685,10 +1975,10 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1) BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2) { auto const dir = path ("build/test/verify_invalid_subtitle_line_length2"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" } }); check_verify_result ( {dir}, @@ -1702,13 +1992,13 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2) BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1) { auto const dir = path ("build/test/verify_valid_closed_caption_line_count1"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, - { 96, 200, 0.3, "lines" } + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, + { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ( {dir}, @@ -1722,12 +2012,12 @@ BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1) BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2) { auto const dir = path ("build/test/verify_valid_closed_caption_line_count2"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 200, 0.0, "We" }, - { 96, 200, 0.1, "have" }, - { 96, 200, 0.2, "four" }, + { 96, 200, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 200, 0.1, dcp::VAlign::CENTER, "have" }, + { 96, 200, 0.2, dcp::VAlign::CENTER, "four" }, }); check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } @@ -1736,13 +2026,13 @@ BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2) BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3) { auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 150, 180, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ( {dir}, @@ -1756,25 +2046,41 @@ BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3) BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4) { auto const dir = path ("build/test/verify_valid_closed_caption_line_count4"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "We" }, - { 96, 300, 0.1, "have" }, - { 150, 180, 0.2, "four" }, - { 190, 250, 0.3, "lines" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "We" }, + { 96, 300, 0.1, dcp::VAlign::CENTER, "have" }, + { 150, 180, 0.2, dcp::VAlign::CENTER, "four" }, + { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" } }); check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }}); } +BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length) +{ + auto const dir = path ("build/test/verify_valid_closed_caption_line_length"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" } + }); + 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_closed_caption_line_length) { auto const dir = path ("build/test/verify_invalid_closed_caption_line_length"); - auto cpl = dcp_with_text ( + auto cpl = dcp_with_text ( dir, { - { 96, 300, 0.0, "0123456789012345678901234567890123" } + { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" } }); check_verify_result ( {dir}, @@ -1785,6 +2091,105 @@ BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length) } +BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1) +{ + auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.0, dcp::VAlign::TOP, "This" }, + { 96, 300, 0.1, dcp::VAlign::TOP, "is" }, + { 96, 300, 0.2, dcp::VAlign::TOP, "fine" }, + }); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2) +{ + auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.0, dcp::VAlign::TOP, "This" }, + { 96, 300, 0.1, dcp::VAlign::TOP, "is" }, + { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" }, + }); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1) +{ + auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.0, dcp::VAlign::TOP, "This" }, + { 96, 300, 0.1, dcp::VAlign::TOP, "is" }, + { 96, 300, 0.2, dcp::VAlign::TOP, "fine" }, + }); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2) +{ + auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2"); + auto cpl = dcp_with_text ( + dir, + { + { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" }, + { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" }, + { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" }, + }); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3) +{ + auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3"); + auto cpl = dcp_with_text_from_file (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml"); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4) +{ + auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4"); + auto cpl = dcp_with_text_from_file (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml"); + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + }); +} + + + BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate) { path const dir("build/test/verify_invalid_sound_frame_rate"); @@ -1794,21 +2199,16 @@ BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate) auto reel_picture = make_shared(picture, 0); auto reel = make_shared(); reel->add (reel_picture); - auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000); + auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none); auto reel_sound = make_shared(sound, 0); reel->add (reel_sound); reel->add (simple_markers()); - auto cpl = make_shared("hello", dcp::ContentKind::TRAILER); + auto cpl = make_shared("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); cpl->add (reel); auto dcp = make_shared(dir); dcp->add (cpl); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "hello" - ); + dcp->set_annotation_text("hello"); + dcp->write_xml(); check_verify_result ( {dir}, @@ -1823,13 +2223,8 @@ BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text) { path const dir("build/test/verify_missing_cpl_annotation_text"); 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); @@ -1854,13 +2249,8 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text) { path const dir("build/test/verify_mismatched_cpl_annotation_text"); 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); auto const cpl = dcp->cpls()[0]; @@ -1885,7 +2275,7 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration) 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::ContentKind::TRAILER)); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); shared_ptr mp = simple_picture (dir, "", 24); shared_ptr ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25); @@ -1899,13 +2289,8 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration) cpl->add (reel); dcp->add (cpl); - 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ( {dir}, @@ -1923,53 +2308,50 @@ verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool a { prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + + auto constexpr reel_length = 192; auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); subs->set_start_time (dcp::Time()); subs->add (simple_subtitle()); subs->write (dir / "subs.mxf"); - auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_subs = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); auto reel1 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "", reel_length), 0), + make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0) ); if (add_to_reel1) { - reel1->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel1->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers1 = make_shared(dcp::Fraction(24, 1), 240, 0); + auto markers1 = make_shared(dcp::Fraction(24, 1), reel_length); markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); reel1->add (markers1); cpl->add (reel1); auto reel2 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "", reel_length), 0), + make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0) ); if (add_to_reel2) { - reel2->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel2->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers2 = make_shared(dcp::Fraction(24, 1), 240, 0); - markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24)); + auto markers2 = make_shared(dcp::Fraction(24, 1), reel_length); + markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24)); reel2->add (markers2); cpl->add (reel2); dcp->add (cpl); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); return cpl; } @@ -1984,7 +2366,7 @@ BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels) { 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() } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, }); } @@ -2009,7 +2391,9 @@ verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, { prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + + auto constexpr reel_length = 192; auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); @@ -2018,43 +2402,38 @@ verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, subs->write (dir / "subs.mxf"); auto reel1 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "", reel_length), 0), + make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0) ); for (int i = 0; i < caps_in_reel1; ++i) { - reel1->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel1->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers1 = make_shared(dcp::Fraction(24, 1), 240, 0); + auto markers1 = make_shared(dcp::Fraction(24, 1), reel_length); markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24)); reel1->add (markers1); cpl->add (reel1); auto reel2 = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "", reel_length), 0), + make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0) ); for (int i = 0; i < caps_in_reel2; ++i) { - reel2->add (make_shared(subs, dcp::Fraction(24, 1), 240, 0)); + reel2->add (make_shared(subs, dcp::Fraction(24, 1), reel_length, 0)); } - auto markers2 = make_shared(dcp::Fraction(24, 1), 240, 0); - markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24)); + auto markers2 = make_shared(dcp::Fraction(24, 1), reel_length); + markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24)); reel2->add (markers2); cpl->add (reel2); dcp->add (cpl); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); return cpl; } @@ -2093,75 +2472,72 @@ verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost { prepare_directory (dir); auto dcp = make_shared(dir); - auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); + + auto constexpr reel_length = 192; auto subs = make_shared(); subs->set_language (dcp::LanguageTag("de-DE")); subs->set_start_time (dcp::Time()); subs->add (simple_subtitle()); subs->write (dir / "subs.mxf"); - auto reel_text = make_shared(subs, dcp::Fraction(24, 1), 240, 0); + auto reel_text = make_shared(subs, dcp::Fraction(24, 1), reel_length, 0); adjust (reel_text); auto reel = make_shared( - make_shared(simple_picture(dir, "", 240), 0), - make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0) + make_shared(simple_picture(dir, "", reel_length), 0), + make_shared(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0) ); reel->add (reel_text); - reel->add (simple_markers(240)); + reel->add (simple_markers(reel_length)); cpl->add (reel); dcp->add (cpl); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ( {dir}, { { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() }, - { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }, }); } BOOST_AUTO_TEST_CASE (verify_text_entry_point) { - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_subtitle_entry_point_must_be_present", dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT, - [](shared_ptr asset) { + [](shared_ptr asset) { asset->unset_entry_point (); } ); - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_subtitle_entry_point_must_be_zero", dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT, - [](shared_ptr asset) { + [](shared_ptr asset) { asset->set_entry_point (4); } ); - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_closed_caption_entry_point_must_be_present", dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT, - [](shared_ptr asset) { + [](shared_ptr asset) { asset->unset_entry_point (); } ); - verify_text_entry_point_check ( + verify_text_entry_point_check ( "build/test/verify_closed_caption_entry_point_must_be_zero", dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT, - [](shared_ptr asset) { + [](shared_ptr asset) { asset->set_entry_point (9); } ); @@ -2174,28 +2550,26 @@ BOOST_AUTO_TEST_CASE (verify_missing_hash) path const dir("build/test/verify_missing_hash"); 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); auto const cpl = dcp->cpls()[0]; + BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U); + BOOST_REQUIRE (cpl->reels()[0]->main_picture()); + auto asset_id = cpl->reels()[0]->main_picture()->id(); { BOOST_REQUIRE (cpl->file()); Editor e(cpl->file().get()); - e.replace("addO7je2lZSNQp55qjCWo5DLKFQ=", ""); + e.delete_first_line_containing(""); } 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") } + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id } }); } @@ -2210,18 +2584,13 @@ verify_markers_test ( { 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); + auto markers_asset = make_shared(dcp::Fraction(24, 1), 24); for (auto const& i: markers) { markers_asset->set (i.first, i.second); } dcp->cpls()[0]->reels()[0]->add(markers_asset); - dcp->write_xml ( - dcp::Standard::SMPTE, - dcp::String::compose("libdcp %1", dcp::version), - dcp::String::compose("libdcp %1", dcp::version), - dcp::LocalTime().as_string(), - "A Test DCP" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ({dir}, test_notes); } @@ -2317,13 +2686,8 @@ BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number) 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }}); } @@ -2333,14 +2697,10 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U); auto cpl = dcp->cpls()[0]; { @@ -2361,13 +2721,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto cpl = dcp->cpls()[0]; @@ -2389,13 +2744,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto const cpl = dcp->cpls()[0]; @@ -2408,8 +2758,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3) 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::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77 }, { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, }); } @@ -2419,13 +2769,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto cpl = dcp->cpls()[0]; @@ -2447,13 +2792,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto cpl = dcp->cpls()[0]; @@ -2475,13 +2815,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto const cpl = dcp->cpls()[0]; @@ -2494,8 +2829,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6) 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::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 75 }, { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, }); } @@ -2505,13 +2840,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto const cpl = dcp->cpls()[0]; @@ -2533,13 +2863,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto const cpl = dcp->cpls()[0]; @@ -2552,8 +2877,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8) 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::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 }, { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, }); } @@ -2563,13 +2888,8 @@ 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" - ); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); auto const cpl = dcp->cpls()[0]; @@ -2582,8 +2902,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9) 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::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77 }, { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() }, }); } @@ -2598,10 +2918,8 @@ BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content) copy_file (i.path(), dir / i.path().filename()); } - string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff"; - path const pkl = dir / ( "pkl_" + pkl_id + ".xml" ); - string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b"; - path const cpl = dir / ( "cpl_" + cpl_id + ".xml"); + path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" ); + path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml"); { Editor e (cpl); @@ -2611,14 +2929,14 @@ BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content) check_verify_result ( {dir}, { - { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl_id, canonical(cpl) }, - { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl), }, + { 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), }, { 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, cpl_id, canonical(cpl) }, - { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl_id, canonical(cpl) } + { 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) } }); } @@ -2631,10 +2949,8 @@ BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content) copy_file (i.path(), dir / i.path().filename()); } - string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b"; - path const cpl = dir / ("cpl_" + cpl_id + ".xml"); - string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff"; - path const pkl = dir / ("pkl_" + pkl_id + ".xml"); + 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 (""); @@ -2643,13 +2959,13 @@ BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content) check_verify_result ( {dir}, { - { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) }, { 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, cpl_id, canonical(cpl) }, - { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl_id, canonical(pkl) }, + { 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) }, }); } @@ -2663,7 +2979,7 @@ BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content) } { - Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml"); + Editor e (dir / dcp_test1_pkl); e.delete_lines (""); } @@ -2684,7 +3000,7 @@ BOOST_AUTO_TEST_CASE (verify_partially_encrypted) signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem"))); signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key")); - auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER); + auto cpl = make_shared("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE); dcp::Key key; @@ -2724,9 +3040,17 @@ BOOST_AUTO_TEST_CASE (verify_partially_encrypted) 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); + d.set_issuer("OpenDCP 0.0.25"); + d.set_creator("OpenDCP 0.0.25"); + d.set_issue_date("2012-07-17T04:45:18+00:00"); + d.set_annotation_text("A Test DCP"); + d.write_xml(signer); - check_verify_result ({dir}, {{dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}}); + check_verify_result ( + {dir}, + { + {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}, + }); } @@ -2737,7 +3061,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k) auto reader = picture.start_read (); auto frame = reader->get_frame (0); verify_j2k (frame, notes); - dump_notes (notes); + BOOST_REQUIRE_EQUAL (notes.size(), 0U); } @@ -2748,7 +3072,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k) auto reader = picture.start_read (); auto frame = reader->get_frame (0); verify_j2k (frame, notes); - dump_notes (notes); + BOOST_REQUIRE_EQUAL (notes.size(), 0U); } @@ -2757,12 +3081,349 @@ 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); + dcp->write_xml (); vector notes; dcp::MonoPictureAsset picture (find_file(dir, "video")); auto reader = picture.start_read (); auto frame = reader->get_frame (0); verify_j2k (frame, notes); - dump_notes (notes); + BOOST_REQUIRE_EQUAL (notes.size(), 0U); +} + + +/** Check that ResourceID and the XML ID being different is spotted */ +BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id) +{ + boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id"; + prepare_directory (dir); + + ASDCP::WriterInfo writer_info; + writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE; + + unsigned int c; + auto mxf_id = dcp::make_uuid (); + Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c); + BOOST_REQUIRE (c == Kumu::UUID_Length); + + auto resource_id = dcp::make_uuid (); + ASDCP::TimedText::TimedTextDescriptor descriptor; + Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + + auto xml_id = dcp::make_uuid (); + ASDCP::TimedText::MXFWriter writer; + auto subs_mxf = dir / "subs.mxf"; + auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096); + BOOST_REQUIRE (ASDCP_SUCCESS(r)); + writer.WriteTimedTextResource (dcp::String::compose( + "" + "" + "urn:uuid:%1" + "Content" + "Annotation" + "2018-10-02T12:25:14" + "1" + "en-US" + "25 1" + "25" + "00:00:00:00" + "" + "" + "" + "Hello world" + "" + "" + "" + "", + xml_id).c_str()); + + writer.Finalize(); + + auto subs_asset = make_shared(subs_mxf); + auto subs_reel = make_shared(subs_asset, dcp::Fraction(24, 1), 240, 0); + + auto cpl = write_dcp_with_single_asset (dir, subs_reel); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID }, + { 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() } + }); +} + + +/** Check that ResourceID and the MXF ID being the same is spotted */ +BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id) +{ + boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id"; + prepare_directory (dir); + + ASDCP::WriterInfo writer_info; + writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE; + + unsigned int c; + auto mxf_id = dcp::make_uuid (); + Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c); + BOOST_REQUIRE (c == Kumu::UUID_Length); + + auto resource_id = mxf_id; + ASDCP::TimedText::TimedTextDescriptor descriptor; + Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + + auto xml_id = resource_id; + ASDCP::TimedText::MXFWriter writer; + auto subs_mxf = dir / "subs.mxf"; + auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096); + BOOST_REQUIRE (ASDCP_SUCCESS(r)); + writer.WriteTimedTextResource (dcp::String::compose( + "" + "" + "urn:uuid:%1" + "Content" + "Annotation" + "2018-10-02T12:25:14+02:00" + "1" + "en-US" + "25 1" + "25" + "00:00:00:00" + "" + "" + "" + "Hello world" + "" + "" + "" + "", + xml_id).c_str()); + + writer.Finalize(); + + auto subs_asset = make_shared(subs_mxf); + auto subs_reel = make_shared(subs_asset, dcp::Fraction(24, 1), 240, 0); + + auto cpl = write_dcp_with_single_asset (dir, subs_reel); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID }, + { 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() }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} } + }); +} + + +/** Check a DCP with a 3D asset marked as 2D */ +BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod) +{ + check_verify_result ( + { private_test / "data" / "xm" }, + { + { + dcp::VerificationNote::Type::WARNING, + dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c")) + }, + { + dcp::VerificationNote::Type::BV21_ERROR, + dcp::VerificationNote::Code::INVALID_STANDARD + }, + }); + +} + + +BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers) +{ + path dir = "build/test/verify_unexpected_things_in_main_markers"; + prepare_directory (dir); + auto dcp = make_simple (dir, 1, 24); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + + { + Editor e (find_cpl(dir)); + e.insert( + " 24", + "024" + ); + } + + dcp::CPL cpl (find_cpl(dir)); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION }, + }); +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_content_kind) +{ + path dir = "build/test/verify_invalid_content_kind"; + prepare_directory (dir); + auto dcp = make_simple (dir, 1, 24); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + + { + Editor e(find_cpl(dir)); + e.replace("trailer", "trip"); + } + + dcp::CPL cpl (find_cpl(dir)); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") } + }); + +} + + +BOOST_AUTO_TEST_CASE(verify_valid_content_kind) +{ + path dir = "build/test/verify_valid_content_kind"; + prepare_directory (dir); + auto dcp = make_simple (dir, 1, 24); + dcp->set_annotation_text("A Test DCP"); + dcp->write_xml(); + + { + Editor e(find_cpl(dir)); + e.replace("trailer", "trip"); + } + + dcp::CPL cpl (find_cpl(dir)); + + check_verify_result ( + { dir }, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) }, + }); + +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1) +{ + path dir = "build/test/verify_invalid_main_picture_active_area_1"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + auto constexpr area = ""; + + { + Editor e(find_cpl(dir)); + e.delete_lines_after(area, 2); + e.insert(area, "4080"); + e.insert(area, "1997"); + } + + dcp::PKL pkl(find_pkl(dir)); + dcp::CPL cpl(find_cpl(dir)); + + check_verify_result( + { dir }, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 4080 is bigger than the asset height 1080", canonical(find_cpl(dir)) }, + }); +} + + +BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2) +{ + path dir = "build/test/verify_invalid_main_picture_active_area_2"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + auto constexpr area = ""; + + { + Editor e(find_cpl(dir)); + e.delete_lines_after(area, 2); + e.insert(area, "5125"); + e.insert(area, "9900"); + } + + dcp::PKL pkl(find_pkl(dir)); + dcp::CPL cpl(find_cpl(dir)); + + check_verify_result( + { dir }, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) }, + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 9900 is bigger than the asset width 1998", canonical(find_cpl(dir)) }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is bigger than the asset height 1080", canonical(find_cpl(dir)) }, + }); +} + + +BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids) +{ + RNGFixer rg; + + path dir = "build/test/verify_duplicate_pkl_asset_ids"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + { + Editor e(find_pkl(dir)); + e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab"); + } + + dcp::PKL pkl(find_pkl(dir)); + + check_verify_result( + { dir }, + { + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) }, + }); +} + + +BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids) +{ + RNGFixer rg; + + path dir = "build/test/verify_duplicate_assetmap_asset_ids"; + prepare_directory(dir); + auto dcp = make_simple(dir, 1, 24); + dcp->write_xml(); + + { + Editor e(find_asset_map(dir)); + e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa"); + } + + dcp::PKL pkl(find_pkl(dir)); + dcp::AssetMap asset_map(find_asset_map(dir)); + + check_verify_result( + { dir }, + { + { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), }, + { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) }, + { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") }, + }); }