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