wip: add AssetMap and fix everything up. dcp-editor
authorCarl Hetherington <cth@carlh.net>
Sun, 24 Apr 2022 19:39:42 +0000 (21:39 +0200)
committerCarl Hetherington <cth@carlh.net>
Sun, 24 Apr 2022 19:39:42 +0000 (21:39 +0200)
src/asset.cc
src/asset.h
src/asset_map.cc [new file with mode: 0644]
src/asset_map.h [new file with mode: 0644]
src/dcp.cc
src/dcp.h
src/interop_subtitle_asset.cc
src/interop_subtitle_asset.h
src/wscript
test/util_test.cc

index eb6f4a079bb81bb5f8e8848de4132341ed617f20..21282e9ddefc19c2239f4b00a75eca8578ea0d9e 100644 (file)
@@ -38,6 +38,7 @@
 
 
 #include "asset.h"
+#include "asset_map.h"
 #include "compose.hpp"
 #include "dcp_assert.h"
 #include "exceptions.h"
@@ -98,37 +99,24 @@ Asset::add_to_pkl (shared_ptr<PKL> pkl, path root) const
 
 
 void
-Asset::write_to_assetmap (xmlpp::Node* node, path root) const
+Asset::add_to_assetmap (AssetMap& asset_map, path root) const
 {
        DCP_ASSERT (_file);
-       write_file_to_assetmap (node, root, _file.get(), _id);
+       add_file_to_assetmap (asset_map, root, _file.get(), _id);
 }
 
 
 void
-Asset::write_file_to_assetmap (xmlpp::Node* node, path root, path file, string id)
+Asset::add_file_to_assetmap (AssetMap& asset_map, path root, path file, string id)
 {
-       auto path = relative_to_root (
-               canonical(root),
-               canonical(file)
-               );
-
-       if (!path) {
+       if (!relative_to_root(root, file)) {
                /* The path of this asset is not within our DCP, so we assume it's an external
                   (referenced) one.
                */
                return;
        }
 
-       auto asset = node->add_child ("Asset");
-       asset->add_child("Id")->add_child_text("urn:uuid:" + id);
-       auto chunk_list = asset->add_child ("ChunkList");
-       auto chunk = chunk_list->add_child ("Chunk");
-
-       chunk->add_child("Path")->add_child_text(path.get().generic_string());
-       chunk->add_child("VolumeIndex")->add_child_text("1");
-       chunk->add_child("Offset")->add_child_text("0");
-       chunk->add_child("Length")->add_child_text(raw_convert<string>(file_size(file)));
+       asset_map.add_asset(id, file, false);
 }
 
 
index 88c7d1d1312582bdd67a5f5cfb2be3366c6e042c..3af49119ba59d5bd586fc6851696a916e7872c23 100644 (file)
@@ -60,6 +60,9 @@ struct asset_test;
 namespace dcp {
 
 
+class AssetMap;
+
+
 /** @class Asset
  *  @brief Parent class for DCP assets, i.e. picture, sound, subtitles, closed captions, CPLs, fonts
  *
@@ -89,7 +92,7 @@ public:
                NoteHandler note
                ) const;
 
-       virtual void write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const;
+       virtual void add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const;
 
        virtual void add_to_pkl (std::shared_ptr<PKL> pkl, boost::filesystem::path root) const;
 
@@ -121,7 +124,7 @@ protected:
        /** The most recent disk file used to read or write this asset */
        mutable boost::optional<boost::filesystem::path> _file;
 
-       static void write_file_to_assetmap (xmlpp::Node* node, boost::filesystem::path root, boost::filesystem::path file, std::string id);
+       static void add_file_to_assetmap (AssetMap& asset_map, boost::filesystem::path root, boost::filesystem::path file, std::string id);
 
 private:
        friend struct ::asset_test;
diff --git a/src/asset_map.cc b/src/asset_map.cc
new file mode 100644 (file)
index 0000000..763d348
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+    Copyright (C) 2022 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "asset_map.h"
+#include "dcp_assert.h"
+#include "raw_convert.h"
+#include "util.h"
+#include "warnings.h"
+LIBDCP_DISABLE_WARNINGS
+#include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
+#include <boost/algorithm/string.hpp>
+
+
+using std::string;
+using std::vector;
+using boost::algorithm::starts_with;
+using namespace dcp;
+
+
+static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
+static string const assetmap_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
+
+
+AssetMap::AssetMap(boost::filesystem::path file)
+       : _file(file)
+{
+       cxml::Document doc("AssetMap");
+       doc.read_file(file);
+
+       if (doc.namespace_uri() == assetmap_interop_ns) {
+               _standard = Standard::INTEROP;
+       } else if (doc.namespace_uri() == assetmap_smpte_ns) {
+               _standard = Standard::SMPTE;
+       } else {
+               boost::throw_exception(XMLError("Unrecognised AssetMap namespace " + doc.namespace_uri()));
+       }
+
+       _annotation_text = doc.optional_string_child("AnnotationText");
+       _issue_date = doc.string_child("IssueDate");
+       _issuer = doc.string_child("Issuer");
+       _creator = doc.string_child("Creator");
+
+       for (auto asset: doc.node_child("AssetList")->node_children("Asset")) {
+               _assets.push_back(Asset(asset, _file->parent_path(), _standard));
+       }
+}
+
+
+vector<boost::filesystem::path>
+AssetMap::pkl_paths() const
+{
+       auto paths = std::vector<boost::filesystem::path>();
+       for (auto asset: _assets) {
+               if (asset.pkl()) {
+                       paths.push_back(asset.path());
+               }
+       }
+       return paths;
+}
+
+
+std::map<std::string, boost::filesystem::path>
+AssetMap::paths() const
+{
+       auto paths = std::map<string, boost::filesystem::path>();
+       for (auto asset: _assets) {
+               paths[asset.id()] = asset.path();
+       }
+       return paths;
+}
+
+
+void
+AssetMap::add_asset(string id, boost::filesystem::path path, bool pkl)
+{
+       _assets.push_back(Asset(id, path, pkl));
+}
+
+
+void
+AssetMap::write_xml(boost::filesystem::path file)
+{
+       xmlpp::Document doc;
+       xmlpp::Element* root;
+
+       switch (_standard) {
+       case Standard::INTEROP:
+               root = doc.create_root_node("AssetMap", assetmap_interop_ns);
+               break;
+       case Standard::SMPTE:
+               root = doc.create_root_node("AssetMap", assetmap_smpte_ns);
+               break;
+       default:
+               DCP_ASSERT (false);
+       }
+
+       root->add_child("Id")->add_child_text("urn:uuid:" + _id);
+       if (_annotation_text) {
+               root->add_child("AnnotationText")->add_child_text(*_annotation_text);
+       }
+
+       switch (_standard) {
+       case Standard::INTEROP:
+               root->add_child("VolumeCount")->add_child_text("1");
+               root->add_child("IssueDate")->add_child_text(_issue_date);
+               root->add_child("Issuer")->add_child_text(_issuer);
+               root->add_child("Creator")->add_child_text(_creator);
+               break;
+       case Standard::SMPTE:
+               root->add_child("Creator")->add_child_text(_creator);
+               root->add_child("VolumeCount")->add_child_text ("1");
+               root->add_child("IssueDate")->add_child_text(_issue_date);
+               root->add_child("Issuer")->add_child_text(_issuer);
+               break;
+       default:
+               DCP_ASSERT (false);
+       }
+
+       auto const dcp_root_directory = file.parent_path();
+
+       auto asset_list = root->add_child("AssetList");
+
+       for (auto asset: _assets) {
+               auto node = asset_list->add_child("Asset");
+               node->add_child("Id")->add_child_text("urn:uuid:" + asset.id());
+               if (asset.pkl()) {
+                       node->add_child("PackingList")->add_child_text("true");
+               }
+               auto chunk_list = node->add_child("ChunkList");
+               auto chunk = chunk_list->add_child("Chunk");
+
+               auto relative_path = relative_to_root(dcp_root_directory, asset.path());
+               DCP_ASSERT(relative_path);
+
+               chunk->add_child("Path")->add_child_text(relative_path->generic_string());
+               chunk->add_child("VolumeIndex")->add_child_text("1");
+               chunk->add_child("Offset")->add_child_text("0");
+               chunk->add_child("Length")->add_child_text(raw_convert<string>(boost::filesystem::file_size(asset.path())));
+       }
+
+       doc.write_to_file_formatted(file.string(), "UTF-8");
+       _file = file;
+}
+
+
+AssetMap::Asset::Asset(cxml::ConstNodePtr node, boost::filesystem::path root, dcp::Standard standard)
+       : Object(remove_urn_uuid(node->string_child("Id")))
+{
+       if (node->node_child("ChunkList")->node_children("Chunk").size() != 1) {
+               boost::throw_exception(XMLError("unsupported asset chunk count"));
+       }
+
+       auto path_from_xml = node->node_child("ChunkList")->node_child("Chunk")->string_child("Path");
+       if (starts_with(path_from_xml, "file://")) {
+               path_from_xml = path_from_xml.substr(7);
+       }
+
+       _path = root / path_from_xml;
+
+       switch (standard) {
+               case Standard::INTEROP:
+                       _pkl = static_cast<bool>(node->optional_node_child("PackingList"));
+                       break;
+               case Standard::SMPTE:
+                       {
+                               auto pkl_bool = node->optional_string_child("PackingList");
+                               _pkl = pkl_bool && *pkl_bool == "true";
+                               break;
+                       }
+       }
+}
+
diff --git a/src/asset_map.h b/src/asset_map.h
new file mode 100644 (file)
index 0000000..906418b
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+    Copyright (C) 2022 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "exceptions.h"
+#include "object.h"
+#include "types.h"
+#include <boost/optional.hpp>
+#include <string>
+
+
+namespace dcp {
+
+
+class AssetMap : public Object
+{
+public:
+       AssetMap(Standard standard, boost::optional<std::string> annotation_text, std::string issue_date, std::string issuer, std::string creator)
+               : _standard(standard)
+               , _annotation_text(annotation_text)
+               , _issue_date(issue_date)
+               , _issuer(issuer)
+               , _creator(creator)
+       {}
+
+       explicit AssetMap(boost::filesystem::path file);
+
+       void add_asset(std::string id, boost::filesystem::path path, bool pkl);
+
+       boost::optional<boost::filesystem::path> path() const {
+               return _file;
+       }
+
+       std::vector<boost::filesystem::path> pkl_paths() const;
+       std::map<std::string, boost::filesystem::path> paths() const;
+       dcp::Standard standard() const {
+               return _standard;
+       }
+
+       void write_xml(boost::filesystem::path file);
+
+       class Asset : public Object
+       {
+       public:
+               Asset(std::string id, boost::filesystem::path path, bool pkl)
+                       : Object(id)
+                       , _path(path)
+                       , _pkl(pkl)
+               {}
+
+               Asset(cxml::ConstNodePtr node, boost::filesystem::path root, dcp::Standard standard);
+
+               boost::filesystem::path path() const {
+                       return _path;
+               }
+
+               bool pkl() const {
+                       return _pkl;
+               }
+
+       private:
+               boost::filesystem::path _path;
+               bool _pkl = false;
+       };
+
+private:
+       Standard _standard;
+       boost::optional<std::string> _annotation_text;
+       std::string _issue_date;
+       std::string _issuer;
+       std::string _creator;
+       std::vector<Asset> _assets;
+       /** The most recent disk file used to read or write this AssetMap */
+       mutable boost::optional<boost::filesystem::path> _file;
+};
+
+
+}
+
+
index 3de683bcc2a0743205c469ba2aa11982f786aa27..ec2a77fa2f52500900b775d0f7aee1ef578100f6 100644 (file)
@@ -91,8 +91,6 @@ using boost::optional;
 using namespace dcp;
 
 
-static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
-static string const assetmap_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
 static string const volindex_interop_ns = "http://www.digicine.com/PROTO-ASDCP-VL-20040311#";
 static string const volindex_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
 
@@ -110,10 +108,9 @@ DCP::DCP (boost::filesystem::path directory)
 
 DCP::DCP (DCP&& other)
        : _directory(std::move(other._directory))
+       , _asset_map(std::move(other._asset_map))
        , _cpls(std::move(other._cpls))
        , _pkls(std::move(other._pkls))
-       , _asset_map(std::move(other._asset_map))
-       , _standard(std::move(other._standard))
 {
 
 }
@@ -122,10 +119,9 @@ DCP::DCP (DCP&& other)
 dcp::DCP& DCP::operator= (DCP&& other)
 {
        _directory = std::move(other._directory);
+       _asset_map = std::move(other._asset_map);
        _cpls = std::move(other._cpls);
        _pkls = std::move(other._pkls);
-       _asset_map = std::move(other._asset_map);
-       _standard = std::move(other._standard);
        return *this;
 }
 
@@ -135,63 +131,26 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
 {
        /* Read the ASSETMAP and PKL */
 
+       boost::filesystem::path asset_map_path;
+
        if (boost::filesystem::exists (_directory / "ASSETMAP")) {
-               _asset_map = _directory / "ASSETMAP";
+               asset_map_path = _directory / "ASSETMAP";
        } else if (boost::filesystem::exists (_directory / "ASSETMAP.xml")) {
-               _asset_map = _directory / "ASSETMAP.xml";
+               asset_map_path = _directory / "ASSETMAP.xml";
        } else {
                boost::throw_exception (MissingAssetmapError(_directory));
        }
 
-       cxml::Document asset_map ("AssetMap");
-
-       asset_map.read_file (_asset_map.get());
-       if (asset_map.namespace_uri() == assetmap_interop_ns) {
-               _standard = Standard::INTEROP;
-       } else if (asset_map.namespace_uri() == assetmap_smpte_ns) {
-               _standard = Standard::SMPTE;
-       } else {
-               boost::throw_exception (XMLError ("Unrecognised Assetmap namespace " + asset_map.namespace_uri()));
-       }
-
-       auto asset_nodes = asset_map.node_child("AssetList")->node_children ("Asset");
-       map<string, boost::filesystem::path> paths;
-       vector<boost::filesystem::path> pkl_paths;
-       for (auto i: asset_nodes) {
-               if (i->node_child("ChunkList")->node_children("Chunk").size() != 1) {
-                       boost::throw_exception (XMLError ("unsupported asset chunk count"));
-               }
-               auto p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
-               if (starts_with (p, "file://")) {
-                       p = p.substr (7);
-               }
-               switch (*_standard) {
-               case Standard::INTEROP:
-                       if (i->optional_node_child("PackingList")) {
-                               pkl_paths.push_back (p);
-                       } else {
-                               paths.insert (make_pair(remove_urn_uuid(i->string_child("Id")), p));
-                       }
-                       break;
-               case Standard::SMPTE:
-               {
-                       auto pkl_bool = i->optional_string_child("PackingList");
-                       if (pkl_bool && *pkl_bool == "true") {
-                               pkl_paths.push_back (p);
-                       } else {
-                               paths.insert (make_pair(remove_urn_uuid(i->string_child("Id")), p));
-                       }
-                       break;
-               }
-               }
-       }
+       _asset_map = AssetMap(asset_map_path);
+       auto const pkl_paths = _asset_map->pkl_paths();
+       auto const standard = _asset_map->standard();
 
        if (pkl_paths.empty()) {
                boost::throw_exception (XMLError ("No packing lists found in asset map"));
        }
 
        for (auto i: pkl_paths) {
-               _pkls.push_back (make_shared<PKL>(_directory / i));
+               _pkls.push_back(make_shared<PKL>(i));
        }
 
        /* Now we have:
@@ -206,10 +165,9 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
        */
        vector<shared_ptr<Asset>> other_assets;
 
-       for (auto i: paths) {
-               auto path = _directory / i.second;
-
-               if (i.second.empty()) {
+       auto paths = _asset_map->paths();
+       for (auto path: paths) {
+               if (path.second == _directory) {
                        /* I can't see how this is valid, but it's
                           been seen in the wild with a DCP that
                           claims to come from ClipsterDCI 5.10.0.5.
@@ -220,9 +178,9 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
                        continue;
                }
 
-               if (!boost::filesystem::exists(path)) {
+               if (!boost::filesystem::exists(path.second)) {
                        if (notes) {
-                               notes->push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_ASSET, path});
+                               notes->push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_ASSET, path.second});
                        }
                        continue;
                }
@@ -230,7 +188,7 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
                /* Find the <Type> for this asset from the PKL that contains the asset */
                optional<string> pkl_type;
                for (auto j: _pkls) {
-                       pkl_type = j->type(i.first);
+                       pkl_type = j->type(path.first);
                        if (pkl_type) {
                                break;
                        }
@@ -251,45 +209,45 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
                pkl_type = pkl_type->substr(0, pkl_type->find(";"));
 
                if (
-                       pkl_type == remove_parameters(CPL::static_pkl_type(*_standard)) ||
-                       pkl_type == remove_parameters(InteropSubtitleAsset::static_pkl_type(*_standard))) {
+                       pkl_type == remove_parameters(CPL::static_pkl_type(standard)) ||
+                       pkl_type == remove_parameters(InteropSubtitleAsset::static_pkl_type(standard))) {
                        auto p = new xmlpp::DomParser;
                        try {
-                               p->parse_file (path.string());
+                               p->parse_file (path.second.string());
                        } catch (std::exception& e) {
                                delete p;
-                               throw ReadError(String::compose("XML error in %1", path.string()), e.what());
+                               throw ReadError(String::compose("XML error in %1", path.second.string()), e.what());
                        }
 
                        auto const root = p->get_document()->get_root_node()->get_name();
                        delete p;
 
                        if (root == "CompositionPlaylist") {
-                               auto cpl = make_shared<CPL>(path);
-                               if (_standard && cpl->standard() != _standard.get() && notes) {
+                               auto cpl = make_shared<CPL>(path.second);
+                               if (cpl->standard() != standard && notes) {
                                        notes->push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_STANDARD});
                                }
                                _cpls.push_back (cpl);
                        } else if (root == "DCSubtitle") {
-                               if (_standard && _standard.get() == Standard::SMPTE && notes) {
+                               if (standard == Standard::SMPTE && notes) {
                                        notes->push_back (VerificationNote(VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_STANDARD));
                                }
-                               other_assets.push_back (make_shared<InteropSubtitleAsset>(path));
+                               other_assets.push_back (make_shared<InteropSubtitleAsset>(path.second));
                        }
                } else if (
-                       *pkl_type == remove_parameters(PictureAsset::static_pkl_type(*_standard)) ||
-                       *pkl_type == remove_parameters(SoundAsset::static_pkl_type(*_standard)) ||
-                       *pkl_type == remove_parameters(AtmosAsset::static_pkl_type(*_standard)) ||
-                       *pkl_type == remove_parameters(SMPTESubtitleAsset::static_pkl_type(*_standard))
+                       *pkl_type == remove_parameters(PictureAsset::static_pkl_type(standard)) ||
+                       *pkl_type == remove_parameters(SoundAsset::static_pkl_type(standard)) ||
+                       *pkl_type == remove_parameters(AtmosAsset::static_pkl_type(standard)) ||
+                       *pkl_type == remove_parameters(SMPTESubtitleAsset::static_pkl_type(standard))
                        ) {
 
                        bool found_threed_marked_as_twod = false;
-                       other_assets.push_back (asset_factory(path, ignore_incorrect_picture_mxf_type, &found_threed_marked_as_twod));
+                       other_assets.push_back (asset_factory(path.second, ignore_incorrect_picture_mxf_type, &found_threed_marked_as_twod));
                        if (found_threed_marked_as_twod && notes) {
-                               notes->push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, path});
+                               notes->push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, path.second});
                        }
-               } else if (*pkl_type == remove_parameters(FontAsset::static_pkl_type(*_standard))) {
-                       other_assets.push_back (make_shared<FontAsset>(i.first, path));
+               } else if (*pkl_type == remove_parameters(FontAsset::static_pkl_type(standard))) {
+                       other_assets.push_back (make_shared<FontAsset>(path.first, path.second));
                } else if (*pkl_type == "image/png") {
                        /* It's an Interop PNG subtitle; let it go */
                } else {
@@ -431,80 +389,6 @@ DCP::write_volindex (Standard standard) const
 }
 
 
-void
-DCP::write_assetmap (
-       Standard standard, string pkl_uuid, boost::filesystem::path pkl_path,
-       string issuer, string creator, string issue_date, string annotation_text
-       ) const
-{
-       auto p = _directory;
-
-       switch (standard) {
-       case Standard::INTEROP:
-               p /= "ASSETMAP";
-               break;
-       case Standard::SMPTE:
-               p /= "ASSETMAP.xml";
-               break;
-       default:
-               DCP_ASSERT (false);
-       }
-
-       xmlpp::Document doc;
-       xmlpp::Element* root;
-
-       switch (standard) {
-       case Standard::INTEROP:
-               root = doc.create_root_node ("AssetMap", assetmap_interop_ns);
-               break;
-       case Standard::SMPTE:
-               root = doc.create_root_node ("AssetMap", assetmap_smpte_ns);
-               break;
-       default:
-               DCP_ASSERT (false);
-       }
-
-       root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
-       root->add_child("AnnotationText")->add_child_text (annotation_text);
-
-       switch (standard) {
-       case Standard::INTEROP:
-               root->add_child("VolumeCount")->add_child_text ("1");
-               root->add_child("IssueDate")->add_child_text (issue_date);
-               root->add_child("Issuer")->add_child_text (issuer);
-               root->add_child("Creator")->add_child_text (creator);
-               break;
-       case Standard::SMPTE:
-               root->add_child("Creator")->add_child_text (creator);
-               root->add_child("VolumeCount")->add_child_text ("1");
-               root->add_child("IssueDate")->add_child_text (issue_date);
-               root->add_child("Issuer")->add_child_text (issuer);
-               break;
-       default:
-               DCP_ASSERT (false);
-       }
-
-       auto asset_list = root->add_child ("AssetList");
-
-       auto asset = asset_list->add_child ("Asset");
-       asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
-       asset->add_child("PackingList")->add_child_text ("true");
-       auto chunk_list = asset->add_child ("ChunkList");
-       auto chunk = chunk_list->add_child ("Chunk");
-       chunk->add_child("Path")->add_child_text (pkl_path.filename().string());
-       chunk->add_child("VolumeIndex")->add_child_text ("1");
-       chunk->add_child("Offset")->add_child_text ("0");
-       chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (pkl_path)));
-
-       for (auto i: assets()) {
-               i->write_to_assetmap (asset_list, _directory);
-       }
-
-       doc.write_to_file_formatted (p.string (), "UTF-8");
-       _asset_map = p;
-}
-
-
 void
 DCP::write_xml (
        string issuer,
@@ -553,7 +437,17 @@ DCP::write_xml (
        pkl->write_xml (pkl_path, signer);
 
        write_volindex (standard);
-       write_assetmap (standard, pkl->id(), pkl_path, issuer, creator, issue_date, annotation_text);
+
+       if (!_asset_map) {
+               _asset_map = AssetMap(standard, annotation_text, issue_date, issuer, creator);
+               _asset_map->add_asset(pkl->id(), pkl_path, true);
+               for (auto asset: assets()) {
+                       asset->add_to_assetmap(*_asset_map, _directory);
+               }
+       }
+
+       auto asset_map_path = _directory / (standard == Standard::INTEROP ? "ASSETMAP" : "ASSETMAP.xml");
+       _asset_map->write_xml(asset_map_path);
 }
 
 
index c3f4e0fdf116399087c079e4d89d22bd3d60bcf9..9403340ab41739c401ec93d53345b4131891a313 100644 (file)
--- a/src/dcp.h
+++ b/src/dcp.h
@@ -41,6 +41,7 @@
 #define LIBDCP_DCP_H
 
 
+#include "asset_map.h"
 #include "certificate.h"
 #include "compose.hpp"
 #include "metadata.h"
@@ -165,7 +166,11 @@ public:
 
        /** @return Standard of a DCP that was read in */
        boost::optional<Standard> standard () const {
-               return _standard;
+               if (!_asset_map) {
+                       return {};
+               }
+
+               return _asset_map->standard();
        }
 
        boost::filesystem::path directory () const {
@@ -180,7 +185,11 @@ public:
        }
 
        boost::optional<boost::filesystem::path> asset_map_path () {
-               return _asset_map;
+               if (!_asset_map) {
+                       return {};
+               }
+
+               return _asset_map->path();
        }
 
        static std::vector<boost::filesystem::path> directories_from_files (std::vector<boost::filesystem::path> files);
@@ -200,15 +209,11 @@ private:
 
        /** The directory that we are writing to */
        boost::filesystem::path _directory;
+       boost::optional<AssetMap> _asset_map;
        /** The CPLs that make up this DCP */
        std::vector<std::shared_ptr<CPL>> _cpls;
        /** The PKLs that make up this DCP */
        std::vector<std::shared_ptr<PKL>> _pkls;
-       /** File that the ASSETMAP was read from or last written to */
-       mutable boost::optional<boost::filesystem::path> _asset_map;
-
-       /** Standard of DCP that was read in */
-       boost::optional<Standard> _standard;
 };
 
 
index 798f1cda2279624fefa6cd5899e2eb1557f63236..a22142dc0821be144ab5806f80c1128954fb4ab4 100644 (file)
@@ -267,15 +267,15 @@ InteropSubtitleAsset::add_font_assets (vector<shared_ptr<Asset>>& assets)
 
 
 void
-InteropSubtitleAsset::write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const
+InteropSubtitleAsset::add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const
 {
-       Asset::write_to_assetmap (node, root);
+       Asset::add_to_assetmap (asset_map, root);
 
        for (auto i: _subtitles) {
                auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
                if (im) {
-                       DCP_ASSERT (im->file());
-                       write_file_to_assetmap (node, root, im->file().get(), im->id());
+                       DCP_ASSERT(im->file());
+                       add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
                }
        }
 }
index b953b303ad4cf5da25479f786dc347a628608a4d..29f944b687f08232abe614dd11f279f1b6c6ad1c 100644 (file)
@@ -48,6 +48,7 @@
 namespace dcp {
 
 
+class AssetMap;
 class InteropLoadFontNode;
 
 
@@ -68,7 +69,7 @@ public:
                NoteHandler note
                ) const override;
 
-       void write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const override;
+       void add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const override;
        void add_to_pkl (std::shared_ptr<PKL> pkl, boost::filesystem::path root) const override;
 
        std::vector<std::shared_ptr<LoadFontNode>> load_font_nodes () const override;
index da4d0d1e2251b33a235aed1959af6edd92a63dc9..5e118c6e748fb1bce6cd2231227ecef9d82325d8 100644 (file)
@@ -37,6 +37,7 @@ def build(bld):
              array_data.cc
              asset.cc
              asset_factory.cc
+             asset_map.cc
              asset_writer.cc
              atmos_asset.cc
              atmos_asset_writer.cc
index 8ca7df44f4cf2d6ddb70e4feb14325d10471a9dd..092769c873a452ca1214f92ad88ad6a667db3cf2 100644 (file)
@@ -93,42 +93,58 @@ BOOST_AUTO_TEST_CASE (content_kind_test)
 /** Test dcp::relative_to_root */
 BOOST_AUTO_TEST_CASE (relative_to_root_test)
 {
+       using namespace boost::filesystem;
+
+       path const base = "build/test/relative_to_root_test";
+
        {
-               boost::filesystem::path root = "a";
+               path root = base / "a";
                root /= "b";
 
-               boost::filesystem::path file = "a";
+               path file = base / "a";
                file /= "b";
                file /= "c";
 
-               boost::optional<boost::filesystem::path> rel = dcp::relative_to_root (root, file);
+               boost::system::error_code ec;
+               create_directories(root, ec);
+               create_directories(file, ec);
+
+               auto rel = dcp::relative_to_root (root, file);
                BOOST_CHECK (rel);
-               BOOST_CHECK_EQUAL (rel.get(), boost::filesystem::path ("c"));
+               BOOST_CHECK_EQUAL (rel.get(), path("c"));
        }
 
        {
-               boost::filesystem::path root = "a";
+               path root = "a";
                root /= "b";
                root /= "c";
 
-               boost::filesystem::path file = "a";
+               path file = "a";
                file /= "b";
 
-               boost::optional<boost::filesystem::path> rel = dcp::relative_to_root (root, file);
+               boost::system::error_code ec;
+               create_directories(root, ec);
+               create_directories(file, ec);
+
+               auto rel = dcp::relative_to_root (root, file);
                BOOST_CHECK (!rel);
        }
 
        {
-               boost::filesystem::path root = "a";
+               path root = "a";
 
-               boost::filesystem::path file = "a";
+               path file = "a";
                file /= "b";
                file /= "c";
 
-               boost::optional<boost::filesystem::path> rel = dcp::relative_to_root (root, file);
+               boost::system::error_code ec;
+               create_directories(root, ec);
+               create_directories(file, ec);
+
+               auto rel = dcp::relative_to_root (root, file);
                BOOST_CHECK (rel);
 
-               boost::filesystem::path check = "b";
+               path check = "b";
                check /= "c";
                BOOST_CHECK_EQUAL (rel.get(), check);
        }