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.colour.get_value_or (dcp::Colour (255, 255, 255)),
163 effective_font.aspect_adjust.get_value_or (1.0),
164 effective_subtitle.in,
165 effective_subtitle.out,
166 effective_text.h_position,
167 effective_text.h_align,
168 effective_text.v_position,
169 effective_text.v_align,
170 effective_text.direction,
172 effective_font.effect.get_value_or (NONE),
173 effective_font.effect_colour.get_value_or (dcp::Colour (0, 0, 0)),
174 effective_subtitle.fade_up_time,
175 effective_subtitle.fade_down_time
181 SubtitleAsset::subtitles_during (Time from, Time to, bool starting) const
183 list<SubtitleString> s;
184 BOOST_FOREACH (SubtitleString const & i, _subtitles) {
185 if ((starting && from <= i.in() && i.in() < to) || (!starting && i.out() >= from && i.in() <= to)) {
194 SubtitleAsset::add (SubtitleString s)
196 _subtitles.push_back (s);
200 SubtitleAsset::latest_subtitle_out () const
203 BOOST_FOREACH (SubtitleString const & i, _subtitles) {
213 SubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
215 if (!Asset::equals (other_asset, options, note)) {
219 shared_ptr<const SubtitleAsset> other = dynamic_pointer_cast<const SubtitleAsset> (other_asset);
224 if (_subtitles != other->_subtitles) {
225 note (DCP_ERROR, "subtitles differ");
232 struct SubtitleSorter {
233 bool operator() (SubtitleString const & a, SubtitleString const & b) {
234 if (a.in() != b.in()) {
235 return a.in() < b.in();
237 return a.v_position() < b.v_position();
241 /** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child
242 * class because the differences between the two are fairly subtle.
245 SubtitleAsset::subtitles_as_xml (xmlpp::Element* root, int time_code_rate, Standard standard) const
247 list<SubtitleString> sorted = _subtitles;
248 sorted.sort (SubtitleSorter ());
250 string const xmlns = standard == SMPTE ? "dcst" : "";
252 /* XXX: script, underlined not supported */
254 optional<string> font;
259 float aspect_adjust = 1.0;
260 Effect effect = NONE;
261 Colour effect_colour;
265 Time last_fade_up_time;
266 Time last_fade_down_time;
268 xmlpp::Element* font_element = 0;
269 xmlpp::Element* subtitle_element = 0;
271 BOOST_FOREACH (SubtitleString const & i, sorted) {
273 /* We will start a new <Font>...</Font> whenever some font property changes.
274 I suppose we should really make an optimal hierarchy of <Font> tags, but
278 bool const font_changed =
280 italic != i.italic() ||
282 colour != i.colour() ||
284 fabs (aspect_adjust - i.aspect_adjust()) > ASPECT_ADJUST_EPSILON ||
285 effect != i.effect() ||
286 effect_colour != i.effect_colour();
290 italic = i.italic ();
292 colour = i.colour ();
294 aspect_adjust = i.aspect_adjust ();
295 effect = i.effect ();
296 effect_colour = i.effect_colour ();
299 if (!font_element || font_changed) {
300 font_element = root->add_child ("Font", xmlns);
302 if (standard == SMPTE) {
303 font_element->set_attribute ("ID", font.get ());
305 font_element->set_attribute ("Id", font.get ());
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));
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", "no");
320 font_element->set_attribute ("Underlined", "no");
322 font_element->set_attribute ("Weight", bold ? "bold" : "normal");
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()
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));
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)));
346 last_fade_up_time = i.fade_up_time ();
347 last_fade_down_time = i.fade_down_time ();
350 xmlpp::Element* text = subtitle_element->add_child ("Text", xmlns);
352 if (i.h_align() != HALIGN_CENTER) {
353 if (standard == SMPTE) {
354 text->set_attribute ("Halign", halign_to_string (i.h_align ()));
356 text->set_attribute ("HAlign", halign_to_string (i.h_align ()));
360 if (i.h_position() > ALIGN_EPSILON) {
361 if (standard == SMPTE) {
362 text->set_attribute ("Hposition", raw_convert<string> (i.h_position() * 100, 6));
364 text->set_attribute ("HPosition", raw_convert<string> (i.h_position() * 100, 6));
368 if (standard == SMPTE) {
369 text->set_attribute ("Valign", valign_to_string (i.v_align()));
371 text->set_attribute ("VAlign", valign_to_string (i.v_align()));
374 if (i.v_position() > ALIGN_EPSILON) {
375 if (standard == SMPTE) {
376 text->set_attribute ("Vposition", raw_convert<string> (i.v_position() * 100, 6));
378 text->set_attribute ("VPosition", raw_convert<string> (i.v_position() * 100, 6));
381 if (standard == SMPTE) {
382 text->set_attribute ("Vposition", "0");
384 text->set_attribute ("VPosition", "0");
388 /* Interop only supports "horizontal" or "vertical" for direction, so only write this
391 if (i.direction() != DIRECTION_LTR && standard == SMPTE) {
392 text->set_attribute ("Direction", direction_to_string (i.direction ()));
395 text->add_child_text (i.text());
400 SubtitleAsset::fonts_with_load_ids () const
402 map<string, Data> out;
403 BOOST_FOREACH (Font const & i, _fonts) {
404 out[i.load_id] = i.data;