Fix crashes when static linking due to tricky lifetime
[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         DCP_ASSERT (!_content_versions.empty());
189         _content_versions[0].as_xml (root);
190
191         xmlpp::Element* rating_list = root->add_child("RatingList");
192         BOOST_FOREACH (Rating i, _ratings) {
193                 i.as_xml (rating_list->add_child("Rating"));
194         }
195
196         xmlpp::Element* reel_list = root->add_child ("ReelList");
197
198         bool first = true;
199         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
200                 xmlpp::Element* asset_list = i->write_to_cpl (reel_list, standard);
201                 if (first && standard == dcp::SMPTE) {
202                         maybe_write_composition_metadata_asset (asset_list);
203                         first = false;
204                 }
205         }
206
207         indent (root, 0);
208
209         if (signer) {
210                 signer->sign (root, standard);
211         }
212
213         doc.write_to_file_formatted (file.string(), "UTF-8");
214
215         set_file (file);
216 }
217
218
219 void
220 CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
221 {
222         cxml::ConstNodePtr fctt = node->node_child("FullContentTitleText");
223         _full_content_title_text = fctt->content();
224         _full_content_title_text_language = fctt->optional_string_attribute("language");
225
226         _release_territory = node->optional_string_child("ReleaseTerritory");
227
228         cxml::ConstNodePtr vn = node->optional_node_child("VersionNumber");
229         if (vn) {
230                 _version_number = raw_convert<int>(vn->content());
231                 /* I decided to check for this number being non-negative on being set, and in the verifier, but not here */
232                 optional<string> vn_status = vn->optional_string_attribute("status");
233                 if (vn_status) {
234                         _status = string_to_status (*vn_status);
235                 }
236         }
237
238         _chain = node->optional_string_child("Chain");
239         _distributor = node->optional_string_child("Distributor");
240         _facility = node->optional_string_child("Facility");
241
242         cxml::ConstNodePtr acv = node->optional_node_child("AlternateContentVersionList");
243         if (acv) {
244                 BOOST_FOREACH (cxml::ConstNodePtr i, acv->node_children("ContentVersion")) {
245                         _content_versions.push_back (ContentVersion(i));
246                 }
247         }
248
249         cxml::ConstNodePtr lum = node->optional_node_child("Luminance");
250         if (lum) {
251                 _luminance = Luminance (lum);
252         }
253
254         _main_sound_configuration = node->optional_string_child("MainSoundConfiguration");
255
256         optional<string> sr = node->optional_string_child("MainSoundSampleRate");
257         if (sr) {
258                 vector<string> sr_bits;
259                 boost::split (sr_bits, *sr, boost::is_any_of(" "));
260                 DCP_ASSERT (sr_bits.size() == 2);
261                 _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
262         }
263
264         _main_picture_stored_area = dcp::Size (
265                 node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
266                 node->node_child("MainPictureStoredArea")->number_child<int>("Height")
267                 );
268
269         _main_picture_active_area = dcp::Size (
270                 node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
271                 node->node_child("MainPictureActiveArea")->number_child<int>("Height")
272                 );
273
274         optional<string> sll = node->optional_string_child("MainSubtitleLanguageList");
275         if (sll) {
276                 vector<string> sll_split;
277                 boost::split (sll_split, *sll, boost::is_any_of(" "));
278                 DCP_ASSERT (!sll_split.empty());
279
280                 /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
281                 size_t first = 0;
282                 if (!_reels.empty()) {
283                         shared_ptr<dcp::ReelSubtitleAsset> sub = _reels.front()->main_subtitle();
284                         if (sub) {
285                                 optional<dcp::LanguageTag> lang = sub->language();
286                                 if (lang && lang->to_string() == sll_split[0]) {
287                                         first = 1;
288                                 }
289                         }
290                 }
291
292                 for (size_t i = first; i < sll_split.size(); ++i) {
293                         _additional_subtitle_languages.push_back (sll_split[i]);
294                 }
295         }
296 }
297
298
299 /** Write a CompositionMetadataAsset node as a child of @param node provided
300  *  the required metadata is stored in the object.  If any required metadata
301  *  is missing this method will do nothing.
302  */
303 void
304 CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
305 {
306         if (
307                 !_main_sound_configuration ||
308                 !_main_sound_sample_rate ||
309                 !_main_picture_stored_area ||
310                 !_main_picture_active_area ||
311                 _reels.empty() ||
312                 !_reels.front()->main_picture()) {
313                 return;
314         }
315
316         xmlpp::Element* meta = node->add_child("meta:CompositionMetadataAsset");
317         meta->set_namespace_declaration (cpl_metadata_ns, "meta");
318
319         meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid());
320
321         shared_ptr<dcp::ReelPictureAsset> mp = _reels.front()->main_picture();
322         meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
323         meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
324
325         xmlpp::Element* fctt = meta->add_child("FullContentTitleText", "meta");
326         if (_full_content_title_text) {
327                 fctt->add_child_text (*_full_content_title_text);
328         }
329         if (_full_content_title_text_language) {
330                 fctt->set_attribute("language", *_full_content_title_text_language);
331         }
332
333         if (_release_territory) {
334                 meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
335         }
336
337         if (_version_number) {
338                 xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
339                 vn->add_child_text(raw_convert<string>(*_version_number));
340                 if (_status) {
341                         vn->set_attribute("status", status_to_string(*_status));
342                 }
343         }
344
345         if (_chain) {
346                 meta->add_child("Chain", "meta")->add_child_text(*_chain);
347         }
348
349         if (_distributor) {
350                 meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
351         }
352
353         if (_facility) {
354                 meta->add_child("Facility", "meta")->add_child_text(*_facility);
355         }
356
357         if (_content_versions.size() > 1) {
358                 xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta");
359                 for (size_t i = 1; i < _content_versions.size(); ++i) {
360                         _content_versions[i].as_xml (vc);
361                 }
362         }
363
364         if (_luminance) {
365                 _luminance->as_xml (meta, "meta");
366         }
367
368         meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
369         meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
370
371         xmlpp::Element* stored = meta->add_child("MainPictureStoredArea", "meta");
372         stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
373         stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
374
375         xmlpp::Element* active = meta->add_child("MainPictureActiveArea", "meta");
376         active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
377         active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
378
379         optional<dcp::LanguageTag> first_subtitle_language;
380         BOOST_FOREACH (shared_ptr<const Reel> i, _reels) {
381                 if (i->main_subtitle()) {
382                         first_subtitle_language = i->main_subtitle()->language();
383                         if (first_subtitle_language) {
384                                 break;
385                         }
386                 }
387         }
388
389         if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
390                 string lang;
391                 if (first_subtitle_language) {
392                         lang = first_subtitle_language->to_string();
393                 }
394                 BOOST_FOREACH (dcp::LanguageTag const& i, _additional_subtitle_languages) {
395                         if (!lang.empty()) {
396                                 lang += " ";
397                         }
398                         lang += i.to_string();
399                 }
400                 meta->add_child("MainSubtitleLanguageList")->add_child_text(lang);
401         }
402
403         if (_reels.front()->main_sound()) {
404                 shared_ptr<const SoundAsset> asset = _reels.front()->main_sound()->asset();
405                 if (asset) {
406                         shared_ptr<SoundAssetReader> reader = asset->start_read ();
407                         ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
408                         ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
409                                 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
410                                 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
411                                 );
412                         if (KM_SUCCESS(r)) {
413                                 xmlpp::Element* mca_subs = meta->add_child("mca:MCASubDescriptors");
414                                 mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
415                                 mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
416                                 mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
417                                 xmlpp::Element* sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
418                                 char buffer[64];
419                                 soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
420                                 sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
421                                 soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
422                                 sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
423                                 soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
424                                 sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
425                                 soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
426                                 sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
427                                 if (!soundfield->MCATagName.empty()) {
428                                         soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
429                                         sf->add_child("MCATagName", "r1")->add_child_text(buffer);
430                                 }
431                                 if (!soundfield->RFC5646SpokenLanguage.empty()) {
432                                         soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
433                                         sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
434                                 }
435
436                                 list<ASDCP::MXF::InterchangeObject*> channels;
437                                 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectsByType(
438                                         asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
439                                         channels
440                                         );
441
442                                 BOOST_FOREACH (ASDCP::MXF::InterchangeObject* i, channels) {
443                                         ASDCP::MXF::AudioChannelLabelSubDescriptor* channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
444                                         xmlpp::Element* ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
445                                         channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
446                                         ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
447                                         channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
448                                         ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
449                                         channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
450                                         ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
451                                         channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
452                                         ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
453                                         if (!channel->MCATagName.empty()) {
454                                                 channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
455                                                 ch->add_child("MCATagName", "r1")->add_child_text(buffer);
456                                         }
457                                         if (!channel->MCAChannelID.empty()) {
458                                                 ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
459                                         }
460                                         if (!channel->RFC5646SpokenLanguage.empty()) {
461                                                 channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
462                                                 ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
463                                         }
464                                         if (!channel->SoundfieldGroupLinkID.empty()) {
465                                                 channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
466                                                 ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
467                                         }
468                                 }
469                         }
470                 }
471         }
472 }
473
474
475 list<shared_ptr<ReelMXF> >
476 CPL::reel_mxfs ()
477 {
478         list<shared_ptr<ReelMXF> > c;
479
480         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
481                 if (i->main_picture ()) {
482                         c.push_back (i->main_picture());
483                 }
484                 if (i->main_sound ()) {
485                         c.push_back (i->main_sound());
486                 }
487                 if (i->main_subtitle ()) {
488                         c.push_back (i->main_subtitle());
489                 }
490                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
491                         c.push_back (j);
492                 }
493                 if (i->atmos ()) {
494                         c.push_back (i->atmos());
495                 }
496         }
497
498         return c;
499 }
500
501 list<shared_ptr<const ReelMXF> >
502 CPL::reel_mxfs () const
503 {
504         list<shared_ptr<const ReelMXF> > c;
505
506         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
507                 if (i->main_picture ()) {
508                         c.push_back (i->main_picture());
509                 }
510                 if (i->main_sound ()) {
511                         c.push_back (i->main_sound());
512                 }
513                 if (i->main_subtitle ()) {
514                         c.push_back (i->main_subtitle());
515                 }
516                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
517                         c.push_back (j);
518                 }
519                 if (i->atmos ()) {
520                         c.push_back (i->atmos());
521                 }
522         }
523
524         return c;
525 }
526
527 bool
528 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
529 {
530         shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
531         if (!other_cpl) {
532                 return false;
533         }
534
535         if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
536                 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
537                 note (DCP_ERROR, s);
538                 return false;
539         }
540
541         if (_content_kind != other_cpl->_content_kind) {
542                 note (DCP_ERROR, "CPL: content kinds differ");
543                 return false;
544         }
545
546         if (_reels.size() != other_cpl->_reels.size()) {
547                 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
548                 return false;
549         }
550
551         list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
552         list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
553
554         while (a != _reels.end ()) {
555                 if (!(*a)->equals (*b, opt, note)) {
556                         return false;
557                 }
558                 ++a;
559                 ++b;
560         }
561
562         return true;
563 }
564
565 /** @return true if we have any encrypted content */
566 bool
567 CPL::encrypted () const
568 {
569         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
570                 if (i->encrypted ()) {
571                         return true;
572                 }
573         }
574
575         return false;
576 }
577
578 /** Add a KDM to this CPL.  If the KDM is for any of this CPLs assets it will be used
579  *  to decrypt those assets.
580  *  @param kdm KDM.
581  */
582 void
583 CPL::add (DecryptedKDM const & kdm)
584 {
585         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
586                 i->add (kdm);
587         }
588 }
589
590 void
591 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
592 {
593         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
594                 i->resolve_refs (assets);
595         }
596 }
597
598 string
599 CPL::pkl_type (Standard standard) const
600 {
601         return static_pkl_type (standard);
602 }
603
604 string
605 CPL::static_pkl_type (Standard standard)
606 {
607         switch (standard) {
608         case INTEROP:
609                 return "text/xml;asdcpKind=CPL";
610         case SMPTE:
611                 return "text/xml";
612         default:
613                 DCP_ASSERT (false);
614         }
615 }
616
617 int64_t
618 CPL::duration () const
619 {
620         int64_t d = 0;
621         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
622                 d += i->duration ();
623         }
624         return d;
625 }
626
627
628 void
629 CPL::set_version_number (int v)
630 {
631         if (v < 0) {
632                 throw BadSettingError ("CPL version number cannot be negative");
633         }
634
635         _version_number = v;
636 }
637
638
639 void
640 CPL::set_content_versions (vector<ContentVersion> v)
641 {
642         set<string> ids;
643         BOOST_FOREACH (ContentVersion i, v) {
644                 if (!ids.insert(i.id).second) {
645                         throw DuplicateIdError ("Duplicate ID in ContentVersion list");
646                 }
647         }
648
649         _content_versions = v;
650 }
651
652
653 ContentVersion
654 CPL::content_version () const
655 {
656         DCP_ASSERT (!_content_versions.empty());
657         return _content_versions[0];
658 }
659
660
661 void
662 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
663 {
664         _additional_subtitle_languages.clear ();
665         BOOST_FOREACH (dcp::LanguageTag const& i, langs) {
666                 _additional_subtitle_languages.push_back (i.to_string());
667         }
668 }