Catch read errors from verify_cpl (e.g. basic failures to read a video frame).
[libdcp.git] / src / asset_map.cc
1 /*
2     Copyright (C) 2012-2022 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34
35 #include "asset_map.h"
36 #include "dcp_assert.h"
37 #include "exceptions.h"
38 #include "filesystem.h"
39 #include "raw_convert.h"
40 #include "warnings.h"
41 LIBDCP_DISABLE_WARNINGS
42 #include <libxml++/libxml++.h>
43 LIBDCP_ENABLE_WARNINGS
44 #include <boost/algorithm/string.hpp>
45
46
47
48 using std::map;
49 using std::string;
50 using std::vector;
51 using boost::algorithm::starts_with;
52 using namespace dcp;
53
54
55 static string const assetmap_interop_ns = "http://www.digicine.com/PROTO-ASDCP-AM-20040311#";
56 static string const assetmap_smpte_ns   = "http://www.smpte-ra.org/schemas/429-9/2007/AM";
57
58
59 AssetMap::AssetMap(boost::filesystem::path file)
60         : _file(file)
61 {
62         cxml::Document doc("AssetMap");
63
64         doc.read_file(dcp::filesystem::fix_long_path(file));
65         if (doc.namespace_uri() == assetmap_interop_ns) {
66                 _standard = Standard::INTEROP;
67         } else if (doc.namespace_uri() == assetmap_smpte_ns) {
68                 _standard = Standard::SMPTE;
69         } else {
70                 boost::throw_exception(XMLError("Unrecognised Assetmap namespace " + doc.namespace_uri()));
71         }
72
73         _id = remove_urn_uuid(doc.string_child("Id"));
74         _annotation_text = doc.optional_string_child("AnnotationText");
75         _issue_date = doc.string_child("IssueDate");
76         _issuer = doc.string_child("Issuer");
77         _creator = doc.string_child("Creator");
78
79         for (auto asset: doc.node_child("AssetList")->node_children("Asset")) {
80                 _assets.push_back(Asset(asset, _file->parent_path(), _standard));
81         }
82 }
83
84
85 AssetMap::Asset::Asset(cxml::ConstNodePtr node, boost::filesystem::path root, dcp::Standard standard)
86         : Object(remove_urn_uuid(node->string_child("Id")))
87 {
88         if (node->node_child("ChunkList")->node_children("Chunk").size() != 1) {
89                 boost::throw_exception (XMLError ("unsupported asset chunk count"));
90         }
91
92         auto path_from_xml = node->node_child("ChunkList")->node_child("Chunk")->string_child("Path");
93         if (starts_with(path_from_xml, "file://")) {
94                 path_from_xml = path_from_xml.substr(7);
95         }
96
97         _path = root / path_from_xml;
98
99         switch (standard) {
100         case Standard::INTEROP:
101                 _pkl = static_cast<bool>(node->optional_node_child("PackingList"));
102                 break;
103         case Standard::SMPTE:
104         {
105                 auto pkl_bool = node->optional_string_child("PackingList");
106                 _pkl = pkl_bool && *pkl_bool == "true";
107                 break;
108         }
109         }
110 }
111
112
113 void
114 AssetMap::add_asset(string id, boost::filesystem::path path, bool pkl)
115 {
116         _assets.push_back(Asset(id, path, pkl));
117 }
118
119
120 void
121 AssetMap::clear_assets()
122 {
123         _assets.clear();
124 }
125
126
127 map<std::string, boost::filesystem::path>
128 AssetMap::asset_ids_and_paths() const
129 {
130         auto paths = map<string, boost::filesystem::path>();
131         for (auto asset: _assets) {
132                 paths[asset.id()] = asset.path();
133         }
134         return paths;
135 }
136
137
138 vector<boost::filesystem::path>
139 AssetMap::pkl_paths() const
140 {
141         auto paths = std::vector<boost::filesystem::path>();
142         for (auto asset: _assets) {
143                 if (asset.pkl()) {
144                         paths.push_back(asset.path());
145                 }
146         }
147         return paths;
148 }
149
150
151 void
152 AssetMap::write_xml(boost::filesystem::path file) const
153 {
154         xmlpp::Document doc;
155         xmlpp::Element* root;
156
157         switch (_standard) {
158         case Standard::INTEROP:
159                 root = doc.create_root_node("AssetMap", assetmap_interop_ns);
160                 break;
161         case Standard::SMPTE:
162                 root = doc.create_root_node("AssetMap", assetmap_smpte_ns);
163                 break;
164         default:
165                 DCP_ASSERT (false);
166         }
167
168         root->add_child("Id")->add_child_text("urn:uuid:" + _id);
169         if (_annotation_text) {
170                 root->add_child("AnnotationText")->add_child_text(*_annotation_text);
171         }
172
173         switch (_standard) {
174         case Standard::INTEROP:
175                 root->add_child("VolumeCount")->add_child_text("1");
176                 root->add_child("IssueDate")->add_child_text(_issue_date);
177                 root->add_child("Issuer")->add_child_text(_issuer);
178                 root->add_child("Creator")->add_child_text(_creator);
179                 break;
180         case Standard::SMPTE:
181                 root->add_child("Creator")->add_child_text(_creator);
182                 root->add_child("VolumeCount")->add_child_text("1");
183                 root->add_child("IssueDate")->add_child_text(_issue_date);
184                 root->add_child("Issuer")->add_child_text(_issuer);
185                 break;
186         default:
187                 DCP_ASSERT (false);
188         }
189
190         auto asset_list = root->add_child("AssetList");
191         for (auto const& asset: _assets) {
192                 asset.write_xml(asset_list, file.parent_path());
193         }
194
195         doc.write_to_file_formatted(dcp::filesystem::fix_long_path(file).string(), "UTF-8");
196         _file = file;
197 }
198
199
200 void
201 AssetMap::Asset::write_xml(xmlpp::Element* asset_list, boost::filesystem::path dcp_root_directory) const
202 {
203         auto node = asset_list->add_child("Asset");
204         node->add_child("Id")->add_child_text("urn:uuid:" + _id);
205         if (_pkl) {
206                 node->add_child("PackingList")->add_child_text("true");
207         }
208         auto chunk_list = node->add_child("ChunkList");
209         auto chunk = chunk_list->add_child("Chunk");
210
211         auto relative_path = relative_to_root(filesystem::canonical(dcp_root_directory), filesystem::canonical(_path));
212         DCP_ASSERT(relative_path);
213
214         chunk->add_child("Path")->add_child_text(relative_path->generic_string());
215         chunk->add_child("VolumeIndex")->add_child_text("1");
216         chunk->add_child("Offset")->add_child_text("0");
217         chunk->add_child("Length")->add_child_text(raw_convert<string>(filesystem::file_size(_path)));
218 }
219