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