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->string_child("MainSoundConfiguration");
252 string sr = node->string_child("MainSoundSampleRate");
253 vector<string> sr_bits;
254 boost::split (sr_bits, sr, boost::is_any_of(" "));
255 DCP_ASSERT (sr_bits.size() == 2);
256 _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
258 _main_picture_stored_area = dcp::Size (
259 node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
260 node->node_child("MainPictureStoredArea")->number_child<int>("Height")
263 _main_picture_active_area = dcp::Size (
264 node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
265 node->node_child("MainPictureActiveArea")->number_child<int>("Height")
268 optional<string> sll = node->optional_string_child("MainSubtitleLanguageList");
270 vector<string> sll_split;
271 boost::split (sll_split, *sll, boost::is_any_of(" "));
272 DCP_ASSERT (!sll_split.empty());
274 /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
276 if (!_reels.empty()) {
277 shared_ptr<dcp::ReelSubtitleAsset> sub = _reels.front()->main_subtitle();
279 optional<dcp::LanguageTag> lang = sub->language();
280 if (lang && lang->to_string() == sll_split[0]) {
286 for (size_t i = first; i < sll_split.size(); ++i) {
287 _additional_subtitle_languages.push_back (sll_split[i]);
293 /** Write a CompositionMetadataAsset node as a child of @param node provided
294 * the required metadata is stored in the object. If any required metadata
295 * is missing this method will do nothing.
298 CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
301 !_main_sound_configuration ||
302 !_main_sound_sample_rate ||
303 !_main_picture_stored_area ||
304 !_main_picture_active_area ||
306 !_reels.front()->main_picture()) {
310 xmlpp::Element* meta = node->add_child("meta:CompositionMetadataAsset");
311 meta->set_namespace_declaration (cpl_metadata_ns, "meta");
313 meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid());
315 shared_ptr<dcp::ReelPictureAsset> mp = _reels.front()->main_picture();
316 meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
317 meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
319 xmlpp::Element* fctt = meta->add_child("FullContentTitleText", "meta");
320 if (_full_content_title_text) {
321 fctt->add_child_text (*_full_content_title_text);
323 if (_full_content_title_text_language) {
324 fctt->set_attribute("language", *_full_content_title_text_language);
327 if (_release_territory) {
328 meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
331 if (_version_number) {
332 xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
333 vn->add_child_text(raw_convert<string>(*_version_number));
335 vn->set_attribute("status", status_to_string(*_status));
340 meta->add_child("Chain", "meta")->add_child_text(*_chain);
344 meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
348 meta->add_child("Facility", "meta")->add_child_text(*_facility);
351 if (_content_versions.size() > 1) {
352 xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta");
353 for (size_t i = 1; i < _content_versions.size(); ++i) {
354 _content_versions[i].as_xml (vc);
359 _luminance->as_xml (meta, "meta");
362 meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
363 meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
365 xmlpp::Element* stored = meta->add_child("MainPictureStoredArea", "meta");
366 stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
367 stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
369 xmlpp::Element* active = meta->add_child("MainPictureActiveArea", "meta");
370 active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
371 active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
373 optional<dcp::LanguageTag> first_subtitle_language;
374 BOOST_FOREACH (shared_ptr<const Reel> i, _reels) {
375 if (i->main_subtitle()) {
376 first_subtitle_language = i->main_subtitle()->language();
377 if (first_subtitle_language) {
383 if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
385 if (first_subtitle_language) {
386 lang = first_subtitle_language->to_string();
388 BOOST_FOREACH (dcp::LanguageTag const& i, _additional_subtitle_languages) {
392 lang += i.to_string();
394 meta->add_child("MainSubtitleLanguageList")->add_child_text(lang);
399 list<shared_ptr<ReelMXF> >
402 list<shared_ptr<ReelMXF> > c;
404 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
405 if (i->main_picture ()) {
406 c.push_back (i->main_picture());
408 if (i->main_sound ()) {
409 c.push_back (i->main_sound());
411 if (i->main_subtitle ()) {
412 c.push_back (i->main_subtitle());
414 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
418 c.push_back (i->atmos());
425 list<shared_ptr<const ReelMXF> >
426 CPL::reel_mxfs () const
428 list<shared_ptr<const ReelMXF> > c;
430 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
431 if (i->main_picture ()) {
432 c.push_back (i->main_picture());
434 if (i->main_sound ()) {
435 c.push_back (i->main_sound());
437 if (i->main_subtitle ()) {
438 c.push_back (i->main_subtitle());
440 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
444 c.push_back (i->atmos());
452 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
454 shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
459 if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
460 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
465 if (_content_kind != other_cpl->_content_kind) {
466 note (DCP_ERROR, "CPL: content kinds differ");
470 if (_reels.size() != other_cpl->_reels.size()) {
471 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
475 list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
476 list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
478 while (a != _reels.end ()) {
479 if (!(*a)->equals (*b, opt, note)) {
489 /** @return true if we have any encrypted content */
491 CPL::encrypted () const
493 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
494 if (i->encrypted ()) {
502 /** Add a KDM to this CPL. If the KDM is for any of this CPLs assets it will be used
503 * to decrypt those assets.
507 CPL::add (DecryptedKDM const & kdm)
509 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
515 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
517 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
518 i->resolve_refs (assets);
523 CPL::pkl_type (Standard standard) const
525 return static_pkl_type (standard);
529 CPL::static_pkl_type (Standard standard)
533 return "text/xml;asdcpKind=CPL";
542 CPL::duration () const
545 BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
553 CPL::set_version_number (int v)
556 throw BadSettingError ("CPL version number cannot be negative");
564 CPL::set_content_versions (vector<ContentVersion> v)
567 BOOST_FOREACH (ContentVersion i, v) {
568 if (!ids.insert(i.id).second) {
569 throw DuplicateIdError ("Duplicate ID in ContentVersion list");
573 _content_versions = v;
578 CPL::content_version () const
580 DCP_ASSERT (!_content_versions.empty());
581 return _content_versions[0];
586 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
588 _additional_subtitle_languages.clear ();
589 BOOST_FOREACH (dcp::LanguageTag const& i, langs) {
590 _additional_subtitle_languages.push_back (i.to_string());