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>
74 using boost::shared_ptr;
75 using boost::dynamic_pointer_cast;
76 using boost::algorithm::starts_with;
79 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
80 static string const assetmap_smpte_ns = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
81 static string const pkl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-PKL-20040311#";
82 static string const pkl_smpte_ns = "http://www.smpte-ra.org/schemas/429-8/2007/PKL";
83 static string const volindex_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
84 static string const volindex_smpte_ns = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
86 DCP::DCP (boost::filesystem::path directory)
87 : _directory (directory)
89 if (!boost::filesystem::exists (directory)) {
90 boost::filesystem::create_directories (directory);
93 _directory = boost::filesystem::canonical (_directory);
96 /** Call this instead of throwing an exception if the error can be tolerated */
97 template<class T> void
98 survivable_error (bool keep_going, dcp::DCP::ReadErrors* errors, T const & e)
102 errors->push_back (shared_ptr<T> (new T (e)));
110 DCP::read (bool keep_going, ReadErrors* errors, bool ignore_incorrect_picture_mxf_type)
112 /* Read the ASSETMAP */
114 boost::filesystem::path asset_map_file;
115 if (boost::filesystem::exists (_directory / "ASSETMAP")) {
116 asset_map_file = _directory / "ASSETMAP";
117 } else if (boost::filesystem::exists (_directory / "ASSETMAP.xml")) {
118 asset_map_file = _directory / "ASSETMAP.xml";
120 boost::throw_exception (DCPReadError (String::compose ("could not find AssetMap file in `%1'", _directory.string())));
123 cxml::Document asset_map ("AssetMap");
125 asset_map.read_file (asset_map_file);
126 if (asset_map.namespace_uri() == assetmap_interop_ns) {
128 } else if (asset_map.namespace_uri() == assetmap_smpte_ns) {
131 boost::throw_exception (XMLError ("Unrecognised Assetmap namespace " + asset_map.namespace_uri()));
134 list<shared_ptr<cxml::Node> > asset_nodes = asset_map.node_child("AssetList")->node_children ("Asset");
135 map<string, boost::filesystem::path> paths;
136 BOOST_FOREACH (shared_ptr<cxml::Node> i, asset_nodes) {
137 if (i->node_child("ChunkList")->node_children("Chunk").size() != 1) {
138 boost::throw_exception (XMLError ("unsupported asset chunk count"));
140 string p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
141 if (starts_with (p, "file://")) {
144 paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
147 /* Read all the assets from the asset map */
148 /* XXX: I think we should be looking at the PKL here to decide type, not
149 the extension of the file.
152 /* Make a list of non-CPL assets so that we can resolve the references
155 list<shared_ptr<Asset> > other_assets;
157 for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
158 boost::filesystem::path path = _directory / i->second;
160 if (!boost::filesystem::exists (path)) {
161 survivable_error (keep_going, errors, MissingAssetError (path));
165 if (boost::filesystem::extension (path) == ".xml") {
166 xmlpp::DomParser* p = new xmlpp::DomParser;
168 p->parse_file (path.string());
169 } catch (std::exception& e) {
174 string const root = p->get_document()->get_root_node()->get_name ();
177 if (root == "CompositionPlaylist") {
178 shared_ptr<CPL> cpl (new CPL (path));
179 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
180 survivable_error (keep_going, errors, MismatchedStandardError ());
182 _cpls.push_back (cpl);
183 } else if (root == "DCSubtitle") {
184 if (_standard && _standard.get() == SMPTE) {
185 survivable_error (keep_going, errors, MismatchedStandardError ());
187 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
189 } else if (boost::filesystem::extension (path) == ".mxf") {
191 /* XXX: asdcplib does not appear to support discovery of read MXFs standard
195 ASDCP::EssenceType_t type;
196 if (ASDCP::EssenceType (path.string().c_str(), type) != ASDCP::RESULT_OK) {
197 throw DCPReadError ("Could not find essence type");
200 case ASDCP::ESS_UNKNOWN:
201 case ASDCP::ESS_MPEG2_VES:
202 throw DCPReadError ("MPEG2 video essences are not supported");
203 case ASDCP::ESS_JPEG_2000:
205 other_assets.push_back (shared_ptr<MonoPictureAsset> (new MonoPictureAsset (path)));
206 } catch (dcp::MXFFileError& e) {
207 if (ignore_incorrect_picture_mxf_type && e.number() == ASDCP::RESULT_SFORMAT) {
208 /* Tried to load it as mono but the error says it's stereo; try that instead */
209 other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
215 case ASDCP::ESS_PCM_24b_48k:
216 case ASDCP::ESS_PCM_24b_96k:
217 other_assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (path)));
219 case ASDCP::ESS_JPEG_2000_S:
220 other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
222 case ASDCP::ESS_TIMED_TEXT:
223 other_assets.push_back (shared_ptr<SMPTESubtitleAsset> (new SMPTESubtitleAsset (path)));
226 throw DCPReadError ("Unknown MXF essence type");
228 } else if (boost::filesystem::extension (path) == ".ttf") {
229 other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
233 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
234 i->resolve_refs (other_assets);
239 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
241 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
242 i->resolve_refs (assets);
247 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
249 list<shared_ptr<CPL> > a = cpls ();
250 list<shared_ptr<CPL> > b = other.cpls ();
252 if (a.size() != b.size()) {
253 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
259 BOOST_FOREACH (shared_ptr<CPL> i, a) {
260 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
261 while (j != b.end() && !(*j)->equals (i, opt, note)) {
274 DCP::add (boost::shared_ptr<CPL> cpl)
276 _cpls.push_back (cpl);
280 DCP::encrypted () const
282 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
283 if (i->encrypted ()) {
292 DCP::add (DecryptedKDM const & kdm)
294 list<DecryptedKDMKey> keys = kdm.keys ();
296 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
297 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
298 if (j.cpl_id() == i->id()) {
305 boost::filesystem::path
306 DCP::write_pkl (Standard standard, string pkl_uuid, XMLMetadata metadata, shared_ptr<const CertificateChain> signer) const
308 boost::filesystem::path p = _directory;
309 p /= String::compose ("pkl_%1.xml", pkl_uuid);
313 if (standard == INTEROP) {
314 pkl = doc.create_root_node("PackingList", pkl_interop_ns);
316 pkl = doc.create_root_node("PackingList", pkl_smpte_ns);
320 pkl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
323 pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
325 /* XXX: this is a bit of a hack */
326 DCP_ASSERT (cpls().size() > 0);
327 pkl->add_child("AnnotationText")->add_child_text (cpls().front()->annotation_text ());
329 pkl->add_child("IssueDate")->add_child_text (metadata.issue_date);
330 pkl->add_child("Issuer")->add_child_text (metadata.issuer);
331 pkl->add_child("Creator")->add_child_text (metadata.creator);
333 xmlpp::Element* asset_list = pkl->add_child("AssetList");
334 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
335 i->write_to_pkl (asset_list, _directory, standard);
339 signer->sign (pkl, standard);
342 doc.write_to_file (p.string (), "UTF-8");
346 /** Write the VOLINDEX file.
347 * @param standard DCP standard to use (INTEROP or SMPTE)
350 DCP::write_volindex (Standard standard) const
352 boost::filesystem::path p = _directory;
365 xmlpp::Element* root;
369 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
372 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
378 root->add_child("Index")->add_child_text ("1");
379 doc.write_to_file (p.string (), "UTF-8");
383 DCP::write_assetmap (Standard standard, string pkl_uuid, int pkl_length, XMLMetadata metadata) const
385 boost::filesystem::path p = _directory;
399 xmlpp::Element* root;
403 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
406 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
412 root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
413 root->add_child("AnnotationText")->add_child_text ("Created by " + metadata.creator);
417 root->add_child("VolumeCount")->add_child_text ("1");
418 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
419 root->add_child("Issuer")->add_child_text (metadata.issuer);
420 root->add_child("Creator")->add_child_text (metadata.creator);
423 root->add_child("Creator")->add_child_text (metadata.creator);
424 root->add_child("VolumeCount")->add_child_text ("1");
425 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
426 root->add_child("Issuer")->add_child_text (metadata.issuer);
432 xmlpp::Node* asset_list = root->add_child ("AssetList");
434 xmlpp::Node* asset = asset_list->add_child ("Asset");
435 asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
436 asset->add_child("PackingList")->add_child_text ("true");
437 xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
438 xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
439 chunk->add_child("Path")->add_child_text ("pkl_" + pkl_uuid + ".xml");
440 chunk->add_child("VolumeIndex")->add_child_text ("1");
441 chunk->add_child("Offset")->add_child_text ("0");
442 chunk->add_child("Length")->add_child_text (raw_convert<string> (pkl_length));
444 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
445 i->write_to_assetmap (asset_list, _directory);
448 /* This must not be the _formatted version otherwise signature digests will be wrong */
449 doc.write_to_file (p.string (), "UTF-8");
452 /** Write all the XML files for this DCP.
453 * @param standand INTEROP or SMPTE.
454 * @param metadata Metadata to use for PKL and asset map files.
455 * @param signer Signer to use, or 0.
460 XMLMetadata metadata,
461 shared_ptr<const CertificateChain> signer
464 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
465 string const filename = "cpl_" + i->id() + ".xml";
466 i->write_xml (_directory / filename, standard, signer);
469 string const pkl_uuid = make_uuid ();
470 boost::filesystem::path const pkl_path = write_pkl (standard, pkl_uuid, metadata, signer);
472 write_volindex (standard);
473 write_assetmap (standard, pkl_uuid, boost::filesystem::file_size (pkl_path), metadata);
476 list<shared_ptr<CPL> >
482 /** @return All assets (including CPLs) */
483 list<shared_ptr<Asset> >
486 list<shared_ptr<Asset> > assets;
487 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
488 assets.push_back (i);
489 BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) {
490 shared_ptr<Asset> o = j->asset_ref().asset ();
491 assets.push_back (o);
492 /* More Interop special-casing */
493 shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o);
495 sub->add_font_assets (assets);