2 Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
34 #include "raw_convert.h"
35 #include "subtitle_asset.h"
38 #include "font_node.h"
39 #include "text_node.h"
40 #include "subtitle_string.h"
41 #include "dcp_assert.h"
44 #include <libxml++/nodes/element.h>
45 #include <boost/algorithm/string.hpp>
46 #include <boost/shared_array.hpp>
47 #include <boost/foreach.hpp>
54 using std::stringstream;
58 using boost::shared_ptr;
59 using boost::shared_array;
60 using boost::optional;
61 using boost::dynamic_pointer_cast;
64 SubtitleAsset::SubtitleAsset ()
69 SubtitleAsset::SubtitleAsset (boost::filesystem::path file)
76 SubtitleAsset::parse_subtitles (
77 shared_ptr<cxml::Document> xml,
78 list<shared_ptr<dcp::FontNode> > font_nodes,
79 list<shared_ptr<dcp::SubtitleNode> > subtitle_nodes
82 /* Make Subtitle objects to represent the raw XML nodes in a sane way */
83 ParseState parse_state;
84 examine_nodes (xml, font_nodes, parse_state);
85 examine_nodes (xml, subtitle_nodes, parse_state);
89 SubtitleAsset::examine_nodes (
90 shared_ptr<const cxml::Node> xml,
91 list<shared_ptr<dcp::SubtitleNode> > const & subtitle_nodes,
92 ParseState& parse_state
95 BOOST_FOREACH (shared_ptr<dcp::SubtitleNode> i, subtitle_nodes) {
96 parse_state.subtitle_nodes.push_back (i);
97 examine_nodes (xml, i->text_nodes, parse_state);
98 examine_nodes (xml, i->font_nodes, parse_state);
99 parse_state.subtitle_nodes.pop_back ();
104 SubtitleAsset::examine_nodes (
105 shared_ptr<const cxml::Node> xml,
106 list<shared_ptr<dcp::FontNode> > const & font_nodes,
107 ParseState& parse_state
110 BOOST_FOREACH (shared_ptr<dcp::FontNode> i, font_nodes) {
112 parse_state.font_nodes.push_back (i);
113 maybe_add_subtitle (i->text, parse_state);
115 examine_nodes (xml, i->subtitle_nodes, parse_state);
116 examine_nodes (xml, i->font_nodes, parse_state);
117 examine_nodes (xml, i->text_nodes, parse_state);
119 parse_state.font_nodes.pop_back ();
124 SubtitleAsset::examine_nodes (
125 shared_ptr<const cxml::Node> xml,
126 list<shared_ptr<dcp::TextNode> > const & text_nodes,
127 ParseState& parse_state
130 BOOST_FOREACH (shared_ptr<dcp::TextNode> i, text_nodes) {
131 parse_state.text_nodes.push_back (i);
132 maybe_add_subtitle (i->text, parse_state);
133 examine_nodes (xml, i->font_nodes, parse_state);
134 parse_state.text_nodes.pop_back ();
139 SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state)
141 if (empty_or_white_space (text)) {
145 if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) {
149 DCP_ASSERT (!parse_state.text_nodes.empty ());
150 DCP_ASSERT (!parse_state.subtitle_nodes.empty ());
152 dcp::FontNode effective_font (parse_state.font_nodes);
153 dcp::TextNode effective_text (*parse_state.text_nodes.back ());
154 dcp::SubtitleNode effective_subtitle (*parse_state.subtitle_nodes.back ());
156 _subtitles.push_back (
159 effective_font.italic.get_value_or (false),
160 effective_font.bold.get_value_or (false),
161 effective_font.underline.get_value_or (false),
162 effective_font.colour.get_value_or (dcp::Colour (255, 255, 255)),
164 effective_font.aspect_adjust.get_value_or (1.0),
165 effective_subtitle.in,
166 effective_subtitle.out,
167 effective_text.h_position,
168 effective_text.h_align,
169 effective_text.v_position,
170 effective_text.v_align,
171 effective_text.direction,
173 effective_font.effect.get_value_or (NONE),
174 effective_font.effect_colour.get_value_or (dcp::Colour (0, 0, 0)),
175 effective_subtitle.fade_up_time,
176 effective_subtitle.fade_down_time
182 SubtitleAsset::subtitles_during (Time from, Time to, bool starting) const
184 list<SubtitleString> s;
185 BOOST_FOREACH (SubtitleString const & i, _subtitles) {
186 if ((starting && from <= i.in() && i.in() < to) || (!starting && i.out() >= from && i.in() <= to)) {
195 SubtitleAsset::add (SubtitleString s)
197 _subtitles.push_back (s);
201 SubtitleAsset::latest_subtitle_out () const
204 BOOST_FOREACH (SubtitleString const & i, _subtitles) {
214 SubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
216 if (!Asset::equals (other_asset, options, note)) {
220 shared_ptr<const SubtitleAsset> other = dynamic_pointer_cast<const SubtitleAsset> (other_asset);
225 if (_subtitles != other->_subtitles) {
226 note (DCP_ERROR, "subtitles differ");
233 struct SubtitleSorter {
234 bool operator() (SubtitleString const & a, SubtitleString const & b) {
235 if (a.in() != b.in()) {
236 return a.in() < b.in();
238 return a.v_position() < b.v_position();
242 /** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child
243 * class because the differences between the two are fairly subtle.
246 SubtitleAsset::subtitles_as_xml (xmlpp::Element* root, int time_code_rate, Standard standard) const
248 list<SubtitleString> sorted = _subtitles;
249 sorted.sort (SubtitleSorter ());
251 string const xmlns = standard == SMPTE ? "dcst" : "";
253 /* XXX: script not supported */
255 optional<string> font;
258 bool underline = false;
261 float aspect_adjust = 1.0;
262 Effect effect = NONE;
263 Colour effect_colour;
267 Time last_fade_up_time;
268 Time last_fade_down_time;
270 xmlpp::Element* font_element = 0;
271 xmlpp::Element* subtitle_element = 0;
273 BOOST_FOREACH (SubtitleString const & i, sorted) {
275 /* We will start a new <Font>...</Font> whenever some font property changes.
276 I suppose we should really make an optimal hierarchy of <Font> tags, but
280 bool const font_changed =
282 italic != i.italic() ||
284 underline != i.underline() ||
285 colour != i.colour() ||
287 fabs (aspect_adjust - i.aspect_adjust()) > ASPECT_ADJUST_EPSILON ||
288 effect != i.effect() ||
289 effect_colour != i.effect_colour();
293 italic = i.italic ();
295 underline = i.underline ();
296 colour = i.colour ();
298 aspect_adjust = i.aspect_adjust ();
299 effect = i.effect ();
300 effect_colour = i.effect_colour ();
303 if (!font_element || font_changed) {
304 font_element = root->add_child ("Font", xmlns);
306 if (standard == SMPTE) {
307 font_element->set_attribute ("ID", font.get ());
309 font_element->set_attribute ("Id", font.get ());
312 font_element->set_attribute ("Italic", italic ? "yes" : "no");
313 font_element->set_attribute ("Color", colour.to_argb_string());
314 font_element->set_attribute ("Size", raw_convert<string> (size));
315 if (fabs (aspect_adjust - 1.0) > ASPECT_ADJUST_EPSILON) {
316 font_element->set_attribute ("AspectAdjust", raw_convert<string> (aspect_adjust));
318 font_element->set_attribute ("Effect", effect_to_string (effect));
319 font_element->set_attribute ("EffectColor", effect_colour.to_argb_string());
320 font_element->set_attribute ("Script", "normal");
321 if (standard == SMPTE) {
322 font_element->set_attribute ("Underline", underline ? "yes" : "no");
324 font_element->set_attribute ("Underlined", underline ? "yes" : "no");
326 font_element->set_attribute ("Weight", bold ? "bold" : "normal");
329 if (!subtitle_element || font_changed ||
330 (last_in != i.in() ||
331 last_out != i.out() ||
332 last_fade_up_time != i.fade_up_time() ||
333 last_fade_down_time != i.fade_down_time()
336 subtitle_element = font_element->add_child ("Subtitle", xmlns);
337 subtitle_element->set_attribute ("SpotNumber", raw_convert<string> (spot_number++));
338 subtitle_element->set_attribute ("TimeIn", i.in().rebase(time_code_rate).as_string(standard));
339 subtitle_element->set_attribute ("TimeOut", i.out().rebase(time_code_rate).as_string(standard));
340 if (standard == SMPTE) {
341 subtitle_element->set_attribute ("FadeUpTime", i.fade_up_time().rebase(time_code_rate).as_string(standard));
342 subtitle_element->set_attribute ("FadeDownTime", i.fade_down_time().rebase(time_code_rate).as_string(standard));
344 subtitle_element->set_attribute ("FadeUpTime", raw_convert<string> (i.fade_up_time().as_editable_units(time_code_rate)));
345 subtitle_element->set_attribute ("FadeDownTime", raw_convert<string> (i.fade_down_time().as_editable_units(time_code_rate)));
350 last_fade_up_time = i.fade_up_time ();
351 last_fade_down_time = i.fade_down_time ();
354 xmlpp::Element* text = subtitle_element->add_child ("Text", xmlns);
356 if (i.h_align() != HALIGN_CENTER) {
357 if (standard == SMPTE) {
358 text->set_attribute ("Halign", halign_to_string (i.h_align ()));
360 text->set_attribute ("HAlign", halign_to_string (i.h_align ()));
364 if (i.h_position() > ALIGN_EPSILON) {
365 if (standard == SMPTE) {
366 text->set_attribute ("Hposition", raw_convert<string> (i.h_position() * 100, 6));
368 text->set_attribute ("HPosition", raw_convert<string> (i.h_position() * 100, 6));
372 if (standard == SMPTE) {
373 text->set_attribute ("Valign", valign_to_string (i.v_align()));
375 text->set_attribute ("VAlign", valign_to_string (i.v_align()));
378 if (i.v_position() > ALIGN_EPSILON) {
379 if (standard == SMPTE) {
380 text->set_attribute ("Vposition", raw_convert<string> (i.v_position() * 100, 6));
382 text->set_attribute ("VPosition", raw_convert<string> (i.v_position() * 100, 6));
385 if (standard == SMPTE) {
386 text->set_attribute ("Vposition", "0");
388 text->set_attribute ("VPosition", "0");
392 /* Interop only supports "horizontal" or "vertical" for direction, so only write this
395 if (i.direction() != DIRECTION_LTR && standard == SMPTE) {
396 text->set_attribute ("Direction", direction_to_string (i.direction ()));
399 text->add_child_text (i.text());
404 SubtitleAsset::fonts_with_load_ids () const
406 map<string, Data> out;
407 BOOST_FOREACH (Font const & i, _fonts) {
408 out[i.load_id] = i.data;