libdcp
cpl.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3 
4  This file is part of libdcp.
5 
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.
10 
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.
15 
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/>.
18 
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
23  including the two.
24 
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.
32 */
33 
34 
40 #include "certificate_chain.h"
41 #include "compose.hpp"
42 #include "cpl.h"
43 #include "dcp_assert.h"
44 #include "local_time.h"
45 #include "metadata.h"
46 #include "raw_convert.h"
47 #include "reel.h"
48 #include "reel_atmos_asset.h"
50 #include "reel_picture_asset.h"
51 #include "reel_sound_asset.h"
52 #include "reel_subtitle_asset.h"
53 #include "util.h"
54 #include "warnings.h"
55 #include "xml.h"
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>
64 
65 
66 using std::cout;
67 using std::dynamic_pointer_cast;
68 using std::list;
69 using std::make_pair;
70 using std::make_shared;
71 using std::pair;
72 using std::set;
73 using std::shared_ptr;
74 using std::string;
75 using std::vector;
76 using boost::optional;
77 using namespace dcp;
78 
79 
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";
86 
87 
88 CPL::CPL (string annotation_text, ContentKind content_kind, Standard standard)
89  /* default _content_title_text to annotation_text */
90  : _issuer ("libdcp" LIBDCP_VERSION)
91  , _creator ("libdcp" LIBDCP_VERSION)
92  , _issue_date (LocalTime().as_string())
93  , _annotation_text (annotation_text)
94  , _content_title_text (annotation_text)
95  , _content_kind (content_kind)
96  , _standard (standard)
97 {
98  ContentVersion cv;
99  cv.label_text = cv.id + LocalTime().as_string();
100  _content_versions.push_back (cv);
101 }
102 
103 
104 CPL::CPL (boost::filesystem::path file)
105  : Asset (file)
106  , _content_kind (ContentKind::FEATURE)
107 {
108  cxml::Document f ("CompositionPlaylist");
109  f.read_file (file);
110 
111  if (f.namespace_uri() == cpl_interop_ns) {
112  _standard = Standard::INTEROP;
113  } else if (f.namespace_uri() == cpl_smpte_ns) {
114  _standard = Standard::SMPTE;
115  } else {
116  boost::throw_exception (XMLError ("Unrecognised CPL namespace " + f.namespace_uri()));
117  }
118 
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");
124  _content_title_text = f.string_child ("ContentTitleText");
125  _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
126  shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
127  if (content_version) {
128  /* XXX: SMPTE should insist that Id is present */
129  _content_versions.push_back (
131  content_version->optional_string_child("Id").get_value_or(""),
132  content_version->string_child("LabelText")
133  )
134  );
135  content_version->done ();
136  } else if (_standard == Standard::SMPTE) {
137  /* ContentVersion is required in SMPTE */
138  throw XMLError ("Missing ContentVersion tag in CPL");
139  }
140  auto rating_list = f.node_child ("RatingList");
141  for (auto i: rating_list->node_children("Rating")) {
142  _ratings.push_back (Rating(i));
143  }
144 
145  for (auto i: f.node_child("ReelList")->node_children("Reel")) {
146  _reels.push_back (make_shared<Reel>(i, _standard));
147  }
148 
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");
154  if (metadata) {
155  read_composition_metadata_asset (metadata);
156  }
157  }
158 
159  f.ignore_child ("Issuer");
160  f.ignore_child ("Signer");
161  f.ignore_child ("Signature");
162 
163  f.done ();
164 }
165 
166 
167 void
168 CPL::add (std::shared_ptr<Reel> reel)
169 {
170  _reels.push_back (reel);
171 }
172 
173 
174 void
175 CPL::write_xml (boost::filesystem::path file, shared_ptr<const CertificateChain> signer) const
176 {
177  xmlpp::Document doc;
178  xmlpp::Element* root;
179  if (_standard == Standard::INTEROP) {
180  root = doc.create_root_node ("CompositionPlaylist", cpl_interop_ns);
181  } else {
182  root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns);
183  }
184 
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);
188  }
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);
192  root->add_child("ContentTitleText")->add_child_text (_content_title_text);
193  root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
194  if (_content_versions.empty()) {
195  ContentVersion cv;
196  cv.as_xml (root);
197  } else {
198  _content_versions[0].as_xml (root);
199  }
200 
201  auto rating_list = root->add_child("RatingList");
202  for (auto i: _ratings) {
203  i.as_xml (rating_list->add_child("Rating"));
204  }
205 
206  auto reel_list = root->add_child ("ReelList");
207 
208  if (_reels.empty()) {
209  throw NoReelsError ();
210  }
211 
212  bool first = true;
213  for (auto i: _reels) {
214  auto asset_list = i->write_to_cpl (reel_list, _standard);
215  if (first && _standard == Standard::SMPTE) {
217  first = false;
218  }
219  }
220 
221  indent (root, 0);
222 
223  if (signer) {
224  signer->sign (root, _standard);
225  }
226 
227  doc.write_to_file_formatted (file.string(), "UTF-8");
228 
229  set_file (file);
230 }
231 
232 
233 void
234 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
235 {
236  _cpl_metadata_id = remove_urn_uuid(node->string_child("Id"));
237 
238  auto fctt = node->node_child("FullContentTitleText");
239  _full_content_title_text = fctt->content();
240  _full_content_title_text_language = fctt->optional_string_attribute("language");
241 
242  _release_territory = node->optional_string_child("ReleaseTerritory");
243  if (_release_territory) {
244  _release_territory_scope = node->node_child("ReleaseTerritory")->optional_string_attribute("scope");
245  }
246 
247  auto vn = node->optional_node_child("VersionNumber");
248  if (vn) {
249  _version_number = raw_convert<int>(vn->content());
250  /* I decided to check for this number being non-negative on being set, and in the verifier, but not here */
251  auto vn_status = vn->optional_string_attribute("status");
252  if (vn_status) {
253  _status = string_to_status (*vn_status);
254  }
255  }
256 
257  _chain = node->optional_string_child("Chain");
258  _distributor = node->optional_string_child("Distributor");
259  _facility = node->optional_string_child("Facility");
260 
261  auto acv = node->optional_node_child("AlternateContentVersionList");
262  if (acv) {
263  for (auto i: acv->node_children("ContentVersion")) {
264  _content_versions.push_back (ContentVersion(i));
265  }
266  }
267 
268  auto lum = node->optional_node_child("Luminance");
269  if (lum) {
270  _luminance = Luminance (lum);
271  }
272 
273  _main_sound_configuration = node->optional_string_child("MainSoundConfiguration");
274 
275  auto sr = node->optional_string_child("MainSoundSampleRate");
276  if (sr) {
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]);
281  }
282 
283  _main_picture_stored_area = dcp::Size (
284  node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
285  node->node_child("MainPictureStoredArea")->number_child<int>("Height")
286  );
287 
288  _main_picture_active_area = dcp::Size (
289  node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
290  node->node_child("MainPictureActiveArea")->number_child<int>("Height")
291  );
292 
293  auto sll = node->optional_string_child("MainSubtitleLanguageList");
294  if (sll) {
295  vector<string> sll_split;
296  boost::split (sll_split, *sll, boost::is_any_of(" "));
297  DCP_ASSERT (!sll_split.empty());
298 
299  /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
300  size_t first = 0;
301  if (!_reels.empty()) {
302  auto sub = _reels.front()->main_subtitle();
303  if (sub) {
304  auto lang = sub->language();
305  if (lang && lang == sll_split[0]) {
306  first = 1;
307  }
308  }
309  }
310 
311  for (auto i = first; i < sll_split.size(); ++i) {
312  _additional_subtitle_languages.push_back (sll_split[i]);
313  }
314  }
315 
316  auto eml = node->optional_node_child ("ExtensionMetadataList");
317  if (eml) {
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;
327  }
328  }
329  }
330  }
331  }
332 }
333 
334 
339 void
341 {
342  if (
343  !_main_sound_configuration ||
344  !_main_sound_sample_rate ||
345  !_main_picture_stored_area ||
346  !_main_picture_active_area ||
347  _reels.empty() ||
348  !_reels.front()->main_picture()) {
349  return;
350  }
351 
352  auto meta = node->add_child("meta:CompositionMetadataAsset");
353  meta->set_namespace_declaration (cpl_metadata_ns, "meta");
354 
355  meta->add_child("Id")->add_child_text("urn:uuid:" + _cpl_metadata_id);
356 
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()));
360 
361  auto fctt = meta->add_child("FullContentTitleText", "meta");
363  fctt->add_child_text (*_full_content_title_text);
364  }
365  if (_full_content_title_text_language) {
366  fctt->set_attribute("language", *_full_content_title_text_language);
367  }
368 
369  if (_release_territory) {
370  meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
371  }
372 
373  if (_version_number) {
374  xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
375  vn->add_child_text(raw_convert<string>(*_version_number));
376  if (_status) {
377  vn->set_attribute("status", status_to_string(*_status));
378  }
379  }
380 
381  if (_chain) {
382  meta->add_child("Chain", "meta")->add_child_text(*_chain);
383  }
384 
385  if (_distributor) {
386  meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
387  }
388 
389  if (_facility) {
390  meta->add_child("Facility", "meta")->add_child_text(*_facility);
391  }
392 
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);
397  }
398  }
399 
400  if (_luminance) {
401  _luminance->as_xml (meta, "meta");
402  }
403 
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");
406 
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));
410 
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));
414 
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) {
420  break;
421  }
422  }
423  }
424 
425  if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
426  string lang;
427  if (first_subtitle_language) {
428  lang = *first_subtitle_language;
429  }
430  for (auto const& i: _additional_subtitle_languages) {
431  if (!lang.empty()) {
432  lang += " ";
433  }
434  lang += i;
435  }
436  meta->add_child("MainSubtitleLanguageList", "meta")->add_child_text(lang);
437  }
438 
439  auto metadata_list = meta->add_child("ExtensionMetadataList", "meta");
440 
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);
448  };
449 
450  /* SMPTE Bv2.1 8.6.3 */
451  add_extension_metadata ("http://isdcf.com/ns/cplmd/app", "Application", "DCP Constraints Profile", "SMPTE-RDD-52:2020-Bv2.1");
452 
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);
455  }
456 
457  if (_reels.front()->main_sound()) {
458  auto asset = _reels.front()->main_sound()->asset();
459  if (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)
465  );
466  if (KM_SUCCESS(r)) {
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");
472  char buffer[64];
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);
484  }
485  if (!soundfield->RFC5646SpokenLanguage.empty()) {
486  soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
487  sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
488  }
489 
490  list<ASDCP::MXF::InterchangeObject*> channels;
491  auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
492  asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
493  channels
494  );
495 
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);
510  }
511  if (!channel->MCAChannelID.empty()) {
512  ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
513  }
514  if (!channel->RFC5646SpokenLanguage.empty()) {
515  channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
516  ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
517  }
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));
521  }
522  }
523  }
524  }
525  }
526 }
527 
528 
529 template <class T>
530 void
531 add_file_assets (vector<shared_ptr<T>>& assets, vector<shared_ptr<Reel>> reels)
532 {
533  for (auto i: reels) {
534  if (i->main_picture ()) {
535  assets.push_back (i->main_picture());
536  }
537  if (i->main_sound ()) {
538  assets.push_back (i->main_sound());
539  }
540  if (i->main_subtitle ()) {
541  assets.push_back (i->main_subtitle());
542  }
543  for (auto j: i->closed_captions()) {
544  assets.push_back (j);
545  }
546  if (i->atmos ()) {
547  assets.push_back (i->atmos());
548  }
549  }
550 }
551 
552 
553 vector<shared_ptr<ReelFileAsset>>
555 {
556  vector<shared_ptr<ReelFileAsset>> c;
557  add_file_assets (c, _reels);
558  return c;
559 }
560 
561 
562 vector<shared_ptr<const ReelFileAsset>>
564 {
565  vector<shared_ptr<const ReelFileAsset>> c;
566  add_file_assets (c, _reels);
567  return c;
568 }
569 
570 
571 bool
572 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
573 {
574  auto other_cpl = dynamic_pointer_cast<const CPL>(other);
575  if (!other_cpl) {
576  return false;
577  }
578 
579  if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
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);
582  return false;
583  }
584 
585  if (_content_kind != other_cpl->_content_kind) {
586  note (NoteType::ERROR, "CPL: content kinds differ");
587  return false;
588  }
589 
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()));
592  return false;
593  }
594 
595  auto a = _reels.begin();
596  auto b = other_cpl->_reels.begin();
597 
598  while (a != _reels.end ()) {
599  if (!(*a)->equals (*b, opt, note)) {
600  return false;
601  }
602  ++a;
603  ++b;
604  }
605 
606  return true;
607 }
608 
609 
610 bool
612 {
613  for (auto i: _reels) {
614  if (i->any_encrypted()) {
615  return true;
616  }
617  }
618 
619  return false;
620 }
621 
622 
623 bool
625 {
626  for (auto i: _reels) {
627  if (!i->all_encrypted()) {
628  return false;
629  }
630  }
631 
632  return true;
633 }
634 
635 
636 void
637 CPL::add (DecryptedKDM const & kdm)
638 {
639  for (auto i: _reels) {
640  i->add (kdm);
641  }
642 }
643 
644 void
645 CPL::resolve_refs (vector<shared_ptr<Asset>> assets)
646 {
647  for (auto i: _reels) {
648  i->resolve_refs (assets);
649  }
650 }
651 
652 string
653 CPL::pkl_type (Standard standard) const
654 {
655  return static_pkl_type (standard);
656 }
657 
658 string
659 CPL::static_pkl_type (Standard standard)
660 {
661  switch (standard) {
662  case Standard::INTEROP:
663  return "text/xml;asdcpKind=CPL";
664  case Standard::SMPTE:
665  return "text/xml";
666  default:
667  DCP_ASSERT (false);
668  }
669 }
670 
671 int64_t
672 CPL::duration () const
673 {
674  int64_t d = 0;
675  for (auto i: _reels) {
676  d += i->duration ();
677  }
678  return d;
679 }
680 
681 
682 void
683 CPL::set_version_number (int v)
684 {
685  if (v < 0) {
686  throw BadSettingError ("CPL version number cannot be negative");
687  }
688 
689  _version_number = v;
690 }
691 
692 
693 void
694 CPL::unset_version_number ()
695 {
696  _version_number = boost::none;
697 }
698 
699 
700 void
701 CPL::set_content_versions (vector<ContentVersion> v)
702 {
703  set<string> ids;
704  for (auto i: v) {
705  if (!ids.insert(i.id).second) {
706  throw DuplicateIdError ("Duplicate ID in ContentVersion list");
707  }
708  }
709 
710  _content_versions = v;
711 }
712 
713 
714 optional<ContentVersion>
715 CPL::content_version () const
716 {
717  if (_content_versions.empty()) {
718  return optional<ContentVersion>();
719  }
720 
721  return _content_versions[0];
722 }
723 
724 
725 void
726 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
727 {
728  _additional_subtitle_languages.clear ();
729  for (auto const& i: langs) {
730  _additional_subtitle_languages.push_back (i.to_string());
731  }
732 }
CertificateChain class.
Parent class for DCP assets, i.e. picture, sound, subtitles, closed captions, CPLs,...
Definition: asset.h:70
boost::optional< boost::filesystem::path > file() const
Definition: asset.h:97
void set_file(boost::filesystem::path file) const
Definition: asset.cc:165
std::vector< std::shared_ptr< Reel > > reels() const
Definition: cpl.h:99
bool all_encrypted() const
Definition: cpl.cc:624
void add(std::shared_ptr< Reel > reel)
Definition: cpl.cc:168
std::vector< std::shared_ptr< const ReelFileAsset > > reel_file_assets() const
Definition: cpl.cc:563
boost::optional< std::string > _release_territory
Definition: cpl.h:353
std::string pkl_type(Standard standard) const override
Definition: cpl.cc:653
std::string _content_title_text
<ContentTitleText>
Definition: cpl.h:339
Standard _standard
Definition: cpl.h:372
std::string _cpl_metadata_id
Definition: cpl.h:346
bool any_encrypted() const
Definition: cpl.cc:611
boost::optional< std::string > _full_content_title_text
Definition: cpl.h:348
void write_xml(boost::filesystem::path file, std::shared_ptr< const CertificateChain >) const
Definition: cpl.cc:175
ContentKind _content_kind
<ContentKind>
Definition: cpl.h:340
void maybe_write_composition_metadata_asset(xmlpp::Element *node) const
Definition: cpl.cc:340
A decrypted KDM.
Definition: decrypted_kdm.h:76
A representation of a local time (down to the second), including its offset from GMT (equivalent to x...
Definition: local_time.h:64
std::string as_string(bool with_millisecond=false) const
Definition: local_time.cc:186
An XML error.
Definition: exceptions.h:164
CPL class.
DCP_ASSERT macro.
LocalTime class.
MXFMetadata class.
Namespace for everything in libdcp.
Definition: array_data.h:50
std::string content_kind_to_string(ContentKind kind)
Definition: types.cc:302
ContentKind content_kind_from_string(std::string kind)
Definition: types.cc:341
Methods for conversion to/from string.
ReelAtmosAsset class.
ReelClosedCaptionAsset class.
ReelPictureAsset class.
ReelSoundAsset class.
ReelSubtitleAsset class.
A class to describe what "equality" means for a particular test.
Definition: types.h:249
bool cpl_annotation_texts_can_differ
Definition: types.h:260
The integer, two-dimensional size of something.
Definition: types.h:71
Utility methods and classes.
Helpers for XML reading with libcxml.