Add Colour::to_rgb_string.
[libdcp.git] / src / dcp.cc
1 /*
2     Copyright (C) 2012-2015 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 /** @file  src/dcp.cc
35  *  @brief DCP class.
36  */
37
38 #include "raw_convert.h"
39 #include "dcp.h"
40 #include "sound_asset.h"
41 #include "picture_asset.h"
42 #include "interop_subtitle_asset.h"
43 #include "smpte_subtitle_asset.h"
44 #include "mono_picture_asset.h"
45 #include "stereo_picture_asset.h"
46 #include "reel_subtitle_asset.h"
47 #include "util.h"
48 #include "metadata.h"
49 #include "exceptions.h"
50 #include "cpl.h"
51 #include "certificate_chain.h"
52 #include "compose.hpp"
53 #include "decrypted_kdm.h"
54 #include "decrypted_kdm_key.h"
55 #include "dcp_assert.h"
56 #include "reel_asset.h"
57 #include "font_asset.h"
58 #include <asdcp/AS_DCP.h>
59 #include <xmlsec/xmldsig.h>
60 #include <xmlsec/app.h>
61 #include <libxml++/libxml++.h>
62 #include <boost/filesystem.hpp>
63 #include <boost/algorithm/string.hpp>
64 #include <boost/foreach.hpp>
65
66 using std::string;
67 using std::list;
68 using std::vector;
69 using std::cout;
70 using std::make_pair;
71 using std::map;
72 using std::cout;
73 using std::cerr;
74 using std::exception;
75 using boost::shared_ptr;
76 using boost::dynamic_pointer_cast;
77 using boost::algorithm::starts_with;
78 using namespace dcp;
79
80 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
81 static string const assetmap_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
82 static string const pkl_interop_ns      = "http://www.digicine.com/PROTO-ASDCP-PKL-20040311#";
83 static string const pkl_smpte_ns        = "http://www.smpte-ra.org/schemas/429-8/2007/PKL";
84 static string const volindex_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
85 static string const volindex_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
86
87 DCP::DCP (boost::filesystem::path directory)
88         : _directory (directory)
89 {
90         if (!boost::filesystem::exists (directory)) {
91                 boost::filesystem::create_directories (directory);
92         }
93
94         _directory = boost::filesystem::canonical (_directory);
95 }
96
97 /** Call this instead of throwing an exception if the error can be tolerated */
98 template<class T> void
99 survivable_error (bool keep_going, dcp::DCP::ReadErrors* errors, T const & e)
100 {
101         if (keep_going) {
102                 if (errors) {
103                         errors->push_back (shared_ptr<T> (new T (e)));
104                 }
105         } else {
106                 throw e;
107         }
108 }
109
110 void
111 DCP::read (bool keep_going, ReadErrors* errors, bool ignore_incorrect_picture_mxf_type)
112 {
113         /* Read the ASSETMAP */
114
115         boost::filesystem::path asset_map_file;
116         if (boost::filesystem::exists (_directory / "ASSETMAP")) {
117                 asset_map_file = _directory / "ASSETMAP";
118         } else if (boost::filesystem::exists (_directory / "ASSETMAP.xml")) {
119                 asset_map_file = _directory / "ASSETMAP.xml";
120         } else {
121                 boost::throw_exception (DCPReadError (String::compose ("could not find AssetMap file in `%1'", _directory.string())));
122         }
123
124         cxml::Document asset_map ("AssetMap");
125
126         asset_map.read_file (asset_map_file);
127         if (asset_map.namespace_uri() == assetmap_interop_ns) {
128                 _standard = INTEROP;
129         } else if (asset_map.namespace_uri() == assetmap_smpte_ns) {
130                 _standard = SMPTE;
131         } else {
132                 boost::throw_exception (XMLError ("Unrecognised Assetmap namespace " + asset_map.namespace_uri()));
133         }
134
135         list<shared_ptr<cxml::Node> > asset_nodes = asset_map.node_child("AssetList")->node_children ("Asset");
136         map<string, boost::filesystem::path> paths;
137         BOOST_FOREACH (shared_ptr<cxml::Node> i, asset_nodes) {
138                 if (i->node_child("ChunkList")->node_children("Chunk").size() != 1) {
139                         boost::throw_exception (XMLError ("unsupported asset chunk count"));
140                 }
141                 string p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
142                 if (starts_with (p, "file://")) {
143                         p = p.substr (7);
144                 }
145                 paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
146         }
147
148         /* Read all the assets from the asset map */
149         /* XXX: I think we should be looking at the PKL here to decide type, not
150            the extension of the file.
151         */
152
153         /* Make a list of non-CPL assets so that we can resolve the references
154            from the CPLs.
155         */
156         list<shared_ptr<Asset> > other_assets;
157
158         for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
159                 boost::filesystem::path path = _directory / i->second;
160
161                 if (!boost::filesystem::exists (path)) {
162                         survivable_error (keep_going, errors, MissingAssetError (path));
163                         continue;
164                 }
165
166                 if (boost::filesystem::extension (path) == ".xml") {
167                         xmlpp::DomParser* p = new xmlpp::DomParser;
168                         try {
169                                 p->parse_file (path.string());
170                         } catch (std::exception& e) {
171                                 delete p;
172                                 continue;
173                         }
174
175                         string const root = p->get_document()->get_root_node()->get_name ();
176                         delete p;
177
178                         if (root == "CompositionPlaylist") {
179                                 shared_ptr<CPL> cpl (new CPL (path));
180                                 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
181                                         survivable_error (keep_going, errors, MismatchedStandardError ());
182                                 }
183                                 _cpls.push_back (cpl);
184                         } else if (root == "DCSubtitle") {
185                                 if (_standard && _standard.get() == SMPTE) {
186                                         survivable_error (keep_going, errors, MismatchedStandardError ());
187                                 }
188                                 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
189                         }
190                 } else if (boost::filesystem::extension (path) == ".mxf") {
191
192                         /* XXX: asdcplib does not appear to support discovery of read MXFs standard
193                            (Interop / SMPTE)
194                         */
195
196                         ASDCP::EssenceType_t type;
197                         if (ASDCP::EssenceType (path.string().c_str(), type) != ASDCP::RESULT_OK) {
198                                 throw DCPReadError ("Could not find essence type");
199                         }
200                         switch (type) {
201                                 case ASDCP::ESS_UNKNOWN:
202                                 case ASDCP::ESS_MPEG2_VES:
203                                         throw DCPReadError ("MPEG2 video essences are not supported");
204                                 case ASDCP::ESS_JPEG_2000:
205                                         try {
206                                                 other_assets.push_back (shared_ptr<MonoPictureAsset> (new MonoPictureAsset (path)));
207                                         } catch (dcp::MXFFileError& e) {
208                                                 if (ignore_incorrect_picture_mxf_type && e.number() == ASDCP::RESULT_SFORMAT) {
209                                                         /* Tried to load it as mono but the error says it's stereo; try that instead */
210                                                         other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
211                                                 } else {
212                                                         throw;
213                                                 }
214                                         }
215                                         break;
216                                 case ASDCP::ESS_PCM_24b_48k:
217                                 case ASDCP::ESS_PCM_24b_96k:
218                                         other_assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (path)));
219                                         break;
220                                 case ASDCP::ESS_JPEG_2000_S:
221                                         other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
222                                         break;
223                                 case ASDCP::ESS_TIMED_TEXT:
224                                         other_assets.push_back (shared_ptr<SMPTESubtitleAsset> (new SMPTESubtitleAsset (path)));
225                                         break;
226                                 default:
227                                         throw DCPReadError ("Unknown MXF essence type");
228                         }
229                 } else if (boost::filesystem::extension (path) == ".ttf") {
230                         other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
231                 }
232         }
233
234         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
235                 i->resolve_refs (other_assets);
236         }
237 }
238
239 void
240 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
241 {
242         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
243                 i->resolve_refs (assets);
244         }
245 }
246
247 bool
248 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
249 {
250         list<shared_ptr<CPL> > a = cpls ();
251         list<shared_ptr<CPL> > b = other.cpls ();
252
253         if (a.size() != b.size()) {
254                 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
255                 return false;
256         }
257
258         bool r = true;
259
260         BOOST_FOREACH (shared_ptr<CPL> i, a) {
261                 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
262                 while (j != b.end() && !(*j)->equals (i, opt, note)) {
263                         ++j;
264                 }
265
266                 if (j == b.end ()) {
267                         r = false;
268                 }
269         }
270
271         return r;
272 }
273
274 void
275 DCP::add (boost::shared_ptr<CPL> cpl)
276 {
277         _cpls.push_back (cpl);
278 }
279
280 bool
281 DCP::encrypted () const
282 {
283         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
284                 if (i->encrypted ()) {
285                         return true;
286                 }
287         }
288
289         return false;
290 }
291
292 void
293 DCP::add (DecryptedKDM const & kdm)
294 {
295         list<DecryptedKDMKey> keys = kdm.keys ();
296
297         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
298                 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
299                         if (j.cpl_id() == i->id()) {
300                                 i->add (kdm);
301                         }
302                 }
303         }
304 }
305
306 /** @return full pathname of PKL file that was written */
307 boost::filesystem::path
308 DCP::write_pkl (string file, Standard standard, string pkl_uuid, XMLMetadata metadata, shared_ptr<const CertificateChain> signer) const
309 {
310         boost::filesystem::path p = _directory;
311         p /= file;
312
313         xmlpp::Document doc;
314         xmlpp::Element* pkl;
315         if (standard == INTEROP) {
316                 pkl = doc.create_root_node("PackingList", pkl_interop_ns);
317         } else {
318                 pkl = doc.create_root_node("PackingList", pkl_smpte_ns);
319         }
320
321         if (signer) {
322                 pkl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
323         }
324
325         pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
326
327         /* XXX: this is a bit of a hack */
328         DCP_ASSERT (cpls().size() > 0);
329         pkl->add_child("AnnotationText")->add_child_text (cpls().front()->annotation_text ());
330
331         pkl->add_child("IssueDate")->add_child_text (metadata.issue_date);
332         pkl->add_child("Issuer")->add_child_text (metadata.issuer);
333         pkl->add_child("Creator")->add_child_text (metadata.creator);
334
335         xmlpp::Element* asset_list = pkl->add_child("AssetList");
336         BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
337                 i->write_to_pkl (asset_list, _directory, standard);
338         }
339
340         if (signer) {
341                 signer->sign (pkl, standard);
342         }
343
344         doc.write_to_file (p.string (), "UTF-8");
345         return p.string ();
346 }
347
348 /** Write the VOLINDEX file.
349  *  @param standard DCP standard to use (INTEROP or SMPTE)
350  */
351 void
352 DCP::write_volindex (Standard standard) const
353 {
354         boost::filesystem::path p = _directory;
355         switch (standard) {
356         case INTEROP:
357                 p /= "VOLINDEX";
358                 break;
359         case SMPTE:
360                 p /= "VOLINDEX.xml";
361                 break;
362         default:
363                 DCP_ASSERT (false);
364         }
365
366         xmlpp::Document doc;
367         xmlpp::Element* root;
368
369         switch (standard) {
370         case INTEROP:
371                 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
372                 break;
373         case SMPTE:
374                 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
375                 break;
376         default:
377                 DCP_ASSERT (false);
378         }
379
380         root->add_child("Index")->add_child_text ("1");
381         doc.write_to_file (p.string (), "UTF-8");
382 }
383
384 void
385 DCP::write_assetmap (Standard standard, string pkl_uuid, boost::filesystem::path pkl_path, XMLMetadata metadata) const
386 {
387         boost::filesystem::path p = _directory;
388
389         switch (standard) {
390         case INTEROP:
391                 p /= "ASSETMAP";
392                 break;
393         case SMPTE:
394                 p /= "ASSETMAP.xml";
395                 break;
396         default:
397                 DCP_ASSERT (false);
398         }
399
400         xmlpp::Document doc;
401         xmlpp::Element* root;
402
403         switch (standard) {
404         case INTEROP:
405                 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
406                 break;
407         case SMPTE:
408                 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
409                 break;
410         default:
411                 DCP_ASSERT (false);
412         }
413
414         root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
415         root->add_child("AnnotationText")->add_child_text ("Created by " + metadata.creator);
416
417         switch (standard) {
418         case INTEROP:
419                 root->add_child("VolumeCount")->add_child_text ("1");
420                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
421                 root->add_child("Issuer")->add_child_text (metadata.issuer);
422                 root->add_child("Creator")->add_child_text (metadata.creator);
423                 break;
424         case SMPTE:
425                 root->add_child("Creator")->add_child_text (metadata.creator);
426                 root->add_child("VolumeCount")->add_child_text ("1");
427                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
428                 root->add_child("Issuer")->add_child_text (metadata.issuer);
429                 break;
430         default:
431                 DCP_ASSERT (false);
432         }
433
434         xmlpp::Node* asset_list = root->add_child ("AssetList");
435
436         xmlpp::Node* asset = asset_list->add_child ("Asset");
437         asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
438         asset->add_child("PackingList")->add_child_text ("true");
439         xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
440         xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
441         chunk->add_child("Path")->add_child_text (pkl_path.filename().string());
442         chunk->add_child("VolumeIndex")->add_child_text ("1");
443         chunk->add_child("Offset")->add_child_text ("0");
444         chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
445
446         BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
447                 i->write_to_assetmap (asset_list, _directory);
448         }
449
450         /* This must not be the _formatted version otherwise signature digests will be wrong */
451         doc.write_to_file (p.string (), "UTF-8");
452 }
453
454 /** Write all the XML files for this DCP.
455  *  @param standand INTEROP or SMPTE.
456  *  @param metadata Metadata to use for PKL and asset map files.
457  *  @param signer Signer to use, or 0.
458  */
459 void
460 DCP::write_xml (
461         Standard standard,
462         XMLMetadata metadata,
463         shared_ptr<const CertificateChain> signer,
464         NameFormat name_format
465         )
466 {
467         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
468                 NameFormat::Map values;
469                 values['t'] = "cpl";
470                 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
471         }
472
473         string const pkl_uuid = make_uuid ();
474         NameFormat::Map values;
475         values['t'] = "pkl";
476         boost::filesystem::path const pkl_path = write_pkl (name_format.get(values, "_" + pkl_uuid + ".xml"), standard, pkl_uuid, metadata, signer);
477
478         write_volindex (standard);
479         write_assetmap (standard, pkl_uuid, pkl_path, metadata);
480 }
481
482 list<shared_ptr<CPL> >
483 DCP::cpls () const
484 {
485         return _cpls;
486 }
487
488 /** @return All assets (including CPLs) */
489 list<shared_ptr<Asset> >
490 DCP::assets () const
491 {
492         list<shared_ptr<Asset> > assets;
493         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
494                 assets.push_back (i);
495                 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
496                         shared_ptr<Asset> o = j->asset_ref().asset ();
497                         assets.push_back (o);
498                         /* More Interop special-casing */
499                         shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
500                         if (sub) {
501                                 sub->add_font_assets (assets);
502                         }
503                 }
504         }
505
506         return assets;
507 }
508
509 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
510 vector<boost::filesystem::path>
511 DCP::directories_from_files (vector<boost::filesystem::path> files)
512 {
513         vector<boost::filesystem::path> d;
514         BOOST_FOREACH (boost::filesystem::path i, files) {
515                 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
516                         d.push_back (i.parent_path ());
517                 }
518         }
519         return d;
520 }