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 "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"
49 #include "exceptions.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>
75 using boost::shared_ptr;
76 using boost::dynamic_pointer_cast;
77 using boost::algorithm::starts_with;
80 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
81 static string const assetmap_smpte_ns = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
82 static string const pkl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-PKL-20040311#";
83 static string const pkl_smpte_ns = "http://www.smpte-ra.org/schemas/429-8/2007/PKL";
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 BOOST_FOREACH (shared_ptr<cxml::Node> i, asset_nodes) {
138 if (i->node_child("ChunkList")->node_children("Chunk").size() != 1) {
139 boost::throw_exception (XMLError ("unsupported asset chunk count"));
141 string p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
142 if (starts_with (p, "file://")) {
145 paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
148 /* Read all the assets from the asset map */
149 /* XXX: I think we should be looking at the PKL here to decide type, not
150 the extension of the file.
153 /* Make a list of non-CPL assets so that we can resolve the references
156 list<shared_ptr<Asset> > other_assets;
158 for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
159 boost::filesystem::path path = _directory / i->second;
161 if (!boost::filesystem::exists (path)) {
162 survivable_error (keep_going, errors, MissingAssetError (path));
166 if (boost::filesystem::extension (path) == ".xml") {
167 xmlpp::DomParser* p = new xmlpp::DomParser;
169 p->parse_file (path.string());
170 } catch (std::exception& e) {
175 string const root = p->get_document()->get_root_node()->get_name ();
178 if (root == "CompositionPlaylist") {
179 shared_ptr<CPL> cpl (new CPL (path));
180 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
181 survivable_error (keep_going, errors, MismatchedStandardError ());
183 _cpls.push_back (cpl);
184 } else if (root == "DCSubtitle") {
185 if (_standard && _standard.get() == SMPTE) {
186 survivable_error (keep_going, errors, MismatchedStandardError ());
188 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
190 } else if (boost::filesystem::extension (path) == ".mxf") {
192 /* XXX: asdcplib does not appear to support discovery of read MXFs standard
196 ASDCP::EssenceType_t type;
197 if (ASDCP::EssenceType (path.string().c_str(), type) != ASDCP::RESULT_OK) {
198 throw DCPReadError ("Could not find essence type");
201 case ASDCP::ESS_UNKNOWN:
202 case ASDCP::ESS_MPEG2_VES:
203 throw DCPReadError ("MPEG2 video essences are not supported");
204 case ASDCP::ESS_JPEG_2000:
206 other_assets.push_back (shared_ptr<MonoPictureAsset> (new MonoPictureAsset (path)));
207 } catch (dcp::MXFFileError& e) {
208 if (ignore_incorrect_picture_mxf_type && e.number() == ASDCP::RESULT_SFORMAT) {
209 /* Tried to load it as mono but the error says it's stereo; try that instead */
210 other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
216 case ASDCP::ESS_PCM_24b_48k:
217 case ASDCP::ESS_PCM_24b_96k:
218 other_assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (path)));
220 case ASDCP::ESS_JPEG_2000_S:
221 other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
223 case ASDCP::ESS_TIMED_TEXT:
224 other_assets.push_back (shared_ptr<SMPTESubtitleAsset> (new SMPTESubtitleAsset (path)));
227 throw DCPReadError (String::compose ("Unknown MXF essence type %1 in %2", int(type), path.string()));
229 } else if (boost::filesystem::extension (path) == ".ttf") {
230 other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
234 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
235 i->resolve_refs (other_assets);
240 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
242 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
243 i->resolve_refs (assets);
248 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
250 list<shared_ptr<CPL> > a = cpls ();
251 list<shared_ptr<CPL> > b = other.cpls ();
253 if (a.size() != b.size()) {
254 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
260 BOOST_FOREACH (shared_ptr<CPL> i, a) {
261 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
262 while (j != b.end() && !(*j)->equals (i, opt, note)) {
275 DCP::add (boost::shared_ptr<CPL> cpl)
277 _cpls.push_back (cpl);
281 DCP::encrypted () const
283 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
284 if (i->encrypted ()) {
293 DCP::add (DecryptedKDM const & kdm)
295 list<DecryptedKDMKey> keys = kdm.keys ();
297 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
298 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
299 if (j.cpl_id() == i->id()) {
306 /** @return full pathname of PKL file that was written */
307 boost::filesystem::path
308 DCP::write_pkl (string file, Standard standard, string pkl_uuid, XMLMetadata metadata, shared_ptr<const CertificateChain> signer) const
310 boost::filesystem::path p = _directory;
315 if (standard == INTEROP) {
316 pkl = doc.create_root_node("PackingList", pkl_interop_ns);
318 pkl = doc.create_root_node("PackingList", pkl_smpte_ns);
322 pkl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
325 pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
326 pkl->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
327 pkl->add_child("IssueDate")->add_child_text (metadata.issue_date);
328 pkl->add_child("Issuer")->add_child_text (metadata.issuer);
329 pkl->add_child("Creator")->add_child_text (metadata.creator);
331 xmlpp::Element* asset_list = pkl->add_child("AssetList");
332 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
333 i->write_to_pkl (asset_list, _directory, standard);
337 signer->sign (pkl, standard);
340 doc.write_to_file (p.string (), "UTF-8");
344 /** Write the VOLINDEX file.
345 * @param standard DCP standard to use (INTEROP or SMPTE)
348 DCP::write_volindex (Standard standard) const
350 boost::filesystem::path p = _directory;
363 xmlpp::Element* root;
367 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
370 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
376 root->add_child("Index")->add_child_text ("1");
377 doc.write_to_file (p.string (), "UTF-8");
381 DCP::write_assetmap (Standard standard, string pkl_uuid, boost::filesystem::path pkl_path, XMLMetadata metadata) const
383 boost::filesystem::path p = _directory;
397 xmlpp::Element* root;
401 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
404 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
410 root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
411 root->add_child("AnnotationText")->add_child_text (metadata.annotation_text);
415 root->add_child("VolumeCount")->add_child_text ("1");
416 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
417 root->add_child("Issuer")->add_child_text (metadata.issuer);
418 root->add_child("Creator")->add_child_text (metadata.creator);
421 root->add_child("Creator")->add_child_text (metadata.creator);
422 root->add_child("VolumeCount")->add_child_text ("1");
423 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
424 root->add_child("Issuer")->add_child_text (metadata.issuer);
430 xmlpp::Node* asset_list = root->add_child ("AssetList");
432 xmlpp::Node* asset = asset_list->add_child ("Asset");
433 asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
434 asset->add_child("PackingList")->add_child_text ("true");
435 xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
436 xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
437 chunk->add_child("Path")->add_child_text (pkl_path.filename().string());
438 chunk->add_child("VolumeIndex")->add_child_text ("1");
439 chunk->add_child("Offset")->add_child_text ("0");
440 chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
442 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
443 i->write_to_assetmap (asset_list, _directory);
446 /* This must not be the _formatted version otherwise signature digests will be wrong */
447 doc.write_to_file (p.string (), "UTF-8");
450 /** Write all the XML files for this DCP.
451 * @param standand INTEROP or SMPTE.
452 * @param metadata Metadata to use for PKL and asset map files.
453 * @param signer Signer to use, or 0.
458 XMLMetadata metadata,
459 shared_ptr<const CertificateChain> signer,
460 NameFormat name_format
463 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
464 NameFormat::Map values;
466 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), standard, signer);
469 string const pkl_uuid = make_uuid ();
470 NameFormat::Map values;
472 boost::filesystem::path const pkl_path = write_pkl (name_format.get(values, "_" + pkl_uuid + ".xml"), standard, pkl_uuid, metadata, signer);
474 write_volindex (standard);
475 write_assetmap (standard, pkl_uuid, pkl_path, metadata);
478 list<shared_ptr<CPL> >
484 /** @return All assets (including CPLs) */
485 list<shared_ptr<Asset> >
488 list<shared_ptr<Asset> > assets;
489 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
490 assets.push_back (i);
491 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
492 shared_ptr<Asset> o = j->asset_ref().asset ();
493 assets.push_back (o);
494 /* More Interop special-casing */
495 shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
497 sub->add_font_assets (assets);
505 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
506 vector<boost::filesystem::path>
507 DCP::directories_from_files (vector<boost::filesystem::path> files)
509 vector<boost::filesystem::path> d;
510 BOOST_FOREACH (boost::filesystem::path i, files) {
511 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
512 d.push_back (i.parent_path ());