*/
+#include "certificate_chain.h"
+#include "compose.hpp"
#include "cpl.h"
-#include "util.h"
-#include "reel.h"
+#include "dcp_assert.h"
+#include "equality_options.h"
+#include "filesystem.h"
+#include "local_time.h"
#include "metadata.h"
-#include "certificate_chain.h"
-#include "xml.h"
+#include "raw_convert.h"
+#include "reel.h"
+#include "reel_atmos_asset.h"
+#include "reel_closed_caption_asset.h"
#include "reel_picture_asset.h"
#include "reel_sound_asset.h"
#include "reel_subtitle_asset.h"
-#include "reel_closed_caption_asset.h"
-#include "reel_atmos_asset.h"
-#include "local_time.h"
-#include "dcp_assert.h"
-#include "compose.hpp"
-#include "raw_convert.h"
+#include "util.h"
+#include "version.h"
+#include "warnings.h"
+#include "xml.h"
+LIBDCP_DISABLE_WARNINGS
#include <asdcp/Metadata.h>
+LIBDCP_ENABLE_WARNINGS
#include <libxml/parser.h>
+LIBDCP_DISABLE_WARNINGS
#include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
#include <boost/algorithm/string.hpp>
CPL::CPL (string annotation_text, ContentKind content_kind, Standard standard)
/* default _content_title_text to annotation_text */
- : _issuer ("libdcp" LIBDCP_VERSION)
- , _creator ("libdcp" LIBDCP_VERSION)
+ : _issuer("libdcp", dcp::version)
+ , _creator("libdcp", dcp::version)
, _issue_date (LocalTime().as_string())
, _annotation_text (annotation_text)
, _content_title_text (annotation_text)
, _content_kind (ContentKind::FEATURE)
{
cxml::Document f ("CompositionPlaylist");
- f.read_file (file);
+ f.read_file(dcp::filesystem::fix_long_path(file));
if (f.namespace_uri() == cpl_interop_ns) {
_standard = Standard::INTEROP;
_creator = f.optional_string_child("Creator").get_value_or("");
_issue_date = f.string_child ("IssueDate");
_content_title_text = f.string_child ("ContentTitleText");
- _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
+ auto content_kind = f.node_child("ContentKind");
+ _content_kind = ContentKind(content_kind->content(), content_kind->optional_string_attribute("scope"));
shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
if (content_version) {
/* XXX: SMPTE should insist that Id is present */
throw XMLError ("Missing ContentVersion tag in CPL");
}
auto rating_list = f.node_child ("RatingList");
- if (rating_list) {
- for (auto i: rating_list->node_children("Rating")) {
- _ratings.push_back (Rating(i));
- }
+ for (auto i: rating_list->node_children("Rating")) {
+ _ratings.push_back (Rating(i));
}
for (auto i: f.node_child("ReelList")->node_children("Reel")) {
}
auto reel_list = f.node_child ("ReelList");
- if (reel_list) {
- auto reels = reel_list->node_children("Reel");
- if (!reels.empty()) {
- auto asset_list = reels.front()->node_child("AssetList");
- auto metadata = asset_list->optional_node_child("CompositionMetadataAsset");
- if (metadata) {
- read_composition_metadata_asset (metadata);
- }
+ auto reels = reel_list->node_children("Reel");
+ if (!reels.empty()) {
+ auto asset_list = reels.front()->node_child("AssetList");
+ auto metadata = asset_list->optional_node_child("CompositionMetadataAsset");
+ if (metadata) {
+ read_composition_metadata_asset (metadata);
+ _read_composition_metadata = true;
}
}
void
-CPL::write_xml (boost::filesystem::path file, shared_ptr<const CertificateChain> signer) const
+CPL::set (std::vector<std::shared_ptr<Reel>> reels)
+{
+ _reels = reels;
+}
+
+
+void
+CPL::write_xml(boost::filesystem::path file, shared_ptr<const CertificateChain> signer, bool include_mca_subdescriptors) const
{
xmlpp::Document doc;
xmlpp::Element* root;
root->add_child("Issuer")->add_child_text (_issuer);
root->add_child("Creator")->add_child_text (_creator);
root->add_child("ContentTitleText")->add_child_text (_content_title_text);
- root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
+ auto content_kind = root->add_child("ContentKind");
+ content_kind->add_child_text(_content_kind.name());
+ if (_content_kind.scope()) {
+ content_kind->set_attribute("scope", *_content_kind.scope());
+ }
if (_content_versions.empty()) {
ContentVersion cv;
cv.as_xml (root);
for (auto i: _reels) {
auto asset_list = i->write_to_cpl (reel_list, _standard);
if (first && _standard == Standard::SMPTE) {
- maybe_write_composition_metadata_asset (asset_list);
+ maybe_write_composition_metadata_asset(asset_list, include_mca_subdescriptors);
first = false;
}
}
signer->sign (root, _standard);
}
- doc.write_to_file_formatted (file.string(), "UTF-8");
+ doc.write_to_file_formatted(dcp::filesystem::fix_long_path(file).string(), "UTF-8");
set_file (file);
}
void
CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
{
- auto fctt = node->node_child("FullContentTitleText");
- _full_content_title_text = fctt->content();
- _full_content_title_text_language = fctt->optional_string_attribute("language");
+ _cpl_metadata_id = remove_urn_uuid(node->string_child("Id"));
+
+ /* FullContentTitleText is compulsory but in DoM #2295 we saw a commercial tool which
+ * apparently didn't include it, so as usual we have to be defensive.
+ */
+ if (auto fctt = node->optional_node_child("FullContentTitleText")) {
+ _full_content_title_text = fctt->content();
+ _full_content_title_text_language = fctt->optional_string_attribute("language");
+ }
_release_territory = node->optional_string_child("ReleaseTerritory");
if (_release_territory) {
_luminance = Luminance (lum);
}
- _main_sound_configuration = node->optional_string_child("MainSoundConfiguration");
+ if (auto msc = node->optional_string_child("MainSoundConfiguration")) {
+ try {
+ _main_sound_configuration = MainSoundConfiguration(*msc);
+ } catch (MainSoundConfigurationError& e) {
+ /* With Interop DCPs this node may not make any sense, but that's OK */
+ if (_standard == dcp::Standard::SMPTE) {
+ throw e;
+ }
+ }
+ }
auto sr = node->optional_string_child("MainSoundSampleRate");
if (sr) {
_main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
}
- _main_picture_stored_area = dcp::Size (
- node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
- node->node_child("MainPictureStoredArea")->number_child<int>("Height")
- );
+ if (_standard == dcp::Standard::SMPTE) {
+ _main_picture_stored_area = dcp::Size (
+ node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
+ node->node_child("MainPictureStoredArea")->number_child<int>("Height")
+ );
+ }
_main_picture_active_area = dcp::Size (
node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
_additional_subtitle_languages.push_back (sll_split[i]);
}
}
+
+ auto eml = node->optional_node_child ("ExtensionMetadataList");
+
+ 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 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 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");
+}
+
+
+void
+CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsset> asset) const
+{
+ auto reader = asset->start_read ();
+ ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
+ ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
+ asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
+ reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
+ );
+ if (KM_SUCCESS(r)) {
+ auto mca_subs = parent->add_child("mca:MCASubDescriptors");
+ mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
+ mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
+ mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
+ auto sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
+ char buffer[64];
+ soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
+ sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+ soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
+ sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
+ soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
+ sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+ soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
+ sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
+ if (!soundfield->MCATagName.empty()) {
+ soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
+ sf->add_child("MCATagName", "r1")->add_child_text(buffer);
+ }
+ if (!soundfield->RFC5646SpokenLanguage.empty()) {
+ soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
+ sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
+ }
+
+ /* Find the MCA subdescriptors in the MXF so that we can also write them here */
+ list<ASDCP::MXF::InterchangeObject*> channels;
+ auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
+ asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
+ channels
+ );
+
+ for (auto i: channels) {
+ auto channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
+ auto ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
+ channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
+ ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+ channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
+ ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
+ channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
+ ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+ channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
+ ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
+ if (!channel->MCATagName.empty()) {
+ channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
+ ch->add_child("MCATagName", "r1")->add_child_text(buffer);
+ }
+ if (!channel->MCAChannelID.empty()) {
+ ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
+ }
+ if (!channel->RFC5646SpokenLanguage.empty()) {
+ channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
+ ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
+ }
+ if (!channel->SoundfieldGroupLinkID.empty()) {
+ channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
+ ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+ }
+ }
+ }
}
* is missing this method will do nothing.
*/
void
-CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
+CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_mca_subdescriptors) const
{
if (
!_main_sound_configuration ||
auto meta = node->add_child("meta:CompositionMetadataAsset");
meta->set_namespace_declaration (cpl_metadata_ns, "meta");
- meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid());
+ meta->add_child("Id")->add_child_text("urn:uuid:" + _cpl_metadata_id);
auto mp = _reels.front()->main_picture();
meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
auto fctt = meta->add_child("FullContentTitleText", "meta");
- if (_full_content_title_text) {
+ if (_full_content_title_text && !_full_content_title_text->empty()) {
fctt->add_child_text (*_full_content_title_text);
}
if (_full_content_title_text_language) {
_luminance->as_xml (meta, "meta");
}
- meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
+ if (_main_sound_configuration) {
+ meta->add_child("MainSoundConfiguration", "meta")->add_child_text(_main_sound_configuration->to_string());
+ }
meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
auto stored = meta->add_child("MainPictureStoredArea", "meta");
meta->add_child("MainSubtitleLanguageList", "meta")->add_child_text(lang);
}
+ auto metadata_list = meta->add_child("ExtensionMetadataList", "meta");
+
+ auto add_extension_metadata = [metadata_list](string scope, string name, string property_name, string property_value) {
+ auto extension = metadata_list->add_child("ExtensionMetadata", "meta");
+ extension->set_attribute("scope", scope);
+ extension->add_child("Name", "meta")->add_child_text(name);
+ auto property = extension->add_child("PropertyList", "meta")->add_child("Property", "meta");
+ property->add_child("Name", "meta")->add_child_text(property_name);
+ property->add_child("Value", "meta")->add_child_text(property_value);
+ };
+
/* SMPTE Bv2.1 8.6.3 */
- auto extension = meta->add_child("ExtensionMetadataList", "meta")->add_child("ExtensionMetadata", "meta");
- extension->set_attribute("scope", "http://isdcf.com/ns/cplmd/app");
- extension->add_child("Name", "meta")->add_child_text("Application");
- auto property = extension->add_child("PropertyList", "meta")->add_child("Property", "meta");
- property->add_child("Name", "meta")->add_child_text("DCP Constraints Profile");
- property->add_child("Value", "meta")->add_child_text("SMPTE-RDD-52:2020-Bv2.1");
+ add_extension_metadata ("http://isdcf.com/ns/cplmd/app", "Application", "DCP Constraints Profile", "SMPTE-RDD-52:2020-Bv2.1");
+
+ if (_sign_language_video_language) {
+ 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) {
- auto reader = asset->start_read ();
- ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
- ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
- asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
- reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
- );
- if (KM_SUCCESS(r)) {
- auto mca_subs = meta->add_child("mca:MCASubDescriptors");
- mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
- mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
- mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
- auto sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
- char buffer[64];
- soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
- sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
- soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
- sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
- soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
- sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
- soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
- sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
- if (!soundfield->MCATagName.empty()) {
- soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
- sf->add_child("MCATagName", "r1")->add_child_text(buffer);
- }
- if (!soundfield->RFC5646SpokenLanguage.empty()) {
- soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
- sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
- }
-
- list<ASDCP::MXF::InterchangeObject*> channels;
- auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
- asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
- channels
- );
-
- for (auto i: channels) {
- auto channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
- auto ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
- channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
- ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
- channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
- ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
- channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
- ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
- channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
- ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
- if (!channel->MCATagName.empty()) {
- channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
- ch->add_child("MCATagName", "r1")->add_child_text(buffer);
- }
- if (!channel->MCAChannelID.empty()) {
- ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
- }
- if (!channel->RFC5646SpokenLanguage.empty()) {
- channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
- ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
- }
- if (!channel->SoundfieldGroupLinkID.empty()) {
- channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
- ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
- }
- }
- }
+ if (asset && include_mca_subdescriptors) {
+ write_mca_subdescriptors(meta, asset);
}
}
}
bool
-CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
+CPL::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const
{
auto other_cpl = dynamic_pointer_cast<const CPL>(other);
if (!other_cpl) {
void
CPL::set_content_versions (vector<ContentVersion> v)
{
- set<string> ids;
+ std::set<string> ids;
for (auto i: v) {
if (!ids.insert(i.id).second) {
throw DuplicateIdError ("Duplicate ID in ContentVersion list");
_additional_subtitle_languages.push_back (i.to_string());
}
}
+
+
+void
+CPL::set_main_picture_active_area(dcp::Size area)
+{
+ if (area.width % 2) {
+ throw BadSettingError("Main picture active area width is not a multiple of 2");
+ }
+
+ if (area.height % 2) {
+ throw BadSettingError("Main picture active area height is not a multiple of 2");
+ }
+
+ _main_picture_active_area = area;
+}
+