da4e056377de9b3c6ac81ae886c61212c2f58309
[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
349         auto extension_metadata = [eml](string scope, string name, string property) -> boost::optional<std::string> {
350                 if (!eml) {
351                         return {};
352                 }
353
354                 for (auto i: eml->node_children("ExtensionMetadata")) {
355                         auto xml_scope = i->optional_string_attribute("scope");
356                         auto xml_name = i->optional_string_child("Name");
357                         if (xml_scope && *xml_scope == scope && xml_name && *xml_name == name) {
358                                 auto property_list = i->node_child("PropertyList");
359                                 for (auto j: property_list->node_children("Property")) {
360                                         auto property_name = j->optional_string_child("Name");
361                                         auto property_value = j->optional_string_child("Value");
362                                         if (property_name && property_value && *property_name == property) {
363                                                 return property_value;
364                                         }
365                                 }
366                         }
367                 }
368
369                 return {};
370         };
371
372         _sign_language_video_language = extension_metadata("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag");
373 }
374
375
376 void
377 CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsset> asset) const
378 {
379         auto reader = asset->start_read ();
380         ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
381         ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
382                 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
383                 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
384                 );
385         if (KM_SUCCESS(r)) {
386                 auto mca_subs = parent->add_child("mca:MCASubDescriptors");
387                 mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
388                 mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
389                 mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
390                 auto sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
391                 char buffer[64];
392                 soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
393                 sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
394                 soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
395                 sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
396                 soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
397                 sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
398                 soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
399                 sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
400                 if (!soundfield->MCATagName.empty()) {
401                         soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
402                         sf->add_child("MCATagName", "r1")->add_child_text(buffer);
403                 }
404                 if (!soundfield->RFC5646SpokenLanguage.empty()) {
405                         soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
406                         sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
407                 }
408
409                 /* Find the MCA subdescriptors in the MXF so that we can also write them here */
410                 list<ASDCP::MXF::InterchangeObject*> channels;
411                 auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
412                         asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
413                         channels
414                         );
415
416                 for (auto i: channels) {
417                         auto channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
418                         auto ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
419                         channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
420                         ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
421                         channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
422                         ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
423                         channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
424                         ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
425                         channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
426                         ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
427                         if (!channel->MCATagName.empty()) {
428                                 channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
429                                 ch->add_child("MCATagName", "r1")->add_child_text(buffer);
430                         }
431                         if (!channel->MCAChannelID.empty()) {
432                                 ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
433                         }
434                         if (!channel->RFC5646SpokenLanguage.empty()) {
435                                 channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
436                                 ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
437                         }
438                         if (!channel->SoundfieldGroupLinkID.empty()) {
439                                 channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
440                                 ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
441                         }
442                 }
443         }
444 }
445
446
447 /** Write a CompositionMetadataAsset node as a child of @param node provided
448  *  the required metadata is stored in the object.  If any required metadata
449  *  is missing this method will do nothing.
450  */
451 void
452 CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_mca_subdescriptors) const
453 {
454         if (
455                 !_main_sound_configuration ||
456                 !_main_sound_sample_rate ||
457                 !_main_picture_stored_area ||
458                 !_main_picture_active_area ||
459                 _reels.empty() ||
460                 !_reels.front()->main_picture()) {
461                 return;
462         }
463
464         auto meta = node->add_child("meta:CompositionMetadataAsset");
465         meta->set_namespace_declaration (cpl_metadata_ns, "meta");
466
467         meta->add_child("Id")->add_child_text("urn:uuid:" + _cpl_metadata_id);
468
469         auto mp = _reels.front()->main_picture();
470         meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
471         meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
472
473         auto fctt = meta->add_child("FullContentTitleText", "meta");
474         if (_full_content_title_text && !_full_content_title_text->empty()) {
475                 fctt->add_child_text (*_full_content_title_text);
476         }
477         if (_full_content_title_text_language) {
478                 fctt->set_attribute("language", *_full_content_title_text_language);
479         }
480
481         if (_release_territory) {
482                 meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
483         }
484
485         if (_version_number) {
486                 xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
487                 vn->add_child_text(raw_convert<string>(*_version_number));
488                 if (_status) {
489                         vn->set_attribute("status", status_to_string(*_status));
490                 }
491         }
492
493         if (_chain) {
494                 meta->add_child("Chain", "meta")->add_child_text(*_chain);
495         }
496
497         if (_distributor) {
498                 meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
499         }
500
501         if (_facility) {
502                 meta->add_child("Facility", "meta")->add_child_text(*_facility);
503         }
504
505         if (_content_versions.size() > 1) {
506                 xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta");
507                 for (size_t i = 1; i < _content_versions.size(); ++i) {
508                         _content_versions[i].as_xml (vc);
509                 }
510         }
511
512         if (_luminance) {
513                 _luminance->as_xml (meta, "meta");
514         }
515
516         if (_main_sound_configuration) {
517                 meta->add_child("MainSoundConfiguration", "meta")->add_child_text(_main_sound_configuration->to_string());
518         }
519         meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
520
521         auto stored = meta->add_child("MainPictureStoredArea", "meta");
522         stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
523         stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
524
525         auto active = meta->add_child("MainPictureActiveArea", "meta");
526         active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
527         active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
528
529         optional<string> first_subtitle_language;
530         for (auto i: _reels) {
531                 if (i->main_subtitle()) {
532                         first_subtitle_language = i->main_subtitle()->language();
533                         if (first_subtitle_language) {
534                                 break;
535                         }
536                 }
537         }
538
539         if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
540                 string lang;
541                 if (first_subtitle_language) {
542                         lang = *first_subtitle_language;
543                 }
544                 for (auto const& i: _additional_subtitle_languages) {
545                         if (!lang.empty()) {
546                                 lang += " ";
547                         }
548                         lang += i;
549                 }
550                 meta->add_child("MainSubtitleLanguageList", "meta")->add_child_text(lang);
551         }
552
553         auto metadata_list = meta->add_child("ExtensionMetadataList", "meta");
554
555         auto add_extension_metadata = [metadata_list](string scope, string name, string property_name, string property_value) {
556                 auto extension = metadata_list->add_child("ExtensionMetadata", "meta");
557                 extension->set_attribute("scope", scope);
558                 extension->add_child("Name", "meta")->add_child_text(name);
559                 auto property = extension->add_child("PropertyList", "meta")->add_child("Property", "meta");
560                 property->add_child("Name", "meta")->add_child_text(property_name);
561                 property->add_child("Value", "meta")->add_child_text(property_value);
562         };
563
564         /* SMPTE Bv2.1 8.6.3 */
565         add_extension_metadata ("http://isdcf.com/ns/cplmd/app", "Application", "DCP Constraints Profile", "SMPTE-RDD-52:2020-Bv2.1");
566
567         if (_sign_language_video_language) {
568                 add_extension_metadata ("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag", *_sign_language_video_language);
569         }
570
571         if (_reels.front()->main_sound()) {
572                 auto asset = _reels.front()->main_sound()->asset();
573                 if (asset && include_mca_subdescriptors) {
574                         write_mca_subdescriptors(meta, asset);
575                 }
576         }
577 }
578
579
580 template <class T>
581 void
582 add_file_assets (vector<shared_ptr<T>>& assets, vector<shared_ptr<Reel>> reels)
583 {
584         for (auto i: reels) {
585                 if (i->main_picture ()) {
586                         assets.push_back (i->main_picture());
587                 }
588                 if (i->main_sound ()) {
589                         assets.push_back (i->main_sound());
590                 }
591                 if (i->main_subtitle ()) {
592                         assets.push_back (i->main_subtitle());
593                 }
594                 for (auto j: i->closed_captions()) {
595                         assets.push_back (j);
596                 }
597                 if (i->atmos ()) {
598                         assets.push_back (i->atmos());
599                 }
600         }
601 }
602
603
604 vector<shared_ptr<ReelFileAsset>>
605 CPL::reel_file_assets ()
606 {
607         vector<shared_ptr<ReelFileAsset>> c;
608         add_file_assets (c, _reels);
609         return c;
610 }
611
612
613 vector<shared_ptr<const ReelFileAsset>>
614 CPL::reel_file_assets () const
615 {
616         vector<shared_ptr<const ReelFileAsset>> c;
617         add_file_assets (c, _reels);
618         return c;
619 }
620
621
622 bool
623 CPL::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const
624 {
625         auto other_cpl = dynamic_pointer_cast<const CPL>(other);
626         if (!other_cpl) {
627                 return false;
628         }
629
630         if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
631                 string const s = "CPL: annotation texts differ: " + _annotation_text.get_value_or("") + " vs " + other_cpl->_annotation_text.get_value_or("") + "\n";
632                 note (NoteType::ERROR, s);
633                 return false;
634         }
635
636         if (_content_kind != other_cpl->_content_kind) {
637                 note (NoteType::ERROR, "CPL: content kinds differ");
638                 return false;
639         }
640
641         if (_reels.size() != other_cpl->_reels.size()) {
642                 note (NoteType::ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
643                 return false;
644         }
645
646         auto a = _reels.begin();
647         auto b = other_cpl->_reels.begin();
648
649         while (a != _reels.end ()) {
650                 if (!(*a)->equals (*b, opt, note)) {
651                         return false;
652                 }
653                 ++a;
654                 ++b;
655         }
656
657         return true;
658 }
659
660
661 bool
662 CPL::any_encrypted () const
663 {
664         for (auto i: _reels) {
665                 if (i->any_encrypted()) {
666                         return true;
667                 }
668         }
669
670         return false;
671 }
672
673
674 bool
675 CPL::all_encrypted () const
676 {
677         for (auto i: _reels) {
678                 if (!i->all_encrypted()) {
679                         return false;
680                 }
681         }
682
683         return true;
684 }
685
686
687 void
688 CPL::add (DecryptedKDM const & kdm)
689 {
690         for (auto i: _reels) {
691                 i->add (kdm);
692         }
693 }
694
695 void
696 CPL::resolve_refs (vector<shared_ptr<Asset>> assets)
697 {
698         for (auto i: _reels) {
699                 i->resolve_refs (assets);
700         }
701 }
702
703 string
704 CPL::pkl_type (Standard standard) const
705 {
706         return static_pkl_type (standard);
707 }
708
709 string
710 CPL::static_pkl_type (Standard standard)
711 {
712         switch (standard) {
713         case Standard::INTEROP:
714                 return "text/xml;asdcpKind=CPL";
715         case Standard::SMPTE:
716                 return "text/xml";
717         default:
718                 DCP_ASSERT (false);
719         }
720 }
721
722 int64_t
723 CPL::duration () const
724 {
725         int64_t d = 0;
726         for (auto i: _reels) {
727                 d += i->duration ();
728         }
729         return d;
730 }
731
732
733 void
734 CPL::set_version_number (int v)
735 {
736         if (v < 0) {
737                 throw BadSettingError ("CPL version number cannot be negative");
738         }
739
740         _version_number = v;
741 }
742
743
744 void
745 CPL::unset_version_number ()
746 {
747         _version_number = boost::none;
748 }
749
750
751 void
752 CPL::set_content_versions (vector<ContentVersion> v)
753 {
754         std::set<string> ids;
755         for (auto i: v) {
756                 if (!ids.insert(i.id).second) {
757                         throw DuplicateIdError ("Duplicate ID in ContentVersion list");
758                 }
759         }
760
761         _content_versions = v;
762 }
763
764
765 optional<ContentVersion>
766 CPL::content_version () const
767 {
768         if (_content_versions.empty()) {
769                 return optional<ContentVersion>();
770         }
771
772         return _content_versions[0];
773 }
774
775
776 void
777 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
778 {
779         _additional_subtitle_languages.clear ();
780         for (auto const& i: langs) {
781                 _additional_subtitle_languages.push_back (i.to_string());
782         }
783 }
784
785
786 void
787 CPL::set_main_picture_active_area(dcp::Size area)
788 {
789         if (area.width % 2) {
790                 throw BadSettingError("Main picture active area width is not a multiple of 2");
791         }
792
793         if (area.height % 2) {
794                 throw BadSettingError("Main picture active area height is not a multiple of 2");
795         }
796
797         _main_picture_active_area = area;
798 }
799