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