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_formatted (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 doc.write_to_file_formatted (p.string (), "UTF-8");
423 /** Write all the XML files for this DCP.
424 * @param standand INTEROP or SMPTE.
425 * @param metadata Metadata to use for PKL and asset map files.
426 * @param signer Signer to use, or 0.
431 XMLMetadata metadata,
432 shared_ptr<const CertificateChain> signer,
433 NameFormat name_format
436 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
437 NameFormat::Map values;
439 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
445 pkl.reset (new PKL (standard, metadata.annotation_text, metadata.issue_date, metadata.issuer, metadata.creator));
446 _pkls.push_back (pkl);
447 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
448 i->add_to_pkl (pkl, _directory);
451 pkl = _pkls.front ();
454 NameFormat::Map values;
456 boost::filesystem::path pkl_path = _directory / name_format.get(values, "_" + pkl->id() + ".xml");
457 pkl->write (pkl_path, signer);
459 write_volindex (standard);
460 write_assetmap (standard, pkl->id(), pkl_path, metadata);
463 list<shared_ptr<CPL> >
469 /** @param ignore_unresolved true to silently ignore unresolved assets, otherwise
470 * an exception is thrown if they are found.
471 * @return All assets (including CPLs).
473 list<shared_ptr<Asset> >
474 DCP::assets (bool ignore_unresolved) const
476 list<shared_ptr<Asset> > assets;
477 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
478 assets.push_back (i);
479 BOOST_FOREACH (shared_ptr<const ReelMXF> j, i->reel_mxfs()) {
480 if (ignore_unresolved && !j->asset_ref().resolved()) {
483 shared_ptr<Asset> o = j->asset_ref().asset ();
484 assets.push_back (o);
485 /* More Interop special-casing */
486 shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
488 sub->add_font_assets (assets);
496 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
497 vector<boost::filesystem::path>
498 DCP::directories_from_files (vector<boost::filesystem::path> files)
500 vector<boost::filesystem::path> d;
501 BOOST_FOREACH (boost::filesystem::path i, files) {
502 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
503 d.push_back (i.parent_path ());