Add set_font_file()
[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, boost::filesystem::path file)
123 {
124         _fonts.push_back (Font (load_id, make_uuid(), file));
125         _load_font_nodes.push_back (shared_ptr<InteropLoadFontNode> (new InteropLoadFontNode (load_id, file.leaf().string ())));
126 }
127
128 bool
129 InteropSubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
130 {
131         if (!SubtitleAsset::equals (other_asset, options, note)) {
132                 return false;
133         }
134
135         shared_ptr<const InteropSubtitleAsset> other = dynamic_pointer_cast<const InteropSubtitleAsset> (other_asset);
136         if (!other) {
137                 return false;
138         }
139
140         if (!options.load_font_nodes_can_differ) {
141                 list<shared_ptr<InteropLoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
142                 list<shared_ptr<InteropLoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
143
144                 while (i != _load_font_nodes.end ()) {
145                         if (j == other->_load_font_nodes.end ()) {
146                                 note (DCP_ERROR, "<LoadFont> nodes differ");
147                                 return false;
148                         }
149
150                         if (**i != **j) {
151                                 note (DCP_ERROR, "<LoadFont> nodes differ");
152                                 return false;
153                         }
154
155                         ++i;
156                         ++j;
157                 }
158         }
159
160         if (_movie_title != other->_movie_title) {
161                 note (DCP_ERROR, "Subtitle movie titles differ");
162                 return false;
163         }
164
165         return true;
166 }
167
168 list<shared_ptr<LoadFontNode> >
169 InteropSubtitleAsset::load_font_nodes () const
170 {
171         list<shared_ptr<LoadFontNode> > lf;
172         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
173         return lf;
174 }
175
176 /** Write this content to an XML file with its fonts alongside */
177 void
178 InteropSubtitleAsset::write (boost::filesystem::path p) const
179 {
180         FILE* f = fopen_boost (p, "w");
181         if (!f) {
182                 throw FileError ("Could not open file for writing", p, -1);
183         }
184
185         string const s = xml_as_string ();
186         /* length() here gives bytes not characters */
187         fwrite (s.c_str(), 1, s.length(), f);
188         fclose (f);
189
190         _file = p;
191
192         /* Image subtitles */
193         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
194                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
195                 if (im) {
196                         im->write_png_file(p.parent_path() / String::compose("%1.png", im->id()));
197                 }
198         }
199
200         /* Fonts */
201         BOOST_FOREACH (shared_ptr<InteropLoadFontNode> i, _load_font_nodes) {
202                 boost::filesystem::path file = p.parent_path() / i->uri;
203                 list<Font>::const_iterator j = _fonts.begin ();
204                 while (j != _fonts.end() && j->load_id != i->id) {
205                         ++j;
206                 }
207                 if (j != _fonts.end ()) {
208                         j->data.write (file);
209                         j->file = file;
210                 }
211         }
212 }
213
214 /** Look at a supplied list of assets and find the fonts.  Then match these
215  *  fonts up with anything requested by a <LoadFont> so that _fonts contains
216  *  a list of font ID, load ID and data.
217  */
218 void
219 InteropSubtitleAsset::resolve_fonts (list<shared_ptr<Asset> > assets)
220 {
221         BOOST_FOREACH (shared_ptr<Asset> i, assets) {
222                 shared_ptr<FontAsset> font = dynamic_pointer_cast<FontAsset> (i);
223                 if (!font) {
224                         continue;
225                 }
226
227                 BOOST_FOREACH (shared_ptr<InteropLoadFontNode> j, _load_font_nodes) {
228                         bool got = false;
229                         BOOST_FOREACH (Font const & k, _fonts) {
230                                 if (k.load_id == j->id) {
231                                         got = true;
232                                         break;
233                                 }
234                         }
235
236                         if (!got && font->file() && j->uri == font->file()->leaf().string()) {
237                                 _fonts.push_back (Font (j->id, i->id(), font->file().get()));
238                         }
239                 }
240         }
241 }
242
243 void
244 InteropSubtitleAsset::add_font_assets (list<shared_ptr<Asset> >& assets)
245 {
246         BOOST_FOREACH (Font const & i, _fonts) {
247                 DCP_ASSERT (i.file);
248                 assets.push_back (shared_ptr<FontAsset> (new FontAsset (i.uuid, i.file.get ())));
249         }
250 }
251
252 void
253 InteropSubtitleAsset::write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const
254 {
255         Asset::write_to_assetmap (node, root);
256
257         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
258                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
259                 if (im) {
260                         DCP_ASSERT (im->file());
261                         write_file_to_assetmap (node, root, im->file().get(), im->id());
262                 }
263         }
264 }
265
266 void
267 InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
268 {
269         Asset::add_to_pkl (pkl, root);
270
271         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
272                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
273                 if (im) {
274                         Data png_image = im->png_image ();
275                         pkl->add_asset (im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png");
276                 }
277         }
278 }
279
280
281 void
282 InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path file)
283 {
284         BOOST_FOREACH (Font& i, _fonts) {
285                 if (i.load_id == load_id) {
286                         i.file = file;
287                 }
288         }
289
290         BOOST_FOREACH (shared_ptr<InteropLoadFontNode> i, _load_font_nodes) {
291                 if (i->id == load_id) {
292                         i->uri = file.filename().string();
293                 }
294         }
295 }
296