2 Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
38 #include "raw_convert.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"
50 #include "exceptions.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"
60 #include "asset_factory.h"
61 #include <asdcp/AS_DCP.h>
62 #include <xmlsec/xmldsig.h>
63 #include <xmlsec/app.h>
64 #include <libxml++/libxml++.h>
65 #include <boost/filesystem.hpp>
66 #include <boost/algorithm/string.hpp>
67 #include <boost/foreach.hpp>
77 using boost::shared_ptr;
78 using boost::dynamic_pointer_cast;
79 using boost::optional;
80 using boost::algorithm::starts_with;
83 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
84 static string const assetmap_smpte_ns = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
85 static string const volindex_interop_ns = "http://www.digicine.com/PROTO-ASDCP-VL-20040311#";
86 static string const volindex_smpte_ns = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
88 DCP::DCP (boost::filesystem::path directory)
89 : _directory (directory)
91 if (!boost::filesystem::exists (directory)) {
92 boost::filesystem::create_directories (directory);
95 _directory = boost::filesystem::canonical (_directory);
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)
104 errors->push_back (shared_ptr<T> (new T (e)));
112 DCP::read (bool keep_going, ReadErrors* errors, bool ignore_incorrect_picture_mxf_type)
114 /* Read the ASSETMAP and PKL */
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";
122 boost::throw_exception (DCPReadError (String::compose ("could not find AssetMap file in `%1'", _directory.string())));
125 cxml::Document asset_map ("AssetMap");
127 asset_map.read_file (asset_map_file);
128 if (asset_map.namespace_uri() == assetmap_interop_ns) {
130 } else if (asset_map.namespace_uri() == assetmap_smpte_ns) {
133 boost::throw_exception (XMLError ("Unrecognised Assetmap namespace " + asset_map.namespace_uri()));
136 list<shared_ptr<cxml::Node> > asset_nodes = asset_map.node_child("AssetList")->node_children ("Asset");
137 map<string, boost::filesystem::path> paths;
138 list<boost::filesystem::path> pkl_paths;
139 BOOST_FOREACH (shared_ptr<cxml::Node> i, asset_nodes) {
140 if (i->node_child("ChunkList")->node_children("Chunk").size() != 1) {
141 boost::throw_exception (XMLError ("unsupported asset chunk count"));
143 string p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
144 if (starts_with (p, "file://")) {
147 switch (*_standard) {
149 if (i->optional_node_child("PackingList")) {
150 pkl_paths.push_back (p);
152 paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
157 optional<string> pkl_bool = i->optional_string_child("PackingList");
158 if (pkl_bool && *pkl_bool == "true") {
159 pkl_paths.push_back (p);
161 paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
168 if (pkl_paths.empty()) {
169 boost::throw_exception (XMLError ("No packing lists found in asset map"));
172 BOOST_FOREACH (boost::filesystem::path i, pkl_paths) {
173 _pkls.push_back (shared_ptr<PKL>(new PKL(_directory / i)));
176 /* Read all the assets from the asset map */
178 /* Make a list of non-CPL/PKL assets so that we can resolve the references
181 list<shared_ptr<Asset> > other_assets;
183 for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
184 boost::filesystem::path path = _directory / i->second;
186 if (!boost::filesystem::exists (path)) {
187 survivable_error (keep_going, errors, MissingAssetError (path));
191 optional<string> pkl_type;
192 BOOST_FOREACH (shared_ptr<PKL> j, _pkls) {
193 pkl_type = j->type(i->first);
199 DCP_ASSERT (pkl_type);
201 if (*pkl_type == CPL::static_pkl_type(*_standard) || *pkl_type == InteropSubtitleAsset::static_pkl_type(*_standard)) {
202 xmlpp::DomParser* p = new xmlpp::DomParser;
204 p->parse_file (path.string());
205 } catch (std::exception& e) {
207 throw DCPReadError(String::compose("XML error in %1", path.string()), e.what());
210 string const root = p->get_document()->get_root_node()->get_name ();
213 if (root == "CompositionPlaylist") {
214 shared_ptr<CPL> cpl (new CPL (path));
215 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
216 survivable_error (keep_going, errors, MismatchedStandardError ());
218 _cpls.push_back (cpl);
219 } else if (root == "DCSubtitle") {
220 if (_standard && _standard.get() == SMPTE) {
221 survivable_error (keep_going, errors, MismatchedStandardError ());
223 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
226 *pkl_type == PictureAsset::static_pkl_type(*_standard) ||
227 *pkl_type == SoundAsset::static_pkl_type(*_standard) ||
228 *pkl_type == AtmosAsset::static_pkl_type(*_standard) ||
229 *pkl_type == SMPTESubtitleAsset::static_pkl_type(*_standard)
232 other_assets.push_back (asset_factory(path, ignore_incorrect_picture_mxf_type));
233 } else if (*pkl_type == FontAsset::static_pkl_type(*_standard)) {
234 other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
235 } else if (*pkl_type == "image/png") {
236 /* It's an Interop PNG subtitle; let it go */
238 throw DCPReadError (String::compose("Unknown asset type %1 in PKL", *pkl_type));
242 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
243 i->resolve_refs (other_assets);
248 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
250 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
251 i->resolve_refs (assets);
256 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
258 list<shared_ptr<CPL> > a = cpls ();
259 list<shared_ptr<CPL> > b = other.cpls ();
261 if (a.size() != b.size()) {
262 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
268 BOOST_FOREACH (shared_ptr<CPL> i, a) {
269 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
270 while (j != b.end() && !(*j)->equals (i, opt, note)) {
283 DCP::add (boost::shared_ptr<CPL> cpl)
285 _cpls.push_back (cpl);
289 DCP::encrypted () const
291 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
292 if (i->encrypted ()) {
300 /** Add a KDM to decrypt this DCP. This method must be called after DCP::read()
301 * or the KDM you specify will be ignored.
302 * @param kdm KDM to use.
305 DCP::add (DecryptedKDM const & kdm)
307 list<DecryptedKDMKey> keys = kdm.keys ();
309 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
310 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
311 if (j.cpl_id() == i->id()) {
318 /** Write the VOLINDEX file.
319 * @param standard DCP standard to use (INTEROP or SMPTE)
322 DCP::write_volindex (Standard standard) const
324 boost::filesystem::path p = _directory;
337 xmlpp::Element* root;
341 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
344 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
350 root->add_child("Index")->add_child_text ("1");
351 doc.write_to_file (p.string (), "UTF-8");
355 DCP::write_assetmap (Standard standard, string pkl_uuid, boost::filesystem::path pkl_path, XMLMetadata metadata) const
357 boost::filesystem::path p = _directory;
371 xmlpp::Element* root;
375 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
378 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
384 root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
385 root->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
389 root->add_child("VolumeCount")->add_child_text ("1");
390 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
391 root->add_child("Issuer")->add_child_text (metadata.issuer);
392 root->add_child("Creator")->add_child_text (metadata.creator);
395 root->add_child("Creator")->add_child_text (metadata.creator);
396 root->add_child("VolumeCount")->add_child_text ("1");
397 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
398 root->add_child("Issuer")->add_child_text (metadata.issuer);
404 xmlpp::Node* asset_list = root->add_child ("AssetList");
406 xmlpp::Node* asset = asset_list->add_child ("Asset");
407 asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
408 asset->add_child("PackingList")->add_child_text ("true");
409 xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
410 xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
411 chunk->add_child("Path")->add_child_text (pkl_path.filename().string());
412 chunk->add_child("VolumeIndex")->add_child_text ("1");
413 chunk->add_child("Offset")->add_child_text ("0");
414 chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
416 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
417 i->write_to_assetmap (asset_list, _directory);
420 /* This must not be the _formatted version otherwise signature digests will be wrong */
421 doc.write_to_file (p.string (), "UTF-8");
424 /** Write all the XML files for this DCP.
425 * @param standand INTEROP or SMPTE.
426 * @param metadata Metadata to use for PKL and asset map files.
427 * @param signer Signer to use, or 0.
432 XMLMetadata metadata,
433 shared_ptr<const CertificateChain> signer,
434 NameFormat name_format
437 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
438 NameFormat::Map values;
440 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
446 pkl.reset (new PKL (standard, metadata.annotation_text, metadata.issue_date, metadata.issuer, metadata.creator));
447 _pkls.push_back (pkl);
448 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
449 i->add_to_pkl (pkl, _directory);
452 pkl = _pkls.front ();
455 NameFormat::Map values;
457 boost::filesystem::path pkl_path = _directory / name_format.get(values, "_" + pkl->id() + ".xml");
458 pkl->write (pkl_path, signer);
460 write_volindex (standard);
461 write_assetmap (standard, pkl->id(), pkl_path, metadata);
464 list<shared_ptr<CPL> >
470 /** @param ignore_unresolved true to silently ignore unresolved assets, otherwise
471 * an exception is thrown if they are found.
472 * @return All assets (including CPLs).
474 list<shared_ptr<Asset> >
475 DCP::assets (bool ignore_unresolved) const
477 list<shared_ptr<Asset> > assets;
478 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
479 assets.push_back (i);
480 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
481 if (ignore_unresolved && !j->asset_ref().resolved()) {
484 shared_ptr<Asset> o = j->asset_ref().asset ();
485 assets.push_back (o);
486 /* More Interop special-casing */
487 shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
489 sub->add_font_assets (assets);
497 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
498 vector<boost::filesystem::path>
499 DCP::directories_from_files (vector<boost::filesystem::path> files)
501 vector<boost::filesystem::path> d;
502 BOOST_FOREACH (boost::filesystem::path i, files) {
503 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
504 d.push_back (i.parent_path ());