Avoid depends_on() so we can build on Ubuntu 16.04.
[libdcp.git] / src / interop_subtitle_asset.cc
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
35 /** @file  src/interop_subtitle_asset.cc
36  *  @brief InteropSubtitleAsset class
37  */
38
39
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_subtitle_asset.h"
48 #include "raw_convert.h"
49 #include "subtitle_asset_internal.h"
50 #include "subtitle_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 InteropSubtitleAsset::InteropSubtitleAsset (boost::filesystem::path file)
74         : SubtitleAsset (file)
75 {
76         _raw_xml = dcp::file_to_string (file);
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_subtitles (e, ps, optional<int>(), Standard::INTEROP);
93                 }
94         }
95
96         for (auto i: _subtitles) {
97                 auto si = dynamic_pointer_cast<SubtitleImage>(i);
98                 if (si) {
99                         si->read_png_file (file.parent_path() / String::compose("%1.png", si->id()));
100                 }
101         }
102 }
103
104
105 InteropSubtitleAsset::InteropSubtitleAsset ()
106 {
107
108 }
109
110
111 string
112 InteropSubtitleAsset::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         root->add_child("SubtitleID")->add_child_text (_id);
119         root->add_child("MovieTitle")->add_child_text (_movie_title);
120         root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
121         root->add_child("Language")->add_child_text (_language);
122
123         for (auto i: _load_font_nodes) {
124                 auto load_font = root->add_child("LoadFont");
125                 load_font->set_attribute ("Id", i->id);
126                 load_font->set_attribute ("URI", i->uri);
127         }
128
129         subtitles_as_xml (root, 250, Standard::INTEROP);
130
131         return format_xml(doc, {});
132 }
133
134
135 void
136 InteropSubtitleAsset::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 InteropSubtitleAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
146 {
147         if (!SubtitleAsset::equals (other_asset, options, note)) {
148                 return false;
149         }
150
151         auto other = dynamic_pointer_cast<const InteropSubtitleAsset> (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 movie titles differ");
178                 return false;
179         }
180
181         return true;
182 }
183
184
185 vector<shared_ptr<LoadFontNode>>
186 InteropSubtitleAsset::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 InteropSubtitleAsset::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: _subtitles) {
210                 auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
211                 if (im) {
212                         im->write_png_file(p.parent_path() / String::compose("%1.png", im->id()));
213                 }
214         }
215
216         /* Fonts */
217         for (auto i: _load_font_nodes) {
218                 auto file = p.parent_path() / i->uri;
219                 auto font_with_id = std::find_if(_fonts.begin(), _fonts.end(), [i](Font const& font) { return font.load_id == i->id; });
220                 if (font_with_id != _fonts.end()) {
221                         font_with_id->data.write(file);
222                         font_with_id->file = file;
223                 }
224         }
225 }
226
227
228 /** Look at a supplied list of assets and find the fonts.  Then match these
229  *  fonts up with anything requested by a <LoadFont> so that _fonts contains
230  *  a list of font ID, load ID and data.
231  */
232 void
233 InteropSubtitleAsset::resolve_fonts (vector<shared_ptr<Asset>> assets)
234 {
235         for (auto asset: assets) {
236                 auto font = dynamic_pointer_cast<FontAsset>(asset);
237                 if (!font) {
238                         continue;
239                 }
240
241                 DCP_ASSERT(_file);
242
243                 for (auto load_font_node: _load_font_nodes) {
244                         auto const path_in_load_font_node = _file->parent_path() / load_font_node->uri;
245                         if (font->file() && path_in_load_font_node == *font->file()) {
246                                 auto existing = std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; });
247                                 if (existing != _fonts.end()) {
248                                         *existing = Font(load_font_node->id, asset->id(), font->file().get());
249                                 } else {
250                                         _fonts.push_back(Font(load_font_node->id, asset->id(), font->file().get()));
251                                 }
252                         }
253                 }
254         }
255 }
256
257
258 vector<shared_ptr<Asset>>
259 InteropSubtitleAsset::font_assets()
260 {
261         vector<shared_ptr<Asset>> assets;
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         return assets;
267 }
268
269
270 vector<shared_ptr<const Asset>>
271 InteropSubtitleAsset::font_assets() const
272 {
273         vector<shared_ptr<const Asset>> assets;
274         for (auto const& i: _fonts) {
275                 DCP_ASSERT (i.file);
276                 assets.push_back(make_shared<const FontAsset>(i.uuid, i.file.get()));
277         }
278         return assets;
279 }
280
281
282 void
283 InteropSubtitleAsset::add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const
284 {
285         Asset::add_to_assetmap(asset_map, root);
286
287         for (auto i: _subtitles) {
288                 auto im = dynamic_pointer_cast<dcp::SubtitleImage>(i);
289                 if (im) {
290                         DCP_ASSERT(im->file());
291                         add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
292                 }
293         }
294 }
295
296
297 void
298 InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
299 {
300         Asset::add_to_pkl (pkl, root);
301
302         for (auto i: _subtitles) {
303                 auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
304                 if (im) {
305                         auto png_image = im->png_image ();
306                         pkl->add_asset(im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png", root.filename().string());
307                 }
308         }
309 }
310
311
312 void
313 InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path file)
314 {
315         for (auto& i: _fonts) {
316                 if (i.load_id == load_id) {
317                         i.file = file;
318                 }
319         }
320
321         for (auto i: _load_font_nodes) {
322                 if (i->id == load_id) {
323                         i->uri = file.filename().string();
324                 }
325         }
326 }
327
328
329 vector<string>
330 InteropSubtitleAsset::unresolved_fonts() const
331 {
332         vector<string> unresolved;
333         for (auto load_font_node: _load_font_nodes) {
334                 if (std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; }) == _fonts.end()) {
335                         unresolved.push_back(load_font_node->id);
336                 }
337         }
338         return unresolved;
339 }
340