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