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>
76 using boost::shared_ptr;
77 using boost::dynamic_pointer_cast;
78 using boost::algorithm::starts_with;
81 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
82 static string const assetmap_smpte_ns = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
83 static string const pkl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-PKL-20040311#";
84 static string const pkl_smpte_ns = "http://www.smpte-ra.org/schemas/429-8/2007/PKL";
85 static string const volindex_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-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 */
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 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));
149 /* Read all the assets from the asset map */
150 /* XXX: I think we should be looking at the PKL here to decide type, not
151 the extension of the file.
154 /* Make a list of non-CPL assets so that we can resolve the references
157 list<shared_ptr<Asset> > other_assets;
159 for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
160 boost::filesystem::path path = _directory / i->second;
162 if (!boost::filesystem::exists (path)) {
163 survivable_error (keep_going, errors, MissingAssetError (path));
167 if (boost::filesystem::extension (path) == ".xml") {
168 xmlpp::DomParser* p = new xmlpp::DomParser;
170 p->parse_file (path.string());
171 } catch (std::exception& e) {
176 string const root = p->get_document()->get_root_node()->get_name ();
179 if (root == "CompositionPlaylist") {
180 shared_ptr<CPL> cpl (new CPL (path));
181 if (_standard && cpl->standard() && cpl->standard().get() != _standard.get()) {
182 survivable_error (keep_going, errors, MismatchedStandardError ());
184 _cpls.push_back (cpl);
185 } else if (root == "DCSubtitle") {
186 if (_standard && _standard.get() == SMPTE) {
187 survivable_error (keep_going, errors, MismatchedStandardError ());
189 other_assets.push_back (shared_ptr<InteropSubtitleAsset> (new InteropSubtitleAsset (path)));
191 } else if (boost::filesystem::extension (path) == ".mxf") {
193 /* XXX: asdcplib does not appear to support discovery of read MXFs standard
197 ASDCP::EssenceType_t type;
198 if (ASDCP::EssenceType (path.string().c_str(), type) != ASDCP::RESULT_OK) {
199 throw DCPReadError ("Could not find essence type");
202 case ASDCP::ESS_UNKNOWN:
203 case ASDCP::ESS_MPEG2_VES:
204 throw DCPReadError ("MPEG2 video essences are not supported");
205 case ASDCP::ESS_JPEG_2000:
207 other_assets.push_back (shared_ptr<MonoPictureAsset> (new MonoPictureAsset (path)));
208 } catch (dcp::MXFFileError& e) {
209 if (ignore_incorrect_picture_mxf_type && e.number() == ASDCP::RESULT_SFORMAT) {
210 /* Tried to load it as mono but the error says it's stereo; try that instead */
211 other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
217 case ASDCP::ESS_PCM_24b_48k:
218 case ASDCP::ESS_PCM_24b_96k:
219 other_assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (path)));
221 case ASDCP::ESS_JPEG_2000_S:
222 other_assets.push_back (shared_ptr<StereoPictureAsset> (new StereoPictureAsset (path)));
224 case ASDCP::ESS_TIMED_TEXT:
225 other_assets.push_back (shared_ptr<SMPTESubtitleAsset> (new SMPTESubtitleAsset (path)));
228 throw DCPReadError ("Unknown MXF essence type");
230 } else if (boost::filesystem::extension (path) == ".ttf") {
231 other_assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
235 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
236 i->resolve_refs (other_assets);
241 DCP::resolve_refs (list<shared_ptr<Asset> > assets)
243 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
244 i->resolve_refs (assets);
249 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
251 list<shared_ptr<CPL> > a = cpls ();
252 list<shared_ptr<CPL> > b = other.cpls ();
254 if (a.size() != b.size()) {
255 note (DCP_ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
261 BOOST_FOREACH (shared_ptr<CPL> i, a) {
262 list<shared_ptr<CPL> >::const_iterator j = b.begin ();
263 while (j != b.end() && !(*j)->equals (i, opt, note)) {
276 DCP::add (boost::shared_ptr<CPL> cpl)
278 _cpls.push_back (cpl);
282 DCP::encrypted () const
284 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
285 if (i->encrypted ()) {
294 DCP::add (DecryptedKDM const & kdm)
296 list<DecryptedKDMKey> keys = kdm.keys ();
298 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
299 BOOST_FOREACH (DecryptedKDMKey const & j, kdm.keys ()) {
300 if (j.cpl_id() == i->id()) {
307 boost::filesystem::path
308 DCP::write_pkl (Standard standard, string pkl_uuid, XMLMetadata metadata, shared_ptr<const CertificateChain> signer) const
310 boost::filesystem::path p = _directory;
311 p /= String::compose ("pkl_%1.xml", pkl_uuid);
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);
327 /* XXX: this is a bit of a hack */
328 DCP_ASSERT (cpls().size() > 0);
329 pkl->add_child("AnnotationText")->add_child_text (cpls().front()->annotation_text ());
331 pkl->add_child("IssueDate")->add_child_text (metadata.issue_date);
332 pkl->add_child("Issuer")->add_child_text (metadata.issuer);
333 pkl->add_child("Creator")->add_child_text (metadata.creator);
335 xmlpp::Element* asset_list = pkl->add_child("AssetList");
336 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
337 i->write_to_pkl (asset_list, _directory, standard);
341 signer->sign (pkl, standard);
344 doc.write_to_file (p.string (), "UTF-8");
348 /** Write the VOLINDEX file.
349 * @param standard DCP standard to use (INTEROP or SMPTE)
352 DCP::write_volindex (Standard standard) const
354 boost::filesystem::path p = _directory;
367 xmlpp::Element* root;
371 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
374 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
380 root->add_child("Index")->add_child_text ("1");
381 doc.write_to_file (p.string (), "UTF-8");
385 DCP::write_assetmap (Standard standard, string pkl_uuid, int pkl_length, XMLMetadata metadata) const
387 boost::filesystem::path p = _directory;
401 xmlpp::Element* root;
405 root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
408 root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
414 root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
415 root->add_child("AnnotationText")->add_child_text ("Created by " + metadata.creator);
419 root->add_child("VolumeCount")->add_child_text ("1");
420 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
421 root->add_child("Issuer")->add_child_text (metadata.issuer);
422 root->add_child("Creator")->add_child_text (metadata.creator);
425 root->add_child("Creator")->add_child_text (metadata.creator);
426 root->add_child("VolumeCount")->add_child_text ("1");
427 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
428 root->add_child("Issuer")->add_child_text (metadata.issuer);
434 xmlpp::Node* asset_list = root->add_child ("AssetList");
436 xmlpp::Node* asset = asset_list->add_child ("Asset");
437 asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
438 asset->add_child("PackingList")->add_child_text ("true");
439 xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
440 xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
441 chunk->add_child("Path")->add_child_text ("pkl_" + pkl_uuid + ".xml");
442 chunk->add_child("VolumeIndex")->add_child_text ("1");
443 chunk->add_child("Offset")->add_child_text ("0");
444 chunk->add_child("Length")->add_child_text (raw_convert<string> (pkl_length));
446 BOOST_FOREACH (shared_ptr<Asset> i, assets ()) {
447 i->write_to_assetmap (asset_list, _directory);
450 /* This must not be the _formatted version otherwise signature digests will be wrong */
451 doc.write_to_file (p.string (), "UTF-8");
454 /** Write all the XML files for this DCP.
455 * @param standand INTEROP or SMPTE.
456 * @param metadata Metadata to use for PKL and asset map files.
457 * @param signer Signer to use, or 0.
462 XMLMetadata metadata,
463 shared_ptr<const CertificateChain> signer
466 BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) {
467 string const filename = "cpl_" + i->id() + ".xml";
468 i->write_xml (_directory / filename, standard, signer);
471 string const pkl_uuid = make_uuid ();
472 boost::filesystem::path const pkl_path = write_pkl (standard, pkl_uuid, metadata, signer);
474 write_volindex (standard);
475 write_assetmap (standard, pkl_uuid, boost::filesystem::file_size (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);