Tidying.
[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 "interop_subtitle_asset.h"
41 #include "interop_load_font_node.h"
42 #include "subtitle_asset_internal.h"
43 #include "xml.h"
44 #include "raw_convert.h"
45 #include "util.h"
46 #include "font_asset.h"
47 #include "dcp_assert.h"
48 #include "compose.hpp"
49 #include "subtitle_image.h"
50 #include <libxml++/libxml++.h>
51 #include <boost/weak_ptr.hpp>
52 #include <cmath>
53 #include <cstdio>
54
55
56 using std::list;
57 using std::string;
58 using std::cout;
59 using std::cerr;
60 using std::map;
61 using std::shared_ptr;
62 using std::dynamic_pointer_cast;
63 using std::vector;
64 using std::make_shared;
65 using boost::shared_array;
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         auto const s = xml_as_string ();
200         /* length() here gives bytes not characters */
201         fwrite (s.c_str(), 1, s.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
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 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