2 Copyright (C) 2012-2021 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.
40 #include "asset_factory.h"
41 #include "atmos_asset.h"
42 #include "certificate_chain.h"
43 #include "compose.hpp"
46 #include "dcp_assert.h"
47 #include "decrypted_kdm.h"
48 #include "decrypted_kdm_key.h"
49 #include "exceptions.h"
50 #include "font_asset.h"
51 #include "interop_subtitle_asset.h"
53 #include "mono_picture_asset.h"
54 #include "picture_asset.h"
56 #include "raw_convert.h"
57 #include "reel_asset.h"
58 #include "reel_subtitle_asset.h"
59 #include "smpte_subtitle_asset.h"
60 #include "sound_asset.h"
61 #include "stereo_picture_asset.h"
65 LIBDCP_DISABLE_WARNINGS
66 #include <asdcp/AS_DCP.h>
67 LIBDCP_ENABLE_WARNINGS
68 #include <xmlsec/xmldsig.h>
69 #include <xmlsec/app.h>
70 LIBDCP_DISABLE_WARNINGS
71 #include <libxml++/libxml++.h>
72 LIBDCP_ENABLE_WARNINGS
73 #include <boost/algorithm/string.hpp>
74 #include <boost/filesystem.hpp>
80 using std::dynamic_pointer_cast;
84 using std::make_shared;
86 using std::shared_ptr;
89 using boost::algorithm::starts_with;
90 using boost::optional;
94 static string const volindex_interop_ns = "http://www.digicine.com/PROTO-ASDCP-VL-20040311#";
95 static string const volindex_smpte_ns = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
98 DCP::DCP (boost::filesystem::path directory)
99 : _directory (directory)
101 if (!boost::filesystem::exists (directory)) {
102 boost::filesystem::create_directories (directory);
105 _directory = boost::filesystem::canonical (_directory);
110 DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_mxf_type)
112 /* Read the ASSETMAP and PKL */
114 boost::filesystem::path asset_map_path;
115 if (boost::filesystem::exists(_directory / "ASSETMAP")) {
116 asset_map_path = _directory / "ASSETMAP";
117 } else if (boost::filesystem::exists(_directory / "ASSETMAP.xml")) {
118 asset_map_path = _directory / "ASSETMAP.xml";
120 boost::throw_exception(MissingAssetmapError(_directory));
123 _asset_map = AssetMap(asset_map_path);
124 auto const pkl_paths = _asset_map->pkl_paths();
125 auto const standard = _asset_map->standard();
127 if (pkl_paths.empty()) {
128 boost::throw_exception (XMLError ("No packing lists found in asset map"));
131 for (auto i: pkl_paths) {
132 _pkls.push_back(make_shared<PKL>(i));
136 paths - map of files in the DCP that are not PKLs; key is ID, value is path.
137 _pkls - PKL objects for each PKL.
139 Read all the assets from the asset map.
142 /* Make a list of non-CPL/PKL assets so that we can resolve the references
145 vector<shared_ptr<Asset>> other_assets;
147 auto ids_and_paths = _asset_map->asset_ids_and_paths();
148 for (auto i: ids_and_paths) {
149 auto path = i.second;
151 if (path == _directory) {
152 /* I can't see how this is valid, but it's
153 been seen in the wild with a DCP that
154 claims to come from ClipsterDCI 5.10.0.5.
157 notes->push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::EMPTY_ASSET_PATH});
162 if (!boost::filesystem::exists(path)) {
164 notes->push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_ASSET, path});
169 /* Find the <Type> for this asset from the PKL that contains the asset */
170 optional<string> pkl_type;
171 for (auto j: _pkls) {
172 pkl_type = j->type(i.first);
179 /* This asset is in the ASSETMAP but not mentioned in any PKL so we don't
180 * need to worry about it.
185 auto remove_parameters = [](string const& n) {
186 return n.substr(0, n.find(";"));
189 /* Remove any optional parameters (after ;) */
190 pkl_type = pkl_type->substr(0, pkl_type->find(";"));
193 pkl_type == remove_parameters(CPL::static_pkl_type(standard)) ||
194 pkl_type == remove_parameters(InteropSubtitleAsset::static_pkl_type(standard))) {
195 auto p = new xmlpp::DomParser;
197 p->parse_file (path.string());
198 } catch (std::exception& e) {
200 throw ReadError(String::compose("XML error in %1", path.string()), e.what());
203 auto const root = p->get_document()->get_root_node()->get_name();
206 if (root == "CompositionPlaylist") {
207 auto cpl = make_shared<CPL>(path);
208 if (cpl->standard() != standard && notes) {
209 notes->push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_STANDARD});
211 _cpls.push_back (cpl);
212 } else if (root == "DCSubtitle") {
213 if (standard == Standard::SMPTE && notes) {
214 notes->push_back (VerificationNote(VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_STANDARD));
216 other_assets.push_back (make_shared<InteropSubtitleAsset>(path));
219 *pkl_type == remove_parameters(PictureAsset::static_pkl_type(standard)) ||
220 *pkl_type == remove_parameters(SoundAsset::static_pkl_type(standard)) ||
221 *pkl_type == remove_parameters(AtmosAsset::static_pkl_type(standard)) ||
222 *pkl_type == remove_parameters(SMPTESubtitleAsset::static_pkl_type(standard))
225 bool found_threed_marked_as_twod = false;
226 other_assets.push_back (asset_factory(path, ignore_incorrect_picture_mxf_type, &found_threed_marked_as_twod));
227 if (found_threed_marked_as_twod && notes) {
228 notes->push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, path});
230 } else if (*pkl_type == remove_parameters(FontAsset::static_pkl_type(standard))) {
231 other_assets.push_back (make_shared<FontAsset>(i.first, path));
232 } else if (*pkl_type == "image/png") {
233 /* It's an Interop PNG subtitle; let it go */
235 throw ReadError (String::compose("Unknown asset type %1 in PKL", *pkl_type));
239 resolve_refs (other_assets);
241 /* While we've got the ASSETMAP lets look and see if this DCP refers to things that are not in its ASSETMAP */
243 for (auto i: cpls()) {
244 for (auto j: i->reel_file_assets()) {
245 if (!j->asset_ref().resolved() && ids_and_paths.find(j->asset_ref().id()) == ids_and_paths.end()) {
246 notes->push_back (VerificationNote(VerificationNote::Type::WARNING, VerificationNote::Code::EXTERNAL_ASSET, j->asset_ref().id()));
255 DCP::resolve_refs (vector<shared_ptr<Asset>> assets)
257 for (auto i: cpls()) {
258 i->resolve_refs (assets);
264 DCP::equals (DCP const & other, EqualityOptions opt, NoteHandler note) const
267 auto b = other.cpls ();
269 if (a.size() != b.size()) {
270 note (NoteType::ERROR, String::compose ("CPL counts differ: %1 vs %2", a.size(), b.size()));
278 while (j != b.end() && !(*j)->equals (i, opt, note)) {
292 DCP::add (shared_ptr<CPL> cpl)
294 _cpls.push_back (cpl);
299 DCP::any_encrypted () const
301 for (auto i: cpls()) {
302 if (i->any_encrypted()) {
312 DCP::all_encrypted () const
314 for (auto i: cpls()) {
315 if (!i->all_encrypted()) {
325 DCP::add (DecryptedKDM const & kdm)
327 auto keys = kdm.keys();
328 for (auto cpl: cpls()) {
329 if (std::any_of(keys.begin(), keys.end(), [cpl](DecryptedKDMKey const& key) { return key.cpl_id() == cpl->id(); })) {
336 /** Write the VOLINDEX file.
337 * @param standard DCP standard to use (INTEROP or SMPTE)
340 DCP::write_volindex (Standard standard) const
344 case Standard::INTEROP:
347 case Standard::SMPTE:
355 xmlpp::Element* root;
358 case Standard::INTEROP:
359 root = doc.create_root_node ("VolumeIndex", volindex_interop_ns);
361 case Standard::SMPTE:
362 root = doc.create_root_node ("VolumeIndex", volindex_smpte_ns);
368 root->add_child("Index")->add_child_text ("1");
369 doc.write_to_file_formatted (p.string (), "UTF-8");
378 string annotation_text,
379 shared_ptr<const CertificateChain> signer,
380 NameFormat name_format
384 throw MiscError ("Cannot write DCP with no CPLs.");
387 auto standard = std::accumulate (
388 std::next(_cpls.begin()), _cpls.end(), _cpls[0]->standard(),
389 [](Standard s, shared_ptr<CPL> c) {
390 if (s != c->standard()) {
391 throw MiscError ("Cannot make DCP with mixed Interop and SMPTE CPLs.");
397 for (auto i: cpls()) {
398 NameFormat::Map values;
400 i->write_xml (_directory / (name_format.get(values, "_" + i->id() + ".xml")), signer);
404 _pkls.push_back(make_shared<PKL>(standard, annotation_text, issue_date, issuer, creator));
407 auto pkl = _pkls.front();
409 /* The assets may have changed since we read the PKL, so re-add them */
411 for (auto asset: assets()) {
412 asset->add_to_pkl(pkl, _directory);
415 NameFormat::Map values;
417 auto pkl_path = _directory / name_format.get(values, "_" + pkl->id() + ".xml");
418 pkl->write (pkl_path, signer);
421 _asset_map = AssetMap(standard, annotation_text, issue_date, issuer, creator);
424 /* The assets may have changed since we read the asset map, so re-add them */
425 _asset_map->clear_assets();
426 _asset_map->add_asset(pkl->id(), pkl_path, true);
427 for (auto asset: assets()) {
428 asset->add_to_assetmap(*_asset_map, _directory);
431 _asset_map->write_xml(
432 _directory / (standard == Standard::INTEROP ? "ASSETMAP" : "ASSETMAP.xml")
435 write_volindex (standard);
439 vector<shared_ptr<CPL>>
446 vector<shared_ptr<Asset>>
447 DCP::assets (bool ignore_unresolved) const
449 vector<shared_ptr<Asset>> assets;
450 for (auto i: cpls()) {
451 assets.push_back (i);
452 for (auto j: i->reel_file_assets()) {
453 if (ignore_unresolved && !j->asset_ref().resolved()) {
457 auto const id = j->asset_ref().id();
458 auto already_got = false;
459 for (auto k: assets) {
466 auto o = j->asset_ref().asset();
467 assets.push_back (o);
468 /* More Interop special-casing */
469 auto sub = dynamic_pointer_cast<InteropSubtitleAsset>(o);
471 sub->add_font_assets (assets);
481 /** Given a list of files that make up 1 or more DCPs, return the DCP directories */
482 vector<boost::filesystem::path>
483 DCP::directories_from_files (vector<boost::filesystem::path> files)
485 vector<boost::filesystem::path> d;
486 for (auto i: files) {
487 if (i.filename() == "ASSETMAP" || i.filename() == "ASSETMAP.xml") {
488 d.push_back (i.parent_path ());