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