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