summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2024-04-18 11:51:03 +0200
committerCarl Hetherington <cth@carlh.net>2024-04-18 11:51:03 +0200
commit816365d20e0c6ef37b6bf499a42a0d3ecad22c05 (patch)
treee3ffd2029657ef0b316f729579077baf051f50a5
parent3ab56573dbfb395fea493096182dc14c09fb961f (diff)
parent869462070671b273ac528e075ac1c00a417cc8a0 (diff)
Merge remote-tracking branch 'origin/main' into v1.9.x
-rw-r--r--src/cpl.cc56
-rw-r--r--src/cpl.h17
-rw-r--r--src/dcp.cc2
-rw-r--r--src/verify.cc4
-rw-r--r--src/verify.h9
-rw-r--r--test/cpl_metadata_test.cc34
-rw-r--r--test/ref/cpl_metadata_test4.xml68
-rw-r--r--test/ref/cpl_metadata_test5.xml68
-rw-r--r--tools/dcpdecryptmxf.cc35
9 files changed, 277 insertions, 16 deletions
diff --git a/src/cpl.cc b/src/cpl.cc
index 6a9a46b5..e8cec95c 100644
--- a/src/cpl.cc
+++ b/src/cpl.cc
@@ -104,7 +104,7 @@ CPL::CPL (string annotation_text, ContentKind content_kind, Standard standard)
}
-CPL::CPL (boost::filesystem::path file)
+CPL::CPL (boost::filesystem::path file, vector<dcp::VerificationNote>* notes)
: Asset (file)
, _content_kind (ContentKind::FEATURE)
{
@@ -116,7 +116,17 @@ CPL::CPL (boost::filesystem::path file)
} else if (f.namespace_uri() == cpl_smpte_ns) {
_standard = Standard::SMPTE;
} else {
- boost::throw_exception (XMLError ("Unrecognised CPL namespace " + f.namespace_uri()));
+ if (notes) {
+ notes->push_back(
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR,
+ dcp::VerificationNote::Code::INVALID_CPL_NAMESPACE,
+ f.namespace_uri(),
+ file
+ )
+ );
+ }
+ _standard = Standard::INTEROP;
}
_id = remove_urn_uuid (f.string_child ("Id"));
@@ -139,7 +149,16 @@ CPL::CPL (boost::filesystem::path file)
content_version->done ();
} else if (_standard == Standard::SMPTE) {
/* ContentVersion is required in SMPTE */
- throw XMLError ("Missing ContentVersion tag in CPL");
+ if (notes) {
+ notes->push_back(
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR,
+ dcp::VerificationNote::Code::MISSING_CPL_CONTENT_VERSION,
+ _id,
+ file
+ )
+ );
+ }
}
auto rating_list = f.node_child ("RatingList");
for (auto i: rating_list->node_children("Rating")) {
@@ -345,21 +364,32 @@ CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
}
auto eml = node->optional_node_child ("ExtensionMetadataList");
- if (eml) {
+
+ auto extension_metadata = [eml](string scope, string name, string property) -> boost::optional<std::string> {
+ if (!eml) {
+ return {};
+ }
+
for (auto i: eml->node_children("ExtensionMetadata")) {
- auto name = i->optional_string_child("Name");
- if (name && *name == "Sign Language Video") {
+ auto xml_scope = i->optional_string_attribute("scope");
+ auto xml_name = i->optional_string_child("Name");
+ if (xml_scope && *xml_scope == scope && xml_name && *xml_name == name) {
auto property_list = i->node_child("PropertyList");
for (auto j: property_list->node_children("Property")) {
- auto name = j->optional_string_child("Name");
- auto value = j->optional_string_child("Value");
- if (name && value && *name == "Language Tag") {
- _sign_language_video_language = *value;
+ auto property_name = j->optional_string_child("Name");
+ auto property_value = j->optional_string_child("Value");
+ if (property_name && property_value && *property_name == property) {
+ return property_value;
}
}
}
}
- }
+
+ return {};
+ };
+
+ _sign_language_video_language = extension_metadata("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag");
+ _dolby_edr_image_transfer_function = extension_metadata("http://www.dolby.com/schemas/2014/EDR-Metadata", "Dolby EDR", "image transfer function");
}
@@ -558,6 +588,10 @@ CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_m
add_extension_metadata ("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag", *_sign_language_video_language);
}
+ if (_dolby_edr_image_transfer_function) {
+ add_extension_metadata("http://www.dolby.com/schemas/2014/EDR-Metadata", "Dolby EDR", "image transfer function", *_dolby_edr_image_transfer_function);
+ }
+
if (_reels.front()->main_sound()) {
auto asset = _reels.front()->main_sound()->asset();
if (asset && include_mca_subdescriptors) {
diff --git a/src/cpl.h b/src/cpl.h
index 25c294eb..824faaa1 100644
--- a/src/cpl.h
+++ b/src/cpl.h
@@ -47,6 +47,7 @@
#include "key.h"
#include "language_tag.h"
#include "rating.h"
+#include "verify.h"
#include <boost/filesystem.hpp>
#include <boost/function.hpp>
#include <boost/optional.hpp>
@@ -82,8 +83,11 @@ class CPL : public Asset
public:
CPL (std::string annotation_text, ContentKind content_kind, Standard standard);
- /** Construct a CPL object from a XML file */
- explicit CPL (boost::filesystem::path file);
+ /** Construct a CPL object from a XML file.
+ * If notes is not null, non-fatal errors will be added.
+ * Exceptions will be thrown on non-recoverable errors.
+ */
+ explicit CPL(boost::filesystem::path file, std::vector<dcp::VerificationNote>* notes = nullptr);
bool equals (
std::shared_ptr<const Asset> other,
@@ -326,6 +330,14 @@ public:
return _sign_language_video_language;
}
+ void set_dolby_edr_image_transfer_function(std::string function) {
+ _dolby_edr_image_transfer_function = function;
+ }
+
+ boost::optional<std::string> dolby_edr_image_transfer_function() const {
+ return _dolby_edr_image_transfer_function;
+ }
+
Standard standard () const {
return _standard;
}
@@ -383,6 +395,7 @@ private:
/* See note for _release_territory above */
std::vector<std::string> _additional_subtitle_languages;
boost::optional<std::string> _sign_language_video_language;
+ boost::optional<std::string> _dolby_edr_image_transfer_function;
bool _read_composition_metadata = false;
std::vector<std::shared_ptr<Reel>> _reels;
diff --git a/src/dcp.cc b/src/dcp.cc
index 2906d575..7b238b97 100644
--- a/src/dcp.cc
+++ b/src/dcp.cc
@@ -234,7 +234,7 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
delete p;
if (root == "CompositionPlaylist") {
- auto cpl = make_shared<CPL>(path);
+ auto cpl = make_shared<CPL>(path, notes);
if (cpl->standard() != standard && notes) {
notes->push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_STANDARD});
}
diff --git a/src/verify.cc b/src/verify.cc
index a61c3a92..cfcce04f 100644
--- a/src/verify.cc
+++ b/src/verify.cc
@@ -2203,6 +2203,10 @@ dcp::note_to_string(VerificationNote note, function<string (string)> process_str
return compose("The <LabelText> in a <ContentVersion> in CPL %1 is empty", note.cpl_id().get());
case VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT:
return compose("CPL has valid <ContentVersion> %1", note.note().get());
+ case VerificationNote::Code::INVALID_CPL_NAMESPACE:
+ return compose("The namespace %1 in CPL %2 is invalid", note.note().get(), note.cpl_id().get());
+ case VerificationNote::Code::MISSING_CPL_CONTENT_VERSION:
+ return compose("The CPL %1 has no <ContentVersion> tag", note.cpl_id().get());
}
return "";
diff --git a/src/verify.h b/src/verify.h
index 77b65760..b9e3623f 100644
--- a/src/verify.h
+++ b/src/verify.h
@@ -492,6 +492,15 @@ public:
*/
EMPTY_CONTENT_VERSION_LABEL_TEXT,
VALID_CONTENT_VERSION_LABEL_TEXT,
+ /** The CPL namespace is not valid.
+ * note contains the invalid namespace
+ * file contains the CPL filename
+ */
+ INVALID_CPL_NAMESPACE,
+ /** A SMPTE CPL does not contain a _<ContentVersion>_ tag
+ * file contains the CPL filename
+ */
+ MISSING_CPL_CONTENT_VERSION
};
VerificationNote (Type type, Code code)
diff --git a/test/cpl_metadata_test.cc b/test/cpl_metadata_test.cc
index 0ebf9078..a811e56f 100644
--- a/test/cpl_metadata_test.cc
+++ b/test/cpl_metadata_test.cc
@@ -450,3 +450,37 @@ BOOST_AUTO_TEST_CASE(check_that_missing_full_content_title_text_is_tolerated)
{
dcp::CPL cpl("test/ref/cpl_metadata_test3.xml");
}
+
+
+BOOST_AUTO_TEST_CASE(check_sign_language_video_language)
+{
+ dcp::CPL cpl("test/ref/cpl_metadata_test3.xml");
+ cpl.set_sign_language_video_language(dcp::LanguageTag("es-PT"));
+ cpl.write_xml("build/test/check_sign_language_video_language.xml", {});
+ check_xml(
+ dcp::file_to_string("test/ref/cpl_metadata_test4.xml"),
+ dcp::file_to_string("build/test/check_sign_language_video_language.xml"),
+ {"Id"}
+ );
+
+ dcp::CPL check("build/test/check_sign_language_video_language.xml");
+ BOOST_CHECK_EQUAL(check.sign_language_video_language().get_value_or(""), "es-PT");
+
+}
+
+
+BOOST_AUTO_TEST_CASE(check_dolby_edr_metadata)
+{
+ dcp::CPL cpl("test/ref/cpl_metadata_test3.xml");
+ cpl.set_dolby_edr_image_transfer_function("PQ10K");
+ cpl.write_xml("build/test/check_dolby_edr_metadata.xml", {});
+ check_xml(
+ dcp::file_to_string("test/ref/cpl_metadata_test5.xml"),
+ dcp::file_to_string("build/test/check_dolby_edr_metadata.xml"),
+ {"Id"}
+ );
+
+ dcp::CPL check("build/test/check_dolby_edr_metadata.xml");
+ BOOST_CHECK_EQUAL(check.dolby_edr_image_transfer_function().get_value_or(""), "PQ10K");
+}
+
diff --git a/test/ref/cpl_metadata_test4.xml b/test/ref/cpl_metadata_test4.xml
new file mode 100644
index 00000000..e4459375
--- /dev/null
+++ b/test/ref/cpl_metadata_test4.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CompositionPlaylist xmlns="http://www.smpte-ra.org/schemas/429-7/2006/CPL">
+ <Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b</Id>
+ <AnnotationText></AnnotationText>
+ <IssueDate>2020-08-28T13:35:06+02:00</IssueDate>
+ <Issuer>libdcp1.6.4devel</Issuer>
+ <Creator>libdcp1.6.4devel</Creator>
+ <ContentTitleText></ContentTitleText>
+ <ContentKind>feature</ContentKind>
+ <ContentVersion>
+ <Id>id</Id>
+ <LabelText>version</LabelText>
+ </ContentVersion>
+ <RatingList/>
+ <ReelList>
+ <Reel>
+ <Id>urn:uuid:46c3eb45-15e5-47d6-8684-d8641e4dc516</Id>
+ <AssetList>
+ <MainPicture>
+ <Id>urn:uuid:e98d059d-645f-4343-a30f-edc61d58b8e0</Id>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>24</Duration>
+ <Hash>JtPL3uT3jyKMLysaqgdBWQb/n2E=</Hash>
+ <FrameRate>24 1</FrameRate>
+ <ScreenAspectRatio>1998 1080</ScreenAspectRatio>
+ </MainPicture>
+ <meta:CompositionMetadataAsset xmlns:meta="http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata">
+ <Id>urn:uuid:d36f4bb3-c4fa-4a95-9915-6fec3110cd71</Id>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <meta:FullContentTitleText/>
+ <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,FSKSync,-,-</meta:MainSoundConfiguration>
+ <meta:MainSoundSampleRate>48000 1</meta:MainSoundSampleRate>
+ <meta:MainPictureStoredArea>
+ <meta:Width>1998</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureStoredArea>
+ <meta:MainPictureActiveArea>
+ <meta:Width>1440</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureActiveArea>
+ <meta:ExtensionMetadataList>
+ <meta:ExtensionMetadata scope="http://isdcf.com/ns/cplmd/app">
+ <meta:Name>Application</meta:Name>
+ <meta:PropertyList>
+ <meta:Property>
+ <meta:Name>DCP Constraints Profile</meta:Name>
+ <meta:Value>SMPTE-RDD-52:2020-Bv2.1</meta:Value>
+ </meta:Property>
+ </meta:PropertyList>
+ </meta:ExtensionMetadata>
+ <meta:ExtensionMetadata scope="http://isdcf.com/2017/10/SignLanguageVideo">
+ <meta:Name>Sign Language Video</meta:Name>
+ <meta:PropertyList>
+ <meta:Property>
+ <meta:Name>Language Tag</meta:Name>
+ <meta:Value>es-PT</meta:Value>
+ </meta:Property>
+ </meta:PropertyList>
+ </meta:ExtensionMetadata>
+ </meta:ExtensionMetadataList>
+ </meta:CompositionMetadataAsset>
+ </AssetList>
+ </Reel>
+ </ReelList>
+</CompositionPlaylist>
diff --git a/test/ref/cpl_metadata_test5.xml b/test/ref/cpl_metadata_test5.xml
new file mode 100644
index 00000000..55209e53
--- /dev/null
+++ b/test/ref/cpl_metadata_test5.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CompositionPlaylist xmlns="http://www.smpte-ra.org/schemas/429-7/2006/CPL">
+ <Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b</Id>
+ <AnnotationText></AnnotationText>
+ <IssueDate>2020-08-28T13:35:06+02:00</IssueDate>
+ <Issuer>libdcp1.6.4devel</Issuer>
+ <Creator>libdcp1.6.4devel</Creator>
+ <ContentTitleText></ContentTitleText>
+ <ContentKind>feature</ContentKind>
+ <ContentVersion>
+ <Id>id</Id>
+ <LabelText>version</LabelText>
+ </ContentVersion>
+ <RatingList/>
+ <ReelList>
+ <Reel>
+ <Id>urn:uuid:46c3eb45-15e5-47d6-8684-d8641e4dc516</Id>
+ <AssetList>
+ <MainPicture>
+ <Id>urn:uuid:e98d059d-645f-4343-a30f-edc61d58b8e0</Id>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>24</Duration>
+ <Hash>JtPL3uT3jyKMLysaqgdBWQb/n2E=</Hash>
+ <FrameRate>24 1</FrameRate>
+ <ScreenAspectRatio>1998 1080</ScreenAspectRatio>
+ </MainPicture>
+ <meta:CompositionMetadataAsset xmlns:meta="http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata">
+ <Id>urn:uuid:d36f4bb3-c4fa-4a95-9915-6fec3110cd71</Id>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <meta:FullContentTitleText/>
+ <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,FSKSync,-,-</meta:MainSoundConfiguration>
+ <meta:MainSoundSampleRate>48000 1</meta:MainSoundSampleRate>
+ <meta:MainPictureStoredArea>
+ <meta:Width>1998</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureStoredArea>
+ <meta:MainPictureActiveArea>
+ <meta:Width>1440</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureActiveArea>
+ <meta:ExtensionMetadataList>
+ <meta:ExtensionMetadata scope="http://isdcf.com/ns/cplmd/app">
+ <meta:Name>Application</meta:Name>
+ <meta:PropertyList>
+ <meta:Property>
+ <meta:Name>DCP Constraints Profile</meta:Name>
+ <meta:Value>SMPTE-RDD-52:2020-Bv2.1</meta:Value>
+ </meta:Property>
+ </meta:PropertyList>
+ </meta:ExtensionMetadata>
+ <meta:ExtensionMetadata scope="http://www.dolby.com/schemas/2014/EDR-Metadata">
+ <meta:Name>Dolby EDR</meta:Name>
+ <meta:PropertyList>
+ <meta:Property>
+ <meta:Name>image transfer function</meta:Name>
+ <meta:Value>PQ10K</meta:Value>
+ </meta:Property>
+ </meta:PropertyList>
+ </meta:ExtensionMetadata>
+ </meta:ExtensionMetadataList>
+ </meta:CompositionMetadataAsset>
+ </AssetList>
+ </Reel>
+ </ReelList>
+</CompositionPlaylist>
diff --git a/tools/dcpdecryptmxf.cc b/tools/dcpdecryptmxf.cc
index 1cdb58ec..451a4d34 100644
--- a/tools/dcpdecryptmxf.cc
+++ b/tools/dcpdecryptmxf.cc
@@ -43,6 +43,8 @@
#include "key.h"
#include "mono_picture_asset.h"
#include "mono_picture_asset_writer.h"
+#include "sound_asset.h"
+#include "sound_asset_writer.h"
#include "util.h"
#include "version.h"
#include <asdcp/AS_DCP.h>
@@ -62,14 +64,14 @@ static void
help (string n)
{
cerr << "Re-write a MXF (decrypting it if required)\n"
- << "Syntax: " << n << " [OPTION] <MXF>]\n"
+ << "Syntax: " << n << " [OPTION] <MXF>\n"
<< " --version show libdcp version\n"
<< " -v, --verbose be verbose\n"
<< " -h, --help show this help\n"
<< " -o, --output output filename\n"
<< " -k, --kdm KDM file\n"
<< " -p, --private-key private key file\n"
- << " -t, --type MXF type: picture or atmos\n"
+ << " -t, --type MXF type: picture, sound or atmos\n"
<< " -i, --ignore-hmac don't raise an error if HMACs don't agree\n";
}
@@ -99,6 +101,7 @@ main (int argc, char* argv[])
enum class Type {
PICTURE,
+ SOUND,
ATMOS,
};
@@ -146,6 +149,8 @@ main (int argc, char* argv[])
case 't':
if (strcmp(optarg, "picture") == 0) {
type = Type::PICTURE;
+ } else if (strcmp(optarg, "sound") == 0) {
+ type = Type::SOUND;
} else if (strcmp(optarg, "atmos") == 0) {
type = Type::ATMOS;
} else {
@@ -234,6 +239,32 @@ main (int argc, char* argv[])
copy (in, writer, ignore_hmac);
break;
}
+ case Type::SOUND:
+ {
+ dcp::SoundAsset in(input_file);
+ add_key(in, decrypted_kdm);
+ /* XXX: this is all a bit of a hack */
+ dcp::SoundAsset out(in.edit_rate(), in.sampling_rate(), in.channels(), dcp::LanguageTag(in.language().get_value_or("en-GB")), dcp::Standard::SMPTE);
+ auto writer = out.start_write(output_file.get(), {}, dcp::SoundAsset::AtmosSync::DISABLED, dcp::SoundAsset::MCASubDescriptors::DISABLED);
+ auto reader = in.start_read();
+ reader->set_check_hmac(!ignore_hmac);
+ for (int64_t i = 0; i < in.intrinsic_duration(); ++i) {
+ auto frame = reader->get_frame(i);
+ std::vector<int32_t*> pointers(frame->channels());
+ for (auto channel = 0; channel < frame->channels(); ++channel) {
+ pointers[channel] = new int32_t[frame->samples()];
+ for (auto sample = 0; sample < frame->samples(); ++sample) {
+ pointers[channel][sample] = frame->get(channel, sample);
+ }
+ }
+ writer->write(pointers.data(), frame->channels(), frame->samples());
+ for (auto channel = 0; channel < frame->channels(); ++channel) {
+ delete[] pointers[channel];
+ }
+ }
+ writer->finalize();
+ break;
+ }
}
} catch (dcp::ReadError& e) {
cerr << "Read error: " << e.what() << "\n";