Trim some unused stream includes / usings.
[libdcp.git] / src / subtitle_asset.cc
1 /*
2     Copyright (C) 2012-2015 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 "raw_convert.h"
35 #include "subtitle_asset.h"
36 #include "util.h"
37 #include "xml.h"
38 #include "font_node.h"
39 #include "text_node.h"
40 #include "subtitle_string.h"
41 #include "dcp_assert.h"
42 #include <asdcp/AS_DCP.h>
43 #include <asdcp/KM_util.h>
44 #include <libxml++/nodes/element.h>
45 #include <boost/algorithm/string.hpp>
46 #include <boost/shared_array.hpp>
47 #include <boost/foreach.hpp>
48
49 using std::string;
50 using std::list;
51 using std::cout;
52 using std::cerr;
53 using std::map;
54 using boost::shared_ptr;
55 using boost::shared_array;
56 using boost::optional;
57 using boost::dynamic_pointer_cast;
58 using namespace dcp;
59
60 SubtitleAsset::SubtitleAsset ()
61 {
62
63 }
64
65 SubtitleAsset::SubtitleAsset (boost::filesystem::path file)
66         : Asset (file)
67 {
68
69 }
70
71 void
72 SubtitleAsset::parse_subtitles (
73         shared_ptr<cxml::Document> xml,
74         list<shared_ptr<dcp::FontNode> > font_nodes,
75         list<shared_ptr<dcp::SubtitleNode> > subtitle_nodes
76         )
77 {
78         /* Make Subtitle objects to represent the raw XML nodes in a sane way */
79         ParseState parse_state;
80         examine_nodes (xml, font_nodes, parse_state);
81         examine_nodes (xml, subtitle_nodes, parse_state);
82 }
83
84 void
85 SubtitleAsset::examine_nodes (
86         shared_ptr<const cxml::Node> xml,
87         list<shared_ptr<dcp::SubtitleNode> > const & subtitle_nodes,
88         ParseState& parse_state
89         )
90 {
91         BOOST_FOREACH (shared_ptr<dcp::SubtitleNode> i, subtitle_nodes) {
92                 parse_state.subtitle_nodes.push_back (i);
93                 examine_nodes (xml, i->text_nodes, parse_state);
94                 examine_nodes (xml, i->font_nodes, parse_state);
95                 parse_state.subtitle_nodes.pop_back ();
96         }
97 }
98
99 void
100 SubtitleAsset::examine_nodes (
101         shared_ptr<const cxml::Node> xml,
102         list<shared_ptr<dcp::FontNode> > const & font_nodes,
103         ParseState& parse_state
104         )
105 {
106         BOOST_FOREACH (shared_ptr<dcp::FontNode> i, font_nodes) {
107
108                 parse_state.font_nodes.push_back (i);
109                 maybe_add_subtitle (i->text, parse_state);
110
111                 examine_nodes (xml, i->subtitle_nodes, parse_state);
112                 examine_nodes (xml, i->font_nodes, parse_state);
113                 examine_nodes (xml, i->text_nodes, parse_state);
114
115                 parse_state.font_nodes.pop_back ();
116         }
117 }
118
119 void
120 SubtitleAsset::examine_nodes (
121         shared_ptr<const cxml::Node> xml,
122         list<shared_ptr<dcp::TextNode> > const & text_nodes,
123         ParseState& parse_state
124         )
125 {
126         BOOST_FOREACH (shared_ptr<dcp::TextNode> i, text_nodes) {
127                 parse_state.text_nodes.push_back (i);
128                 maybe_add_subtitle (i->text, parse_state);
129                 examine_nodes (xml, i->font_nodes, parse_state);
130                 parse_state.text_nodes.pop_back ();
131         }
132 }
133
134 void
135 SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state)
136 {
137         if (empty_or_white_space (text)) {
138                 return;
139         }
140
141         if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) {
142                 return;
143         }
144
145         DCP_ASSERT (!parse_state.text_nodes.empty ());
146         DCP_ASSERT (!parse_state.subtitle_nodes.empty ());
147
148         dcp::FontNode effective_font (parse_state.font_nodes);
149         dcp::TextNode effective_text (*parse_state.text_nodes.back ());
150         dcp::SubtitleNode effective_subtitle (*parse_state.subtitle_nodes.back ());
151
152         _subtitles.push_back (
153                 SubtitleString (
154                         effective_font.id,
155                         effective_font.italic.get_value_or (false),
156                         effective_font.bold.get_value_or (false),
157                         effective_font.underline.get_value_or (false),
158                         effective_font.colour.get_value_or (dcp::Colour (255, 255, 255)),
159                         effective_font.size,
160                         effective_font.aspect_adjust.get_value_or (1.0),
161                         effective_subtitle.in,
162                         effective_subtitle.out,
163                         effective_text.h_position,
164                         effective_text.h_align,
165                         effective_text.v_position,
166                         effective_text.v_align,
167                         effective_text.direction,
168                         text,
169                         effective_font.effect.get_value_or (NONE),
170                         effective_font.effect_colour.get_value_or (dcp::Colour (0, 0, 0)),
171                         effective_subtitle.fade_up_time,
172                         effective_subtitle.fade_down_time
173                         )
174                 );
175 }
176
177 list<SubtitleString>
178 SubtitleAsset::subtitles_during (Time from, Time to, bool starting) const
179 {
180         list<SubtitleString> s;
181         BOOST_FOREACH (SubtitleString const & i, _subtitles) {
182                 if ((starting && from <= i.in() && i.in() < to) || (!starting && i.out() >= from && i.in() <= to)) {
183                         s.push_back (i);
184                 }
185         }
186
187         return s;
188 }
189
190 void
191 SubtitleAsset::add (SubtitleString s)
192 {
193         _subtitles.push_back (s);
194 }
195
196 Time
197 SubtitleAsset::latest_subtitle_out () const
198 {
199         Time t;
200         BOOST_FOREACH (SubtitleString const & i, _subtitles) {
201                 if (i.out() > t) {
202                         t = i.out ();
203                 }
204         }
205
206         return t;
207 }
208
209 bool
210 SubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
211 {
212         if (!Asset::equals (other_asset, options, note)) {
213                 return false;
214         }
215
216         shared_ptr<const SubtitleAsset> other = dynamic_pointer_cast<const SubtitleAsset> (other_asset);
217         if (!other) {
218                 return false;
219         }
220
221         if (_subtitles != other->_subtitles) {
222                 note (DCP_ERROR, "subtitles differ");
223                 return false;
224         }
225
226         return true;
227 }
228
229 struct SubtitleSorter {
230         bool operator() (SubtitleString const & a, SubtitleString const & b) {
231                 if (a.in() != b.in()) {
232                         return a.in() < b.in();
233                 }
234                 return a.v_position() < b.v_position();
235         }
236 };
237
238 /** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child
239  *  class because the differences between the two are fairly subtle.
240  */
241 void
242 SubtitleAsset::subtitles_as_xml (xmlpp::Element* root, int time_code_rate, Standard standard) const
243 {
244         list<SubtitleString> sorted = _subtitles;
245         sorted.sort (SubtitleSorter ());
246
247         string const xmlns = standard == SMPTE ? "dcst" : "";
248
249         /* XXX: script not supported */
250
251         optional<string> font;
252         bool italic = false;
253         bool bold = false;
254         bool underline = false;
255         Colour colour;
256         int size = 0;
257         float aspect_adjust = 1.0;
258         Effect effect = NONE;
259         Colour effect_colour;
260         int spot_number = 1;
261         Time last_in;
262         Time last_out;
263         Time last_fade_up_time;
264         Time last_fade_down_time;
265
266         xmlpp::Element* font_element = 0;
267         xmlpp::Element* subtitle_element = 0;
268
269         BOOST_FOREACH (SubtitleString const & i, sorted) {
270
271                 /* We will start a new <Font>...</Font> whenever some font property changes.
272                    I suppose we should really make an optimal hierarchy of <Font> tags, but
273                    that seems hard.
274                 */
275
276                 bool const font_changed =
277                         font          != i.font()          ||
278                         italic        != i.italic()        ||
279                         bold          != i.bold()          ||
280                         underline     != i.underline()     ||
281                         colour        != i.colour()        ||
282                         size          != i.size()          ||
283                         fabs (aspect_adjust - i.aspect_adjust()) > ASPECT_ADJUST_EPSILON ||
284                         effect        != i.effect()        ||
285                         effect_colour != i.effect_colour();
286
287                 if (font_changed) {
288                         font = i.font ();
289                         italic = i.italic ();
290                         bold = i.bold ();
291                         underline = i.underline ();
292                         colour = i.colour ();
293                         size = i.size ();
294                         aspect_adjust = i.aspect_adjust ();
295                         effect = i.effect ();
296                         effect_colour = i.effect_colour ();
297                 }
298
299                 if (!font_element || font_changed) {
300                         font_element = root->add_child ("Font", xmlns);
301                         if (font) {
302                                 if (standard == SMPTE) {
303                                         font_element->set_attribute ("ID", font.get ());
304                                 } else {
305                                         font_element->set_attribute ("Id", font.get ());
306                                 }
307                         }
308                         font_element->set_attribute ("Italic", italic ? "yes" : "no");
309                         font_element->set_attribute ("Color", colour.to_argb_string());
310                         font_element->set_attribute ("Size", raw_convert<string> (size));
311                         if (fabs (aspect_adjust - 1.0) > ASPECT_ADJUST_EPSILON) {
312                                 font_element->set_attribute ("AspectAdjust", raw_convert<string> (aspect_adjust));
313                         }
314                         font_element->set_attribute ("Effect", effect_to_string (effect));
315                         font_element->set_attribute ("EffectColor", effect_colour.to_argb_string());
316                         font_element->set_attribute ("Script", "normal");
317                         if (standard == SMPTE) {
318                                 font_element->set_attribute ("Underline", underline ? "yes" : "no");
319                         } else {
320                                 font_element->set_attribute ("Underlined", underline ? "yes" : "no");
321                         }
322                         font_element->set_attribute ("Weight", bold ? "bold" : "normal");
323                 }
324
325                 if (!subtitle_element || font_changed ||
326                     (last_in != i.in() ||
327                      last_out != i.out() ||
328                      last_fade_up_time != i.fade_up_time() ||
329                      last_fade_down_time != i.fade_down_time()
330                             )) {
331
332                         subtitle_element = font_element->add_child ("Subtitle", xmlns);
333                         subtitle_element->set_attribute ("SpotNumber", raw_convert<string> (spot_number++));
334                         subtitle_element->set_attribute ("TimeIn", i.in().rebase(time_code_rate).as_string(standard));
335                         subtitle_element->set_attribute ("TimeOut", i.out().rebase(time_code_rate).as_string(standard));
336                         if (standard == SMPTE) {
337                                 subtitle_element->set_attribute ("FadeUpTime", i.fade_up_time().rebase(time_code_rate).as_string(standard));
338                                 subtitle_element->set_attribute ("FadeDownTime", i.fade_down_time().rebase(time_code_rate).as_string(standard));
339                         } else {
340                                 subtitle_element->set_attribute ("FadeUpTime", raw_convert<string> (i.fade_up_time().as_editable_units(time_code_rate)));
341                                 subtitle_element->set_attribute ("FadeDownTime", raw_convert<string> (i.fade_down_time().as_editable_units(time_code_rate)));
342                         }
343
344                         last_in = i.in ();
345                         last_out = i.out ();
346                         last_fade_up_time = i.fade_up_time ();
347                         last_fade_down_time = i.fade_down_time ();
348                 }
349
350                 xmlpp::Element* text = subtitle_element->add_child ("Text", xmlns);
351
352                 if (i.h_align() != HALIGN_CENTER) {
353                         if (standard == SMPTE) {
354                                 text->set_attribute ("Halign", halign_to_string (i.h_align ()));
355                         } else {
356                                 text->set_attribute ("HAlign", halign_to_string (i.h_align ()));
357                         }
358                 }
359
360                 if (i.h_position() > ALIGN_EPSILON) {
361                         if (standard == SMPTE) {
362                                 text->set_attribute ("Hposition", raw_convert<string> (i.h_position() * 100, 6));
363                         } else {
364                                 text->set_attribute ("HPosition", raw_convert<string> (i.h_position() * 100, 6));
365                         }
366                 }
367
368                 if (standard == SMPTE) {
369                         text->set_attribute ("Valign", valign_to_string (i.v_align()));
370                 } else {
371                         text->set_attribute ("VAlign", valign_to_string (i.v_align()));
372                 }
373
374                 if (i.v_position() > ALIGN_EPSILON) {
375                         if (standard == SMPTE) {
376                                 text->set_attribute ("Vposition", raw_convert<string> (i.v_position() * 100, 6));
377                         } else {
378                                 text->set_attribute ("VPosition", raw_convert<string> (i.v_position() * 100, 6));
379                         }
380                 } else {
381                         if (standard == SMPTE) {
382                                 text->set_attribute ("Vposition", "0");
383                         } else {
384                                 text->set_attribute ("VPosition", "0");
385                         }
386                 }
387
388                 /* Interop only supports "horizontal" or "vertical" for direction, so only write this
389                    for SMPTE.
390                 */
391                 if (i.direction() != DIRECTION_LTR && standard == SMPTE) {
392                         text->set_attribute ("Direction", direction_to_string (i.direction ()));
393                 }
394
395                 text->add_child_text (i.text());
396         }
397 }
398
399 map<string, Data>
400 SubtitleAsset::fonts_with_load_ids () const
401 {
402         map<string, Data> out;
403         BOOST_FOREACH (Font const & i, _fonts) {
404                 out[i.load_id] = i.data;
405         }
406         return out;
407 }