diff options
| author | Carl Hetherington <cth@carlh.net> | 2021-01-18 17:06:23 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2021-01-18 17:06:23 +0100 |
| commit | b2e68c20550fce629d9ebaf1fca5244d1e2ca517 (patch) | |
| tree | 70ec866c91e440d82cd885e723f8bf0afb9ec137 | |
| parent | 36310b78f8fd84554f2c87dc513bd04efe0fd69a (diff) | |
Bv2.1 8.6.3: <ExtensionMetadata> must be present and have precise contents.
| -rw-r--r-- | src/verify.cc | 64 | ||||
| -rw-r--r-- | src/verify.h | 4 | ||||
| -rw-r--r-- | test/verify_test.cc | 217 |
3 files changed, 273 insertions, 12 deletions
diff --git a/src/verify.cc b/src/verify.cc index d5b80b9f..58e777fb 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -1003,6 +1003,64 @@ check_text_timing (vector<shared_ptr<dcp::Reel>> reels, vector<VerificationNote> } +void +check_extension_metadata (shared_ptr<dcp::CPL> cpl, vector<VerificationNote>& notes) +{ + DCP_ASSERT (cpl->file()); + cxml::Document doc ("CompositionPlaylist"); + doc.read_file (cpl->file().get()); + + auto missing = false; + string malformed; + + if (auto reel_list = doc.node_child("ReelList")) { + auto reels = reel_list->node_children("Reel"); + if (!reels.empty()) { + if (auto asset_list = reels[0]->optional_node_child("AssetList")) { + if (auto metadata = asset_list->optional_node_child("CompositionMetadataAsset")) { + if (auto extension_list = metadata->optional_node_child("ExtensionMetadataList")) { + missing = true; + for (auto extension: extension_list->node_children("ExtensionMetadata")) { + if (extension->optional_string_attribute("scope").get_value_or("") != "http://isdcf.com/ns/cplmd/app") { + continue; + } + missing = false; + if (auto name = extension->optional_node_child("Name")) { + if (name->content() != "Application") { + malformed = "<Name> should be 'Application'"; + } + } + if (auto property_list = extension->optional_node_child("PropertyList")) { + if (auto property = property_list->optional_node_child("Property")) { + if (auto name = property->optional_node_child("Name")) { + if (name->content() != "DCP Constraints Profile") { + malformed = "<Name> property should be 'DCP Constraints Profile'"; + } + } + if (auto value = property->optional_node_child("Value")) { + if (value->content() != "SMPTE-RDD-52:2020-Bv2.1") { + malformed = "<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"; + } + } + } + } + } + } else { + missing = true; + } + } + } + } + } + + if (missing) { + notes.push_back ({VerificationNote::VERIFY_BV21_ERROR, VerificationNote::MISSING_EXTENSION_METADATA}); + } else if (!malformed.empty()) { + notes.push_back ({VerificationNote::VERIFY_BV21_ERROR, VerificationNote::INVALID_EXTENSION_METADATA, malformed}); + } +} + + vector<VerificationNote> dcp::verify ( vector<boost::filesystem::path> directories, @@ -1240,6 +1298,8 @@ dcp::verify ( } else if (!cpl->version_number()) { notes.push_back ({VerificationNote::VERIFY_BV21_ERROR, VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER}); } + + check_extension_metadata (cpl, notes); } } @@ -1377,6 +1437,10 @@ dcp::note_to_string (dcp::VerificationNote note) return "There should be a <CompositionMetadataAsset> tag"; case dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER: return "The CPL metadata must contain a <VersionNumber>"; + case dcp::VerificationNote::MISSING_EXTENSION_METADATA: + return "The CPL metadata must contain <ExtensionMetadata>"; + case dcp::VerificationNote::INVALID_EXTENSION_METADATA: + return String::compose("The <ExtensionMetadata> is malformed in some way: %1", note.note().get()); } return ""; diff --git a/src/verify.h b/src/verify.h index 65095a8f..60100435 100644 --- a/src/verify.h +++ b/src/verify.h @@ -169,6 +169,10 @@ public: MISSING_CPL_METADATA, /** CPL metadata should contain <VersionNumber> of 1, at least */ MISSING_CPL_METADATA_VERSION_NUMBER, + /** There must be an <ExtensionMetadata> in <CompositionMetadataAsset> Bv2.1_8.6.3 */ + MISSING_EXTENSION_METADATA, + /** <ExtensionMetadata> must have a particular form Bv2.1_8.6.3 */ + INVALID_EXTENSION_METADATA, }; VerificationNote (Type type, Code code) diff --git a/test/verify_test.cc b/test/verify_test.cc index fd76209b..34f95aad 100644 --- a/test/verify_test.cc +++ b/test/verify_test.cc @@ -148,6 +148,27 @@ public: BOOST_REQUIRE (_content != old_content); } + void delete_lines (string from, string to) + { + vector<string> lines; + boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on); + bool deleting = false; + auto old_content = _content; + _content = ""; + for (auto i: lines) { + if (i.find(from) != string::npos) { + deleting = true; + } + if (!deleting) { + _content += i + "\n"; + } + if (deleting && i.find(to) != string::npos) { + deleting = false; + } + } + BOOST_REQUIRE (_content != old_content); + } + private: boost::filesystem::path _path; std::string _content; @@ -166,18 +187,10 @@ dump_notes (vector<dcp::VerificationNote> const & notes) static void -check_verify_result (vector<boost::filesystem::path> dir, vector<std::pair<dcp::VerificationNote::Type, dcp::VerificationNote::Code>> types_and_codes) +check_verify_result (vector<boost::filesystem::path> dir, vector<dcp::VerificationNote> test_notes) { auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); - BOOST_REQUIRE_EQUAL (notes.size(), types_and_codes.size()); - auto i = notes.begin(); - auto j = types_and_codes.begin(); - while (i != notes.end()) { - BOOST_CHECK_EQUAL (i->type(), j->first); - BOOST_CHECK_EQUAL (i->code(), j->second); - ++i; - ++j; - } + BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size()); } @@ -2057,7 +2070,7 @@ void verify_markers_test ( boost::filesystem::path dir, vector<pair<dcp::Marker, dcp::Time>> markers, - vector<std::pair<dcp::VerificationNote::Type, dcp::VerificationNote::Code>> types_and_codes + vector<dcp::VerificationNote> test_notes ) { auto dcp = make_simple (dir); @@ -2068,7 +2081,7 @@ verify_markers_test ( } dcp->cpls()[0]->reels()[0]->add(markers_asset); dcp->write_xml (dcp::SMPTE); - check_verify_result ({dir}, types_and_codes); + check_verify_result ({dir}, test_notes); } @@ -2165,3 +2178,183 @@ BOOST_AUTO_TEST_CASE (verify_cpl_metadata_version) check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER }}); } + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata1) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata1"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata2) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata2"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA } + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata3) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata3"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.replace ("<meta:Name>A", "<meta:NameX>A"); + e.replace ("n</meta:Name>", "n</meta:NameX>"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata4) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata4"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.replace ("Application", "Fred"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> property should be 'Application'") }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata5) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata5"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.replace ("DCP Constraints Profile", "Fred"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'") }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata6) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata6"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.replace ("<meta:Value>", "<meta:ValueX>"); + e.replace ("</meta:Value>", "</meta:ValueX>"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata7) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata7"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'") }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata8) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata8"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.replace ("<meta:Property>", "<meta:PropertyX>"); + e.replace ("</meta:Property>", "</meta:PropertyX>"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + }); +} + + +BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata9) +{ + boost::filesystem::path dir = "build/test/verify_cpl_extension_metadata9"; + auto dcp = make_simple (dir); + dcp->write_xml (dcp::SMPTE); + { + Editor e (dcp->cpls()[0]->file().get()); + e.replace ("<meta:PropertyList>", "<meta:PropertyListX>"); + e.replace ("</meta:PropertyList>", "</meta:PropertyListX>"); + } + + check_verify_result ( + {dir}, + { + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }, + { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }, + }); +} + + |
