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 DCP_ASSERT (!_content_versions.empty());
189 _content_versions[0].as_xml (root);
191 xmlpp::Element* rating_list = root->add_child("RatingList");
192 BOOST_FOREACH (Rating i, _ratings) {
193 i.as_xml (rating_list->add_child("Rating"));
196 xmlpp::Element* reel_list = root->add_child ("ReelList");
199 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
200 xmlpp::Element* asset_list = i->write_to_cpl (reel_list, standard);
201 if (first && standard == dcp::SMPTE) {
202 maybe_write_composition_metadata_asset (asset_list);
210 signer->sign (root, standard);
213 doc.write_to_file_formatted (file.string(), "UTF-8");
220 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
222 cxml::ConstNodePtr fctt = node->node_child("FullContentTitleText");
223 _full_content_title_text = fctt->content();
224 _full_content_title_text_language = fctt->optional_string_attribute("language");
226 _release_territory = node->optional_string_child("ReleaseTerritory");
228 cxml::ConstNodePtr vn = node->optional_node_child("VersionNumber");
230 _version_number = raw_convert<int>(vn->content());
231 /* I decided to check for this number being non-negative on being set, and in the verifier, but not here */
232 optional<string> vn_status = vn->optional_string_attribute("status");
234 _status = string_to_status (*vn_status);
238 _chain = node->optional_string_child("Chain");
239 _distributor = node->optional_string_child("Distributor");
240 _facility = node->optional_string_child("Facility");
242 cxml::ConstNodePtr acv = node->optional_node_child("AlternateContentVersionList");
244 BOOST_FOREACH (cxml::ConstNodePtr i, acv->node_children("ContentVersion")) {
245 _content_versions.push_back (ContentVersion(i));
249 cxml::ConstNodePtr lum = node->optional_node_child("Luminance");
251 _luminance = Luminance (lum);
254 _main_sound_configuration = node->optional_string_child("MainSoundConfiguration");
256 optional<string> sr = node->optional_string_child("MainSoundSampleRate");
258 vector<string> sr_bits;
259 boost::split (sr_bits, *sr, boost::is_any_of(" "));
260 DCP_ASSERT (sr_bits.size() == 2);
261 _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
264 _main_picture_stored_area = dcp::Size (
265 node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
266 node->node_child("MainPictureStoredArea")->number_child<int>("Height")
269 _main_picture_active_area = dcp::Size (
270 node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
271 node->node_child("MainPictureActiveArea")->number_child<int>("Height")
274 optional<string> sll = node->optional_string_child("MainSubtitleLanguageList");
276 vector<string> sll_split;
277 boost::split (sll_split, *sll, boost::is_any_of(" "));
278 DCP_ASSERT (!sll_split.empty());
280 /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
282 if (!_reels.empty()) {
283 shared_ptr<dcp::ReelSubtitleAsset> sub = _reels.front()->main_subtitle();
285 optional<dcp::LanguageTag> lang = sub->language();
286 if (lang && lang->to_string() == sll_split[0]) {
292 for (size_t i = first; i < sll_split.size(); ++i) {
293 _additional_subtitle_languages.push_back (sll_split[i]);
299 /** Write a CompositionMetadataAsset node as a child of @param node provided
300 * the required metadata is stored in the object. If any required metadata
301 * is missing this method will do nothing.
304 CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
307 !_main_sound_configuration ||
308 !_main_sound_sample_rate ||
309 !_main_picture_stored_area ||
310 !_main_picture_active_area ||
312 !_reels.front()->main_picture()) {
316 xmlpp::Element* meta = node->add_child("meta:CompositionMetadataAsset");
317 meta->set_namespace_declaration (cpl_metadata_ns, "meta");
319 meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid());
321 shared_ptr<dcp::ReelPictureAsset> mp = _reels.front()->main_picture();
322 meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
323 meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
325 xmlpp::Element* fctt = meta->add_child("FullContentTitleText", "meta");
326 if (_full_content_title_text) {
327 fctt->add_child_text (*_full_content_title_text);
329 if (_full_content_title_text_language) {
330 fctt->set_attribute("language", *_full_content_title_text_language);
333 if (_release_territory) {
334 meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
337 if (_version_number) {
338 xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
339 vn->add_child_text(raw_convert<string>(*_version_number));
341 vn->set_attribute("status", status_to_string(*_status));
346 meta->add_child("Chain", "meta")->add_child_text(*_chain);
350 meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
354 meta->add_child("Facility", "meta")->add_child_text(*_facility);
357 if (_content_versions.size() > 1) {
358 xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta");
359 for (size_t i = 1; i < _content_versions.size(); ++i) {
360 _content_versions[i].as_xml (vc);
365 _luminance->as_xml (meta, "meta");
368 meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
369 meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
371 xmlpp::Element* stored = meta->add_child("MainPictureStoredArea", "meta");
372 stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
373 stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
375 xmlpp::Element* active = meta->add_child("MainPictureActiveArea", "meta");
376 active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
377 active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
379 optional<dcp::LanguageTag> first_subtitle_language;
380 BOOST_FOREACH (shared_ptr<const Reel> i, _reels) {
381 if (i->main_subtitle()) {
382 first_subtitle_language = i->main_subtitle()->language();
383 if (first_subtitle_language) {
389 if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
391 if (first_subtitle_language) {
392 lang = first_subtitle_language->to_string();
394 BOOST_FOREACH (dcp::LanguageTag const& i, _additional_subtitle_languages) {
398 lang += i.to_string();
400 meta->add_child("MainSubtitleLanguageList")->add_child_text(lang);
403 if (_reels.front()->main_sound()) {
404 shared_ptr<const SoundAsset> asset = _reels.front()->main_sound()->asset();
406 shared_ptr<SoundAssetReader> reader = asset->start_read ();
407 ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
408 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
409 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
410 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
413 xmlpp::Element* mca_subs = meta->add_child("mca:MCASubDescriptors");
414 mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
415 mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
416 mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
417 xmlpp::Element* sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
419 soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
420 sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
421 soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
422 sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
423 soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
424 sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
425 soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
426 sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
427 if (!soundfield->MCATagName.empty()) {
428 soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
429 sf->add_child("MCATagName", "r1")->add_child_text(buffer);
431 if (!soundfield->RFC5646SpokenLanguage.empty()) {
432 soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
433 sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
436 list<ASDCP::MXF::InterchangeObject*> channels;
437 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectsByType(
438 asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
442 BOOST_FOREACH (ASDCP::MXF::InterchangeObject* i, channels) {
443 ASDCP::MXF::AudioChannelLabelSubDescriptor* channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
444 xmlpp::Element* ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
445 channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
446 ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
447 channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
448 ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
449 channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
450 ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
451 channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
452 ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
453 if (!channel->MCATagName.empty()) {
454 channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
455 ch->add_child("MCATagName", "r1")->add_child_text(buffer);
457 if (!channel->MCAChannelID.empty()) {
458 ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
460 if (!channel->RFC5646SpokenLanguage.empty()) {
461 channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
462 ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
464 if (!channel->SoundfieldGroupLinkID.empty()) {
465 channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
466 ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
475 list<shared_ptr<ReelMXF> >
478 list<shared_ptr<ReelMXF> > c;
480 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
481 if (i->main_picture ()) {
482 c.push_back (i->main_picture());
484 if (i->main_sound ()) {
485 c.push_back (i->main_sound());
487 if (i->main_subtitle ()) {
488 c.push_back (i->main_subtitle());
490 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
494 c.push_back (i->atmos());
501 list<shared_ptr<const ReelMXF> >
502 CPL::reel_mxfs () const
504 list<shared_ptr<const ReelMXF> > c;
506 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
507 if (i->main_picture ()) {
508 c.push_back (i->main_picture());
510 if (i->main_sound ()) {
511 c.push_back (i->main_sound());
513 if (i->main_subtitle ()) {
514 c.push_back (i->main_subtitle());
516 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
520 c.push_back (i->atmos());
528 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
530 shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
535 if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
536 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
541 if (_content_kind != other_cpl->_content_kind) {
542 note (DCP_ERROR, "CPL: content kinds differ");
546 if (_reels.size() != other_cpl->_reels.size()) {
547 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
551 list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
552 list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
554 while (a != _reels.end ()) {
555 if (!(*a)->equals (*b, opt, note)) {
565 /** @return true if we have any encrypted content */
567 CPL::encrypted () const
569 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
570 if (i->encrypted ()) {
578 /** Add a KDM to this CPL. If the KDM is for any of this CPLs assets it will be used
579 * to decrypt those assets.
583 CPL::add (DecryptedKDM const & kdm)
585 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
591 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
593 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
594 i->resolve_refs (assets);
599 CPL::pkl_type (Standard standard) const
601 return static_pkl_type (standard);
605 CPL::static_pkl_type (Standard standard)
609 return "text/xml;asdcpKind=CPL";
618 CPL::duration () const
621 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
629 CPL::set_version_number (int v)
632 throw BadSettingError ("CPL version number cannot be negative");
640 CPL::set_content_versions (vector<ContentVersion> v)
643 BOOST_FOREACH (ContentVersion i, v) {
644 if (!ids.insert(i.id).second) {
645 throw DuplicateIdError ("Duplicate ID in ContentVersion list");
649 _content_versions = v;
654 CPL::content_version () const
656 DCP_ASSERT (!_content_versions.empty());
657 return _content_versions[0];
662 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
664 _additional_subtitle_languages.clear ();
665 BOOST_FOREACH (dcp::LanguageTag const& i, langs) {
666 _additional_subtitle_languages.push_back (i.to_string());