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