Fix missing version string when Popen communicate returns byte strings.
[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         shared_ptr<cxml::Document> xml (new cxml::Document ("DCSubtitle"));
65         xml->read_file (file);
66         _id = xml->string_child ("SubtitleID");
67         _reel_number = xml->string_child ("ReelNumber");
68         _language = xml->string_child ("Language");
69         _movie_title = xml->string_child ("MovieTitle");
70         _load_font_nodes = type_children<InteropLoadFontNode> (xml, "LoadFont");
71
72         /* Now we need to drop down to xmlpp */
73
74         list<ParseState> ps;
75         xmlpp::Node::NodeList c = xml->node()->get_children ();
76         for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
77                 xmlpp::Element const * e = dynamic_cast<xmlpp::Element const *> (*i);
78                 if (e && (e->get_name() == "Font" || e->get_name() == "Subtitle")) {
79                         parse_subtitles (e, ps, optional<int>(), INTEROP);
80                 }
81         }
82
83         BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
84                 shared_ptr<SubtitleImage> si = dynamic_pointer_cast<SubtitleImage>(i);
85                 if (si) {
86                         si->read_png_file (file.parent_path() / String::compose("%1.png", si->id()));
87                 }
88         }
89 }
90
91 InteropSubtitleAsset::InteropSubtitleAsset ()
92 {
93
94 }
95
96 string
97 InteropSubtitleAsset::xml_as_string () const
98 {
99         xmlpp::Document doc;
100         xmlpp::Element* root = doc.create_root_node ("DCSubtitle");
101         root->set_attribute ("Version", "1.0");
102
103         root->add_child("SubtitleID")->add_child_text (_id);
104         root->add_child("MovieTitle")->add_child_text (_movie_title);
105         root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
106         root->add_child("Language")->add_child_text (_language);
107
108         for (list<shared_ptr<InteropLoadFontNode> >::const_iterator i = _load_font_nodes.begin(); i != _load_font_nodes.end(); ++i) {
109                 xmlpp::Element* load_font = root->add_child("LoadFont");
110                 load_font->set_attribute ("Id", (*i)->id);
111                 load_font->set_attribute ("URI", (*i)->uri);
112         }
113
114         subtitles_as_xml (root, 250, INTEROP);
115
116         return doc.write_to_string ("UTF-8");
117 }
118
119 void
120 InteropSubtitleAsset::add_font (string load_id, boost::filesystem::path file)
121 {
122         _fonts.push_back (Font (load_id, make_uuid(), file));
123         _load_font_nodes.push_back (shared_ptr<InteropLoadFontNode> (new InteropLoadFontNode (load_id, file.leaf().string ())));
124 }
125
126 bool
127 InteropSubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
128 {
129         if (!SubtitleAsset::equals (other_asset, options, note)) {
130                 return false;
131         }
132
133         shared_ptr<const InteropSubtitleAsset> other = dynamic_pointer_cast<const InteropSubtitleAsset> (other_asset);
134         if (!other) {
135                 return false;
136         }
137
138         list<shared_ptr<InteropLoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
139         list<shared_ptr<InteropLoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
140
141         while (i != _load_font_nodes.end ()) {
142                 if (j == other->_load_font_nodes.end ()) {
143                         note (DCP_ERROR, "<LoadFont> nodes differ");
144                         return false;
145                 }
146
147                 if (**i != **j) {
148                         note (DCP_ERROR, "<LoadFont> nodes differ");
149                         return false;
150                 }
151
152                 ++i;
153                 ++j;
154         }
155
156         if (_movie_title != other->_movie_title) {
157                 note (DCP_ERROR, "Subtitle movie titles differ");
158                 return false;
159         }
160
161         return true;
162 }
163
164 list<shared_ptr<LoadFontNode> >
165 InteropSubtitleAsset::load_font_nodes () const
166 {
167         list<shared_ptr<LoadFontNode> > lf;
168         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
169         return lf;
170 }
171
172 /** Write this content to an XML file with its fonts alongside */
173 void
174 InteropSubtitleAsset::write (boost::filesystem::path p) const
175 {
176         FILE* f = fopen_boost (p, "w");
177         if (!f) {
178                 throw FileError ("Could not open file for writing", p, -1);
179         }
180
181         string const s = xml_as_string ();
182         /* length() here gives bytes not characters */
183         fwrite (s.c_str(), 1, s.length(), f);
184         fclose (f);
185
186         _file = p;
187
188         /* Image subtitles */
189         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
190                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
191                 if (im) {
192                         im->write_png_file(p.parent_path() / String::compose("%1.png", im->id()));
193                 }
194         }
195
196         /* Fonts */
197         BOOST_FOREACH (shared_ptr<InteropLoadFontNode> i, _load_font_nodes) {
198                 boost::filesystem::path file = p.parent_path() / i->uri;
199                 FILE* f = fopen_boost (file, "wb");
200                 if (!f) {
201                         throw FileError ("could not open font file for writing", file, errno);
202                 }
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                         fwrite (j->data.data().get(), 1, j->data.size(), f);
209                         j->file = file;
210                 }
211                 fclose (f);
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                         Data 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 }