Put xmlns:dsig on Signer and Signature rather than on the whole
[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 "atmos_asset.h"
42 #include "picture_asset.h"
43 #include "interop_subtitle_asset.h"
44 #include "smpte_subtitle_asset.h"
45 #include "mono_picture_asset.h"
46 #include "stereo_picture_asset.h"
47 #include "reel_subtitle_asset.h"
48 #include "util.h"
49 #include "metadata.h"
50 #include "exceptions.h"
51 #include "cpl.h"
52 #include "certificate_chain.h"
53 #include "compose.hpp"
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 <asdcp/AS_DCP.h>
60 #include <xmlsec/xmldsig.h>
61 #include <xmlsec/app.h>
62 #include <libxml++/libxml++.h>
63 #include <boost/filesystem.hpp>
64 #include <boost/algorithm/string.hpp>
65 #include <boost/foreach.hpp>
66
67 using std::string;
68 using std::list;
69 using std::vector;
70 using std::cout;
71 using std::make_pair;
72 using std::map;
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-VL-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                                 throw DCPReadError(String::compose("XML error in %1", path.string()), e.what());
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                                 case ASDCP::ESS_DCDATA_DOLBY_ATMOS:
227                                         other_assets.push_back (shared_ptr<AtmosAsset> (new AtmosAsset (path)));
228                                         break;
229                                 default:
230                                         throw DCPReadError (String::compose ("Unknown MXF essence type %1 in %2", int(type), path.string()));
231                         }
232                 } else if (boost::filesystem::extension (path) == ".ttf") {
233                         other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
234                 }
235         }
236
237         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
238                 i->resolve_refs (other_assets);
239         }
240 }
241
242 void
243 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
244 {
245         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
246                 i->resolve_refs (assets);
247         }
248 }
249
250 bool
251 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
252 {
253         list<shared_ptr<CPL> > a = cpls ();
254         list<shared_ptr<CPL> > b = other.cpls ();
255
256         if (a.size() != b.size()) {
257                 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
258                 return false;
259         }
260
261         bool r = true;
262
263         BOOST_FOREACH (shared_ptr<CPL> i, a) {
264                 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
265                 while (j != b.end() && !(*j)->equals (i, opt, note)) {
266                         ++j;
267                 }
268
269                 if (j == b.end ()) {
270                         r = false;
271                 }
272         }
273
274         return r;
275 }
276
277 void
278 DCP::add (boost::shared_ptr<CPL> cpl)
279 {
280         _cpls.push_back (cpl);
281 }
282
283 bool
284 DCP::encrypted () const
285 {
286         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
287                 if (i->encrypted ()) {
288                         return true;
289                 }
290         }
291
292         return false;
293 }
294
295 void
296 DCP::add (DecryptedKDM const & kdm)
297 {
298         list<DecryptedKDMKey> keys = kdm.keys ();
299
300         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
301                 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
302                         if (j.cpl_id() == i->id()) {
303                                 i->add (kdm);
304                         }
305                 }
306         }
307 }
308
309 /** @return full pathname of PKL file that was written */
310 boost::filesystem::path
311 DCP::write_pkl (string file, Standard standard, string pkl_uuid, XMLMetadata metadata, shared_ptr<const CertificateChain> signer) const
312 {
313         boost::filesystem::path p = _directory;
314         p /= file;
315
316         xmlpp::Document doc;
317         xmlpp::Element* pkl;
318         if (standard == INTEROP) {
319                 pkl = doc.create_root_node("PackingList", pkl_interop_ns);
320         } else {
321                 pkl = doc.create_root_node("PackingList", pkl_smpte_ns);
322         }
323
324         pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
325         pkl->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
326         pkl->add_child("IssueDate")->add_child_text (metadata.issue_date);
327         pkl->add_child("Issuer")->add_child_text (metadata.issuer);
328         pkl->add_child("Creator")->add_child_text (metadata.creator);
329
330         xmlpp::Element* asset_list = pkl->add_child("AssetList");
331         BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
332                 i->write_to_pkl (asset_list, _directory, standard);
333         }
334
335         if (signer) {
336                 signer->sign (pkl, standard);
337         }
338
339         doc.write_to_file (p.string (), "UTF-8");
340         return p.string ();
341 }
342
343 /** Write the VOLINDEX file.
344  *  @param standard DCP standard to use (INTEROP or SMPTE)
345  */
346 void
347 DCP::write_volindex (Standard standard) const
348 {
349         boost::filesystem::path p = _directory;
350         switch (standard) {
351         case INTEROP:
352                 p /= "VOLINDEX";
353                 break;
354         case SMPTE:
355                 p /= "VOLINDEX.xml";
356                 break;
357         default:
358                 DCP_ASSERT (false);
359         }
360
361         xmlpp::Document doc;
362         xmlpp::Element* root;
363
364         switch (standard) {
365         case INTEROP:
366                 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
367                 break;
368         case SMPTE:
369                 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
370                 break;
371         default:
372                 DCP_ASSERT (false);
373         }
374
375         root->add_child("Index")->add_child_text ("1");
376         doc.write_to_file (p.string (), "UTF-8");
377 }
378
379 void
380 DCP::write_assetmap (Standard standard, string pkl_uuid, boost::filesystem::path pkl_path, XMLMetadata metadata) const
381 {
382         boost::filesystem::path p = _directory;
383
384         switch (standard) {
385         case INTEROP:
386                 p /= "ASSETMAP";
387                 break;
388         case SMPTE:
389                 p /= "ASSETMAP.xml";
390                 break;
391         default:
392                 DCP_ASSERT (false);
393         }
394
395         xmlpp::Document doc;
396         xmlpp::Element* root;
397
398         switch (standard) {
399         case INTEROP:
400                 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
401                 break;
402         case SMPTE:
403                 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
404                 break;
405         default:
406                 DCP_ASSERT (false);
407         }
408
409         root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
410         root->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
411
412         switch (standard) {
413         case INTEROP:
414                 root->add_child("VolumeCount")->add_child_text ("1");
415                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
416                 root->add_child("Issuer")->add_child_text (metadata.issuer);
417                 root->add_child("Creator")->add_child_text (metadata.creator);
418                 break;
419         case SMPTE:
420                 root->add_child("Creator")->add_child_text (metadata.creator);
421                 root->add_child("VolumeCount")->add_child_text ("1");
422                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
423                 root->add_child("Issuer")->add_child_text (metadata.issuer);
424                 break;
425         default:
426                 DCP_ASSERT (false);
427         }
428
429         xmlpp::Node* asset_list = root->add_child ("AssetList");
430
431         xmlpp::Node* asset = asset_list->add_child ("Asset");
432         asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
433         asset->add_child("PackingList")->add_child_text ("true");
434         xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
435         xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
436         chunk->add_child("Path")->add_child_text (pkl_path.filename().string());
437         chunk->add_child("VolumeIndex")->add_child_text ("1");
438         chunk->add_child("Offset")->add_child_text ("0");
439         chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
440
441         BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
442                 i->write_to_assetmap (asset_list, _directory);
443         }
444
445         /* This must not be the _formatted version otherwise signature digests will be wrong */
446         doc.write_to_file (p.string (), "UTF-8");
447 }
448
449 /** Write all the XML files for this DCP.
450  *  @param standand INTEROP or SMPTE.
451  *  @param metadata Metadata to use for PKL and asset map files.
452  *  @param signer Signer to use, or 0.
453  */
454 void
455 DCP::write_xml (
456         Standard standard,
457         XMLMetadata metadata,
458         shared_ptr<const CertificateChain> signer,
459         NameFormat name_format
460         )
461 {
462         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
463                 NameFormat::Map values;
464                 values['t'] = "cpl";
465                 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
466         }
467
468         string const pkl_uuid = make_uuid ();
469         NameFormat::Map values;
470         values['t'] = "pkl";
471         boost::filesystem::path const pkl_path = write_pkl (name_format.get(values, "_" + pkl_uuid + ".xml"), standard, pkl_uuid, metadata, signer);
472
473         write_volindex (standard);
474         write_assetmap (standard, pkl_uuid, pkl_path, metadata);
475 }
476
477 list<shared_ptr<CPL> >
478 DCP::cpls () const
479 {
480         return _cpls;
481 }
482
483 /** @param ignore_unresolved true to silently ignore unresolved assets, otherwise
484  *  an exception is thrown if they are found.
485  *  @return All assets (including CPLs).
486  */
487 list<shared_ptr<Asset> >
488 DCP::assets (bool ignore_unresolved) const
489 {
490         list<shared_ptr<Asset> > assets;
491         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
492                 assets.push_back (i);
493                 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
494                         if (ignore_unresolved && !j->asset_ref().resolved()) {
495                                 continue;
496                         }
497                         shared_ptr<Asset> o = j->asset_ref().asset ();
498                         assets.push_back (o);
499                         /* More Interop special-casing */
500                         shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
501                         if (sub) {
502                                 sub->add_font_assets (assets);
503                         }
504                 }
505         }
506
507         return assets;
508 }
509
510 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
511 vector<boost::filesystem::path>
512 DCP::directories_from_files (vector<boost::filesystem::path> files)
513 {
514         vector<boost::filesystem::path> d;
515         BOOST_FOREACH (boost::filesystem::path i, files) {
516                 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
517                         d.push_back (i.parent_path ());
518                 }
519         }
520         return d;
521 }