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