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