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)));
177 paths - files in the DCP that are not PKLs.
178 _pkls - PKL objects for each PKL.
180 Read all the assets from the asset map.
183 /* Make a list of non-CPL/PKL assets so that we can resolve the references
186 list<shared_ptr<Asset> > other_assets;
188 for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
189 boost::filesystem::path path = _directory / i->second;
191 if (i->second.empty()) {
192 /* I can't see how this is valid, but it's
193 been seen in the wild with a DCP that
194 claims to come from ClipsterDCI 5.10.0.5.
196 survivable_error (keep_going, errors, EmptyAssetPathError(i->first));
200 if (!boost::filesystem::exists(path)) {
201 survivable_error (keep_going, errors, MissingAssetError (path));
205 /* Find the <Type> for this asset from the PKL that contains the asset */
206 optional<string> pkl_type;
207 BOOST_FOREACH (shared_ptr<PKL> j, _pkls) {
208 pkl_type = j->type(i->first);
215 /* This asset is in the ASSETMAP but not mentioned in any PKL so we don't
216 * need to worry about it.
221 if (*pkl_type == CPL::static_pkl_type(*_standard) || *pkl_type == InteropSubtitleAsset::static_pkl_type(*_standard)) {
222 xmlpp::DomParser* p = new xmlpp::DomParser;
224 p->parse_file (path.string());
225 } catch (std::exception& e) {
227 throw DCPReadError(String::compose("XML error in %1", path.string()), e.what());
230 string const root = p->get_document()->get_root_node()->get_name ();
233 if (root == "CompositionPlaylist") {
234 shared_ptr<CPL> cpl (new CPL (path));
235 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
236 survivable_error (keep_going, errors, MismatchedStandardError ());
238 _cpls.push_back (cpl);
239 } else if (root == "DCSubtitle") {
240 if (_standard && _standard.get() == SMPTE) {
241 survivable_error (keep_going, errors, MismatchedStandardError ());
243 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
246 *pkl_type == PictureAsset::static_pkl_type(*_standard) ||
247 *pkl_type == SoundAsset::static_pkl_type(*_standard) ||
248 *pkl_type == AtmosAsset::static_pkl_type(*_standard) ||
249 *pkl_type == SMPTESubtitleAsset::static_pkl_type(*_standard)
252 other_assets.push_back (asset_factory(path, ignore_incorrect_picture_mxf_type));
253 } else if (*pkl_type == FontAsset::static_pkl_type(*_standard)) {
254 other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
255 } else if (*pkl_type == "image/png") {
256 /* It's an Interop PNG subtitle; let it go */
258 throw DCPReadError (String::compose("Unknown asset type %1 in PKL", *pkl_type));
262 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
263 i->resolve_refs (other_assets);
268 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
270 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
271 i->resolve_refs (assets);
276 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
278 list<shared_ptr<CPL> > a = cpls ();
279 list<shared_ptr<CPL> > b = other.cpls ();
281 if (a.size() != b.size()) {
282 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
288 BOOST_FOREACH (shared_ptr<CPL> i, a) {
289 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
290 while (j != b.end() && !(*j)->equals (i, opt, note)) {
303 DCP::add (boost::shared_ptr<CPL> cpl)
305 _cpls.push_back (cpl);
309 DCP::encrypted () const
311 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
312 if (i->encrypted ()) {
320 /** Add a KDM to decrypt this DCP. This method must be called after DCP::read()
321 * or the KDM you specify will be ignored.
322 * @param kdm KDM to use.
325 DCP::add (DecryptedKDM const & kdm)
327 list<DecryptedKDMKey> keys = kdm.keys ();
329 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
330 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
331 if (j.cpl_id() == i->id()) {
338 /** Write the VOLINDEX file.
339 * @param standard DCP standard to use (INTEROP or SMPTE)
342 DCP::write_volindex (Standard standard) const
344 boost::filesystem::path p = _directory;
357 xmlpp::Element* root;
361 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
364 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
370 root->add_child("Index")->add_child_text ("1");
371 doc.write_to_file_formatted (p.string (), "UTF-8");
375 DCP::write_assetmap (Standard standard, string pkl_uuid, boost::filesystem::path pkl_path, XMLMetadata metadata) const
377 boost::filesystem::path p = _directory;
391 xmlpp::Element* root;
395 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
398 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
404 root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
405 root->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
409 root->add_child("VolumeCount")->add_child_text ("1");
410 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
411 root->add_child("Issuer")->add_child_text (metadata.issuer);
412 root->add_child("Creator")->add_child_text (metadata.creator);
415 root->add_child("Creator")->add_child_text (metadata.creator);
416 root->add_child("VolumeCount")->add_child_text ("1");
417 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
418 root->add_child("Issuer")->add_child_text (metadata.issuer);
424 xmlpp::Node* asset_list = root->add_child ("AssetList");
426 xmlpp::Node* asset = asset_list->add_child ("Asset");
427 asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
428 asset->add_child("PackingList")->add_child_text ("true");
429 xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
430 xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
431 chunk->add_child("Path")->add_child_text (pkl_path.filename().string());
432 chunk->add_child("VolumeIndex")->add_child_text ("1");
433 chunk->add_child("Offset")->add_child_text ("0");
434 chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
436 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
437 i->write_to_assetmap (asset_list, _directory);
440 doc.write_to_file_formatted (p.string (), "UTF-8");
443 /** Write all the XML files for this DCP.
444 * @param standand INTEROP or SMPTE.
445 * @param metadata Metadata to use for PKL and asset map files.
446 * @param signer Signer to use, or 0.
451 XMLMetadata metadata,
452 shared_ptr<const CertificateChain> signer,
453 NameFormat name_format
456 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
457 NameFormat::Map values;
459 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
465 pkl.reset (new PKL (standard, metadata.annotation_text, metadata.issue_date, metadata.issuer, metadata.creator));
466 _pkls.push_back (pkl);
467 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
468 i->add_to_pkl (pkl, _directory);
471 pkl = _pkls.front ();
474 NameFormat::Map values;
476 boost::filesystem::path pkl_path = _directory / name_format.get(values, "_" + pkl->id() + ".xml");
477 pkl->write (pkl_path, signer);
479 write_volindex (standard);
480 write_assetmap (standard, pkl->id(), pkl_path, metadata);
483 list<shared_ptr<CPL> >
489 /** @param ignore_unresolved true to silently ignore unresolved assets, otherwise
490 * an exception is thrown if they are found.
491 * @return All assets (including CPLs).
493 list<shared_ptr<Asset> >
494 DCP::assets (bool ignore_unresolved) const
496 list<shared_ptr<Asset> > assets;
497 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
498 assets.push_back (i);
499 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
500 if (ignore_unresolved && !j->asset_ref().resolved()) {
503 shared_ptr<Asset> o = j->asset_ref().asset ();
504 assets.push_back (o);
505 /* More Interop special-casing */
506 shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
508 sub->add_font_assets (assets);
516 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
517 vector<boost::filesystem::path>
518 DCP::directories_from_files (vector<boost::filesystem::path> files)
520 vector<boost::filesystem::path> d;
521 BOOST_FOREACH (boost::filesystem::path i, files) {
522 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
523 d.push_back (i.parent_path ());