Bv2.1 8.6.3: <ExtensionMetadata> must be present and have precise contents.
authorCarl Hetherington <cth@carlh.net>
Mon, 18 Jan 2021 16:06:23 +0000 (17:06 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 18 Jan 2021 16:06:23 +0000 (17:06 +0100)
src/verify.cc
src/verify.h
test/verify_test.cc

index d5b80b9fc3c6a739e5e974225de54ed1cfe52521..58e777fb49942d58abc65fd96ad379667a0adc27 100644 (file)
@@ -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 "";
index 65095a8f795801227b6c36c13bdd1ce4530d79c8..60100435adacb73b0df022e65d69f7b2072ab955 100644 (file)
@@ -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)
index fd76209bc2ee177e72fff245d895863ce8bb58b9..34f95aadcca6ff19cc8b6ed525681c887cd77c60 100644 (file)
@@ -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 },
+               });
+}
+
+