43 #include "compose.hpp"
53 #include "mono_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/filesystem.hpp>
74 #include <boost/algorithm/string.hpp>
85 using std::make_shared;
87 using std::shared_ptr;
88 using std::dynamic_pointer_cast;
89 using boost::optional;
90 using boost::algorithm::starts_with;
94 static string const assetmap_interop_ns =
"http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
95 static string const assetmap_smpte_ns =
"http://www.smpte-ra.org/schemas/429-9/2007/AM";
96 static string const volindex_interop_ns =
"http://www.digicine.com/PROTO-ASDCP-VL-20040311#";
97 static string const volindex_smpte_ns =
"http://www.smpte-ra.org/schemas/429-9/2007/AM";
101 : _directory (directory)
103 if (!boost::filesystem::exists (directory)) {
104 boost::filesystem::create_directories (directory);
112 DCP::read (vector<dcp::VerificationNote>* notes,
bool ignore_incorrect_picture_mxf_type)
116 if (boost::filesystem::exists (
_directory /
"ASSETMAP")) {
118 }
else if (boost::filesystem::exists (
_directory /
"ASSETMAP.xml")) {
124 cxml::Document asset_map (
"AssetMap");
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 auto asset_nodes = asset_map.node_child(
"AssetList")->node_children (
"Asset");
136 map<string, boost::filesystem::path> paths;
137 vector<boost::filesystem::path> pkl_paths;
138 for (
auto i: asset_nodes) {
139 if (i->node_child(
"ChunkList")->node_children(
"Chunk").size() != 1) {
140 boost::throw_exception (
XMLError (
"unsupported asset chunk count"));
142 auto p = i->node_child(
"ChunkList")->node_child(
"Chunk")->string_child (
"Path");
143 if (starts_with (p,
"file://")) {
147 case Standard::INTEROP:
148 if (i->optional_node_child(
"PackingList")) {
149 pkl_paths.push_back (p);
151 paths.insert (make_pair(remove_urn_uuid(i->string_child(
"Id")), p));
154 case Standard::SMPTE:
156 auto pkl_bool = i->optional_string_child(
"PackingList");
157 if (pkl_bool && *pkl_bool ==
"true") {
158 pkl_paths.push_back (p);
160 paths.insert (make_pair(remove_urn_uuid(i->string_child(
"Id")), p));
167 if (pkl_paths.empty()) {
168 boost::throw_exception (
XMLError (
"No packing lists found in asset map"));
171 for (
auto i: pkl_paths) {
185 vector<shared_ptr<Asset>> other_assets;
187 for (
auto i: paths) {
190 if (i.second.empty()) {
201 if (!boost::filesystem::exists(path)) {
209 optional<string> pkl_type;
210 for (
auto j:
_pkls) {
211 pkl_type = j->type(i.first);
224 auto remove_parameters = [](
string const& n) {
225 return n.substr(0, n.find(
";"));
229 pkl_type = pkl_type->substr(0, pkl_type->find(
";"));
232 pkl_type == remove_parameters(CPL::static_pkl_type(*
_standard)) ||
233 pkl_type == remove_parameters(InteropSubtitleAsset::static_pkl_type(*
_standard))) {
234 auto p =
new xmlpp::DomParser;
236 p->parse_file (path.string());
237 }
catch (std::exception& e) {
239 throw ReadError(String::compose(
"XML error in %1", path.string()), e.what());
242 auto const root = p->get_document()->get_root_node()->get_name();
245 if (root ==
"CompositionPlaylist") {
246 auto cpl = make_shared<CPL>(path);
250 _cpls.push_back (cpl);
251 }
else if (root ==
"DCSubtitle") {
255 other_assets.push_back (make_shared<InteropSubtitleAsset>(path));
258 *pkl_type == remove_parameters(PictureAsset::static_pkl_type(*
_standard)) ||
259 *pkl_type == remove_parameters(SoundAsset::static_pkl_type(*
_standard)) ||
260 *pkl_type == remove_parameters(AtmosAsset::static_pkl_type(*
_standard)) ||
261 *pkl_type == remove_parameters(SMPTESubtitleAsset::static_pkl_type(*
_standard))
264 bool found_threed_marked_as_twod =
false;
265 other_assets.push_back (
asset_factory(path, ignore_incorrect_picture_mxf_type, &found_threed_marked_as_twod));
266 if (found_threed_marked_as_twod && notes) {
269 }
else if (*pkl_type == remove_parameters(FontAsset::static_pkl_type(*
_standard))) {
270 other_assets.push_back (make_shared<FontAsset>(i.first, path));
271 }
else if (*pkl_type ==
"image/png") {
274 throw ReadError (String::compose(
"Unknown asset type %1 in PKL", *pkl_type));
278 resolve_refs (other_assets);
282 for (
auto i: cpls()) {
283 for (
auto j: i->reel_file_assets()) {
284 if (!j->asset_ref().resolved() && paths.find(j->asset_ref().id()) == paths.end()) {
294 DCP::resolve_refs (vector<shared_ptr<Asset>> assets)
296 for (
auto i: cpls()) {
306 auto b = other.cpls ();
308 if (a.size() != b.size()) {
309 note (NoteType::ERROR, String::compose (
"CPL counts differ: %1 vs %2", a.size(), b.size()));
317 while (j != b.end() && !(*j)->equals (i, opt, note)) {
331 DCP::add (shared_ptr<CPL> cpl)
333 _cpls.push_back (cpl);
338 DCP::any_encrypted ()
const
340 for (
auto i: cpls()) {
341 if (i->any_encrypted()) {
351 DCP::all_encrypted ()
const
353 for (
auto i: cpls()) {
354 if (!i->all_encrypted()) {
366 auto keys = kdm.
keys ();
368 for (
auto i: cpls()) {
369 for (
auto const& j: kdm.
keys()) {
370 if (j.cpl_id() == i->id()) {
386 case Standard::INTEROP:
389 case Standard::SMPTE:
397 xmlpp::Element* root;
400 case Standard::INTEROP:
401 root = doc.create_root_node (
"VolumeIndex", volindex_interop_ns);
403 case Standard::SMPTE:
404 root = doc.create_root_node (
"VolumeIndex", volindex_smpte_ns);
410 root->add_child(
"Index")->add_child_text (
"1");
411 doc.write_to_file_formatted (p.string (),
"UTF-8");
417 Standard standard,
string pkl_uuid, boost::filesystem::path pkl_path,
418 string issuer,
string creator,
string issue_date,
string annotation_text
424 case Standard::INTEROP:
427 case Standard::SMPTE:
435 xmlpp::Element* root;
438 case Standard::INTEROP:
439 root = doc.create_root_node (
"AssetMap", assetmap_interop_ns);
441 case Standard::SMPTE:
442 root = doc.create_root_node (
"AssetMap", assetmap_smpte_ns);
448 root->add_child(
"Id")->add_child_text (
"urn:uuid:" + make_uuid());
449 root->add_child(
"AnnotationText")->add_child_text (annotation_text);
452 case Standard::INTEROP:
453 root->add_child(
"VolumeCount")->add_child_text (
"1");
454 root->add_child(
"IssueDate")->add_child_text (issue_date);
455 root->add_child(
"Issuer")->add_child_text (issuer);
456 root->add_child(
"Creator")->add_child_text (creator);
458 case Standard::SMPTE:
459 root->add_child(
"Creator")->add_child_text (creator);
460 root->add_child(
"VolumeCount")->add_child_text (
"1");
461 root->add_child(
"IssueDate")->add_child_text (issue_date);
462 root->add_child(
"Issuer")->add_child_text (issuer);
468 auto asset_list = root->add_child (
"AssetList");
470 auto asset = asset_list->add_child (
"Asset");
471 asset->add_child(
"Id")->add_child_text (
"urn:uuid:" + pkl_uuid);
472 asset->add_child(
"PackingList")->add_child_text (
"true");
473 auto chunk_list = asset->add_child (
"ChunkList");
474 auto chunk = chunk_list->add_child (
"Chunk");
475 chunk->add_child(
"Path")->add_child_text (pkl_path.filename().string());
476 chunk->add_child(
"VolumeIndex")->add_child_text (
"1");
477 chunk->add_child(
"Offset")->add_child_text (
"0");
478 chunk->add_child(
"Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
481 i->write_to_assetmap (asset_list,
_directory);
484 doc.write_to_file_formatted (p.string (),
"UTF-8");
494 string annotation_text,
495 shared_ptr<const CertificateChain> signer,
500 throw MiscError (
"Cannot write DCP with no CPLs.");
505 [](Standard s, shared_ptr<CPL> c) {
506 if (s != c->standard()) {
507 throw MiscError (
"Cannot make DCP with mixed Interop and SMPTE CPLs.");
513 for (
auto i: cpls()) {
514 NameFormat::Map values;
516 i->write_xml (_directory / (name_format.get(values,
"_" + i->id() +
".xml")), signer);
522 pkl = make_shared<PKL>(standard, annotation_text, issue_date, issuer, creator);
523 _pkls.push_back (pkl);
524 for (
auto i: assets()) {
525 i->add_to_pkl (pkl, _directory);
528 pkl = _pkls.front ();
531 NameFormat::Map values;
533 auto pkl_path = _directory / name_format.get(values,
"_" + pkl->id() +
".xml");
534 pkl->write (pkl_path, signer);
536 write_volindex (standard);
537 write_assetmap (standard, pkl->id(), pkl_path, issuer, creator, issue_date, annotation_text);
541 vector<shared_ptr<CPL>>
548 vector<shared_ptr<Asset>>
551 vector<shared_ptr<Asset>>
assets;
552 for (
auto i: cpls()) {
554 for (
auto j: i->reel_file_assets()) {
555 if (ignore_unresolved && !j->asset_ref().resolved()) {
559 auto const id = j->asset_ref().id();
560 auto already_got =
false;
568 auto o = j->asset_ref().asset();
571 auto sub = dynamic_pointer_cast<InteropSubtitleAsset>(o);
573 sub->add_font_assets (
assets);
584 vector<boost::filesystem::path>
587 vector<boost::filesystem::path> d;
588 for (
auto i: files) {
589 if (i.filename() ==
"ASSETMAP" || i.filename() ==
"ASSETMAP.xml") {
590 d.push_back (i.parent_path ());
A class to create or read a DCP.
DCP(boost::filesystem::path directory)
void write_xml(std::string issuer=String::compose("libdcp %1", dcp::version), std::string creator=String::compose("libdcp %1", dcp::version), std::string issue_date=LocalTime().as_string(), std::string annotation_text=String::compose("Created by libdcp %1", dcp::version), std::shared_ptr< const CertificateChain > signer=std::shared_ptr< const CertificateChain >(), NameFormat name_format=NameFormat("%t"))
bool equals(DCP const &other, EqualityOptions options, NoteHandler note) const
static std::vector< boost::filesystem::path > directories_from_files(std::vector< boost::filesystem::path > files)
std::vector< std::shared_ptr< Asset > > assets(bool ignore_unresolved=false) const
std::vector< std::shared_ptr< PKL > > _pkls
boost::filesystem::path _directory
void write_assetmap(Standard standard, std::string pkl_uuid, boost::filesystem::path pkl_path, std::string issuer, std::string creator, std::string issue_date, std::string annotation_text) const
std::vector< std::shared_ptr< CPL > > _cpls
boost::optional< Standard > standard() const
void read(std::vector< VerificationNote > *notes=nullptr, bool ignore_incorrect_picture_mxf_type=false)
void write_volindex(Standard standard) const
boost::optional< boost::filesystem::path > _asset_map
boost::optional< Standard > _standard
std::vector< DecryptedKDMKey > keys() const
A miscellaneous exception.
Thrown when no ASSETMAP was found when trying to read a DCP.
Any error that occurs when reading data from a DCP.
@ THREED_ASSET_MARKED_AS_TWOD
Exceptions thrown by libdcp.
InteropSubtitleAsset class.
Namespace for everything in libdcp.
std::shared_ptr< Asset > asset_factory(boost::filesystem::path path, bool ignore_incorrect_picture_mxf_type, bool *found_threed_marked_as_twod=nullptr)
Methods for conversion to/from string.
SMPTESubtitleAsset class.
StereoPictureAsset class.
A class to describe what "equality" means for a particular test.
Utility methods and classes.
dcp::verify() method and associated code