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