41 #include "compose.hpp"
56 LIBDCP_DISABLE_WARNINGS
57 #include <asdcp/Metadata.h>
58 LIBDCP_ENABLE_WARNINGS
59 #include <libxml/parser.h>
60 LIBDCP_DISABLE_WARNINGS
61 #include <libxml++/libxml++.h>
62 LIBDCP_ENABLE_WARNINGS
63 #include <boost/algorithm/string.hpp>
67 using std::dynamic_pointer_cast;
70 using std::make_shared;
73 using std::shared_ptr;
76 using boost::optional;
80 static string const cpl_interop_ns =
"http://www.digicine.com/PROTO-ASDCP-CPL-20040511#";
81 static string const cpl_smpte_ns =
"http://www.smpte-ra.org/schemas/429-7/2006/CPL";
82 static string const cpl_metadata_ns =
"http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata";
83 static string const mca_sub_descriptors_ns =
"http://isdcf.com/ns/cplmd/mca";
84 static string const smpte_395_ns =
"http://www.smpte-ra.org/reg/395/2014/13/1/aaf";
85 static string const smpte_335_ns =
"http://www.smpte-ra.org/reg/335/2012";
88 CPL::CPL (
string annotation_text, ContentKind content_kind, Standard standard)
90 : _issuer (
"libdcp" LIBDCP_VERSION)
91 , _creator (
"libdcp" LIBDCP_VERSION)
93 , _annotation_text (annotation_text)
94 , _content_title_text (annotation_text)
95 , _content_kind (content_kind)
96 , _standard (standard)
100 _content_versions.push_back (cv);
104 CPL::CPL (boost::filesystem::path file)
106 , _content_kind (ContentKind::FEATURE)
108 cxml::Document f (
"CompositionPlaylist");
111 if (f.namespace_uri() == cpl_interop_ns) {
113 }
else if (f.namespace_uri() == cpl_smpte_ns) {
116 boost::throw_exception (
XMLError (
"Unrecognised CPL namespace " + f.namespace_uri()));
119 _id = remove_urn_uuid (f.string_child (
"Id"));
120 _annotation_text = f.optional_string_child(
"AnnotationText");
121 _issuer = f.optional_string_child(
"Issuer").get_value_or(
"");
122 _creator = f.optional_string_child(
"Creator").get_value_or(
"");
123 _issue_date = f.string_child (
"IssueDate");
126 shared_ptr<cxml::Node> content_version = f.optional_node_child (
"ContentVersion");
127 if (content_version) {
129 _content_versions.push_back (
131 content_version->optional_string_child(
"Id").get_value_or(
""),
132 content_version->string_child(
"LabelText")
135 content_version->done ();
136 }
else if (
_standard == Standard::SMPTE) {
138 throw XMLError (
"Missing ContentVersion tag in CPL");
140 auto rating_list = f.node_child (
"RatingList");
141 for (
auto i: rating_list->node_children(
"Rating")) {
142 _ratings.push_back (
Rating(i));
145 for (
auto i: f.node_child(
"ReelList")->node_children(
"Reel")) {
146 _reels.push_back (make_shared<Reel>(i,
_standard));
149 auto reel_list = f.node_child (
"ReelList");
150 auto reels = reel_list->node_children(
"Reel");
151 if (!
reels.empty()) {
152 auto asset_list =
reels.front()->node_child(
"AssetList");
153 auto metadata = asset_list->optional_node_child(
"CompositionMetadataAsset");
155 read_composition_metadata_asset (metadata);
159 f.ignore_child (
"Issuer");
160 f.ignore_child (
"Signer");
161 f.ignore_child (
"Signature");
170 _reels.push_back (reel);
175 CPL::write_xml (boost::filesystem::path file, shared_ptr<const CertificateChain> signer)
const
178 xmlpp::Element* root;
180 root = doc.create_root_node (
"CompositionPlaylist", cpl_interop_ns);
182 root = doc.create_root_node (
"CompositionPlaylist", cpl_smpte_ns);
185 root->add_child(
"Id")->add_child_text (
"urn:uuid:" + _id);
186 if (_annotation_text) {
187 root->add_child(
"AnnotationText")->add_child_text (*_annotation_text);
189 root->add_child(
"IssueDate")->add_child_text (_issue_date);
190 root->add_child(
"Issuer")->add_child_text (_issuer);
191 root->add_child(
"Creator")->add_child_text (_creator);
194 if (_content_versions.empty()) {
198 _content_versions[0].as_xml (root);
201 auto rating_list = root->add_child(
"RatingList");
202 for (
auto i: _ratings) {
203 i.as_xml (rating_list->add_child(
"Rating"));
206 auto reel_list = root->add_child (
"ReelList");
208 if (_reels.empty()) {
213 for (
auto i: _reels) {
214 auto asset_list = i->write_to_cpl (reel_list,
_standard);
215 if (first &&
_standard == Standard::SMPTE) {
227 doc.write_to_file_formatted (
file.string(),
"UTF-8");
234 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
238 auto fctt = node->node_child(
"FullContentTitleText");
240 _full_content_title_text_language = fctt->optional_string_attribute(
"language");
244 _release_territory_scope = node->node_child(
"ReleaseTerritory")->optional_string_attribute(
"scope");
247 auto vn = node->optional_node_child(
"VersionNumber");
249 _version_number = raw_convert<int>(vn->content());
251 auto vn_status = vn->optional_string_attribute(
"status");
253 _status = string_to_status (*vn_status);
257 _chain = node->optional_string_child(
"Chain");
258 _distributor = node->optional_string_child(
"Distributor");
259 _facility = node->optional_string_child(
"Facility");
261 auto acv = node->optional_node_child(
"AlternateContentVersionList");
263 for (
auto i: acv->node_children(
"ContentVersion")) {
268 auto lum = node->optional_node_child(
"Luminance");
273 _main_sound_configuration = node->optional_string_child(
"MainSoundConfiguration");
275 auto sr = node->optional_string_child(
"MainSoundSampleRate");
277 vector<string> sr_bits;
278 boost::split (sr_bits, *sr, boost::is_any_of(
" "));
279 DCP_ASSERT (sr_bits.size() == 2);
280 _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
284 node->node_child(
"MainPictureStoredArea")->number_child<
int>(
"Width"),
285 node->node_child(
"MainPictureStoredArea")->number_child<
int>(
"Height")
289 node->node_child(
"MainPictureActiveArea")->number_child<
int>(
"Width"),
290 node->node_child(
"MainPictureActiveArea")->number_child<
int>(
"Height")
293 auto sll = node->optional_string_child(
"MainSubtitleLanguageList");
295 vector<string> sll_split;
296 boost::split (sll_split, *sll, boost::is_any_of(
" "));
297 DCP_ASSERT (!sll_split.empty());
301 if (!_reels.empty()) {
302 auto sub = _reels.front()->main_subtitle();
304 auto lang = sub->language();
305 if (lang && lang == sll_split[0]) {
311 for (
auto i = first; i < sll_split.size(); ++i) {
312 _additional_subtitle_languages.push_back (sll_split[i]);
316 auto eml = node->optional_node_child (
"ExtensionMetadataList");
318 for (
auto i: eml->node_children(
"ExtensionMetadata")) {
319 auto name = i->optional_string_child(
"Name");
320 if (name && *name ==
"Sign Language Video") {
321 auto property_list = i->node_child(
"PropertyList");
322 for (
auto j: property_list->node_children(
"Property")) {
323 auto name = j->optional_string_child(
"Name");
324 auto value = j->optional_string_child(
"Value");
325 if (name && value && *name ==
"Language Tag") {
326 _sign_language_video_language = *value;
343 !_main_sound_configuration ||
344 !_main_sound_sample_rate ||
345 !_main_picture_stored_area ||
346 !_main_picture_active_area ||
348 !_reels.front()->main_picture()) {
352 auto meta = node->add_child(
"meta:CompositionMetadataAsset");
353 meta->set_namespace_declaration (cpl_metadata_ns,
"meta");
357 auto mp = _reels.front()->main_picture();
358 meta->add_child(
"EditRate")->add_child_text(mp->edit_rate().as_string());
359 meta->add_child(
"IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
361 auto fctt = meta->add_child(
"FullContentTitleText",
"meta");
365 if (_full_content_title_text_language) {
366 fctt->set_attribute(
"language", *_full_content_title_text_language);
373 if (_version_number) {
374 xmlpp::Element* vn = meta->add_child(
"VersionNumber",
"meta");
375 vn->add_child_text(raw_convert<string>(*_version_number));
377 vn->set_attribute(
"status", status_to_string(*_status));
382 meta->add_child(
"Chain",
"meta")->add_child_text(*_chain);
386 meta->add_child(
"Distributor",
"meta")->add_child_text(*_distributor);
390 meta->add_child(
"Facility",
"meta")->add_child_text(*_facility);
393 if (_content_versions.size() > 1) {
394 xmlpp::Element* vc = meta->add_child(
"AlternateContentVersionList",
"meta");
395 for (
size_t i = 1; i < _content_versions.size(); ++i) {
396 _content_versions[i].as_xml (vc);
401 _luminance->as_xml (meta,
"meta");
404 meta->add_child(
"MainSoundConfiguration",
"meta")->add_child_text(*_main_sound_configuration);
405 meta->add_child(
"MainSoundSampleRate",
"meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) +
" 1");
407 auto stored = meta->add_child(
"MainPictureStoredArea",
"meta");
408 stored->add_child(
"Width",
"meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
409 stored->add_child(
"Height",
"meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
411 auto active = meta->add_child(
"MainPictureActiveArea",
"meta");
412 active->add_child(
"Width",
"meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
413 active->add_child(
"Height",
"meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
415 optional<string> first_subtitle_language;
416 for (
auto i: _reels) {
417 if (i->main_subtitle()) {
418 first_subtitle_language = i->main_subtitle()->language();
419 if (first_subtitle_language) {
425 if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
427 if (first_subtitle_language) {
428 lang = *first_subtitle_language;
430 for (
auto const& i: _additional_subtitle_languages) {
436 meta->add_child(
"MainSubtitleLanguageList",
"meta")->add_child_text(lang);
439 auto metadata_list = meta->add_child(
"ExtensionMetadataList",
"meta");
441 auto add_extension_metadata = [metadata_list](
string scope,
string name,
string property_name,
string property_value) {
442 auto extension = metadata_list->add_child(
"ExtensionMetadata",
"meta");
443 extension->set_attribute(
"scope", scope);
444 extension->add_child(
"Name",
"meta")->add_child_text(name);
445 auto property = extension->add_child(
"PropertyList",
"meta")->add_child(
"Property",
"meta");
446 property->add_child(
"Name",
"meta")->add_child_text(property_name);
447 property->add_child(
"Value",
"meta")->add_child_text(property_value);
451 add_extension_metadata (
"http://isdcf.com/ns/cplmd/app",
"Application",
"DCP Constraints Profile",
"SMPTE-RDD-52:2020-Bv2.1");
453 if (_sign_language_video_language) {
454 add_extension_metadata (
"http://isdcf.com/2017/10/SignLanguageVideo",
"Sign Language Video",
"Language Tag", *_sign_language_video_language);
457 if (_reels.front()->main_sound()) {
458 auto asset = _reels.front()->main_sound()->asset();
460 auto reader = asset->start_read ();
461 ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
462 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
463 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
464 reinterpret_cast<ASDCP::MXF::InterchangeObject**
>(&soundfield)
467 auto mca_subs = meta->add_child(
"mca:MCASubDescriptors");
468 mca_subs->set_namespace_declaration (mca_sub_descriptors_ns,
"mca");
469 mca_subs->set_namespace_declaration (smpte_395_ns,
"r0");
470 mca_subs->set_namespace_declaration (smpte_335_ns,
"r1");
471 auto sf = mca_subs->add_child(
"SoundfieldGroupLabelSubDescriptor",
"r0");
473 soundfield->InstanceUID.EncodeString(buffer,
sizeof(buffer));
474 sf->add_child(
"InstanceID",
"r1")->add_child_text(
"urn:uuid:" +
string(buffer));
475 soundfield->MCALabelDictionaryID.EncodeString(buffer,
sizeof(buffer));
476 sf->add_child(
"MCALabelDictionaryID",
"r1")->add_child_text(
"urn:smpte:ul:" +
string(buffer));
477 soundfield->MCALinkID.EncodeString(buffer,
sizeof(buffer));
478 sf->add_child(
"MCALinkID",
"r1")->add_child_text(
"urn:uuid:" +
string(buffer));
479 soundfield->MCATagSymbol.EncodeString(buffer,
sizeof(buffer));
480 sf->add_child(
"MCATagSymbol",
"r1")->add_child_text(buffer);
481 if (!soundfield->MCATagName.empty()) {
482 soundfield->MCATagName.get().EncodeString(buffer,
sizeof(buffer));
483 sf->add_child(
"MCATagName",
"r1")->add_child_text(buffer);
485 if (!soundfield->RFC5646SpokenLanguage.empty()) {
486 soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer,
sizeof(buffer));
487 sf->add_child(
"RFC5646SpokenLanguage",
"r1")->add_child_text(buffer);
490 list<ASDCP::MXF::InterchangeObject*> channels;
491 auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
492 asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
496 for (
auto i: channels) {
497 auto channel =
reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*
>(i);
498 auto ch = mca_subs->add_child(
"AudioChannelLabelSubDescriptor",
"r0");
499 channel->InstanceUID.EncodeString(buffer,
sizeof(buffer));
500 ch->add_child(
"InstanceID",
"r1")->add_child_text(
"urn:uuid:" +
string(buffer));
501 channel->MCALabelDictionaryID.EncodeString(buffer,
sizeof(buffer));
502 ch->add_child(
"MCALabelDictionaryID",
"r1")->add_child_text(
"urn:smpte:ul:" +
string(buffer));
503 channel->MCALinkID.EncodeString(buffer,
sizeof(buffer));
504 ch->add_child(
"MCALinkID",
"r1")->add_child_text(
"urn:uuid:" +
string(buffer));
505 channel->MCATagSymbol.EncodeString(buffer,
sizeof(buffer));
506 ch->add_child(
"MCATagSymbol",
"r1")->add_child_text(buffer);
507 if (!channel->MCATagName.empty()) {
508 channel->MCATagName.get().EncodeString(buffer,
sizeof(buffer));
509 ch->add_child(
"MCATagName",
"r1")->add_child_text(buffer);
511 if (!channel->MCAChannelID.empty()) {
512 ch->add_child(
"MCAChannelID",
"r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
514 if (!channel->RFC5646SpokenLanguage.empty()) {
515 channel->RFC5646SpokenLanguage.get().EncodeString(buffer,
sizeof(buffer));
516 ch->add_child(
"RFC5646SpokenLanguage",
"r1")->add_child_text(buffer);
518 if (!channel->SoundfieldGroupLinkID.empty()) {
519 channel->SoundfieldGroupLinkID.get().EncodeString(buffer,
sizeof(buffer));
520 ch->add_child(
"SoundfieldGroupLinkID",
"r1")->add_child_text(
"urn:uuid:" +
string(buffer));
531 add_file_assets (vector<shared_ptr<T>>& assets, vector<shared_ptr<Reel>> reels)
533 for (
auto i: reels) {
534 if (i->main_picture ()) {
535 assets.push_back (i->main_picture());
537 if (i->main_sound ()) {
538 assets.push_back (i->main_sound());
540 if (i->main_subtitle ()) {
541 assets.push_back (i->main_subtitle());
543 for (
auto j: i->closed_captions()) {
544 assets.push_back (j);
547 assets.push_back (i->atmos());
553 vector<shared_ptr<ReelFileAsset>>
556 vector<shared_ptr<ReelFileAsset>> c;
557 add_file_assets (c, _reels);
562 vector<shared_ptr<const ReelFileAsset>>
565 vector<shared_ptr<const ReelFileAsset>> c;
566 add_file_assets (c, _reels);
572 CPL::equals (shared_ptr<const Asset> other,
EqualityOptions opt, NoteHandler note)
const
574 auto other_cpl = dynamic_pointer_cast<const CPL>(other);
580 string const s =
"CPL: annotation texts differ: " + _annotation_text.get_value_or(
"") +
" vs " + other_cpl->_annotation_text.get_value_or(
"") +
"\n";
581 note (NoteType::ERROR, s);
586 note (NoteType::ERROR,
"CPL: content kinds differ");
590 if (_reels.size() != other_cpl->_reels.size()) {
591 note (NoteType::ERROR, String::compose (
"CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
595 auto a = _reels.begin();
596 auto b = other_cpl->_reels.begin();
598 while (a != _reels.end ()) {
599 if (!(*a)->equals (*b, opt, note)) {
613 for (
auto i: _reels) {
614 if (i->any_encrypted()) {
626 for (
auto i: _reels) {
627 if (!i->all_encrypted()) {
639 for (
auto i: _reels) {
645 CPL::resolve_refs (vector<shared_ptr<Asset>> assets)
647 for (
auto i: _reels) {
648 i->resolve_refs (assets);
655 return static_pkl_type (standard);
659 CPL::static_pkl_type (Standard standard)
662 case Standard::INTEROP:
663 return "text/xml;asdcpKind=CPL";
664 case Standard::SMPTE:
672 CPL::duration ()
const
675 for (
auto i: _reels) {
683 CPL::set_version_number (
int v)
694 CPL::unset_version_number ()
696 _version_number = boost::none;
701 CPL::set_content_versions (vector<ContentVersion> v)
705 if (!ids.insert(i.id).second) {
710 _content_versions = v;
714 optional<ContentVersion>
715 CPL::content_version ()
const
717 if (_content_versions.empty()) {
718 return optional<ContentVersion>();
721 return _content_versions[0];
726 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag>
const& langs)
728 _additional_subtitle_languages.clear ();
729 for (
auto const& i: langs) {
730 _additional_subtitle_languages.push_back (i.to_string());
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>
std::string _cpl_metadata_id
bool any_encrypted() const
boost::optional< std::string > _full_content_title_text
void write_xml(boost::filesystem::path file, std::shared_ptr< const CertificateChain >) const
ContentKind _content_kind
<ContentKind>
void maybe_write_composition_metadata_asset(xmlpp::Element *node) const
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) const
Namespace for everything in libdcp.
std::string content_kind_to_string(ContentKind kind)
ContentKind content_kind_from_string(std::string kind)
Methods for conversion to/from string.
ReelClosedCaptionAsset class.
A class to describe what "equality" means for a particular test.
bool cpl_annotation_texts_can_differ
The integer, two-dimensional size of something.
Utility methods and classes.
Helpers for XML reading with libcxml.