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