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