Add OpenSSL licence exception.
[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 "AS_DCP.h"
54 #include "decrypted_kdm.h"
55 #include "decrypted_kdm_key.h"
56 #include "dcp_assert.h"
57 #include "reel_asset.h"
58 #include "font_asset.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 #include <iostream>
66
67 using std::string;
68 using std::list;
69 using std::cout;
70 using std::ostream;
71 using std::make_pair;
72 using std::map;
73 using std::cout;
74 using std::cerr;
75 using std::exception;
76 using boost::shared_ptr;
77 using boost::dynamic_pointer_cast;
78 using boost::algorithm::starts_with;
79 using namespace dcp;
80
81 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
82 static string const assetmap_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
83 static string const pkl_interop_ns      = "http://www.digicine.com/PROTO-ASDCP-PKL-20040311#";
84 static string const pkl_smpte_ns        = "http://www.smpte-ra.org/schemas/429-8/2007/PKL";
85 static string const volindex_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
86 static string const volindex_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
87
88 DCP::DCP (boost::filesystem::path directory)
89         : _directory (directory)
90 {
91         if (!boost::filesystem::exists (directory)) {
92                 boost::filesystem::create_directories (directory);
93         }
94
95         _directory = boost::filesystem::canonical (_directory);
96 }
97
98 /** Call this instead of throwing an exception if the error can be tolerated */
99 template<class T> void
100 survivable_error (bool keep_going, dcp::DCP::ReadErrors* errors, T const & e)
101 {
102         if (keep_going) {
103                 if (errors) {
104                         errors->push_back (shared_ptr<T> (new T (e)));
105                 }
106         } else {
107                 throw e;
108         }
109 }
110
111 void
112 DCP::read (bool keep_going, ReadErrors* errors, bool ignore_incorrect_picture_mxf_type)
113 {
114         /* Read the ASSETMAP */
115
116         boost::filesystem::path asset_map_file;
117         if (boost::filesystem::exists (_directory / "ASSETMAP")) {
118                 asset_map_file = _directory / "ASSETMAP";
119         } else if (boost::filesystem::exists (_directory / "ASSETMAP.xml")) {
120                 asset_map_file = _directory / "ASSETMAP.xml";
121         } else {
122                 boost::throw_exception (DCPReadError (String::compose ("could not find AssetMap file in `%1'", _directory.string())));
123         }
124
125         cxml::Document asset_map ("AssetMap");
126
127         asset_map.read_file (asset_map_file);
128         if (asset_map.namespace_uri() == assetmap_interop_ns) {
129                 _standard = INTEROP;
130         } else if (asset_map.namespace_uri() == assetmap_smpte_ns) {
131                 _standard = SMPTE;
132         } else {
133                 boost::throw_exception (XMLError ("Unrecognised Assetmap namespace " + asset_map.namespace_uri()));
134         }
135
136         list<shared_ptr<cxml::Node> > asset_nodes = asset_map.node_child("AssetList")->node_children ("Asset");
137         map<string, boost::filesystem::path> paths;
138         BOOST_FOREACH (shared_ptr<cxml::Node> i, asset_nodes) {
139                 if (i->node_child("ChunkList")->node_children("Chunk").size() != 1) {
140                         boost::throw_exception (XMLError ("unsupported asset chunk count"));
141                 }
142                 string p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
143                 if (starts_with (p, "file://")) {
144                         p = p.substr (7);
145                 }
146                 paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
147         }
148
149         /* Read all the assets from the asset map */
150         /* XXX: I think we should be looking at the PKL here to decide type, not
151            the extension of the file.
152         */
153
154         /* Make a list of non-CPL assets so that we can resolve the references
155            from the CPLs.
156         */
157         list<shared_ptr<Asset> > other_assets;
158
159         for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
160                 boost::filesystem::path path = _directory / i->second;
161
162                 if (!boost::filesystem::exists (path)) {
163                         survivable_error (keep_going, errors, MissingAssetError (path));
164                         continue;
165                 }
166
167                 if (boost::filesystem::extension (path) == ".xml") {
168                         xmlpp::DomParser* p = new xmlpp::DomParser;
169                         try {
170                                 p->parse_file (path.string());
171                         } catch (std::exception& e) {
172                                 delete p;
173                                 continue;
174                         }
175
176                         string const root = p->get_document()->get_root_node()->get_name ();
177                         delete p;
178
179                         if (root == "CompositionPlaylist") {
180                                 shared_ptr<CPL> cpl (new CPL (path));
181                                 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
182                                         survivable_error (keep_going, errors, MismatchedStandardError ());
183                                 }
184                                 _cpls.push_back (cpl);
185                         } else if (root == "DCSubtitle") {
186                                 if (_standard && _standard.get() == SMPTE) {
187                                         survivable_error (keep_going, errors, MismatchedStandardError ());
188                                 }
189                                 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
190                         }
191                 } else if (boost::filesystem::extension (path) == ".mxf") {
192
193                         /* XXX: asdcplib does not appear to support discovery of read MXFs standard
194                            (Interop / SMPTE)
195                         */
196
197                         ASDCP::EssenceType_t type;
198                         if (ASDCP::EssenceType (path.string().c_str(), type) != ASDCP::RESULT_OK) {
199                                 throw DCPReadError ("Could not find essence type");
200                         }
201                         switch (type) {
202                                 case ASDCP::ESS_UNKNOWN:
203                                 case ASDCP::ESS_MPEG2_VES:
204                                         throw DCPReadError ("MPEG2 video essences are not supported");
205                                 case ASDCP::ESS_JPEG_2000:
206                                         try {
207                                                 other_assets.push_back (shared_ptr<MonoPictureAsset> (new MonoPictureAsset (path)));
208                                         } catch (dcp::MXFFileError& e) {
209                                                 if (ignore_incorrect_picture_mxf_type && e.number() == ASDCP::RESULT_SFORMAT) {
210                                                         /* Tried to load it as mono but the error says it's stereo; try that instead */
211                                                         other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
212                                                 } else {
213                                                         throw;
214                                                 }
215                                         }
216                                         break;
217                                 case ASDCP::ESS_PCM_24b_48k:
218                                 case ASDCP::ESS_PCM_24b_96k:
219                                         other_assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (path)));
220                                         break;
221                                 case ASDCP::ESS_JPEG_2000_S:
222                                         other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
223                                         break;
224                                 case ASDCP::ESS_TIMED_TEXT:
225                                         other_assets.push_back (shared_ptr<SMPTESubtitleAsset> (new SMPTESubtitleAsset (path)));
226                                         break;
227                                 default:
228                                         throw DCPReadError ("Unknown MXF essence type");
229                         }
230                 } else if (boost::filesystem::extension (path) == ".ttf") {
231                         other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
232                 }
233         }
234
235         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
236                 i->resolve_refs (other_assets);
237         }
238 }
239
240 void
241 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
242 {
243         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
244                 i->resolve_refs (assets);
245         }
246 }
247
248 bool
249 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
250 {
251         list<shared_ptr<CPL> > a = cpls ();
252         list<shared_ptr<CPL> > b = other.cpls ();
253
254         if (a.size() != b.size()) {
255                 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
256                 return false;
257         }
258
259         bool r = true;
260
261         BOOST_FOREACH (shared_ptr<CPL> i, a) {
262                 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
263                 while (j != b.end() && !(*j)->equals (i, opt, note)) {
264                         ++j;
265                 }
266
267                 if (j == b.end ()) {
268                         r = false;
269                 }
270         }
271
272         return r;
273 }
274
275 void
276 DCP::add (boost::shared_ptr<CPL> cpl)
277 {
278         _cpls.push_back (cpl);
279 }
280
281 bool
282 DCP::encrypted () const
283 {
284         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
285                 if (i->encrypted ()) {
286                         return true;
287                 }
288         }
289
290         return false;
291 }
292
293 void
294 DCP::add (DecryptedKDM const & kdm)
295 {
296         list<DecryptedKDMKey> keys = kdm.keys ();
297
298         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
299                 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
300                         if (j.cpl_id() == i->id()) {
301                                 i->add (kdm);
302                         }
303                 }
304         }
305 }
306
307 boost::filesystem::path
308 DCP::write_pkl (Standard standard, string pkl_uuid, XMLMetadata metadata, shared_ptr<const CertificateChain> signer) const
309 {
310         boost::filesystem::path p = _directory;
311         p /= String::compose ("pkl_%1.xml", pkl_uuid);
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, int pkl_length, 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_" + pkl_uuid + ".xml");
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> (pkl_length));
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         )
465 {
466         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
467                 string const filename = "cpl_" + i->id() + ".xml";
468                 i->write_xml (_directory / filename, standard, signer);
469         }
470
471         string const pkl_uuid = make_uuid ();
472         boost::filesystem::path const pkl_path = write_pkl (standard, pkl_uuid, metadata, signer);
473
474         write_volindex (standard);
475         write_assetmap (standard, pkl_uuid, boost::filesystem::file_size (pkl_path), metadata);
476 }
477
478 list<shared_ptr<CPL> >
479 DCP::cpls () const
480 {
481         return _cpls;
482 }
483
484 /** @return All assets (including CPLs) */
485 list<shared_ptr<Asset> >
486 DCP::assets () const
487 {
488         list<shared_ptr<Asset> > assets;
489         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
490                 assets.push_back (i);
491                 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
492                         shared_ptr<Asset> o = j->asset_ref().asset ();
493                         assets.push_back (o);
494                         /* More Interop special-casing */
495                         shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
496                         if (sub) {
497                                 sub->add_font_assets (assets);
498                         }
499                 }
500         }
501
502         return assets;
503 }