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