Tidy up handling of language metadata for subtitles.
[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         if (_reels.front()->main_sound()) {
408                 shared_ptr<const SoundAsset> asset = _reels.front()->main_sound()->asset();
409                 if (asset) {
410                         shared_ptr<SoundAssetReader> reader = asset->start_read ();
411                         ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
412                         ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
413                                 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
414                                 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
415                                 );
416                         if (KM_SUCCESS(r)) {
417                                 xmlpp::Element* mca_subs = meta->add_child("mca:MCASubDescriptors");
418                                 mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
419                                 mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
420                                 mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
421                                 xmlpp::Element* sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
422                                 char buffer[64];
423                                 soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
424                                 sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
425                                 soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
426                                 sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
427                                 soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
428                                 sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
429                                 soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
430                                 sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
431                                 if (!soundfield->MCATagName.empty()) {
432                                         soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
433                                         sf->add_child("MCATagName", "r1")->add_child_text(buffer);
434                                 }
435                                 if (!soundfield->RFC5646SpokenLanguage.empty()) {
436                                         soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
437                                         sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
438                                 }
439
440                                 list<ASDCP::MXF::InterchangeObject*> channels;
441                                 ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectsByType(
442                                         asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
443                                         channels
444                                         );
445
446                                 BOOST_FOREACH (ASDCP::MXF::InterchangeObject* i, channels) {
447                                         ASDCP::MXF::AudioChannelLabelSubDescriptor* channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
448                                         xmlpp::Element* ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
449                                         channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
450                                         ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
451                                         channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
452                                         ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
453                                         channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
454                                         ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
455                                         channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
456                                         ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
457                                         if (!channel->MCATagName.empty()) {
458                                                 channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
459                                                 ch->add_child("MCATagName", "r1")->add_child_text(buffer);
460                                         }
461                                         if (!channel->MCAChannelID.empty()) {
462                                                 ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
463                                         }
464                                         if (!channel->RFC5646SpokenLanguage.empty()) {
465                                                 channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
466                                                 ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
467                                         }
468                                         if (!channel->SoundfieldGroupLinkID.empty()) {
469                                                 channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
470                                                 ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
471                                         }
472                                 }
473                         }
474                 }
475         }
476 }
477
478
479 list<shared_ptr<ReelMXF> >
480 CPL::reel_mxfs ()
481 {
482         list<shared_ptr<ReelMXF> > c;
483
484         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
485                 if (i->main_picture ()) {
486                         c.push_back (i->main_picture());
487                 }
488                 if (i->main_sound ()) {
489                         c.push_back (i->main_sound());
490                 }
491                 if (i->main_subtitle ()) {
492                         c.push_back (i->main_subtitle());
493                 }
494                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
495                         c.push_back (j);
496                 }
497                 if (i->atmos ()) {
498                         c.push_back (i->atmos());
499                 }
500         }
501
502         return c;
503 }
504
505 list<shared_ptr<const ReelMXF> >
506 CPL::reel_mxfs () const
507 {
508         list<shared_ptr<const ReelMXF> > c;
509
510         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
511                 if (i->main_picture ()) {
512                         c.push_back (i->main_picture());
513                 }
514                 if (i->main_sound ()) {
515                         c.push_back (i->main_sound());
516                 }
517                 if (i->main_subtitle ()) {
518                         c.push_back (i->main_subtitle());
519                 }
520                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
521                         c.push_back (j);
522                 }
523                 if (i->atmos ()) {
524                         c.push_back (i->atmos());
525                 }
526         }
527
528         return c;
529 }
530
531 bool
532 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
533 {
534         shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
535         if (!other_cpl) {
536                 return false;
537         }
538
539         if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
540                 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
541                 note (DCP_ERROR, s);
542                 return false;
543         }
544
545         if (_content_kind != other_cpl->_content_kind) {
546                 note (DCP_ERROR, "CPL: content kinds differ");
547                 return false;
548         }
549
550         if (_reels.size() != other_cpl->_reels.size()) {
551                 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
552                 return false;
553         }
554
555         list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
556         list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
557
558         while (a != _reels.end ()) {
559                 if (!(*a)->equals (*b, opt, note)) {
560                         return false;
561                 }
562                 ++a;
563                 ++b;
564         }
565
566         return true;
567 }
568
569 /** @return true if we have any encrypted content */
570 bool
571 CPL::encrypted () const
572 {
573         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
574                 if (i->encrypted ()) {
575                         return true;
576                 }
577         }
578
579         return false;
580 }
581
582 /** Add a KDM to this CPL.  If the KDM is for any of this CPLs assets it will be used
583  *  to decrypt those assets.
584  *  @param kdm KDM.
585  */
586 void
587 CPL::add (DecryptedKDM const & kdm)
588 {
589         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
590                 i->add (kdm);
591         }
592 }
593
594 void
595 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
596 {
597         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
598                 i->resolve_refs (assets);
599         }
600 }
601
602 string
603 CPL::pkl_type (Standard standard) const
604 {
605         return static_pkl_type (standard);
606 }
607
608 string
609 CPL::static_pkl_type (Standard standard)
610 {
611         switch (standard) {
612         case INTEROP:
613                 return "text/xml;asdcpKind=CPL";
614         case SMPTE:
615                 return "text/xml";
616         default:
617                 DCP_ASSERT (false);
618         }
619 }
620
621 int64_t
622 CPL::duration () const
623 {
624         int64_t d = 0;
625         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
626                 d += i->duration ();
627         }
628         return d;
629 }
630
631
632 void
633 CPL::set_version_number (int v)
634 {
635         if (v < 0) {
636                 throw BadSettingError ("CPL version number cannot be negative");
637         }
638
639         _version_number = v;
640 }
641
642
643 void
644 CPL::set_content_versions (vector<ContentVersion> v)
645 {
646         set<string> ids;
647         BOOST_FOREACH (ContentVersion i, v) {
648                 if (!ids.insert(i.id).second) {
649                         throw DuplicateIdError ("Duplicate ID in ContentVersion list");
650                 }
651         }
652
653         _content_versions = v;
654 }
655
656
657 optional<ContentVersion>
658 CPL::content_version () const
659 {
660         if (_content_versions.empty()) {
661                 return optional<ContentVersion>();
662         }
663
664         return _content_versions[0];
665 }
666
667
668 void
669 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
670 {
671         _additional_subtitle_languages.clear ();
672         BOOST_FOREACH (dcp::LanguageTag const& i, langs) {
673                 _additional_subtitle_languages.push_back (i.to_string());
674         }
675 }