Cleanup: pass EqualityOptions as const&
[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 "font_asset.h"
43 #include "file.h"
44 #include "interop_load_font_node.h"
45 #include "interop_subtitle_asset.h"
46 #include "raw_convert.h"
47 #include "subtitle_asset_internal.h"
48 #include "subtitle_image.h"
49 #include "util.h"
50 #include "warnings.h"
51 #include "xml.h"
52 LIBDCP_DISABLE_WARNINGS
53 #include <libxml++/libxml++.h>
54 LIBDCP_ENABLE_WARNINGS
55 #include <boost/weak_ptr.hpp>
56 #include <cmath>
57 #include <cstdio>
58
59
60 using std::cerr;
61 using std::cout;
62 using std::dynamic_pointer_cast;
63 using std::make_shared;
64 using std::shared_ptr;
65 using std::string;
66 using std::vector;
67 using boost::optional;
68 using namespace dcp;
69
70
71 InteropSubtitleAsset::InteropSubtitleAsset (boost::filesystem::path file)
72         : SubtitleAsset (file)
73 {
74         _raw_xml = dcp::file_to_string (file);
75
76         auto xml = make_shared<cxml::Document>("DCSubtitle");
77         xml->read_file (file);
78         _id = xml->string_child ("SubtitleID");
79         _reel_number = xml->string_child ("ReelNumber");
80         _language = xml->string_child ("Language");
81         _movie_title = xml->string_child ("MovieTitle");
82         _load_font_nodes = type_children<InteropLoadFontNode> (xml, "LoadFont");
83
84         /* Now we need to drop down to xmlpp */
85
86         vector<ParseState> ps;
87         for (auto i: xml->node()->get_children()) {
88                 auto e = dynamic_cast<xmlpp::Element const *>(i);
89                 if (e && (e->get_name() == "Font" || e->get_name() == "Subtitle")) {
90                         parse_subtitles (e, ps, optional<int>(), Standard::INTEROP);
91                 }
92         }
93
94         for (auto i: _subtitles) {
95                 auto si = dynamic_pointer_cast<SubtitleImage>(i);
96                 if (si) {
97                         si->read_png_file (file.parent_path() / String::compose("%1.png", si->id()));
98                 }
99         }
100 }
101
102
103 InteropSubtitleAsset::InteropSubtitleAsset ()
104 {
105
106 }
107
108
109 string
110 InteropSubtitleAsset::xml_as_string () const
111 {
112         xmlpp::Document doc;
113         auto root = doc.create_root_node ("DCSubtitle");
114         root->set_attribute ("Version", "1.0");
115
116         root->add_child("SubtitleID")->add_child_text (_id);
117         root->add_child("MovieTitle")->add_child_text (_movie_title);
118         root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
119         root->add_child("Language")->add_child_text (_language);
120
121         for (auto i: _load_font_nodes) {
122                 auto load_font = root->add_child("LoadFont");
123                 load_font->set_attribute ("Id", i->id);
124                 load_font->set_attribute ("URI", i->uri);
125         }
126
127         subtitles_as_xml (root, 250, Standard::INTEROP);
128
129         return format_xml(doc, {});
130 }
131
132
133 void
134 InteropSubtitleAsset::add_font (string load_id, dcp::ArrayData data)
135 {
136         _fonts.push_back (Font(load_id, make_uuid(), data));
137         auto const uri = String::compose("font_%1.ttf", _load_font_nodes.size());
138         _load_font_nodes.push_back (make_shared<InteropLoadFontNode>(load_id, uri));
139 }
140
141
142 bool
143 InteropSubtitleAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
144 {
145         if (!SubtitleAsset::equals (other_asset, options, note)) {
146                 return false;
147         }
148
149         auto other = dynamic_pointer_cast<const InteropSubtitleAsset> (other_asset);
150         if (!other) {
151                 return false;
152         }
153
154         if (!options.load_font_nodes_can_differ) {
155                 auto i = _load_font_nodes.begin();
156                 auto j = other->_load_font_nodes.begin();
157
158                 while (i != _load_font_nodes.end ()) {
159                         if (j == other->_load_font_nodes.end ()) {
160                                 note (NoteType::ERROR, "<LoadFont> nodes differ");
161                                 return false;
162                         }
163
164                         if (**i != **j) {
165                                 note (NoteType::ERROR, "<LoadFont> nodes differ");
166                                 return false;
167                         }
168
169                         ++i;
170                         ++j;
171                 }
172         }
173
174         if (_movie_title != other->_movie_title) {
175                 note (NoteType::ERROR, "Subtitle movie titles differ");
176                 return false;
177         }
178
179         return true;
180 }
181
182
183 vector<shared_ptr<LoadFontNode>>
184 InteropSubtitleAsset::load_font_nodes () const
185 {
186         vector<shared_ptr<LoadFontNode>> lf;
187         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
188         return lf;
189 }
190
191
192 void
193 InteropSubtitleAsset::write (boost::filesystem::path p) const
194 {
195         File f(p, "wb");
196         if (!f) {
197                 throw FileError ("Could not open file for writing", p, -1);
198         }
199
200         _raw_xml = xml_as_string ();
201         /* length() here gives bytes not characters */
202         f.write(_raw_xml->c_str(), 1, _raw_xml->length());
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 font_with_id = std::find_if(_fonts.begin(), _fonts.end(), [i](Font const& font) { return font.load_id == i->id; });
218                 if (font_with_id != _fonts.end()) {
219                         font_with_id->data.write(file);
220                         font_with_id->file = file;
221                 }
222         }
223 }
224
225
226 /** Look at a supplied list of assets and find the fonts.  Then match these
227  *  fonts up with anything requested by a <LoadFont> so that _fonts contains
228  *  a list of font ID, load ID and data.
229  */
230 void
231 InteropSubtitleAsset::resolve_fonts (vector<shared_ptr<Asset>> assets)
232 {
233         for (auto asset: assets) {
234                 auto font = dynamic_pointer_cast<FontAsset>(asset);
235                 if (!font) {
236                         continue;
237                 }
238
239                 DCP_ASSERT(_file);
240
241                 for (auto load_font_node: _load_font_nodes) {
242                         auto const path_in_load_font_node = _file->parent_path() / load_font_node->uri;
243                         if (font->file() && path_in_load_font_node == *font->file()) {
244                                 auto existing = std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; });
245                                 if (existing != _fonts.end()) {
246                                         *existing = Font(load_font_node->id, asset->id(), font->file().get());
247                                 } else {
248                                         _fonts.push_back(Font(load_font_node->id, asset->id(), font->file().get()));
249                                 }
250                         }
251                 }
252         }
253 }
254
255
256 vector<shared_ptr<Asset>>
257 InteropSubtitleAsset::font_assets()
258 {
259         vector<shared_ptr<Asset>> assets;
260         for (auto const& i: _fonts) {
261                 DCP_ASSERT (i.file);
262                 assets.push_back(make_shared<FontAsset>(i.uuid, i.file.get()));
263         }
264         return assets;
265 }
266
267
268 vector<shared_ptr<const Asset>>
269 InteropSubtitleAsset::font_assets() const
270 {
271         vector<shared_ptr<const Asset>> assets;
272         for (auto const& i: _fonts) {
273                 DCP_ASSERT (i.file);
274                 assets.push_back(make_shared<const FontAsset>(i.uuid, i.file.get()));
275         }
276         return assets;
277 }
278
279
280 void
281 InteropSubtitleAsset::add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const
282 {
283         Asset::add_to_assetmap(asset_map, root);
284
285         for (auto i: _subtitles) {
286                 auto im = dynamic_pointer_cast<dcp::SubtitleImage>(i);
287                 if (im) {
288                         DCP_ASSERT(im->file());
289                         add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
290                 }
291         }
292 }
293
294
295 void
296 InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
297 {
298         Asset::add_to_pkl (pkl, root);
299
300         for (auto i: _subtitles) {
301                 auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
302                 if (im) {
303                         auto png_image = im->png_image ();
304                         pkl->add_asset(im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png", root.filename().string());
305                 }
306         }
307 }
308
309
310 void
311 InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path file)
312 {
313         for (auto& i: _fonts) {
314                 if (i.load_id == load_id) {
315                         i.file = file;
316                 }
317         }
318
319         for (auto i: _load_font_nodes) {
320                 if (i->id == load_id) {
321                         i->uri = file.filename().string();
322                 }
323         }
324 }
325
326
327 vector<string>
328 InteropSubtitleAsset::unresolved_fonts() const
329 {
330         vector<string> unresolved;
331         for (auto load_font_node: _load_font_nodes) {
332                 if (std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; }) == _fonts.end()) {
333                         unresolved.push_back(load_font_node->id);
334                 }
335         }
336         return unresolved;
337 }
338