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"
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>
54 using boost::shared_ptr;
55 using boost::shared_array;
56 using boost::optional;
57 using boost::dynamic_pointer_cast;
60 SubtitleAsset::SubtitleAsset ()
65 SubtitleAsset::SubtitleAsset (boost::filesystem::path file)
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
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);
85 SubtitleAsset::examine_nodes (
86 shared_ptr<const cxml::Node> xml,
87 list<shared_ptr<dcp::SubtitleNode> > const & subtitle_nodes,
88 ParseState& parse_state
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 ();
100 SubtitleAsset::examine_nodes (
101 shared_ptr<const cxml::Node> xml,
102 list<shared_ptr<dcp::FontNode> > const & font_nodes,
103 ParseState& parse_state
106 BOOST_FOREACH (shared_ptr<dcp::FontNode> i, font_nodes) {
108 parse_state.font_nodes.push_back (i);
109 maybe_add_subtitle (i->text, parse_state);
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);
115 parse_state.font_nodes.pop_back ();
120 SubtitleAsset::examine_nodes (
121 shared_ptr<const cxml::Node> xml,
122 list<shared_ptr<dcp::TextNode> > const & text_nodes,
123 ParseState& parse_state
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 ();
135 SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state)
137 if (empty_or_white_space (text)) {
141 if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) {
145 DCP_ASSERT (!parse_state.text_nodes.empty ());
146 DCP_ASSERT (!parse_state.subtitle_nodes.empty ());
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 ());
152 _subtitles.push_back (
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)),
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,
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
178 SubtitleAsset::subtitles_during (Time from, Time to, bool starting) const
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)) {
191 SubtitleAsset::add (SubtitleString s)
193 _subtitles.push_back (s);
197 SubtitleAsset::latest_subtitle_out () const
200 BOOST_FOREACH (SubtitleString const & i, _subtitles) {
210 SubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
212 if (!Asset::equals (other_asset, options, note)) {
216 shared_ptr<const SubtitleAsset> other = dynamic_pointer_cast<const SubtitleAsset> (other_asset);
221 if (_subtitles != other->_subtitles) {
222 note (DCP_ERROR, "subtitles differ");
229 struct SubtitleSorter {
230 bool operator() (SubtitleString const & a, SubtitleString const & b) {
231 if (a.in() != b.in()) {
232 return a.in() < b.in();
234 return a.v_position() < b.v_position();
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.
242 SubtitleAsset::subtitles_as_xml (xmlpp::Element* root, int time_code_rate, Standard standard) const
244 list<SubtitleString> sorted = _subtitles;
245 sorted.sort (SubtitleSorter ());
247 string const xmlns = standard == SMPTE ? "dcst" : "";
249 /* XXX: script not supported */
251 optional<string> font;
254 bool underline = false;
257 float aspect_adjust = 1.0;
258 Effect effect = NONE;
259 Colour effect_colour;
263 Time last_fade_up_time;
264 Time last_fade_down_time;
266 xmlpp::Element* font_element = 0;
267 xmlpp::Element* subtitle_element = 0;
269 BOOST_FOREACH (SubtitleString const & i, sorted) {
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
276 bool const font_changed =
278 italic != i.italic() ||
280 underline != i.underline() ||
281 colour != i.colour() ||
283 fabs (aspect_adjust - i.aspect_adjust()) > ASPECT_ADJUST_EPSILON ||
284 effect != i.effect() ||
285 effect_colour != i.effect_colour();
289 italic = i.italic ();
291 underline = i.underline ();
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", underline ? "yes" : "no");
320 font_element->set_attribute ("Underlined", underline ? "yes" : "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;