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 <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>
76 using boost::shared_ptr;
77 using boost::dynamic_pointer_cast;
78 using boost::optional;
79 using boost::algorithm::starts_with;
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";
87 DCP::DCP (boost::filesystem::path directory)
88 : _directory (directory)
90 if (!boost::filesystem::exists (directory)) {
91 boost::filesystem::create_directories (directory);
94 _directory = boost::filesystem::canonical (_directory);
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)
103 errors->push_back (shared_ptr<T> (new T (e)));
111 DCP::read (bool keep_going, ReadErrors* errors, bool ignore_incorrect_picture_mxf_type)
113 /* Read the ASSETMAP */
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";
121 boost::throw_exception (DCPReadError (String::compose ("could not find AssetMap file in `%1'", _directory.string())));
124 cxml::Document asset_map ("AssetMap");
126 asset_map.read_file (asset_map_file);
127 if (asset_map.namespace_uri() == assetmap_interop_ns) {
129 } else if (asset_map.namespace_uri() == assetmap_smpte_ns) {
132 boost::throw_exception (XMLError ("Unrecognised Assetmap namespace " + asset_map.namespace_uri()));
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"));
142 string p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
143 if (starts_with (p, "file://")) {
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") {
154 boost::throw_exception (XMLError ("No packing list found in asset map"));
157 _pkl.reset (new PKL (_directory / *pkl_path));
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.
164 /* Make a list of non-CPL assets so that we can resolve the references
167 list<shared_ptr<Asset> > other_assets;
169 for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
170 boost::filesystem::path path = _directory / i->second;
172 if (!boost::filesystem::exists (path)) {
173 survivable_error (keep_going, errors, MissingAssetError (path));
177 if (boost::filesystem::extension (path) == ".xml") {
178 xmlpp::DomParser* p = new xmlpp::DomParser;
180 p->parse_file (path.string());
181 } catch (std::exception& e) {
183 throw DCPReadError(String::compose("XML error in %1", path.string()), e.what());
186 string const root = p->get_document()->get_root_node()->get_name ();
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 ());
194 _cpls.push_back (cpl);
195 } else if (root == "DCSubtitle") {
196 if (_standard && _standard.get() == SMPTE) {
197 survivable_error (keep_going, errors, MismatchedStandardError ());
199 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
201 } else if (boost::filesystem::extension (path) == ".mxf") {
203 /* XXX: asdcplib does not appear to support discovery of read MXFs standard
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");
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:
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)));
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)));
231 case ASDCP::ESS_JPEG_2000_S:
232 other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
234 case ASDCP::ESS_TIMED_TEXT:
235 other_assets.push_back (shared_ptr<SMPTESubtitleAsset> (new SMPTESubtitleAsset (path)));
237 case ASDCP::ESS_DCDATA_DOLBY_ATMOS:
238 other_assets.push_back (shared_ptr<AtmosAsset> (new AtmosAsset (path)));
241 throw DCPReadError (String::compose ("Unknown MXF essence type %1 in %2", int(type), path.string()));
243 } else if (boost::filesystem::extension (path) == ".ttf") {
244 other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
248 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
249 i->resolve_refs (other_assets);
254 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
256 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
257 i->resolve_refs (assets);
262 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
264 list<shared_ptr<CPL> > a = cpls ();
265 list<shared_ptr<CPL> > b = other.cpls ();
267 if (a.size() != b.size()) {
268 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
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)) {
289 DCP::add (boost::shared_ptr<CPL> cpl)
291 _cpls.push_back (cpl);
295 DCP::encrypted () const
297 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
298 if (i->encrypted ()) {
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.
311 DCP::add (DecryptedKDM const & kdm)
313 list<DecryptedKDMKey> keys = kdm.keys ();
315 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
316 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
317 if (j.cpl_id() == i->id()) {
324 /** Write the VOLINDEX file.
325 * @param standard DCP standard to use (INTEROP or SMPTE)
328 DCP::write_volindex (Standard standard) const
330 boost::filesystem::path p = _directory;
343 xmlpp::Element* root;
347 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
350 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
356 root->add_child("Index")->add_child_text ("1");
357 doc.write_to_file (p.string (), "UTF-8");
361 DCP::write_assetmap (Standard standard, string pkl_uuid, boost::filesystem::path pkl_path, XMLMetadata metadata) const
363 boost::filesystem::path p = _directory;
377 xmlpp::Element* root;
381 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
384 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
390 root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
391 root->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
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);
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);
410 xmlpp::Node* asset_list = root->add_child ("AssetList");
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)));
422 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
423 i->write_to_assetmap (asset_list, _directory);
426 /* This must not be the _formatted version otherwise signature digests will be wrong */
427 doc.write_to_file (p.string (), "UTF-8");
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.
438 XMLMetadata metadata,
439 shared_ptr<const CertificateChain> signer,
440 NameFormat name_format
443 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
444 NameFormat::Map values;
446 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
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);
456 NameFormat::Map values;
458 boost::filesystem::path pkl_path = _directory / name_format.get(values, "_" + _pkl->id() + ".xml");
459 _pkl->write (pkl_path, signer);
461 write_volindex (standard);
462 write_assetmap (standard, _pkl->id(), pkl_path, metadata);
465 list<shared_ptr<CPL> >
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).
475 list<shared_ptr<Asset> >
476 DCP::assets (bool ignore_unresolved) const
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()) {
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);
490 sub->add_font_assets (assets);
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)
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 ());