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