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