Add some detail to a verification warning.
[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                 FILE* f = fopen_boost (file, "wb");
202                 if (!f) {
203                         throw FileError ("could not open font file for writing", file, errno);
204                 }
205                 list<Font>::const_iterator j = _fonts.begin ();
206                 while (j != _fonts.end() && j->load_id != i->id) {
207                         ++j;
208                 }
209                 if (j != _fonts.end ()) {
210                         fwrite (j->data.data().get(), 1, j->data.size(), f);
211                         j->file = file;
212                 }
213                 fclose (f);
214         }
215 }
216
217 /** Look at a supplied list of assets and find the fonts.  Then match these
218  *  fonts up with anything requested by a <LoadFont> so that _fonts contains
219  *  a list of font ID, load ID and data.
220  */
221 void
222 InteropSubtitleAsset::resolve_fonts (list<shared_ptr<Asset> > assets)
223 {
224         BOOST_FOREACH (shared_ptr<Asset> i, assets) {
225                 shared_ptr<FontAsset> font = dynamic_pointer_cast<FontAsset> (i);
226                 if (!font) {
227                         continue;
228                 }
229
230                 BOOST_FOREACH (shared_ptr<InteropLoadFontNode> j, _load_font_nodes) {
231                         bool got = false;
232                         BOOST_FOREACH (Font const & k, _fonts) {
233                                 if (k.load_id == j->id) {
234                                         got = true;
235                                         break;
236                                 }
237                         }
238
239                         if (!got && font->file() && j->uri == font->file()->leaf().string()) {
240                                 _fonts.push_back (Font (j->id, i->id(), font->file().get()));
241                         }
242                 }
243         }
244 }
245
246 void
247 InteropSubtitleAsset::add_font_assets (list<shared_ptr<Asset> >& assets)
248 {
249         BOOST_FOREACH (Font const & i, _fonts) {
250                 DCP_ASSERT (i.file);
251                 assets.push_back (shared_ptr<FontAsset> (new FontAsset (i.uuid, i.file.get ())));
252         }
253 }
254
255 void
256 InteropSubtitleAsset::write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const
257 {
258         Asset::write_to_assetmap (node, root);
259
260         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
261                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
262                 if (im) {
263                         DCP_ASSERT (im->file());
264                         write_file_to_assetmap (node, root, im->file().get(), im->id());
265                 }
266         }
267 }
268
269 void
270 InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
271 {
272         Asset::add_to_pkl (pkl, root);
273
274         BOOST_FOREACH (shared_ptr<dcp::Subtitle> i, _subtitles) {
275                 shared_ptr<dcp::SubtitleImage> im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
276                 if (im) {
277                         Data png_image = im->png_image ();
278                         pkl->add_asset (im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png");
279                 }
280         }
281 }