41 #include "compose.hpp"
45 #include "filesystem.h"
58 LIBDCP_DISABLE_WARNINGS
59 #include <asdcp/Metadata.h>
60 LIBDCP_ENABLE_WARNINGS
61 #include <libxml/parser.h>
62 LIBDCP_DISABLE_WARNINGS
63 #include <libxml++/libxml++.h>
64 LIBDCP_ENABLE_WARNINGS
65 #include <boost/algorithm/string.hpp>
69 using std::dynamic_pointer_cast;
72 using std::make_shared;
75 using std::shared_ptr;
78 using boost::optional;
82 static string const cpl_interop_ns =
"http://www.digicine.com/PROTO-ASDCP-CPL-20040511#";
83 static string const cpl_smpte_ns =
"http://www.smpte-ra.org/schemas/429-7/2006/CPL";
84 static string const cpl_metadata_ns =
"http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata";
85 static string const mca_sub_descriptors_ns =
"http://isdcf.com/ns/cplmd/mca";
86 static string const smpte_395_ns =
"http://www.smpte-ra.org/reg/395/2014/13/1/aaf";
87 static string const smpte_335_ns =
"http://www.smpte-ra.org/reg/335/2012";
90 CPL::CPL (
string annotation_text,
ContentKind content_kind, Standard standard)
92 : _issuer(
"libdcp",
dcp::version)
93 , _creator(
"libdcp",
dcp::version)
95 , _annotation_text (annotation_text)
96 , _content_title_text (annotation_text)
97 , _content_kind (content_kind)
98 , _standard (standard)
102 _content_versions.push_back (cv);
106 CPL::CPL (boost::filesystem::path file, vector<dcp::VerificationNote>* notes)
110 cxml::Document f (
"CompositionPlaylist");
111 f.read_file(dcp::filesystem::fix_long_path(
file));
113 if (f.namespace_uri() == cpl_interop_ns) {
115 }
else if (f.namespace_uri() == cpl_smpte_ns) {
121 dcp::VerificationNote::Type::ERROR,
131 _id = remove_urn_uuid (f.string_child (
"Id"));
132 _annotation_text = f.optional_string_child(
"AnnotationText");
133 _issuer = f.optional_string_child(
"Issuer").get_value_or(
"");
134 _creator = f.optional_string_child(
"Creator").get_value_or(
"");
135 _issue_date = f.string_child (
"IssueDate");
139 shared_ptr<cxml::Node> content_version = f.optional_node_child (
"ContentVersion");
140 if (content_version) {
142 _content_versions.push_back (
144 content_version->optional_string_child(
"Id").get_value_or(
""),
145 content_version->string_child(
"LabelText")
148 content_version->done ();
149 }
else if (
_standard == Standard::SMPTE) {
154 dcp::VerificationNote::Type::ERROR,
162 auto rating_list = f.node_child (
"RatingList");
163 for (
auto i: rating_list->node_children(
"Rating")) {
164 _ratings.push_back (
Rating(i));
167 for (
auto i: f.node_child(
"ReelList")->node_children(
"Reel")) {
168 _reels.push_back (make_shared<Reel>(i,
_standard));
171 auto reel_list = f.node_child (
"ReelList");
172 auto reels = reel_list->node_children(
"Reel");
173 if (!
reels.empty()) {
174 auto asset_list =
reels.front()->node_child(
"AssetList");
175 auto metadata = asset_list->optional_node_child(
"CompositionMetadataAsset");
177 read_composition_metadata_asset (metadata);
178 _read_composition_metadata =
true;
182 f.ignore_child (
"Issuer");
183 f.ignore_child (
"Signer");
184 f.ignore_child (
"Signature");
193 _reels.push_back (reel);
198 CPL::set (std::vector<std::shared_ptr<Reel>> reels)
205 CPL::write_xml(boost::filesystem::path file, shared_ptr<const CertificateChain> signer,
bool include_mca_subdescriptors)
const
208 xmlpp::Element* root;
210 root = doc.create_root_node (
"CompositionPlaylist", cpl_interop_ns);
212 root = doc.create_root_node (
"CompositionPlaylist", cpl_smpte_ns);
215 cxml::add_text_child(root,
"Id",
"urn:uuid:" + _id);
216 if (_annotation_text) {
217 cxml::add_text_child(root,
"AnnotationText", *_annotation_text);
219 cxml::add_text_child(root,
"IssueDate", _issue_date);
220 cxml::add_text_child(root,
"Issuer", _issuer);
221 cxml::add_text_child(root,
"Creator", _creator);
223 auto content_kind = cxml::add_child(root,
"ContentKind");
228 if (_content_versions.empty()) {
232 _content_versions[0].as_xml (root);
235 auto rating_list = cxml::add_child(root,
"RatingList");
236 for (
auto i: _ratings) {
237 i.as_xml(cxml::add_child(rating_list,
"Rating"));
240 auto reel_list = cxml::add_child(root,
"ReelList");
242 if (_reels.empty()) {
247 for (
auto i: _reels) {
248 auto asset_list = i->write_to_cpl (reel_list,
_standard);
249 if (first &&
_standard == Standard::SMPTE) {
261 doc.write_to_file_formatted(dcp::filesystem::fix_long_path(
file).
string(),
"UTF-8");
268 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
275 if (
auto fctt = node->optional_node_child(
"FullContentTitleText")) {
277 _full_content_title_text_language = fctt->optional_string_attribute(
"language");
282 _release_territory_scope = node->node_child(
"ReleaseTerritory")->optional_string_attribute(
"scope");
285 auto vn = node->optional_node_child(
"VersionNumber");
287 _version_number = raw_convert<int>(vn->content());
289 auto vn_status = vn->optional_string_attribute(
"status");
291 _status = string_to_status (*vn_status);
295 _chain = node->optional_string_child(
"Chain");
296 _distributor = node->optional_string_child(
"Distributor");
297 _facility = node->optional_string_child(
"Facility");
299 auto acv = node->optional_node_child(
"AlternateContentVersionList");
301 for (
auto i: acv->node_children(
"ContentVersion")) {
306 auto lum = node->optional_node_child(
"Luminance");
311 if (
auto msc = node->optional_string_child(
"MainSoundConfiguration")) {
322 auto sr = node->optional_string_child(
"MainSoundSampleRate");
324 vector<string> sr_bits;
325 boost::split (sr_bits, *sr, boost::is_any_of(
" "));
326 DCP_ASSERT (sr_bits.size() == 2);
327 _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
332 node->node_child(
"MainPictureStoredArea")->number_child<
int>(
"Width"),
333 node->node_child(
"MainPictureStoredArea")->number_child<
int>(
"Height")
338 node->node_child(
"MainPictureActiveArea")->number_child<
int>(
"Width"),
339 node->node_child(
"MainPictureActiveArea")->number_child<
int>(
"Height")
342 auto sll = node->optional_string_child(
"MainSubtitleLanguageList");
344 vector<string> sll_split;
345 boost::split (sll_split, *sll, boost::is_any_of(
" "));
346 DCP_ASSERT (!sll_split.empty());
350 if (!_reels.empty()) {
351 auto sub = _reels.front()->main_subtitle();
353 auto lang = sub->language();
354 if (lang && lang == sll_split[0]) {
360 for (
auto i = first; i < sll_split.size(); ++i) {
361 _additional_subtitle_languages.push_back (sll_split[i]);
365 auto eml = node->optional_node_child (
"ExtensionMetadataList");
367 auto extension_metadata = [eml](
string scope,
string name,
string property) -> boost::optional<std::string> {
372 for (
auto i: eml->node_children(
"ExtensionMetadata")) {
373 auto xml_scope = i->optional_string_attribute(
"scope");
374 auto xml_name = i->optional_string_child(
"Name");
375 if (xml_scope && *xml_scope == scope && xml_name && *xml_name == name) {
376 auto property_list = i->node_child(
"PropertyList");
377 for (
auto j: property_list->node_children(
"Property")) {
378 auto property_name = j->optional_string_child(
"Name");
379 auto property_value = j->optional_string_child(
"Value");
380 if (property_name && property_value && *property_name == property) {
381 return property_value;
390 _sign_language_video_language = extension_metadata(
"http://isdcf.com/2017/10/SignLanguageVideo",
"Sign Language Video",
"Language Tag");
391 _dolby_edr_image_transfer_function = extension_metadata(
"http://www.dolby.com/schemas/2014/EDR-Metadata",
"Dolby EDR",
"image transfer function");
396 CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsset> asset)
const
398 auto reader = asset->start_read ();
399 ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
400 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
401 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
402 reinterpret_cast<ASDCP::MXF::InterchangeObject**
>(&soundfield)
405 auto mca_subs = cxml::add_child(parent,
"mca:MCASubDescriptors");
406 mca_subs->set_namespace_declaration (mca_sub_descriptors_ns,
"mca");
407 mca_subs->set_namespace_declaration (smpte_395_ns,
"r0");
408 mca_subs->set_namespace_declaration (smpte_335_ns,
"r1");
409 auto sf = cxml::add_child(mca_subs,
"SoundfieldGroupLabelSubDescriptor",
string(
"r0"));
411 soundfield->InstanceUID.EncodeString(buffer,
sizeof(buffer));
412 cxml::add_child(sf,
"InstanceID",
string(
"r1"))->add_child_text(
"urn:uuid:" +
string(buffer));
413 soundfield->MCALabelDictionaryID.EncodeString(buffer,
sizeof(buffer));
414 cxml::add_child(sf,
"MCALabelDictionaryID",
string(
"r1"))->add_child_text(
"urn:smpte:ul:" +
string(buffer));
415 soundfield->MCALinkID.EncodeString(buffer,
sizeof(buffer));
416 cxml::add_child(sf,
"MCALinkID",
string(
"r1"))->add_child_text(
"urn:uuid:" +
string(buffer));
417 soundfield->MCATagSymbol.EncodeString(buffer,
sizeof(buffer));
418 cxml::add_child(sf,
"MCATagSymbol",
string(
"r1"))->add_child_text(buffer);
419 if (!soundfield->MCATagName.empty()) {
420 soundfield->MCATagName.get().EncodeString(buffer,
sizeof(buffer));
421 cxml::add_child(sf,
"MCATagName",
string(
"r1"))->add_child_text(buffer);
423 if (!soundfield->RFC5646SpokenLanguage.empty()) {
424 soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer,
sizeof(buffer));
425 cxml::add_child(sf,
"RFC5646SpokenLanguage",
string(
"r1"))->add_child_text(buffer);
429 list<ASDCP::MXF::InterchangeObject*> channels;
430 auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
431 asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
435 for (
auto i: channels) {
436 auto channel =
reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*
>(i);
437 auto ch = cxml::add_child(mca_subs,
"AudioChannelLabelSubDescriptor",
string(
"r0"));
438 channel->InstanceUID.EncodeString(buffer,
sizeof(buffer));
439 cxml::add_child(ch,
"InstanceID",
string(
"r1"))->add_child_text(
"urn:uuid:" +
string(buffer));
440 channel->MCALabelDictionaryID.EncodeString(buffer,
sizeof(buffer));
441 cxml::add_child(ch,
"MCALabelDictionaryID",
string(
"r1"))->add_child_text(
"urn:smpte:ul:" +
string(buffer));
442 channel->MCALinkID.EncodeString(buffer,
sizeof(buffer));
443 cxml::add_child(ch,
"MCALinkID",
string(
"r1"))->add_child_text(
"urn:uuid:" +
string(buffer));
444 channel->MCATagSymbol.EncodeString(buffer,
sizeof(buffer));
445 cxml::add_child(ch,
"MCATagSymbol",
string(
"r1"))->add_child_text(buffer);
446 if (!channel->MCATagName.empty()) {
447 channel->MCATagName.get().EncodeString(buffer,
sizeof(buffer));
448 cxml::add_child(ch,
"MCATagName",
string(
"r1"))->add_child_text(buffer);
450 if (!channel->MCAChannelID.empty()) {
451 cxml::add_child(ch,
"MCAChannelID",
string(
"r1"))->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
453 if (!channel->RFC5646SpokenLanguage.empty()) {
454 channel->RFC5646SpokenLanguage.get().EncodeString(buffer,
sizeof(buffer));
455 cxml::add_child(ch,
"RFC5646SpokenLanguage",
string(
"r1"))->add_child_text(buffer);
457 if (!channel->SoundfieldGroupLinkID.empty()) {
458 channel->SoundfieldGroupLinkID.get().EncodeString(buffer,
sizeof(buffer));
459 cxml::add_child(ch,
"SoundfieldGroupLinkID",
string(
"r1"))->add_child_text(
"urn:uuid:" +
string(buffer));
474 !_main_sound_configuration ||
475 !_main_sound_sample_rate ||
476 !_main_picture_stored_area ||
477 !_main_picture_active_area ||
479 !_reels.front()->main_picture()) {
483 auto meta = cxml::add_child(node,
"meta:CompositionMetadataAsset");
484 meta->set_namespace_declaration (cpl_metadata_ns,
"meta");
488 auto mp = _reels.front()->main_picture();
489 cxml::add_text_child(meta,
"EditRate", mp->edit_rate().as_string());
490 cxml::add_text_child(meta,
"IntrinsicDuration", raw_convert<string>(mp->intrinsic_duration()));
492 auto fctt = cxml::add_child(meta,
"FullContentTitleText",
string(
"meta"));
496 if (_full_content_title_text_language) {
497 fctt->set_attribute(
"language", *_full_content_title_text_language);
501 cxml::add_child(meta,
"ReleaseTerritory",
string(
"meta"))->add_child_text(*
_release_territory);
504 if (_version_number) {
505 auto vn = cxml::add_child(meta,
"VersionNumber",
string(
"meta"));
506 vn->add_child_text(raw_convert<string>(*_version_number));
508 vn->set_attribute(
"status", status_to_string(*_status));
513 cxml::add_child(meta,
"Chain",
string(
"meta"))->add_child_text(*_chain);
517 cxml::add_child(meta,
"Distributor",
string(
"meta"))->add_child_text(*_distributor);
521 cxml::add_child(meta,
"Facility",
string(
"meta"))->add_child_text(*_facility);
524 if (_content_versions.size() > 1) {
525 auto vc = cxml::add_child(meta,
"AlternateContentVersionList",
string(
"meta"));
526 for (
size_t i = 1; i < _content_versions.size(); ++i) {
527 _content_versions[i].as_xml (vc);
532 _luminance->as_xml (meta,
"meta");
535 if (_main_sound_configuration) {
536 cxml::add_child(meta,
"MainSoundConfiguration",
string(
"meta"))->add_child_text(_main_sound_configuration->to_string());
538 cxml::add_child(meta,
"MainSoundSampleRate",
string(
"meta"))->add_child_text(raw_convert<string>(*_main_sound_sample_rate) +
" 1");
540 auto stored = cxml::add_child(meta,
"MainPictureStoredArea",
string(
"meta"));
541 cxml::add_child(stored,
"Width",
string(
"meta"))->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
542 cxml::add_child(stored,
"Height",
string(
"meta"))->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
544 auto active = cxml::add_child(meta,
"MainPictureActiveArea",
string(
"meta"));
545 cxml::add_child(active,
"Width",
string(
"meta"))->add_child_text(raw_convert<string>(_main_picture_active_area->width));
546 cxml::add_child(active,
"Height",
string(
"meta"))->add_child_text(raw_convert<string>(_main_picture_active_area->height));
548 optional<string> first_subtitle_language;
549 for (
auto i: _reels) {
550 if (i->main_subtitle()) {
551 first_subtitle_language = i->main_subtitle()->language();
552 if (first_subtitle_language) {
558 if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
560 if (first_subtitle_language) {
561 lang = *first_subtitle_language;
563 for (
auto const& i: _additional_subtitle_languages) {
569 cxml::add_child(meta,
"MainSubtitleLanguageList",
string(
"meta"))->add_child_text(lang);
572 auto metadata_list = cxml::add_child(meta,
"ExtensionMetadataList",
string(
"meta"));
574 auto add_extension_metadata = [metadata_list](
string scope,
string name,
string property_name,
string property_value) {
575 auto extension = cxml::add_child(metadata_list,
"ExtensionMetadata",
string(
"meta"));
576 extension->set_attribute(
"scope", scope);
577 cxml::add_child(extension,
"Name",
string(
"meta"))->add_child_text(name);
578 auto property = cxml::add_child(cxml::add_child(extension,
"PropertyList",
string(
"meta")),
"Property",
string(
"meta"));
579 cxml::add_child(property,
"Name",
string(
"meta"))->add_child_text(property_name);
580 cxml::add_child(property,
"Value",
string(
"meta"))->add_child_text(property_value);
584 add_extension_metadata (
"http://isdcf.com/ns/cplmd/app",
"Application",
"DCP Constraints Profile",
"SMPTE-RDD-52:2020-Bv2.1");
586 if (_sign_language_video_language) {
587 add_extension_metadata (
"http://isdcf.com/2017/10/SignLanguageVideo",
"Sign Language Video",
"Language Tag", *_sign_language_video_language);
590 if (_dolby_edr_image_transfer_function) {
591 add_extension_metadata(
"http://www.dolby.com/schemas/2014/EDR-Metadata",
"Dolby EDR",
"image transfer function", *_dolby_edr_image_transfer_function);
594 if (_reels.front()->main_sound()) {
595 auto asset = _reels.front()->main_sound()->asset();
596 if (asset && include_mca_subdescriptors) {
597 write_mca_subdescriptors(meta, asset);
605 add_file_assets (vector<shared_ptr<T>>& assets, vector<shared_ptr<Reel>> reels)
607 for (
auto i: reels) {
608 if (i->main_picture ()) {
609 assets.push_back (i->main_picture());
611 if (i->main_sound ()) {
612 assets.push_back (i->main_sound());
614 if (i->main_subtitle ()) {
615 assets.push_back (i->main_subtitle());
617 for (
auto j: i->closed_captions()) {
618 assets.push_back (j);
621 assets.push_back (i->atmos());
627 vector<shared_ptr<ReelFileAsset>>
630 vector<shared_ptr<ReelFileAsset>> c;
631 add_file_assets (c, _reels);
636 vector<shared_ptr<const ReelFileAsset>>
639 vector<shared_ptr<const ReelFileAsset>> c;
640 add_file_assets (c, _reels);
646 CPL::equals(shared_ptr<const Asset> other,
EqualityOptions const& opt, NoteHandler note)
const
648 auto other_cpl = dynamic_pointer_cast<const CPL>(other);
654 string const s =
"CPL: annotation texts differ: " + _annotation_text.get_value_or(
"") +
" vs " + other_cpl->_annotation_text.get_value_or(
"") +
"\n";
655 note (NoteType::ERROR, s);
660 note (NoteType::ERROR,
"CPL: content kinds differ");
664 if (_reels.size() != other_cpl->_reels.size()) {
665 note (NoteType::ERROR, String::compose (
"CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
669 auto a = _reels.begin();
670 auto b = other_cpl->_reels.begin();
672 while (a != _reels.end ()) {
673 if (!(*a)->equals (*b, opt, note)) {
687 for (
auto i: _reels) {
688 if (i->any_encrypted()) {
700 for (
auto i: _reels) {
701 if (!i->all_encrypted()) {
713 for (
auto i: _reels) {
719 CPL::resolve_refs (vector<shared_ptr<Asset>> assets)
721 for (
auto i: _reels) {
722 i->resolve_refs (assets);
729 return static_pkl_type (standard);
733 CPL::static_pkl_type (Standard standard)
736 case Standard::INTEROP:
737 return "text/xml;asdcpKind=CPL";
738 case Standard::SMPTE:
746 CPL::duration ()
const
749 for (
auto i: _reels) {
757 CPL::set_version_number (
int v)
768 CPL::unset_version_number ()
770 _version_number = boost::none;
775 CPL::set_content_versions (vector<ContentVersion> v)
777 std::set<string> ids;
779 if (!ids.insert(i.id).second) {
784 _content_versions = v;
788 optional<ContentVersion>
789 CPL::content_version ()
const
791 if (_content_versions.empty()) {
792 return optional<ContentVersion>();
795 return _content_versions[0];
800 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag>
const& langs)
802 _additional_subtitle_languages.clear ();
803 for (
auto const& i: langs) {
804 _additional_subtitle_languages.push_back (i.to_string());
810 CPL::set_main_picture_active_area(
dcp::Size area)
812 if (area.width % 2) {
813 throw BadSettingError(
"Main picture active area width is not a multiple of 2");
816 if (area.height % 2) {
817 throw BadSettingError(
"Main picture active area height is not a multiple of 2");
820 _main_picture_active_area = area;
Parent class for DCP assets, i.e. picture, sound, subtitles, closed captions, CPLs,...
boost::optional< boost::filesystem::path > file() const
void set_file(boost::filesystem::path file) const
std::vector< std::shared_ptr< Reel > > reels() const
bool all_encrypted() const
void add(std::shared_ptr< Reel > reel)
std::vector< std::shared_ptr< const ReelFileAsset > > reel_file_assets() const
boost::optional< std::string > _release_territory
std::string pkl_type(Standard standard) const override
std::string _content_title_text
<ContentTitleText>
void maybe_write_composition_metadata_asset(xmlpp::Element *node, bool include_mca_subdescriptors) const
std::string _cpl_metadata_id
ContentKind content_kind() const
bool any_encrypted() const
boost::optional< std::string > _full_content_title_text
void write_xml(boost::filesystem::path file, std::shared_ptr< const CertificateChain >, bool include_mca_subdescriptors=true) const
ContentKind _content_kind
<ContentKind>
A class to describe what "equality" means for a particular test.
bool cpl_annotation_texts_can_differ
A representation of a local time (down to the second), including its offset from GMT (equivalent to x...
std::string as_string(bool with_millisecond=false, bool with_timezone=true) const
@ MISSING_CPL_CONTENT_VERSION
Class to describe what equality means when calling Asset::equals().
Namespace for everything in libdcp.
Methods for conversion to/from string.
The integer, two-dimensional size of something.
Utility methods and classes.
Versioning variables that are written by the build system.
Helpers for XML reading with libcxml.