Remove debug.
[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 "pkl.h"
60 #include <asdcp/AS_DCP.h>
61 #include <xmlsec/xmldsig.h>
62 #include <xmlsec/app.h>
63 #include <libxml++/libxml++.h>
64 #include <boost/filesystem.hpp>
65 #include <boost/algorithm/string.hpp>
66 #include <boost/foreach.hpp>
67
68 using std::string;
69 using std::list;
70 using std::vector;
71 using std::cout;
72 using std::make_pair;
73 using std::map;
74 using std::cerr;
75 using std::exception;
76 using boost::shared_ptr;
77 using boost::dynamic_pointer_cast;
78 using boost::optional;
79 using boost::algorithm::starts_with;
80 using namespace dcp;
81
82 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
83 static string const assetmap_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
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 and PKL */
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         optional<boost::filesystem::path> pkl_path;
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                 optional<string> pkl_bool = i->optional_string_child("PackingList");
147                 if (pkl_bool && *pkl_bool == "true") {
148                         pkl_path = p;
149                 } else {
150                         paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
151                 }
152         }
153
154         if (!pkl_path) {
155                 boost::throw_exception (XMLError ("No packing list found in asset map"));
156         }
157
158         _pkl.reset (new PKL (_directory / *pkl_path));
159
160         /* Read all the assets from the asset map */
161
162         /* Make a list of non-CPL/PKL assets so that we can resolve the references
163            from the CPLs.
164         */
165         list<shared_ptr<Asset> > other_assets;
166
167         for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
168                 boost::filesystem::path path = _directory / i->second;
169
170                 if (!boost::filesystem::exists (path)) {
171                         survivable_error (keep_going, errors, MissingAssetError (path));
172                         continue;
173                 }
174
175                 string const pkl_type = _pkl->type(i->first);
176
177                 if (pkl_type == CPL::static_pkl_type(*_standard) || pkl_type == InteropSubtitleAsset::static_pkl_type(*_standard)) {
178                         xmlpp::DomParser* p = new xmlpp::DomParser;
179                         try {
180                                 p->parse_file (path.string());
181                         } catch (std::exception& e) {
182                                 delete p;
183                                 throw DCPReadError(String::compose("XML error in %1", path.string()), e.what());
184                         }
185
186                         string const root = p->get_document()->get_root_node()->get_name ();
187                         delete p;
188
189                         if (root == "CompositionPlaylist") {
190                                 shared_ptr<CPL> cpl (new CPL (path));
191                                 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
192                                         survivable_error (keep_going, errors, MismatchedStandardError ());
193                                 }
194                                 _cpls.push_back (cpl);
195                         } else if (root == "DCSubtitle") {
196                                 if (_standard && _standard.get() == SMPTE) {
197                                         survivable_error (keep_going, errors, MismatchedStandardError ());
198                                 }
199                                 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
200                         }
201                 } else if (
202                         pkl_type == PictureAsset::static_pkl_type(*_standard) ||
203                         pkl_type == SoundAsset::static_pkl_type(*_standard) ||
204                         pkl_type == AtmosAsset::static_pkl_type(*_standard) ||
205                         pkl_type == SMPTESubtitleAsset::static_pkl_type(*_standard)
206                         ) {
207
208                         /* XXX: asdcplib does not appear to support discovery of read MXFs standard
209                            (Interop / SMPTE)
210                         */
211
212                         ASDCP::EssenceType_t type;
213                         if (ASDCP::EssenceType (path.string().c_str(), type) != ASDCP::RESULT_OK) {
214                                 throw DCPReadError ("Could not find essence type");
215                         }
216                         switch (type) {
217                                 case ASDCP::ESS_UNKNOWN:
218                                 case ASDCP::ESS_MPEG2_VES:
219                                         throw DCPReadError ("MPEG2 video essences are not supported");
220                                 case ASDCP::ESS_JPEG_2000:
221                                         try {
222                                                 other_assets.push_back (shared_ptr<MonoPictureAsset> (new MonoPictureAsset (path)));
223                                         } catch (dcp::MXFFileError& e) {
224                                                 if (ignore_incorrect_picture_mxf_type && e.number() == ASDCP::RESULT_SFORMAT) {
225                                                         /* Tried to load it as mono but the error says it's stereo; try that instead */
226                                                         other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
227                                                 } else {
228                                                         throw;
229                                                 }
230                                         }
231                                         break;
232                                 case ASDCP::ESS_PCM_24b_48k:
233                                 case ASDCP::ESS_PCM_24b_96k:
234                                         other_assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (path)));
235                                         break;
236                                 case ASDCP::ESS_JPEG_2000_S:
237                                         other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
238                                         break;
239                                 case ASDCP::ESS_TIMED_TEXT:
240                                         other_assets.push_back (shared_ptr<SMPTESubtitleAsset> (new SMPTESubtitleAsset (path)));
241                                         break;
242                                 case ASDCP::ESS_DCDATA_DOLBY_ATMOS:
243                                         other_assets.push_back (shared_ptr<AtmosAsset> (new AtmosAsset (path)));
244                                         break;
245                                 default:
246                                         throw DCPReadError (String::compose ("Unknown MXF essence type %1 in %2", int(type), path.string()));
247                         }
248                 } else if (pkl_type == FontAsset::static_pkl_type(*_standard)) {
249                         other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
250                 }
251         }
252
253         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
254                 i->resolve_refs (other_assets);
255         }
256 }
257
258 void
259 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
260 {
261         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
262                 i->resolve_refs (assets);
263         }
264 }
265
266 bool
267 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
268 {
269         list<shared_ptr<CPL> > a = cpls ();
270         list<shared_ptr<CPL> > b = other.cpls ();
271
272         if (a.size() != b.size()) {
273                 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
274                 return false;
275         }
276
277         bool r = true;
278
279         BOOST_FOREACH (shared_ptr<CPL> i, a) {
280                 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
281                 while (j != b.end() && !(*j)->equals (i, opt, note)) {
282                         ++j;
283                 }
284
285                 if (j == b.end ()) {
286                         r = false;
287                 }
288         }
289
290         return r;
291 }
292
293 void
294 DCP::add (boost::shared_ptr<CPL> cpl)
295 {
296         _cpls.push_back (cpl);
297 }
298
299 bool
300 DCP::encrypted () const
301 {
302         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
303                 if (i->encrypted ()) {
304                         return true;
305                 }
306         }
307
308         return false;
309 }
310
311 /** Add a KDM to decrypt this DCP.  This method must be called after DCP::read()
312  *  or the KDM you specify will be ignored.
313  *  @param kdm KDM to use.
314  */
315 void
316 DCP::add (DecryptedKDM const & kdm)
317 {
318         list<DecryptedKDMKey> keys = kdm.keys ();
319
320         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
321                 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
322                         if (j.cpl_id() == i->id()) {
323                                 i->add (kdm);
324                         }
325                 }
326         }
327 }
328
329 /** Write the VOLINDEX file.
330  *  @param standard DCP standard to use (INTEROP or SMPTE)
331  */
332 void
333 DCP::write_volindex (Standard standard) const
334 {
335         boost::filesystem::path p = _directory;
336         switch (standard) {
337         case INTEROP:
338                 p /= "VOLINDEX";
339                 break;
340         case SMPTE:
341                 p /= "VOLINDEX.xml";
342                 break;
343         default:
344                 DCP_ASSERT (false);
345         }
346
347         xmlpp::Document doc;
348         xmlpp::Element* root;
349
350         switch (standard) {
351         case INTEROP:
352                 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
353                 break;
354         case SMPTE:
355                 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
356                 break;
357         default:
358                 DCP_ASSERT (false);
359         }
360
361         root->add_child("Index")->add_child_text ("1");
362         doc.write_to_file (p.string (), "UTF-8");
363 }
364
365 void
366 DCP::write_assetmap (Standard standard, string pkl_uuid, boost::filesystem::path pkl_path, XMLMetadata metadata) const
367 {
368         boost::filesystem::path p = _directory;
369
370         switch (standard) {
371         case INTEROP:
372                 p /= "ASSETMAP";
373                 break;
374         case SMPTE:
375                 p /= "ASSETMAP.xml";
376                 break;
377         default:
378                 DCP_ASSERT (false);
379         }
380
381         xmlpp::Document doc;
382         xmlpp::Element* root;
383
384         switch (standard) {
385         case INTEROP:
386                 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
387                 break;
388         case SMPTE:
389                 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
390                 break;
391         default:
392                 DCP_ASSERT (false);
393         }
394
395         root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
396         root->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
397
398         switch (standard) {
399         case INTEROP:
400                 root->add_child("VolumeCount")->add_child_text ("1");
401                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
402                 root->add_child("Issuer")->add_child_text (metadata.issuer);
403                 root->add_child("Creator")->add_child_text (metadata.creator);
404                 break;
405         case SMPTE:
406                 root->add_child("Creator")->add_child_text (metadata.creator);
407                 root->add_child("VolumeCount")->add_child_text ("1");
408                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
409                 root->add_child("Issuer")->add_child_text (metadata.issuer);
410                 break;
411         default:
412                 DCP_ASSERT (false);
413         }
414
415         xmlpp::Node* asset_list = root->add_child ("AssetList");
416
417         xmlpp::Node* asset = asset_list->add_child ("Asset");
418         asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
419         asset->add_child("PackingList")->add_child_text ("true");
420         xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
421         xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
422         chunk->add_child("Path")->add_child_text (pkl_path.filename().string());
423         chunk->add_child("VolumeIndex")->add_child_text ("1");
424         chunk->add_child("Offset")->add_child_text ("0");
425         chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
426
427         BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
428                 i->write_to_assetmap (asset_list, _directory);
429         }
430
431         /* This must not be the _formatted version otherwise signature digests will be wrong */
432         doc.write_to_file (p.string (), "UTF-8");
433 }
434
435 /** Write all the XML files for this DCP.
436  *  @param standand INTEROP or SMPTE.
437  *  @param metadata Metadata to use for PKL and asset map files.
438  *  @param signer Signer to use, or 0.
439  */
440 void
441 DCP::write_xml (
442         Standard standard,
443         XMLMetadata metadata,
444         shared_ptr<const CertificateChain> signer,
445         NameFormat name_format
446         )
447 {
448         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
449                 NameFormat::Map values;
450                 values['t'] = "cpl";
451                 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
452         }
453
454         if (!_pkl) {
455                 _pkl.reset (new PKL (standard, metadata.annotation_text, metadata.issue_date, metadata.issuer, metadata.creator));
456                 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
457                         i->add_to_pkl (_pkl, _directory);
458                 }
459         }
460
461         NameFormat::Map values;
462         values['t'] = "pkl";
463         boost::filesystem::path pkl_path = _directory / name_format.get(values, "_" + _pkl->id() + ".xml");
464         _pkl->write (pkl_path, signer);
465
466         write_volindex (standard);
467         write_assetmap (standard, _pkl->id(), pkl_path, metadata);
468 }
469
470 list<shared_ptr<CPL> >
471 DCP::cpls () const
472 {
473         return _cpls;
474 }
475
476 /** @param ignore_unresolved true to silently ignore unresolved assets, otherwise
477  *  an exception is thrown if they are found.
478  *  @return All assets (including CPLs).
479  */
480 list<shared_ptr<Asset> >
481 DCP::assets (bool ignore_unresolved) const
482 {
483         list<shared_ptr<Asset> > assets;
484         BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
485                 assets.push_back (i);
486                 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
487                         if (ignore_unresolved && !j->asset_ref().resolved()) {
488                                 continue;
489                         }
490                         shared_ptr<Asset> o = j->asset_ref().asset ();
491                         assets.push_back (o);
492                         /* More Interop special-casing */
493                         shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
494                         if (sub) {
495                                 sub->add_font_assets (assets);
496                         }
497                 }
498         }
499
500         return assets;
501 }
502
503 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
504 vector<boost::filesystem::path>
505 DCP::directories_from_files (vector<boost::filesystem::path> files)
506 {
507         vector<boost::filesystem::path> d;
508         BOOST_FOREACH (boost::filesystem::path i, files) {
509                 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
510                         d.push_back (i.parent_path ());
511                 }
512         }
513         return d;
514 }