Fix signed-ness warning on macOS.
[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 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
229 /** Look at a supplied list of assets and find the fonts.  Then match these
230  *  fonts up with anything requested by a <LoadFont> so that _fonts contains
231  *  a list of font ID, load ID and data.
232  */
233 void
234 InteropSubtitleAsset::resolve_fonts (vector<shared_ptr<Asset>> assets)
235 {
236         for (auto asset: assets) {
237                 auto font = dynamic_pointer_cast<FontAsset>(asset);
238                 if (!font) {
239                         continue;
240                 }
241
242                 for (auto load_font_node: _load_font_nodes) {
243                         auto iter = std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; });
244
245                         DCP_ASSERT(_file);
246                         auto const path_in_load_font_node = _file->parent_path() / load_font_node->uri;
247
248                         if (iter == _fonts.end() && font->file() && path_in_load_font_node == *font->file()) {
249                                 _fonts.push_back(Font(load_font_node->id, asset->id(), font->file().get()));
250                         }
251                 }
252         }
253 }
254
255
256 void
257 InteropSubtitleAsset::add_font_assets (vector<shared_ptr<Asset>>& assets)
258 {
259         for (auto const& i: _fonts) {
260                 DCP_ASSERT (i.file);
261                 assets.push_back (make_shared<FontAsset>(i.uuid, i.file.get()));
262         }
263 }
264
265
266 void
267 InteropSubtitleAsset::add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const
268 {
269         Asset::add_to_assetmap(asset_map, root);
270
271         for (auto i: _subtitles) {
272                 auto im = dynamic_pointer_cast<dcp::SubtitleImage>(i);
273                 if (im) {
274                         DCP_ASSERT(im->file());
275                         add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
276                 }
277         }
278 }
279
280
281 void
282 InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
283 {
284         Asset::add_to_pkl (pkl, root);
285
286         for (auto i: _subtitles) {
287                 auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
288                 if (im) {
289                         auto png_image = im->png_image ();
290                         pkl->add_asset(im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png", root.filename().string());
291                 }
292         }
293 }
294
295
296 void
297 InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path file)
298 {
299         for (auto& i: _fonts) {
300                 if (i.load_id == load_id) {
301                         i.file = file;
302                 }
303         }
304
305         for (auto i: _load_font_nodes) {
306                 if (i->id == load_id) {
307                         i->uri = file.filename().string();
308                 }
309         }
310 }
311