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 "equality_options.h"
45 #include "filesystem.h"
46 #include "local_time.h"
47 #include "metadata.h"
48 #include "raw_convert.h"
49 #include "reel.h"
50 #include "reel_atmos_asset.h"
51 #include "reel_picture_asset.h"
52 #include "reel_sound_asset.h"
53 #include "reel_text_asset.h"
54 #include "util.h"
55 #include "version.h"
56 #include "warnings.h"
57 #include "xml.h"
58 LIBDCP_DISABLE_WARNINGS
59 #include <asdcp/Metadata.h>
60 LIBDCP_ENABLE_WARNINGS
61 #include <libxml/parser.h>
62 LIBDCP_DISABLE_WARNINGS
63 #include <libxml++/libxml++.h>
64 LIBDCP_ENABLE_WARNINGS
65 #include <boost/algorithm/string.hpp>
66 
67 
68 using std::cout;
69 using std::dynamic_pointer_cast;
70 using std::list;
71 using std::make_pair;
72 using std::make_shared;
73 using std::pair;
74 using std::set;
75 using std::shared_ptr;
76 using std::string;
77 using std::vector;
78 using boost::optional;
79 using namespace dcp;
80 
81 
82 static string const cpl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#";
83 static string const cpl_smpte_ns = "http://www.smpte-ra.org/schemas/429-7/2006/CPL";
84 static string const cpl_metadata_ns = "http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata";
85 static string const mca_sub_descriptors_ns = "http://isdcf.com/ns/cplmd/mca";
86 static string const smpte_395_ns = "http://www.smpte-ra.org/reg/395/2014/13/1/aaf";
87 static string const smpte_335_ns = "http://www.smpte-ra.org/reg/335/2012";
88 
89 
90 CPL::CPL (string annotation_text, ContentKind content_kind, Standard standard)
91  /* default _content_title_text to annotation_text */
92  : _issuer("libdcp", dcp::version)
93  , _creator("libdcp", dcp::version)
94  , _issue_date (LocalTime().as_string())
95  , _annotation_text (annotation_text)
96  , _content_title_text (annotation_text)
97  , _content_kind (content_kind)
98  , _standard (standard)
99 {
100  ContentVersion cv;
101  cv.label_text = cv.id + LocalTime().as_string();
102  _content_versions.push_back (cv);
103 }
104 
105 
106 CPL::CPL (boost::filesystem::path file, vector<dcp::VerificationNote>* notes)
107  : Asset (file)
108  , _content_kind (ContentKind::FEATURE)
109 {
110  cxml::Document f ("CompositionPlaylist");
111  f.read_file(dcp::filesystem::fix_long_path(file));
112 
113  if (f.namespace_uri() == cpl_interop_ns) {
114  _standard = Standard::INTEROP;
115  } else if (f.namespace_uri() == cpl_smpte_ns) {
116  _standard = Standard::SMPTE;
117  } else {
118  if (notes) {
119  notes->push_back(
121  dcp::VerificationNote::Type::ERROR,
123  f.namespace_uri(),
124  file
125  )
126  );
127  }
128  _standard = Standard::INTEROP;
129  }
130 
131  _id = remove_urn_uuid (f.string_child ("Id"));
132  _annotation_text = f.optional_string_child("AnnotationText");
133  _issuer = f.optional_string_child("Issuer").get_value_or("");
134  _creator = f.optional_string_child("Creator").get_value_or("");
135  _issue_date = f.string_child ("IssueDate");
136  _content_title_text = f.string_child ("ContentTitleText");
137  auto content_kind = f.node_child("ContentKind");
138  _content_kind = ContentKind(content_kind->content(), content_kind->optional_string_attribute("scope"));
139  shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
140  if (content_version) {
141  /* XXX: SMPTE should insist that Id is present */
142  _content_versions.push_back (
144  content_version->optional_string_child("Id").get_value_or(""),
145  content_version->string_child("LabelText")
146  )
147  );
148  content_version->done ();
149  } else if (_standard == Standard::SMPTE) {
150  /* ContentVersion is required in SMPTE */
151  if (notes) {
152  notes->push_back(
154  dcp::VerificationNote::Type::ERROR,
156  _id,
157  file
158  )
159  );
160  }
161  }
162  auto rating_list = f.node_child ("RatingList");
163  for (auto i: rating_list->node_children("Rating")) {
164  _ratings.push_back (Rating(i));
165  }
166 
167  for (auto i: f.node_child("ReelList")->node_children("Reel")) {
168  _reels.push_back (make_shared<Reel>(i, _standard));
169  }
170 
171  auto reel_list = f.node_child ("ReelList");
172  auto reels = reel_list->node_children("Reel");
173  if (!reels.empty()) {
174  auto asset_list = reels.front()->node_child("AssetList");
175  auto metadata = asset_list->optional_node_child("CompositionMetadataAsset");
176  if (metadata) {
177  read_composition_metadata_asset (metadata);
178  _read_composition_metadata = true;
179  }
180  }
181 
182  f.ignore_child ("Issuer");
183  f.ignore_child ("Signer");
184  f.ignore_child ("Signature");
185 
186  f.done ();
187 }
188 
189 
190 void
191 CPL::add (std::shared_ptr<Reel> reel)
192 {
193  _reels.push_back (reel);
194 }
195 
196 
197 void
198 CPL::set (std::vector<std::shared_ptr<Reel>> reels)
199 {
200  _reels = reels;
201 }
202 
203 
204 void
205 CPL::write_xml(boost::filesystem::path file, shared_ptr<const CertificateChain> signer, bool include_mca_subdescriptors) const
206 {
207  xmlpp::Document doc;
208  xmlpp::Element* root;
209  if (_standard == Standard::INTEROP) {
210  root = doc.create_root_node ("CompositionPlaylist", cpl_interop_ns);
211  } else {
212  root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns);
213  }
214 
215  cxml::add_text_child(root, "Id", "urn:uuid:" + _id);
216  if (_annotation_text) {
217  cxml::add_text_child(root, "AnnotationText", *_annotation_text);
218  }
219  cxml::add_text_child(root, "IssueDate", _issue_date);
220  cxml::add_text_child(root, "Issuer", _issuer);
221  cxml::add_text_child(root, "Creator", _creator);
222  cxml::add_text_child(root, "ContentTitleText", _content_title_text);
223  auto content_kind = cxml::add_child(root, "ContentKind");
224  content_kind->add_child_text(_content_kind.name());
225  if (_content_kind.scope()) {
226  content_kind->set_attribute("scope", *_content_kind.scope());
227  }
228  if (_content_versions.empty()) {
229  ContentVersion cv;
230  cv.as_xml (root);
231  } else {
232  _content_versions[0].as_xml (root);
233  }
234 
235  auto rating_list = cxml::add_child(root, "RatingList");
236  for (auto i: _ratings) {
237  i.as_xml(cxml::add_child(rating_list, "Rating"));
238  }
239 
240  auto reel_list = cxml::add_child(root, "ReelList");
241 
242  if (_reels.empty()) {
243  throw NoReelsError ();
244  }
245 
246  bool first = true;
247  for (auto i: _reels) {
248  auto asset_list = i->write_to_cpl (reel_list, _standard);
249  if (first && _standard == Standard::SMPTE) {
250  maybe_write_composition_metadata_asset(asset_list, include_mca_subdescriptors);
251  first = false;
252  }
253  }
254 
255  indent (root, 0);
256 
257  if (signer) {
258  signer->sign (root, _standard);
259  }
260 
261  doc.write_to_file_formatted(dcp::filesystem::fix_long_path(file).string(), "UTF-8");
262 
263  set_file (file);
264 }
265 
266 
267 void
268 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
269 {
270  _cpl_metadata_id = remove_urn_uuid(node->string_child("Id"));
271 
272  /* FullContentTitleText is compulsory but in DoM #2295 we saw a commercial tool which
273  * apparently didn't include it, so as usual we have to be defensive.
274  */
275  if (auto fctt = node->optional_node_child("FullContentTitleText")) {
276  _full_content_title_text = fctt->content();
277  _full_content_title_text_language = fctt->optional_string_attribute("language");
278  }
279 
280  _release_territory = node->optional_string_child("ReleaseTerritory");
281  if (_release_territory) {
282  _release_territory_scope = node->node_child("ReleaseTerritory")->optional_string_attribute("scope");
283  }
284 
285  auto vn = node->optional_node_child("VersionNumber");
286  if (vn) {
287  _version_number = raw_convert<int>(vn->content());
288  /* I decided to check for this number being non-negative on being set, and in the verifier, but not here */
289  auto vn_status = vn->optional_string_attribute("status");
290  if (vn_status) {
291  _status = string_to_status (*vn_status);
292  }
293  }
294 
295  _chain = node->optional_string_child("Chain");
296  _distributor = node->optional_string_child("Distributor");
297  _facility = node->optional_string_child("Facility");
298 
299  auto acv = node->optional_node_child("AlternateContentVersionList");
300  if (acv) {
301  for (auto i: acv->node_children("ContentVersion")) {
302  _content_versions.push_back (ContentVersion(i));
303  }
304  }
305 
306  auto lum = node->optional_node_child("Luminance");
307  if (lum) {
308  _luminance = Luminance (lum);
309  }
310 
311  if (auto msc = node->optional_string_child("MainSoundConfiguration")) {
312  try {
313  _main_sound_configuration = MainSoundConfiguration(*msc);
314  } catch (MainSoundConfigurationError& e) {
315  /* With Interop DCPs this node may not make any sense, but that's OK */
316  if (_standard == dcp::Standard::SMPTE) {
317  throw e;
318  }
319  }
320  }
321 
322  auto sr = node->optional_string_child("MainSoundSampleRate");
323  if (sr) {
324  vector<string> sr_bits;
325  boost::split (sr_bits, *sr, boost::is_any_of(" "));
326  DCP_ASSERT (sr_bits.size() == 2);
327  _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
328  }
329 
330  if (_standard == dcp::Standard::SMPTE) {
331  _main_picture_stored_area = dcp::Size (
332  node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
333  node->node_child("MainPictureStoredArea")->number_child<int>("Height")
334  );
335  }
336 
337  _main_picture_active_area = dcp::Size (
338  node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
339  node->node_child("MainPictureActiveArea")->number_child<int>("Height")
340  );
341 
342  auto sll = node->optional_string_child("MainSubtitleLanguageList");
343  if (sll) {
344  vector<string> sll_split;
345  boost::split (sll_split, *sll, boost::is_any_of(" "));
346  DCP_ASSERT (!sll_split.empty());
347 
348  /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
349  size_t first = 0;
350  if (!_reels.empty()) {
351  auto sub = _reels.front()->main_subtitle();
352  if (sub) {
353  auto lang = sub->language();
354  if (lang && lang == sll_split[0]) {
355  first = 1;
356  }
357  }
358  }
359 
360  for (auto i = first; i < sll_split.size(); ++i) {
361  _additional_subtitle_languages.push_back (sll_split[i]);
362  }
363  }
364 
365  auto eml = node->optional_node_child ("ExtensionMetadataList");
366 
367  auto extension_metadata = [eml](string scope, string name, string property) -> boost::optional<std::string> {
368  if (!eml) {
369  return {};
370  }
371 
372  for (auto i: eml->node_children("ExtensionMetadata")) {
373  auto xml_scope = i->optional_string_attribute("scope");
374  auto xml_name = i->optional_string_child("Name");
375  if (xml_scope && *xml_scope == scope && xml_name && *xml_name == name) {
376  auto property_list = i->node_child("PropertyList");
377  for (auto j: property_list->node_children("Property")) {
378  auto property_name = j->optional_string_child("Name");
379  auto property_value = j->optional_string_child("Value");
380  if (property_name && property_value && *property_name == property) {
381  return property_value;
382  }
383  }
384  }
385  }
386 
387  return {};
388  };
389 
390  _sign_language_video_language = extension_metadata("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag");
391  _dolby_edr_image_transfer_function = extension_metadata("http://www.dolby.com/schemas/2014/EDR-Metadata", "Dolby EDR", "image transfer function");
392 }
393 
394 
395 void
396 CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsset> asset) const
397 {
398  auto reader = asset->start_read ();
399  ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
400  ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
401  asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
402  reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
403  );
404  if (KM_SUCCESS(r)) {
405  auto mca_subs = cxml::add_child(parent, "mca:MCASubDescriptors");
406  mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
407  mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
408  mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
409  auto sf = cxml::add_child(mca_subs, "SoundfieldGroupLabelSubDescriptor", string("r0"));
410  char buffer[64];
411  soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
412  cxml::add_child(sf, "InstanceID", string("r1"))->add_child_text("urn:uuid:" + string(buffer));
413  soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
414  cxml::add_child(sf, "MCALabelDictionaryID", string("r1"))->add_child_text("urn:smpte:ul:" + string(buffer));
415  soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
416  cxml::add_child(sf, "MCALinkID", string("r1"))->add_child_text("urn:uuid:" + string(buffer));
417  soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
418  cxml::add_child(sf, "MCATagSymbol", string("r1"))->add_child_text(buffer);
419  if (!soundfield->MCATagName.empty()) {
420  soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
421  cxml::add_child(sf, "MCATagName", string("r1"))->add_child_text(buffer);
422  }
423  if (!soundfield->RFC5646SpokenLanguage.empty()) {
424  soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
425  cxml::add_child(sf, "RFC5646SpokenLanguage", string("r1"))->add_child_text(buffer);
426  }
427 
428  /* Find the MCA subdescriptors in the MXF so that we can also write them here */
429  list<ASDCP::MXF::InterchangeObject*> channels;
430  auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
431  asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
432  channels
433  );
434 
435  for (auto i: channels) {
436  auto channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
437  auto ch = cxml::add_child(mca_subs, "AudioChannelLabelSubDescriptor", string("r0"));
438  channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
439  cxml::add_child(ch, "InstanceID", string("r1"))->add_child_text("urn:uuid:" + string(buffer));
440  channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
441  cxml::add_child(ch, "MCALabelDictionaryID", string("r1"))->add_child_text("urn:smpte:ul:" + string(buffer));
442  channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
443  cxml::add_child(ch, "MCALinkID", string("r1"))->add_child_text("urn:uuid:" + string(buffer));
444  channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
445  cxml::add_child(ch, "MCATagSymbol", string("r1"))->add_child_text(buffer);
446  if (!channel->MCATagName.empty()) {
447  channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
448  cxml::add_child(ch, "MCATagName", string("r1"))->add_child_text(buffer);
449  }
450  if (!channel->MCAChannelID.empty()) {
451  cxml::add_child(ch, "MCAChannelID", string("r1"))->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
452  }
453  if (!channel->RFC5646SpokenLanguage.empty()) {
454  channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
455  cxml::add_child(ch, "RFC5646SpokenLanguage", string("r1"))->add_child_text(buffer);
456  }
457  if (!channel->SoundfieldGroupLinkID.empty()) {
458  channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
459  cxml::add_child(ch, "SoundfieldGroupLinkID", string("r1"))->add_child_text("urn:uuid:" + string(buffer));
460  }
461  }
462  }
463 }
464 
465 
470 void
471 CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_mca_subdescriptors) const
472 {
473  if (
474  !_main_sound_configuration ||
475  !_main_sound_sample_rate ||
476  !_main_picture_stored_area ||
477  !_main_picture_active_area ||
478  _reels.empty() ||
479  !_reels.front()->main_picture()) {
480  return;
481  }
482 
483  auto meta = cxml::add_child(node, "meta:CompositionMetadataAsset");
484  meta->set_namespace_declaration (cpl_metadata_ns, "meta");
485 
486  cxml::add_text_child(meta, "Id", "urn:uuid:" + _cpl_metadata_id);
487 
488  auto mp = _reels.front()->main_picture();
489  cxml::add_text_child(meta, "EditRate", mp->edit_rate().as_string());
490  cxml::add_text_child(meta, "IntrinsicDuration", raw_convert<string>(mp->intrinsic_duration()));
491 
492  auto fctt = cxml::add_child(meta, "FullContentTitleText", string("meta"));
494  fctt->add_child_text (*_full_content_title_text);
495  }
496  if (_full_content_title_text_language) {
497  fctt->set_attribute("language", *_full_content_title_text_language);
498  }
499 
500  if (_release_territory) {
501  cxml::add_child(meta, "ReleaseTerritory", string("meta"))->add_child_text(*_release_territory);
502  }
503 
504  if (_version_number) {
505  auto vn = cxml::add_child(meta, "VersionNumber", string("meta"));
506  vn->add_child_text(raw_convert<string>(*_version_number));
507  if (_status) {
508  vn->set_attribute("status", status_to_string(*_status));
509  }
510  }
511 
512  if (_chain) {
513  cxml::add_child(meta, "Chain", string("meta"))->add_child_text(*_chain);
514  }
515 
516  if (_distributor) {
517  cxml::add_child(meta, "Distributor", string("meta"))->add_child_text(*_distributor);
518  }
519 
520  if (_facility) {
521  cxml::add_child(meta, "Facility", string("meta"))->add_child_text(*_facility);
522  }
523 
524  if (_content_versions.size() > 1) {
525  auto vc = cxml::add_child(meta, "AlternateContentVersionList", string("meta"));
526  for (size_t i = 1; i < _content_versions.size(); ++i) {
527  _content_versions[i].as_xml (vc);
528  }
529  }
530 
531  if (_luminance) {
532  _luminance->as_xml (meta, "meta");
533  }
534 
535  if (_main_sound_configuration) {
536  cxml::add_child(meta, "MainSoundConfiguration", string("meta"))->add_child_text(_main_sound_configuration->to_string());
537  }
538  cxml::add_child(meta, "MainSoundSampleRate", string("meta"))->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
539 
540  auto stored = cxml::add_child(meta, "MainPictureStoredArea", string("meta"));
541  cxml::add_child(stored, "Width", string("meta"))->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
542  cxml::add_child(stored, "Height", string("meta"))->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
543 
544  auto active = cxml::add_child(meta, "MainPictureActiveArea", string("meta"));
545  cxml::add_child(active, "Width", string("meta"))->add_child_text(raw_convert<string>(_main_picture_active_area->width));
546  cxml::add_child(active, "Height", string("meta"))->add_child_text(raw_convert<string>(_main_picture_active_area->height));
547 
548  optional<string> first_subtitle_language;
549  for (auto i: _reels) {
550  if (i->main_subtitle()) {
551  first_subtitle_language = i->main_subtitle()->language();
552  if (first_subtitle_language) {
553  break;
554  }
555  }
556  }
557 
558  if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
559  string lang;
560  if (first_subtitle_language) {
561  lang = *first_subtitle_language;
562  }
563  for (auto const& i: _additional_subtitle_languages) {
564  if (!lang.empty()) {
565  lang += " ";
566  }
567  lang += i;
568  }
569  cxml::add_child(meta, "MainSubtitleLanguageList", string("meta"))->add_child_text(lang);
570  }
571 
572  auto metadata_list = cxml::add_child(meta, "ExtensionMetadataList", string("meta"));
573 
574  auto add_extension_metadata = [metadata_list](string scope, string name, string property_name, string property_value) {
575  auto extension = cxml::add_child(metadata_list, "ExtensionMetadata", string("meta"));
576  extension->set_attribute("scope", scope);
577  cxml::add_child(extension, "Name", string("meta"))->add_child_text(name);
578  auto property = cxml::add_child(cxml::add_child(extension, "PropertyList", string("meta")), "Property", string("meta"));
579  cxml::add_child(property, "Name", string("meta"))->add_child_text(property_name);
580  cxml::add_child(property, "Value", string("meta"))->add_child_text(property_value);
581  };
582 
583  /* SMPTE Bv2.1 8.6.3 */
584  add_extension_metadata ("http://isdcf.com/ns/cplmd/app", "Application", "DCP Constraints Profile", "SMPTE-RDD-52:2020-Bv2.1");
585 
586  if (_sign_language_video_language) {
587  add_extension_metadata ("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag", *_sign_language_video_language);
588  }
589 
590  if (_dolby_edr_image_transfer_function) {
591  add_extension_metadata("http://www.dolby.com/schemas/2014/EDR-Metadata", "Dolby EDR", "image transfer function", *_dolby_edr_image_transfer_function);
592  }
593 
594  if (_reels.front()->main_sound()) {
595  auto asset = _reels.front()->main_sound()->asset();
596  if (asset && include_mca_subdescriptors) {
597  write_mca_subdescriptors(meta, asset);
598  }
599  }
600 }
601 
602 
603 template <class T>
604 void
605 add_file_assets (vector<shared_ptr<T>>& assets, vector<shared_ptr<Reel>> reels)
606 {
607  for (auto i: reels) {
608  if (i->main_picture ()) {
609  assets.push_back (i->main_picture());
610  }
611  if (i->main_sound ()) {
612  assets.push_back (i->main_sound());
613  }
614  if (i->main_subtitle ()) {
615  assets.push_back (i->main_subtitle());
616  }
617  for (auto j: i->closed_captions()) {
618  assets.push_back (j);
619  }
620  if (i->atmos ()) {
621  assets.push_back (i->atmos());
622  }
623  }
624 }
625 
626 
627 vector<shared_ptr<ReelFileAsset>>
629 {
630  vector<shared_ptr<ReelFileAsset>> c;
631  add_file_assets (c, _reels);
632  return c;
633 }
634 
635 
636 vector<shared_ptr<const ReelFileAsset>>
638 {
639  vector<shared_ptr<const ReelFileAsset>> c;
640  add_file_assets (c, _reels);
641  return c;
642 }
643 
644 
645 bool
646 CPL::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const
647 {
648  auto other_cpl = dynamic_pointer_cast<const CPL>(other);
649  if (!other_cpl) {
650  return false;
651  }
652 
653  if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
654  string const s = "CPL: annotation texts differ: " + _annotation_text.get_value_or("") + " vs " + other_cpl->_annotation_text.get_value_or("") + "\n";
655  note (NoteType::ERROR, s);
656  return false;
657  }
658 
659  if (_content_kind != other_cpl->_content_kind) {
660  note (NoteType::ERROR, "CPL: content kinds differ");
661  return false;
662  }
663 
664  if (_reels.size() != other_cpl->_reels.size()) {
665  note (NoteType::ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
666  return false;
667  }
668 
669  auto a = _reels.begin();
670  auto b = other_cpl->_reels.begin();
671 
672  while (a != _reels.end ()) {
673  if (!(*a)->equals (*b, opt, note)) {
674  return false;
675  }
676  ++a;
677  ++b;
678  }
679 
680  return true;
681 }
682 
683 
684 bool
686 {
687  for (auto i: _reels) {
688  if (i->any_encrypted()) {
689  return true;
690  }
691  }
692 
693  return false;
694 }
695 
696 
697 bool
699 {
700  for (auto i: _reels) {
701  if (!i->all_encrypted()) {
702  return false;
703  }
704  }
705 
706  return true;
707 }
708 
709 
710 void
711 CPL::add (DecryptedKDM const & kdm)
712 {
713  for (auto i: _reels) {
714  i->add (kdm);
715  }
716 }
717 
718 void
719 CPL::resolve_refs (vector<shared_ptr<Asset>> assets)
720 {
721  for (auto i: _reels) {
722  i->resolve_refs (assets);
723  }
724 }
725 
726 string
727 CPL::pkl_type (Standard standard) const
728 {
729  return static_pkl_type (standard);
730 }
731 
732 string
733 CPL::static_pkl_type (Standard standard)
734 {
735  switch (standard) {
736  case Standard::INTEROP:
737  return "text/xml;asdcpKind=CPL";
738  case Standard::SMPTE:
739  return "text/xml";
740  default:
741  DCP_ASSERT (false);
742  }
743 }
744 
745 int64_t
746 CPL::duration () const
747 {
748  int64_t d = 0;
749  for (auto i: _reels) {
750  d += i->duration ();
751  }
752  return d;
753 }
754 
755 
756 void
757 CPL::set_version_number (int v)
758 {
759  if (v < 0) {
760  throw BadSettingError ("CPL version number cannot be negative");
761  }
762 
763  _version_number = v;
764 }
765 
766 
767 void
768 CPL::unset_version_number ()
769 {
770  _version_number = boost::none;
771 }
772 
773 
774 void
775 CPL::set_content_versions (vector<ContentVersion> v)
776 {
777  std::set<string> ids;
778  for (auto i: v) {
779  if (!ids.insert(i.id).second) {
780  throw DuplicateIdError ("Duplicate ID in ContentVersion list");
781  }
782  }
783 
784  _content_versions = v;
785 }
786 
787 
788 optional<ContentVersion>
789 CPL::content_version () const
790 {
791  if (_content_versions.empty()) {
792  return optional<ContentVersion>();
793  }
794 
795  return _content_versions[0];
796 }
797 
798 
799 void
800 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
801 {
802  _additional_subtitle_languages.clear ();
803  for (auto const& i: langs) {
804  _additional_subtitle_languages.push_back (i.to_string());
805  }
806 }
807 
808 
809 void
810 CPL::set_main_picture_active_area(dcp::Size area)
811 {
812  if (area.width % 2) {
813  throw BadSettingError("Main picture active area width is not a multiple of 2");
814  }
815 
816  if (area.height % 2) {
817  throw BadSettingError("Main picture active area height is not a multiple of 2");
818  }
819 
820  _main_picture_active_area = area;
821 }
822 
CertificateChain class.
Parent class for DCP assets, i.e. picture, sound, subtitles, closed captions, CPLs,...
Definition: asset.h:73
boost::optional< boost::filesystem::path > file() const
Definition: asset.h:100
void set_file(boost::filesystem::path file) const
Definition: asset.cc:163
std::vector< std::shared_ptr< Reel > > reels() const
Definition: cpl.h:112
bool all_encrypted() const
Definition: cpl.cc:698
void add(std::shared_ptr< Reel > reel)
Definition: cpl.cc:191
std::vector< std::shared_ptr< const ReelFileAsset > > reel_file_assets() const
Definition: cpl.cc:637
boost::optional< std::string > _release_territory
Definition: cpl.h:383
std::string pkl_type(Standard standard) const override
Definition: cpl.cc:727
std::string _content_title_text
<ContentTitleText>
Definition: cpl.h:369
void maybe_write_composition_metadata_asset(xmlpp::Element *node, bool include_mca_subdescriptors) const
Definition: cpl.cc:471
Standard _standard
Definition: cpl.h:404
std::string _cpl_metadata_id
Definition: cpl.h:376
ContentKind content_kind() const
Definition: cpl.h:188
bool any_encrypted() const
Definition: cpl.cc:685
boost::optional< std::string > _full_content_title_text
Definition: cpl.h:378
void write_xml(boost::filesystem::path file, std::shared_ptr< const CertificateChain >, bool include_mca_subdescriptors=true) const
Definition: cpl.cc:205
ContentKind _content_kind
<ContentKind>
Definition: cpl.h:370
A decrypted KDM.
Definition: decrypted_kdm.h:75
A class to describe what "equality" means for a particular test.
A representation of a local time (down to the second), including its offset from GMT (equivalent to x...
Definition: local_time.h:68
std::string as_string(bool with_millisecond=false, bool with_timezone=true) const
Definition: local_time.cc:188
CPL class.
DCP_ASSERT macro.
Class to describe what equality means when calling Asset::equals().
LocalTime class.
MXFMetadata class.
Namespace for everything in libdcp.
Definition: array_data.h:50
Methods for conversion to/from string.
Reel class.
ReelAtmosAsset class.
ReelPictureAsset class.
ReelSoundAsset class.
ReelTextAsset class.
The integer, two-dimensional size of something.
Definition: types.h:71
Utility methods and classes.
Versioning variables that are written by the build system.
Helpers for XML reading with libcxml.