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