libdcp
interop_text_asset.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012-2021 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 
40 #include "compose.hpp"
41 #include "dcp_assert.h"
42 #include "equality_options.h"
43 #include "filesystem.h"
44 #include "font_asset.h"
45 #include "file.h"
46 #include "interop_load_font_node.h"
47 #include "interop_text_asset.h"
48 #include "raw_convert.h"
49 #include "text_asset_internal.h"
50 #include "text_image.h"
51 #include "util.h"
52 #include "warnings.h"
53 #include "xml.h"
54 LIBDCP_DISABLE_WARNINGS
55 #include <libxml++/libxml++.h>
56 LIBDCP_ENABLE_WARNINGS
57 #include <boost/weak_ptr.hpp>
58 #include <cmath>
59 #include <cstdio>
60 
61 
62 using std::cerr;
63 using std::cout;
64 using std::dynamic_pointer_cast;
65 using std::make_shared;
66 using std::shared_ptr;
67 using std::string;
68 using std::vector;
69 using boost::optional;
70 using namespace dcp;
71 
72 
73 InteropTextAsset::InteropTextAsset(boost::filesystem::path file)
74  : TextAsset(file)
75 {
76  _raw_xml = dcp::file_to_string(file, 10 * 1024 * 1024);
77 
78  auto xml = make_shared<cxml::Document>("DCSubtitle");
79  xml->read_file(dcp::filesystem::fix_long_path(file));
80  _id = xml->string_child ("SubtitleID");
81  _reel_number = xml->string_child ("ReelNumber");
82  _language = xml->string_child ("Language");
83  _movie_title = xml->string_child ("MovieTitle");
84  _load_font_nodes = type_children<InteropLoadFontNode> (xml, "LoadFont");
85 
86  /* Now we need to drop down to xmlpp */
87 
88  vector<ParseState> ps;
89  for (auto i: xml->node()->get_children()) {
90  auto e = dynamic_cast<xmlpp::Element const *>(i);
91  if (e && (e->get_name() == "Font" || e->get_name() == "Subtitle")) {
92  parse_texts (e, ps, optional<int>(), Standard::INTEROP);
93  }
94  }
95 
96  for (auto i: _texts) {
97  auto si = dynamic_pointer_cast<TextImage>(i);
98  if (si) {
99  si->read_png_file (file.parent_path() / String::compose("%1.png", si->id()));
100  }
101  }
102 }
103 
104 
105 InteropTextAsset::InteropTextAsset()
106 {
107 
108 }
109 
110 
111 string
112 InteropTextAsset::xml_as_string() const
113 {
114  xmlpp::Document doc;
115  auto root = doc.create_root_node ("DCSubtitle");
116  root->set_attribute ("Version", "1.0");
117 
118  cxml::add_text_child(root, "SubtitleID", _id);
119  cxml::add_text_child(root, "MovieTitle", _movie_title);
120  cxml::add_text_child(root, "ReelNumber", raw_convert<string> (_reel_number));
121  cxml::add_text_child(root, "Language", _language);
122 
123  for (auto i: _load_font_nodes) {
124  auto load_font = cxml::add_child(root, "LoadFont");
125  load_font->set_attribute ("Id", i->id);
126  load_font->set_attribute ("URI", i->uri);
127  }
128 
129  texts_as_xml(root, 250, Standard::INTEROP);
130 
131  return format_xml(doc, {});
132 }
133 
134 
135 void
136 InteropTextAsset::add_font(string load_id, dcp::ArrayData data)
137 {
138  _fonts.push_back (Font(load_id, make_uuid(), data));
139  auto const uri = String::compose("font_%1.ttf", _load_font_nodes.size());
140  _load_font_nodes.push_back (make_shared<InteropLoadFontNode>(load_id, uri));
141 }
142 
143 
144 bool
145 InteropTextAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
146 {
147  if (!TextAsset::equals (other_asset, options, note)) {
148  return false;
149  }
150 
151  auto other = dynamic_pointer_cast<const InteropTextAsset> (other_asset);
152  if (!other) {
153  return false;
154  }
155 
156  if (!options.load_font_nodes_can_differ) {
157  auto i = _load_font_nodes.begin();
158  auto j = other->_load_font_nodes.begin();
159 
160  while (i != _load_font_nodes.end ()) {
161  if (j == other->_load_font_nodes.end ()) {
162  note (NoteType::ERROR, "<LoadFont> nodes differ");
163  return false;
164  }
165 
166  if (**i != **j) {
167  note (NoteType::ERROR, "<LoadFont> nodes differ");
168  return false;
169  }
170 
171  ++i;
172  ++j;
173  }
174  }
175 
176  if (_movie_title != other->_movie_title) {
177  note (NoteType::ERROR, "Subtitle or caption movie titles differ");
178  return false;
179  }
180 
181  return true;
182 }
183 
184 
185 vector<shared_ptr<LoadFontNode>>
186 InteropTextAsset::load_font_nodes() const
187 {
188  vector<shared_ptr<LoadFontNode>> lf;
189  copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
190  return lf;
191 }
192 
193 
194 void
195 InteropTextAsset::write(boost::filesystem::path p) const
196 {
197  File f(p, "wb");
198  if (!f) {
199  throw FileError ("Could not open file for writing", p, -1);
200  }
201 
202  _raw_xml = xml_as_string ();
203  /* length() here gives bytes not characters */
204  f.write(_raw_xml->c_str(), 1, _raw_xml->length());
205 
206  _file = p;
207 
208  /* Image subtitles */
209  for (auto i: _texts) {
210  if (auto im = dynamic_pointer_cast<dcp::TextImage>(i)) {
211  im->write_png_file(p.parent_path() / String::compose("%1.png", im->id()));
212  }
213  }
214 
215  /* Fonts */
216  for (auto i: _load_font_nodes) {
217  auto file = p.parent_path() / i->uri;
218  auto font_with_id = std::find_if(_fonts.begin(), _fonts.end(), [i](Font const& font) { return font.load_id == i->id; });
219  if (font_with_id != _fonts.end()) {
220  font_with_id->data.write(file);
221  font_with_id->file = file;
222  }
223  }
224 }
225 
226 
231 void
232 InteropTextAsset::resolve_fonts(vector<shared_ptr<Asset>> assets)
233 {
234  for (auto asset: assets) {
235  auto font = dynamic_pointer_cast<FontAsset>(asset);
236  if (!font) {
237  continue;
238  }
239 
240  DCP_ASSERT(_file);
241 
242  for (auto load_font_node: _load_font_nodes) {
243  auto const path_in_load_font_node = _file->parent_path() / load_font_node->uri;
244  if (font->file() && path_in_load_font_node == *font->file()) {
245  auto existing = std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; });
246  if (existing != _fonts.end()) {
247  *existing = Font(load_font_node->id, asset->id(), font->file().get());
248  } else {
249  _fonts.push_back(Font(load_font_node->id, asset->id(), font->file().get()));
250  }
251  }
252  }
253  }
254 }
255 
256 
257 vector<shared_ptr<Asset>>
258 InteropTextAsset::font_assets()
259 {
260  vector<shared_ptr<Asset>> assets;
261  for (auto const& i: _fonts) {
262  DCP_ASSERT (i.file);
263  assets.push_back(make_shared<FontAsset>(i.uuid, i.file.get()));
264  }
265  return assets;
266 }
267 
268 
269 vector<shared_ptr<const Asset>>
270 InteropTextAsset::font_assets() const
271 {
272  vector<shared_ptr<const Asset>> assets;
273  for (auto const& i: _fonts) {
274  DCP_ASSERT (i.file);
275  assets.push_back(make_shared<const FontAsset>(i.uuid, i.file.get()));
276  }
277  return assets;
278 }
279 
280 
281 void
282 InteropTextAsset::add_to_assetmap(AssetMap& asset_map, boost::filesystem::path root) const
283 {
284  Asset::add_to_assetmap(asset_map, root);
285 
286  for (auto i: _texts) {
287  auto im = dynamic_pointer_cast<dcp::TextImage>(i);
288  if (im) {
289  DCP_ASSERT(im->file());
290  add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
291  }
292  }
293 }
294 
295 
296 void
297 InteropTextAsset::add_to_pkl(shared_ptr<PKL> pkl, boost::filesystem::path root) const
298 {
299  Asset::add_to_pkl (pkl, root);
300 
301  for (auto i: _texts) {
302  auto im = dynamic_pointer_cast<dcp::TextImage>(i);
303  if (im) {
304  auto png_image = im->png_image ();
305  pkl->add_asset(im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png", root.filename().string());
306  }
307  }
308 }
309 
310 
311 void
312 InteropTextAsset::set_font_file(string load_id, boost::filesystem::path file)
313 {
314  for (auto& i: _fonts) {
315  if (i.load_id == load_id) {
316  i.file = file;
317  }
318  }
319 
320  for (auto i: _load_font_nodes) {
321  if (i->id == load_id) {
322  i->uri = file.filename().string();
323  }
324  }
325 }
326 
327 
328 vector<string>
330 {
331  vector<string> unresolved;
332  for (auto load_font_node: _load_font_nodes) {
333  if (std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; }) == _fonts.end()) {
334  unresolved.push_back(load_font_node->id);
335  }
336  }
337  return unresolved;
338 }
339 
Class to hold an arbitrary block of data.
Definition: array_data.h:55
boost::optional< boost::filesystem::path > file() const
Definition: asset.h:100
boost::optional< boost::filesystem::path > _file
Definition: asset.h:143
A class to describe what "equality" means for a particular test.
An exception related to a file.
Definition: exceptions.h:56
Definition: file.h:49
size_t write(const void *ptr, size_t size, size_t nmemb)
Definition: file.cc:106
void write(boost::filesystem::path path) const override
void resolve_fonts(std::vector< std::shared_ptr< Asset >> assets)
std::vector< std::string > unresolved_fonts() const
boost::optional< boost::filesystem::path > file
Definition: text_asset.h:205
A parent for classes representing a file containing subtitles or captions.
Definition: text_asset.h:97
boost::optional< std::string > _raw_xml
Definition: text_asset.h:212
void texts_as_xml(xmlpp::Element *root, int time_code_rate, Standard standard) const
Definition: text_asset.cc:727
std::vector< std::shared_ptr< Text > > _texts
Definition: text_asset.h:183
std::vector< Font > _fonts
Definition: text_asset.h:209
static std::string format_xml(xmlpp::Document const &document, boost::optional< std::pair< std::string, std::string >> xml_namespace)
Definition: text_asset.cc:967
DCP_ASSERT macro.
Class to describe what equality means when calling Asset::equals().
FontAsset class.
InteropLoadFontNode class.
InteropTextAsset class.
Namespace for everything in libdcp.
Definition: array_data.h:50
std::string make_digest(boost::filesystem::path filename, boost::function< void(int64_t, int64_t)>)
Methods for conversion to/from string.
Internal TextAsset helpers.
TextImage class.
Utility methods and classes.
Helpers for XML reading with libcxml.