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