0116c11c539a1a2d0390a6ac1003a6af9f8427b5
[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 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         _fonts.clear();
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                                 _fonts.push_back(Font(load_font_node->id, asset->id(), font->file().get()));
247                         }
248                 }
249         }
250 }
251
252
253 vector<shared_ptr<Asset>>
254 InteropSubtitleAsset::font_assets()
255 {
256         vector<shared_ptr<Asset>> assets;
257         for (auto const& i: _fonts) {
258                 DCP_ASSERT (i.file);
259                 assets.push_back(make_shared<FontAsset>(i.uuid, i.file.get()));
260         }
261         return assets;
262 }
263
264
265 vector<shared_ptr<const Asset>>
266 InteropSubtitleAsset::font_assets() const
267 {
268         vector<shared_ptr<const Asset>> assets;
269         for (auto const& i: _fonts) {
270                 DCP_ASSERT (i.file);
271                 assets.push_back(make_shared<const FontAsset>(i.uuid, i.file.get()));
272         }
273         return assets;
274 }
275
276
277 void
278 InteropSubtitleAsset::add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const
279 {
280         Asset::add_to_assetmap(asset_map, root);
281
282         for (auto i: _subtitles) {
283                 auto im = dynamic_pointer_cast<dcp::SubtitleImage>(i);
284                 if (im) {
285                         DCP_ASSERT(im->file());
286                         add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
287                 }
288         }
289 }
290
291
292 void
293 InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
294 {
295         Asset::add_to_pkl (pkl, root);
296
297         for (auto i: _subtitles) {
298                 auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
299                 if (im) {
300                         auto png_image = im->png_image ();
301                         pkl->add_asset(im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png", root.filename().string());
302                 }
303         }
304 }
305
306
307 void
308 InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path file)
309 {
310         for (auto& i: _fonts) {
311                 if (i.load_id == load_id) {
312                         i.file = file;
313                 }
314         }
315
316         for (auto i: _load_font_nodes) {
317                 if (i->id == load_id) {
318                         i->uri = file.filename().string();
319                 }
320         }
321 }
322
323
324 vector<string>
325 InteropSubtitleAsset::unresolved_fonts() const
326 {
327         vector<string> unresolved;
328         for (auto load_font_node: _load_font_nodes) {
329                 if (std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; }) == _fonts.end()) {
330                         unresolved.push_back(load_font_node->id);
331                 }
332         }
333         return unresolved;
334 }
335