2 Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
38 #include "certificate_chain.h"
40 #include "reel_picture_asset.h"
41 #include "reel_sound_asset.h"
42 #include "reel_subtitle_asset.h"
43 #include "reel_closed_caption_asset.h"
44 #include "reel_atmos_asset.h"
45 #include "local_time.h"
46 #include "dcp_assert.h"
47 #include "compose.hpp"
48 #include "raw_convert.h"
49 #include <asdcp/Metadata.h>
50 #include <libxml/parser.h>
51 #include <libxml++/libxml++.h>
52 #include <boost/algorithm/string.hpp>
53 #include <boost/foreach.hpp>
62 using boost::shared_ptr;
63 using boost::optional;
64 using boost::dynamic_pointer_cast;
68 static string const cpl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#";
69 static string const cpl_smpte_ns = "http://www.smpte-ra.org/schemas/429-7/2006/CPL";
70 static string const cpl_metadata_ns = "http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata";
71 static string const mca_sub_descriptors_ns = "http://isdcf.com/ns/cplmd/mca";
72 static string const smpte_395_ns = "http://www.smpte-ra.org/reg/395/2014/13/1/aaf";
73 static string const smpte_335_ns = "http://www.smpte-ra.org/reg/335/2012";
76 CPL::CPL (string annotation_text, ContentKind content_kind)
77 /* default _content_title_text to annotation_text */
78 : _issuer ("libdcp" LIBDCP_VERSION)
79 , _creator ("libdcp" LIBDCP_VERSION)
80 , _issue_date (LocalTime().as_string())
81 , _annotation_text (annotation_text)
82 , _content_title_text (annotation_text)
83 , _content_kind (content_kind)
86 cv.label_text = cv.id + LocalTime().as_string();
87 _content_versions.push_back (cv);
90 /** Construct a CPL object from a XML file */
91 CPL::CPL (boost::filesystem::path file)
93 , _content_kind (FEATURE)
95 cxml::Document f ("CompositionPlaylist");
98 if (f.namespace_uri() == cpl_interop_ns) {
100 } else if (f.namespace_uri() == cpl_smpte_ns) {
103 boost::throw_exception (XMLError ("Unrecognised CPL namespace " + f.namespace_uri()));
106 _id = remove_urn_uuid (f.string_child ("Id"));
107 _annotation_text = f.optional_string_child("AnnotationText").get_value_or("");
108 _issuer = f.optional_string_child("Issuer").get_value_or("");
109 _creator = f.optional_string_child("Creator").get_value_or("");
110 _issue_date = f.string_child ("IssueDate");
111 _content_title_text = f.string_child ("ContentTitleText");
112 _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
113 shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
114 if (content_version) {
115 /* XXX: SMPTE should insist that Id is present */
116 _content_versions.push_back (
118 content_version->optional_string_child("Id").get_value_or(""),
119 content_version->string_child("LabelText")
122 content_version->done ();
123 } else if (_standard == SMPTE) {
124 /* ContentVersion is required in SMPTE */
125 throw XMLError ("Missing ContentVersion tag in CPL");
127 cxml::ConstNodePtr rating_list = f.node_child ("RatingList");
129 BOOST_FOREACH (cxml::ConstNodePtr i, rating_list->node_children("Rating")) {
130 _ratings.push_back (Rating(i));
133 _reels = type_grand_children<Reel> (f, "ReelList", "Reel");
135 cxml::ConstNodePtr reel_list = f.node_child ("ReelList");
137 list<cxml::NodePtr> reels = reel_list->node_children("Reel");
138 if (!reels.empty()) {
139 cxml::ConstNodePtr asset_list = reels.front()->node_child("AssetList");
140 cxml::ConstNodePtr metadata = asset_list->optional_node_child("CompositionMetadataAsset");
142 read_composition_metadata_asset (metadata);
148 f.ignore_child ("Issuer");
149 f.ignore_child ("Signer");
150 f.ignore_child ("Signature");
155 /** Add a reel to this CPL.
156 * @param reel Reel to add.
159 CPL::add (boost::shared_ptr<Reel> reel)
161 _reels.push_back (reel);
164 /** Write an CompositonPlaylist XML file.
166 * @param file Filename to write.
167 * @param standard INTEROP or SMPTE.
168 * @param signer Signer to sign the CPL, or 0 to add no signature.
171 CPL::write_xml (boost::filesystem::path file, Standard standard, shared_ptr<const CertificateChain> signer) const
174 xmlpp::Element* root;
175 if (standard == INTEROP) {
176 root = doc.create_root_node ("CompositionPlaylist", cpl_interop_ns);
178 root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns);
181 root->add_child("Id")->add_child_text ("urn:uuid:" + _id);
182 root->add_child("AnnotationText")->add_child_text (_annotation_text);
183 root->add_child("IssueDate")->add_child_text (_issue_date);
184 root->add_child("Issuer")->add_child_text (_issuer);
185 root->add_child("Creator")->add_child_text (_creator);
186 root->add_child("ContentTitleText")->add_child_text (_content_title_text);
187 root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
188 if (_content_versions.empty()) {
192 _content_versions[0].as_xml (root);
195 xmlpp::Element* rating_list = root->add_child("RatingList");
196 BOOST_FOREACH (Rating i, _ratings) {
197 i.as_xml (rating_list->add_child("Rating"));
200 xmlpp::Element* reel_list = root->add_child ("ReelList");
203 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
204 xmlpp::Element* asset_list = i->write_to_cpl (reel_list, standard);
205 if (first && standard == dcp::SMPTE) {
206 maybe_write_composition_metadata_asset (asset_list);
214 signer->sign (root, standard);
217 doc.write_to_file_formatted (file.string(), "UTF-8");
224 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
226 cxml::ConstNodePtr fctt = node->node_child("FullContentTitleText");
227 _full_content_title_text = fctt->content();
228 _full_content_title_text_language = fctt->optional_string_attribute("language");
230 _release_territory = node->optional_string_child("ReleaseTerritory");
232 cxml::ConstNodePtr vn = node->optional_node_child("VersionNumber");
234 _version_number = raw_convert<int>(vn->content());
235 /* I decided to check for this number being non-negative on being set, and in the verifier, but not here */
236 optional<string> vn_status = vn->optional_string_attribute("status");
238 _status = string_to_status (*vn_status);
242 _chain = node->optional_string_child("Chain");
243 _distributor = node->optional_string_child("Distributor");
244 _facility = node->optional_string_child("Facility");
246 cxml::ConstNodePtr acv = node->optional_node_child("AlternateContentVersionList");
248 BOOST_FOREACH (cxml::ConstNodePtr i, acv->node_children("ContentVersion")) {
249 _content_versions.push_back (ContentVersion(i));
253 cxml::ConstNodePtr lum = node->optional_node_child("Luminance");
255 _luminance = Luminance (lum);
258 _main_sound_configuration = node->optional_string_child("MainSoundConfiguration");
260 optional<string> sr = node->optional_string_child("MainSoundSampleRate");
262 vector<string> sr_bits;
263 boost::split (sr_bits, *sr, boost::is_any_of(" "));
264 DCP_ASSERT (sr_bits.size() == 2);
265 _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
268 _main_picture_stored_area = dcp::Size (
269 node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
270 node->node_child("MainPictureStoredArea")->number_child<int>("Height")
273 _main_picture_active_area = dcp::Size (
274 node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
275 node->node_child("MainPictureActiveArea")->number_child<int>("Height")
278 optional<string> sll = node->optional_string_child("MainSubtitleLanguageList");
280 vector<string> sll_split;
281 boost::split (sll_split, *sll, boost::is_any_of(" "));
282 DCP_ASSERT (!sll_split.empty());
284 /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
286 if (!_reels.empty()) {
287 shared_ptr<dcp::ReelSubtitleAsset> sub = _reels.front()->main_subtitle();
289 optional<string> lang = sub->language();
290 if (lang && lang == sll_split[0]) {
296 for (size_t i = first; i < sll_split.size(); ++i) {
297 _additional_subtitle_languages.push_back (sll_split[i]);
303 /** Write a CompositionMetadataAsset node as a child of @param node provided
304 * the required metadata is stored in the object. If any required metadata
305 * is missing this method will do nothing.
308 CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
311 !_main_sound_configuration ||
312 !_main_sound_sample_rate ||
313 !_main_picture_stored_area ||
314 !_main_picture_active_area ||
316 !_reels.front()->main_picture()) {
320 xmlpp::Element* meta = node->add_child("meta:CompositionMetadataAsset");
321 meta->set_namespace_declaration (cpl_metadata_ns, "meta");
323 meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid());
325 shared_ptr<dcp::ReelPictureAsset> mp = _reels.front()->main_picture();
326 meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
327 meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
329 xmlpp::Element* fctt = meta->add_child("FullContentTitleText", "meta");
330 if (_full_content_title_text) {
331 fctt->add_child_text (*_full_content_title_text);
333 if (_full_content_title_text_language) {
334 fctt->set_attribute("language", *_full_content_title_text_language);
337 if (_release_territory) {
338 meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
341 if (_version_number) {
342 xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
343 vn->add_child_text(raw_convert<string>(*_version_number));
345 vn->set_attribute("status", status_to_string(*_status));
350 meta->add_child("Chain", "meta")->add_child_text(*_chain);
354 meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
358 meta->add_child("Facility", "meta")->add_child_text(*_facility);
361 if (_content_versions.size() > 1) {
362 xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta");
363 for (size_t i = 1; i < _content_versions.size(); ++i) {
364 _content_versions[i].as_xml (vc);
369 _luminance->as_xml (meta, "meta");
372 meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
373 meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
375 xmlpp::Element* stored = meta->add_child("MainPictureStoredArea", "meta");
376 stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
377 stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
379 xmlpp::Element* active = meta->add_child("MainPictureActiveArea", "meta");
380 active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
381 active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
383 optional<string> first_subtitle_language;
384 BOOST_FOREACH (shared_ptr<const Reel> i, _reels) {
385 if (i->main_subtitle()) {
386 first_subtitle_language = i->main_subtitle()->language();
387 if (first_subtitle_language) {
393 if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
395 if (first_subtitle_language) {
396 lang = *first_subtitle_language;
398 BOOST_FOREACH (string const& i, _additional_subtitle_languages) {
404 meta->add_child("MainSubtitleLanguageList")->add_child_text(lang);
407 if (_reels.front()->main_sound()) {
408 shared_ptr<const SoundAsset> asset = _reels.front()->main_sound()->asset();
410 shared_ptr<SoundAssetReader> reader = asset->start_read ();
411 ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
412 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
413 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
414 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
417 xmlpp::Element* mca_subs = meta->add_child("mca:MCASubDescriptors");
418 mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
419 mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
420 mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
421 xmlpp::Element* sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
423 soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
424 sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
425 soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
426 sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
427 soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
428 sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
429 soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
430 sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
431 if (!soundfield->MCATagName.empty()) {
432 soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
433 sf->add_child("MCATagName", "r1")->add_child_text(buffer);
435 if (!soundfield->RFC5646SpokenLanguage.empty()) {
436 soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
437 sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
440 list<ASDCP::MXF::InterchangeObject*> channels;
441 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectsByType(
442 asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
446 BOOST_FOREACH (ASDCP::MXF::InterchangeObject* i, channels) {
447 ASDCP::MXF::AudioChannelLabelSubDescriptor* channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
448 xmlpp::Element* ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
449 channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
450 ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
451 channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
452 ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
453 channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
454 ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
455 channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
456 ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
457 if (!channel->MCATagName.empty()) {
458 channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
459 ch->add_child("MCATagName", "r1")->add_child_text(buffer);
461 if (!channel->MCAChannelID.empty()) {
462 ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
464 if (!channel->RFC5646SpokenLanguage.empty()) {
465 channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
466 ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
468 if (!channel->SoundfieldGroupLinkID.empty()) {
469 channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
470 ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
479 list<shared_ptr<ReelMXF> >
482 list<shared_ptr<ReelMXF> > c;
484 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
485 if (i->main_picture ()) {
486 c.push_back (i->main_picture());
488 if (i->main_sound ()) {
489 c.push_back (i->main_sound());
491 if (i->main_subtitle ()) {
492 c.push_back (i->main_subtitle());
494 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
498 c.push_back (i->atmos());
505 list<shared_ptr<const ReelMXF> >
506 CPL::reel_mxfs () const
508 list<shared_ptr<const ReelMXF> > c;
510 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
511 if (i->main_picture ()) {
512 c.push_back (i->main_picture());
514 if (i->main_sound ()) {
515 c.push_back (i->main_sound());
517 if (i->main_subtitle ()) {
518 c.push_back (i->main_subtitle());
520 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
524 c.push_back (i->atmos());
532 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
534 shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
539 if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
540 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
545 if (_content_kind != other_cpl->_content_kind) {
546 note (DCP_ERROR, "CPL: content kinds differ");
550 if (_reels.size() != other_cpl->_reels.size()) {
551 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
555 list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
556 list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
558 while (a != _reels.end ()) {
559 if (!(*a)->equals (*b, opt, note)) {
569 /** @return true if we have any encrypted content */
571 CPL::encrypted () const
573 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
574 if (i->encrypted ()) {
582 /** Add a KDM to this CPL. If the KDM is for any of this CPLs assets it will be used
583 * to decrypt those assets.
587 CPL::add (DecryptedKDM const & kdm)
589 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
595 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
597 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
598 i->resolve_refs (assets);
603 CPL::pkl_type (Standard standard) const
605 return static_pkl_type (standard);
609 CPL::static_pkl_type (Standard standard)
613 return "text/xml;asdcpKind=CPL";
622 CPL::duration () const
625 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
633 CPL::set_version_number (int v)
636 throw BadSettingError ("CPL version number cannot be negative");
644 CPL::set_content_versions (vector<ContentVersion> v)
647 BOOST_FOREACH (ContentVersion i, v) {
648 if (!ids.insert(i.id).second) {
649 throw DuplicateIdError ("Duplicate ID in ContentVersion list");
653 _content_versions = v;
657 optional<ContentVersion>
658 CPL::content_version () const
660 if (_content_versions.empty()) {
661 return optional<ContentVersion>();
664 return _content_versions[0];
669 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
671 _additional_subtitle_languages.clear ();
672 BOOST_FOREACH (dcp::LanguageTag const& i, langs) {
673 _additional_subtitle_languages.push_back (i.to_string());