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 <libxml/parser.h>
50 #include <libxml++/libxml++.h>
51 #include <boost/algorithm/string.hpp>
52 #include <boost/foreach.hpp>
61 using boost::shared_ptr;
62 using boost::optional;
63 using boost::dynamic_pointer_cast;
67 static string const cpl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#";
68 static string const cpl_smpte_ns = "http://www.smpte-ra.org/schemas/429-7/2006/CPL";
69 static string const cpl_metadata_ns = "http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata";
72 CPL::CPL (string annotation_text, ContentKind content_kind)
73 /* default _content_title_text to annotation_text */
74 : _issuer ("libdcp" LIBDCP_VERSION)
75 , _creator ("libdcp" LIBDCP_VERSION)
76 , _issue_date (LocalTime().as_string())
77 , _annotation_text (annotation_text)
78 , _content_title_text (annotation_text)
79 , _content_kind (content_kind)
82 cv.label_text = cv.id + LocalTime().as_string();
83 _content_versions.push_back (cv);
86 /** Construct a CPL object from a XML file */
87 CPL::CPL (boost::filesystem::path file)
89 , _content_kind (FEATURE)
91 cxml::Document f ("CompositionPlaylist");
94 if (f.namespace_uri() == cpl_interop_ns) {
96 } else if (f.namespace_uri() == cpl_smpte_ns) {
99 boost::throw_exception (XMLError ("Unrecognised CPL namespace " + f.namespace_uri()));
102 _id = remove_urn_uuid (f.string_child ("Id"));
103 _annotation_text = f.optional_string_child("AnnotationText").get_value_or("");
104 _issuer = f.optional_string_child("Issuer").get_value_or("");
105 _creator = f.optional_string_child("Creator").get_value_or("");
106 _issue_date = f.string_child ("IssueDate");
107 _content_title_text = f.string_child ("ContentTitleText");
108 _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
109 shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
110 if (content_version) {
111 /* XXX: SMPTE should insist that Id is present */
112 _content_versions.push_back (
114 content_version->optional_string_child("Id").get_value_or(""),
115 content_version->string_child("LabelText")
118 content_version->done ();
119 } else if (_standard == SMPTE) {
120 /* ContentVersion is required in SMPTE */
121 throw XMLError ("Missing ContentVersion tag in CPL");
123 cxml::ConstNodePtr rating_list = f.node_child ("RatingList");
125 BOOST_FOREACH (cxml::ConstNodePtr i, rating_list->node_children("Rating")) {
126 _ratings.push_back (Rating(i));
129 _reels = type_grand_children<Reel> (f, "ReelList", "Reel");
131 cxml::ConstNodePtr reel_list = f.node_child ("ReelList");
133 list<cxml::NodePtr> reels = reel_list->node_children("Reel");
134 if (!reels.empty()) {
135 cxml::ConstNodePtr asset_list = reels.front()->node_child("AssetList");
136 cxml::ConstNodePtr metadata = asset_list->optional_node_child("CompositionMetadataAsset");
138 read_composition_metadata_asset (metadata);
144 f.ignore_child ("Issuer");
145 f.ignore_child ("Signer");
146 f.ignore_child ("Signature");
151 /** Add a reel to this CPL.
152 * @param reel Reel to add.
155 CPL::add (boost::shared_ptr<Reel> reel)
157 _reels.push_back (reel);
160 /** Write an CompositonPlaylist XML file.
162 * @param file Filename to write.
163 * @param standard INTEROP or SMPTE.
164 * @param signer Signer to sign the CPL, or 0 to add no signature.
167 CPL::write_xml (boost::filesystem::path file, Standard standard, shared_ptr<const CertificateChain> signer) const
170 xmlpp::Element* root;
171 if (standard == INTEROP) {
172 root = doc.create_root_node ("CompositionPlaylist", cpl_interop_ns);
174 root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns);
177 root->add_child("Id")->add_child_text ("urn:uuid:" + _id);
178 root->add_child("AnnotationText")->add_child_text (_annotation_text);
179 root->add_child("IssueDate")->add_child_text (_issue_date);
180 root->add_child("Issuer")->add_child_text (_issuer);
181 root->add_child("Creator")->add_child_text (_creator);
182 root->add_child("ContentTitleText")->add_child_text (_content_title_text);
183 root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
184 DCP_ASSERT (!_content_versions.empty());
185 _content_versions[0].as_xml (root);
187 xmlpp::Element* rating_list = root->add_child("RatingList");
188 BOOST_FOREACH (Rating i, _ratings) {
189 i.as_xml (rating_list->add_child("Rating"));
192 xmlpp::Element* reel_list = root->add_child ("ReelList");
195 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
196 xmlpp::Element* asset_list = i->write_to_cpl (reel_list, standard);
197 if (first && standard == dcp::SMPTE) {
198 maybe_write_composition_metadata_asset (asset_list);
206 signer->sign (root, standard);
209 doc.write_to_file_formatted (file.string(), "UTF-8");
216 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
218 cxml::ConstNodePtr fctt = node->node_child("FullContentTitleText");
219 _full_content_title_text = fctt->content();
220 _full_content_title_text_language = fctt->optional_string_attribute("language");
222 _release_territory = node->optional_string_child("ReleaseTerritory");
224 cxml::ConstNodePtr vn = node->optional_node_child("VersionNumber");
226 _version_number = raw_convert<int>(vn->content());
227 /* I decided to check for this number being non-negative on being set, and in the verifier, but not here */
228 optional<string> vn_status = vn->optional_string_attribute("status");
230 _status = string_to_status (*vn_status);
234 _chain = node->optional_string_child("Chain");
235 _distributor = node->optional_string_child("Distributor");
236 _facility = node->optional_string_child("Facility");
238 cxml::ConstNodePtr acv = node->optional_node_child("AlternateContentVersionList");
240 BOOST_FOREACH (cxml::ConstNodePtr i, acv->node_children("ContentVersion")) {
241 _content_versions.push_back (ContentVersion(i));
245 cxml::ConstNodePtr lum = node->optional_node_child("Luminance");
247 _luminance = Luminance (lum);
250 _main_sound_configuration = node->optional_string_child("MainSoundConfiguration");
252 optional<string> sr = node->optional_string_child("MainSoundSampleRate");
254 vector<string> sr_bits;
255 boost::split (sr_bits, *sr, boost::is_any_of(" "));
256 DCP_ASSERT (sr_bits.size() == 2);
257 _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
260 _main_picture_stored_area = dcp::Size (
261 node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
262 node->node_child("MainPictureStoredArea")->number_child<int>("Height")
265 _main_picture_active_area = dcp::Size (
266 node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
267 node->node_child("MainPictureActiveArea")->number_child<int>("Height")
270 optional<string> sll = node->optional_string_child("MainSubtitleLanguageList");
272 vector<string> sll_split;
273 boost::split (sll_split, *sll, boost::is_any_of(" "));
274 DCP_ASSERT (!sll_split.empty());
276 /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
278 if (!_reels.empty()) {
279 shared_ptr<dcp::ReelSubtitleAsset> sub = _reels.front()->main_subtitle();
281 optional<dcp::LanguageTag> lang = sub->language();
282 if (lang && lang->to_string() == sll_split[0]) {
288 for (size_t i = first; i < sll_split.size(); ++i) {
289 _additional_subtitle_languages.push_back (sll_split[i]);
295 /** Write a CompositionMetadataAsset node as a child of @param node provided
296 * the required metadata is stored in the object. If any required metadata
297 * is missing this method will do nothing.
300 CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
303 !_main_sound_configuration ||
304 !_main_sound_sample_rate ||
305 !_main_picture_stored_area ||
306 !_main_picture_active_area ||
308 !_reels.front()->main_picture()) {
312 xmlpp::Element* meta = node->add_child("meta:CompositionMetadataAsset");
313 meta->set_namespace_declaration (cpl_metadata_ns, "meta");
315 meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid());
317 shared_ptr<dcp::ReelPictureAsset> mp = _reels.front()->main_picture();
318 meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
319 meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
321 xmlpp::Element* fctt = meta->add_child("FullContentTitleText", "meta");
322 if (_full_content_title_text) {
323 fctt->add_child_text (*_full_content_title_text);
325 if (_full_content_title_text_language) {
326 fctt->set_attribute("language", *_full_content_title_text_language);
329 if (_release_territory) {
330 meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
333 if (_version_number) {
334 xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
335 vn->add_child_text(raw_convert<string>(*_version_number));
337 vn->set_attribute("status", status_to_string(*_status));
342 meta->add_child("Chain", "meta")->add_child_text(*_chain);
346 meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
350 meta->add_child("Facility", "meta")->add_child_text(*_facility);
353 if (_content_versions.size() > 1) {
354 xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta");
355 for (size_t i = 1; i < _content_versions.size(); ++i) {
356 _content_versions[i].as_xml (vc);
361 _luminance->as_xml (meta, "meta");
364 meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
365 meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
367 xmlpp::Element* stored = meta->add_child("MainPictureStoredArea", "meta");
368 stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
369 stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
371 xmlpp::Element* active = meta->add_child("MainPictureActiveArea", "meta");
372 active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
373 active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
375 optional<dcp::LanguageTag> first_subtitle_language;
376 BOOST_FOREACH (shared_ptr<const Reel> i, _reels) {
377 if (i->main_subtitle()) {
378 first_subtitle_language = i->main_subtitle()->language();
379 if (first_subtitle_language) {
385 if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
387 if (first_subtitle_language) {
388 lang = first_subtitle_language->to_string();
390 BOOST_FOREACH (dcp::LanguageTag const& i, _additional_subtitle_languages) {
394 lang += i.to_string();
396 meta->add_child("MainSubtitleLanguageList")->add_child_text(lang);
401 list<shared_ptr<ReelMXF> >
404 list<shared_ptr<ReelMXF> > c;
406 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
407 if (i->main_picture ()) {
408 c.push_back (i->main_picture());
410 if (i->main_sound ()) {
411 c.push_back (i->main_sound());
413 if (i->main_subtitle ()) {
414 c.push_back (i->main_subtitle());
416 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
420 c.push_back (i->atmos());
427 list<shared_ptr<const ReelMXF> >
428 CPL::reel_mxfs () const
430 list<shared_ptr<const ReelMXF> > c;
432 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
433 if (i->main_picture ()) {
434 c.push_back (i->main_picture());
436 if (i->main_sound ()) {
437 c.push_back (i->main_sound());
439 if (i->main_subtitle ()) {
440 c.push_back (i->main_subtitle());
442 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
446 c.push_back (i->atmos());
454 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
456 shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
461 if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
462 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
467 if (_content_kind != other_cpl->_content_kind) {
468 note (DCP_ERROR, "CPL: content kinds differ");
472 if (_reels.size() != other_cpl->_reels.size()) {
473 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
477 list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
478 list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
480 while (a != _reels.end ()) {
481 if (!(*a)->equals (*b, opt, note)) {
491 /** @return true if we have any encrypted content */
493 CPL::encrypted () const
495 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
496 if (i->encrypted ()) {
504 /** Add a KDM to this CPL. If the KDM is for any of this CPLs assets it will be used
505 * to decrypt those assets.
509 CPL::add (DecryptedKDM const & kdm)
511 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
517 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
519 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
520 i->resolve_refs (assets);
525 CPL::pkl_type (Standard standard) const
527 return static_pkl_type (standard);
531 CPL::static_pkl_type (Standard standard)
535 return "text/xml;asdcpKind=CPL";
544 CPL::duration () const
547 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
555 CPL::set_version_number (int v)
558 throw BadSettingError ("CPL version number cannot be negative");
566 CPL::set_content_versions (vector<ContentVersion> v)
569 BOOST_FOREACH (ContentVersion i, v) {
570 if (!ids.insert(i.id).second) {
571 throw DuplicateIdError ("Duplicate ID in ContentVersion list");
575 _content_versions = v;
580 CPL::content_version () const
582 DCP_ASSERT (!_content_versions.empty());
583 return _content_versions[0];
588 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
590 _additional_subtitle_languages.clear ();
591 BOOST_FOREACH (dcp::LanguageTag const& i, langs) {
592 _additional_subtitle_languages.push_back (i.to_string());