Run dcpverify on everything in the libdcp-test-private metadata set.
[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->string_child("MainSoundConfiguration");
251
252         string sr = node->string_child("MainSoundSampleRate");
253         vector<string> sr_bits;
254         boost::split (sr_bits, sr, boost::is_any_of(" "));
255         DCP_ASSERT (sr_bits.size() == 2);
256         _main_sound_sample_rate = raw_convert<int>(sr_bits[0]);
257
258         _main_picture_stored_area = dcp::Size (
259                 node->node_child("MainPictureStoredArea")->number_child<int>("Width"),
260                 node->node_child("MainPictureStoredArea")->number_child<int>("Height")
261                 );
262
263         _main_picture_active_area = dcp::Size (
264                 node->node_child("MainPictureActiveArea")->number_child<int>("Width"),
265                 node->node_child("MainPictureActiveArea")->number_child<int>("Height")
266                 );
267
268         optional<string> sll = node->optional_string_child("MainSubtitleLanguageList");
269         if (sll) {
270                 vector<string> sll_split;
271                 boost::split (sll_split, *sll, boost::is_any_of(" "));
272                 DCP_ASSERT (!sll_split.empty());
273
274                 /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */
275                 size_t first = 0;
276                 if (!_reels.empty()) {
277                         shared_ptr<dcp::ReelSubtitleAsset> sub = _reels.front()->main_subtitle();
278                         if (sub) {
279                                 optional<dcp::LanguageTag> lang = sub->language();
280                                 if (lang && lang->to_string() == sll_split[0]) {
281                                         first = 1;
282                                 }
283                         }
284                 }
285
286                 for (size_t i = first; i < sll_split.size(); ++i) {
287                         _additional_subtitle_languages.push_back (sll_split[i]);
288                 }
289         }
290 }
291
292
293 /** Write a CompositionMetadataAsset node as a child of @param node provided
294  *  the required metadata is stored in the object.  If any required metadata
295  *  is missing this method will do nothing.
296  */
297 void
298 CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
299 {
300         if (
301                 !_main_sound_configuration ||
302                 !_main_sound_sample_rate ||
303                 !_main_picture_stored_area ||
304                 !_main_picture_active_area ||
305                 _reels.empty() ||
306                 !_reels.front()->main_picture()) {
307                 return;
308         }
309
310         xmlpp::Element* meta = node->add_child("meta:CompositionMetadataAsset");
311         meta->set_namespace_declaration (cpl_metadata_ns, "meta");
312
313         meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid());
314
315         shared_ptr<dcp::ReelPictureAsset> mp = _reels.front()->main_picture();
316         meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string());
317         meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration()));
318
319         xmlpp::Element* fctt = meta->add_child("FullContentTitleText", "meta");
320         if (_full_content_title_text) {
321                 fctt->add_child_text (*_full_content_title_text);
322         }
323         if (_full_content_title_text_language) {
324                 fctt->set_attribute("language", *_full_content_title_text_language);
325         }
326
327         if (_release_territory) {
328                 meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory);
329         }
330
331         if (_version_number) {
332                 xmlpp::Element* vn = meta->add_child("VersionNumber", "meta");
333                 vn->add_child_text(raw_convert<string>(*_version_number));
334                 if (_status) {
335                         vn->set_attribute("status", status_to_string(*_status));
336                 }
337         }
338
339         if (_chain) {
340                 meta->add_child("Chain", "meta")->add_child_text(*_chain);
341         }
342
343         if (_distributor) {
344                 meta->add_child("Distributor", "meta")->add_child_text(*_distributor);
345         }
346
347         if (_facility) {
348                 meta->add_child("Facility", "meta")->add_child_text(*_facility);
349         }
350
351         if (_content_versions.size() > 1) {
352                 xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta");
353                 for (size_t i = 1; i < _content_versions.size(); ++i) {
354                         _content_versions[i].as_xml (vc);
355                 }
356         }
357
358         if (_luminance) {
359                 _luminance->as_xml (meta, "meta");
360         }
361
362         meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
363         meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
364
365         xmlpp::Element* stored = meta->add_child("MainPictureStoredArea", "meta");
366         stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width));
367         stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height));
368
369         xmlpp::Element* active = meta->add_child("MainPictureActiveArea", "meta");
370         active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width));
371         active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height));
372
373         optional<dcp::LanguageTag> first_subtitle_language;
374         BOOST_FOREACH (shared_ptr<const Reel> i, _reels) {
375                 if (i->main_subtitle()) {
376                         first_subtitle_language = i->main_subtitle()->language();
377                         if (first_subtitle_language) {
378                                 break;
379                         }
380                 }
381         }
382
383         if (first_subtitle_language || !_additional_subtitle_languages.empty()) {
384                 string lang;
385                 if (first_subtitle_language) {
386                         lang = first_subtitle_language->to_string();
387                 }
388                 BOOST_FOREACH (dcp::LanguageTag const& i, _additional_subtitle_languages) {
389                         if (!lang.empty()) {
390                                 lang += " ";
391                         }
392                         lang += i.to_string();
393                 }
394                 meta->add_child("MainSubtitleLanguageList")->add_child_text(lang);
395         }
396 }
397
398
399 list<shared_ptr<ReelMXF> >
400 CPL::reel_mxfs ()
401 {
402         list<shared_ptr<ReelMXF> > c;
403
404         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
405                 if (i->main_picture ()) {
406                         c.push_back (i->main_picture());
407                 }
408                 if (i->main_sound ()) {
409                         c.push_back (i->main_sound());
410                 }
411                 if (i->main_subtitle ()) {
412                         c.push_back (i->main_subtitle());
413                 }
414                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
415                         c.push_back (j);
416                 }
417                 if (i->atmos ()) {
418                         c.push_back (i->atmos());
419                 }
420         }
421
422         return c;
423 }
424
425 list<shared_ptr<const ReelMXF> >
426 CPL::reel_mxfs () const
427 {
428         list<shared_ptr<const ReelMXF> > c;
429
430         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
431                 if (i->main_picture ()) {
432                         c.push_back (i->main_picture());
433                 }
434                 if (i->main_sound ()) {
435                         c.push_back (i->main_sound());
436                 }
437                 if (i->main_subtitle ()) {
438                         c.push_back (i->main_subtitle());
439                 }
440                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
441                         c.push_back (j);
442                 }
443                 if (i->atmos ()) {
444                         c.push_back (i->atmos());
445                 }
446         }
447
448         return c;
449 }
450
451 bool
452 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
453 {
454         shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
455         if (!other_cpl) {
456                 return false;
457         }
458
459         if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
460                 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
461                 note (DCP_ERROR, s);
462                 return false;
463         }
464
465         if (_content_kind != other_cpl->_content_kind) {
466                 note (DCP_ERROR, "CPL: content kinds differ");
467                 return false;
468         }
469
470         if (_reels.size() != other_cpl->_reels.size()) {
471                 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
472                 return false;
473         }
474
475         list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
476         list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
477
478         while (a != _reels.end ()) {
479                 if (!(*a)->equals (*b, opt, note)) {
480                         return false;
481                 }
482                 ++a;
483                 ++b;
484         }
485
486         return true;
487 }
488
489 /** @return true if we have any encrypted content */
490 bool
491 CPL::encrypted () const
492 {
493         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
494                 if (i->encrypted ()) {
495                         return true;
496                 }
497         }
498
499         return false;
500 }
501
502 /** Add a KDM to this CPL.  If the KDM is for any of this CPLs assets it will be used
503  *  to decrypt those assets.
504  *  @param kdm KDM.
505  */
506 void
507 CPL::add (DecryptedKDM const & kdm)
508 {
509         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
510                 i->add (kdm);
511         }
512 }
513
514 void
515 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
516 {
517         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
518                 i->resolve_refs (assets);
519         }
520 }
521
522 string
523 CPL::pkl_type (Standard standard) const
524 {
525         return static_pkl_type (standard);
526 }
527
528 string
529 CPL::static_pkl_type (Standard standard)
530 {
531         switch (standard) {
532         case INTEROP:
533                 return "text/xml;asdcpKind=CPL";
534         case SMPTE:
535                 return "text/xml";
536         default:
537                 DCP_ASSERT (false);
538         }
539 }
540
541 int64_t
542 CPL::duration () const
543 {
544         int64_t d = 0;
545         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
546                 d += i->duration ();
547         }
548         return d;
549 }
550
551
552 void
553 CPL::set_version_number (int v)
554 {
555         if (v < 0) {
556                 throw BadSettingError ("CPL version number cannot be negative");
557         }
558
559         _version_number = v;
560 }
561
562
563 void
564 CPL::set_content_versions (vector<ContentVersion> v)
565 {
566         set<string> ids;
567         BOOST_FOREACH (ContentVersion i, v) {
568                 if (!ids.insert(i.id).second) {
569                         throw DuplicateIdError ("Duplicate ID in ContentVersion list");
570                 }
571         }
572
573         _content_versions = v;
574 }
575
576
577 ContentVersion
578 CPL::content_version () const
579 {
580         DCP_ASSERT (!_content_versions.empty());
581         return _content_versions[0];
582 }
583
584
585 void
586 CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs)
587 {
588         _additional_subtitle_languages.clear ();
589         BOOST_FOREACH (dcp::LanguageTag const& i, langs) {
590                 _additional_subtitle_languages.push_back (i.to_string());
591         }
592 }