Accept fonts as data blocks rather than files.
[libdcp.git] / src / interop_subtitle_asset.cc
1 /*
2     Copyright (C) 2012-2018 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 #include "interop_subtitle_asset.h"
35 #include "interop_load_font_node.h"
36 #include "subtitle_asset_internal.h"
37 #include "xml.h"
38 #include "raw_convert.h"
39 #include "util.h"
40 #include "font_asset.h"
41 #include "dcp_assert.h"
42 #include "compose.hpp"
43 #include "subtitle_image.h"
44 #include <libxml++/libxml++.h>
45 #include <boost/foreach.hpp>
46 #include <boost/weak_ptr.hpp>
47 #include <cmath>
48 #include <cstdio>
49
50 using std::list;
51 using std::string;
52 using std::cout;
53 using std::cerr;
54 using std::map;
55 using boost::shared_ptr;
56 using boost::shared_array;
57 using boost::optional;
58 using boost::dynamic_pointer_cast;
59 using namespace dcp;
60
61 InteropSubtitleAsset::InteropSubtitleAsset (boost::filesystem::path file)
62         : SubtitleAsset (file)
63 {
64         _raw_xml = dcp::file_to_string (file);
65
66         shared_ptr<cxml::Document> xml (new cxml::Document ("DCSubtitle"));
67         xml->read_file (file);
68         _id = xml->string_child ("SubtitleID");
69         _reel_number = xml->string_child ("ReelNumber");
70         _language = xml->string_child ("Language");
71         _movie_title = xml->string_child ("MovieTitle");
72         _load_font_nodes = type_children<InteropLoadFontNode> (xml, "LoadFont");
73
74         /* Now we need to drop down to xmlpp */
75
76         list<ParseState> ps;
77         xmlpp::Node::NodeList c = xml->node()->get_children ();
78         for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
79                 xmlpp::Element const * e = dynamic_cast<xmlpp::Element const *> (*i);
80                 if (e && (e->get_name() == "Font" || e->get_name() == "Subtitle")) {
81                         parse_subtitles (e, ps, optional<int>(), INTEROP);
82                 }
83         }
84
85         BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
86                 shared_ptr<SubtitleImage> si = dynamic_pointer_cast<SubtitleImage>(i);
87                 if (si) {
88                         si->read_png_file (file.parent_path() / String::compose("%1.png", si->id()));
89                 }
90         }
91 }
92
93 InteropSubtitleAsset::InteropSubtitleAsset ()
94 {
95
96 }
97
98 string
99 InteropSubtitleAsset::xml_as_string () const
100 {
101         xmlpp::Document doc;
102         xmlpp::Element* root = doc.create_root_node ("DCSubtitle");
103         root->set_attribute ("Version", "1.0");
104
105         root->add_child("SubtitleID")->add_child_text (_id);
106         root->add_child("MovieTitle")->add_child_text (_movie_title);
107         root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
108         root->add_child("Language")->add_child_text (_language);
109
110         for (list<shared_ptr<InteropLoadFontNode> >::const_iterator i = _load_font_nodes.begin(); i != _load_font_nodes.end(); ++i) {
111                 xmlpp::Element* load_font = root->add_child("LoadFont");
112                 load_font->set_attribute ("Id", (*i)->id);
113                 load_font->set_attribute ("URI", (*i)->uri);
114         }
115
116         subtitles_as_xml (root, 250, INTEROP);
117
118         return doc.write_to_string ("UTF-8");
119 }
120
121 void
122 InteropSubtitleAsset::add_font (string load_id, dcp::ArrayData data)
123 {
124         _fonts.push_back (Font(load_id, make_uuid(), data));
125         string const uri = String::compose("font_%1.ttf", _load_font_nodes.size());
126         _load_font_nodes.push_back (shared_ptr<InteropLoadFontNode>(new InteropLoadFontNode(load_id, uri)));
127 }
128
129 bool
130 InteropSubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
131 {
132         if (!SubtitleAsset::equals (other_asset, options, note)) {
133                 return false;
134         }
135
136         shared_ptr<const InteropSubtitleAsset> other = dynamic_pointer_cast<const InteropSubtitleAsset> (other_asset);
137         if (!other) {
138                 return false;
139         }
140
141         if (!options.load_font_nodes_can_differ) {
142                 list<shared_ptr<InteropLoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
143                 list<shared_ptr<InteropLoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
144
145                 while (i != _load_font_nodes.end ()) {
146                         if (j == other->_load_font_nodes.end ()) {
147                                 note (DCP_ERROR, "<LoadFont> nodes differ");
148                                 return false;
149                         }
150
151                         if (**i != **j) {
152                                 note (DCP_ERROR, "<LoadFont> nodes differ");
153                                 return false;
154                         }
155
156                         ++i;
157                         ++j;
158                 }
159         }
160
161         if (_movie_title != other->_movie_title) {
162                 note (DCP_ERROR, "Subtitle movie titles differ");
163                 return false;
164         }
165
166         return true;
167 }
168
169 list<shared_ptr<LoadFontNode> >
170 InteropSubtitleAsset::load_font_nodes () const
171 {
172         list<shared_ptr<LoadFontNode> > lf;
173         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
174         return lf;
175 }
176
177 /** Write this content to an XML file with its fonts alongside */
178 void
179 InteropSubtitleAsset::write (boost::filesystem::path p) const
180 {
181         FILE* f = fopen_boost (p, "w");
182         if (!f) {
183                 throw FileError ("Could not open file for writing", p, -1);
184         }
185
186         string const s = xml_as_string ();
187         /* length() here gives bytes not characters */
188         fwrite (s.c_str(), 1, s.length(), f);
189         fclose (f);
190
191         _file = p;
192
193         /* Image subtitles */
194         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
195                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
196                 if (im) {
197                         im->write_png_file(p.parent_path() / String::compose("%1.png", im->id()));
198                 }
199         }
200
201         /* Fonts */
202         BOOST_FOREACH (shared_ptr<InteropLoadFontNode> i, _load_font_nodes) {
203                 boost::filesystem::path file = p.parent_path() / i->uri;
204                 list<Font>::const_iterator j = _fonts.begin ();
205                 while (j != _fonts.end() && j->load_id != i->id) {
206                         ++j;
207                 }
208                 if (j != _fonts.end ()) {
209                         j->data.write (file);
210                         j->file = file;
211                 }
212         }
213 }
214
215 /** Look at a supplied list of assets and find the fonts.  Then match these
216  *  fonts up with anything requested by a <LoadFont> so that _fonts contains
217  *  a list of font ID, load ID and data.
218  */
219 void
220 InteropSubtitleAsset::resolve_fonts (list<shared_ptr<Asset> > assets)
221 {
222         BOOST_FOREACH (shared_ptr<Asset> i, assets) {
223                 shared_ptr<FontAsset> font = dynamic_pointer_cast<FontAsset> (i);
224                 if (!font) {
225                         continue;
226                 }
227
228                 BOOST_FOREACH (shared_ptr<InteropLoadFontNode> j, _load_font_nodes) {
229                         bool got = false;
230                         BOOST_FOREACH (Font const & k, _fonts) {
231                                 if (k.load_id == j->id) {
232                                         got = true;
233                                         break;
234                                 }
235                         }
236
237                         if (!got && font->file() && j->uri == font->file()->leaf().string()) {
238                                 _fonts.push_back (Font (j->id, i->id(), font->file().get()));
239                         }
240                 }
241         }
242 }
243
244 void
245 InteropSubtitleAsset::add_font_assets (list<shared_ptr<Asset> >& assets)
246 {
247         BOOST_FOREACH (Font const & i, _fonts) {
248                 DCP_ASSERT (i.file);
249                 assets.push_back (shared_ptr<FontAsset> (new FontAsset (i.uuid, i.file.get ())));
250         }
251 }
252
253 void
254 InteropSubtitleAsset::write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const
255 {
256         Asset::write_to_assetmap (node, root);
257
258         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
259                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
260                 if (im) {
261                         DCP_ASSERT (im->file());
262                         write_file_to_assetmap (node, root, im->file().get(), im->id());
263                 }
264         }
265 }
266
267 void
268 InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
269 {
270         Asset::add_to_pkl (pkl, root);
271
272         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
273                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
274                 if (im) {
275                         ArrayData png_image = im->png_image ();
276                         pkl->add_asset (im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png");
277                 }
278         }
279 }
280
281
282 void
283 InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path file)
284 {
285         BOOST_FOREACH (Font& i, _fonts) {
286                 if (i.load_id == load_id) {
287                         i.file = file;
288                 }
289         }
290
291         BOOST_FOREACH (shared_ptr<InteropLoadFontNode> i, _load_font_nodes) {
292                 if (i->id == load_id) {
293                         i->uri = file.filename().string();
294                 }
295         }
296 }
297