2 Copyright (C) 2012-2016 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 "compose.hpp"
36 #include "subtitle_asset.h"
37 #include "subtitle_asset_internal.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/lexical_cast.hpp>
47 #include <boost/shared_array.hpp>
48 #include <boost/foreach.hpp>
56 using boost::shared_ptr;
57 using boost::shared_array;
58 using boost::optional;
59 using boost::dynamic_pointer_cast;
60 using boost::lexical_cast;
63 SubtitleAsset::SubtitleAsset ()
68 SubtitleAsset::SubtitleAsset (boost::filesystem::path file)
75 string_attribute (xmlpp::Element const * node, string name)
77 xmlpp::Attribute* a = node->get_attribute (name);
79 throw XMLError (String::compose ("missing attribute %1", name));
81 return string (a->get_value ());
85 optional_string_attribute (xmlpp::Element const * node, string name)
87 xmlpp::Attribute* a = node->get_attribute (name);
89 return optional<string>();
91 return string (a->get_value ());
95 optional_bool_attribute (xmlpp::Element const * node, string name)
97 optional<string> s = optional_string_attribute (node, name);
99 return optional<bool> ();
102 return (s.get() == "1" || s.get() == "yes");
107 optional_number_attribute (xmlpp::Element const * node, string name)
109 boost::optional<std::string> s = optional_string_attribute (node, name);
111 return boost::optional<T> ();
114 std::string t = s.get ();
115 boost::erase_all (t, " ");
116 return raw_convert<T> (t);
119 SubtitleAsset::ParseState
120 SubtitleAsset::font_node_state (xmlpp::Element const * node, Standard standard) const
124 if (standard == INTEROP) {
125 ps.font_id = optional_string_attribute (node, "Id");
127 ps.font_id = optional_string_attribute (node, "ID");
129 ps.size = optional_number_attribute<int64_t> (node, "Size");
130 ps.aspect_adjust = optional_number_attribute<float> (node, "AspectAdjust");
131 ps.italic = optional_bool_attribute (node, "Italic");
132 ps.bold = optional_string_attribute(node, "Weight").get_value_or("normal") == "bold";
133 if (standard == INTEROP) {
134 ps.underline = optional_bool_attribute (node, "Underlined");
136 ps.underline = optional_bool_attribute (node, "Underline");
138 optional<string> c = optional_string_attribute (node, "Color");
140 ps.colour = Colour (c.get ());
142 optional<string> const e = optional_string_attribute (node, "Effect");
144 ps.effect = string_to_effect (e.get ());
146 c = optional_string_attribute (node, "EffectColor");
148 ps.effect_colour = Colour (c.get ());
154 SubtitleAsset::ParseState
155 SubtitleAsset::text_node_state (xmlpp::Element const * node) const
159 optional<float> hp = optional_number_attribute<float> (node, "HPosition");
161 hp = optional_number_attribute<float> (node, "Hposition");
164 ps.h_position = hp.get () / 100;
167 optional<string> ha = optional_string_attribute (node, "HAlign");
169 ha = optional_string_attribute (node, "Halign");
172 ps.h_align = string_to_halign (ha.get ());
175 optional<float> vp = optional_number_attribute<float> (node, "VPosition");
177 vp = optional_number_attribute<float> (node, "Vposition");
180 ps.v_position = vp.get () / 100;
183 optional<string> va = optional_string_attribute (node, "VAlign");
185 va = optional_string_attribute (node, "Valign");
188 ps.v_align = string_to_valign (va.get ());
191 optional<string> d = optional_string_attribute (node, "Direction");
193 ps.direction = string_to_direction (d.get ());
199 SubtitleAsset::ParseState
200 SubtitleAsset::subtitle_node_state (xmlpp::Element const * node, optional<int> tcr) const
203 ps.in = Time (string_attribute(node, "TimeIn"), tcr);
204 ps.out = Time (string_attribute(node, "TimeOut"), tcr);
205 ps.fade_up_time = fade_time (node, "FadeUpTime", tcr);
206 ps.fade_down_time = fade_time (node, "FadeDownTime", tcr);
211 SubtitleAsset::fade_time (xmlpp::Element const * node, string name, optional<int> tcr) const
213 string const u = optional_string_attribute(node, name).get_value_or ("");
217 t = Time (0, 0, 0, 20, 250);
218 } else if (u.find (":") != string::npos) {
221 t = Time (0, 0, 0, lexical_cast<int> (u), tcr.get_value_or(250));
224 if (t > Time (0, 0, 8, 0, 250)) {
225 t = Time (0, 0, 8, 0, 250);
232 SubtitleAsset::parse_subtitles (xmlpp::Element const * node, list<ParseState>& state, optional<int> tcr, Standard standard)
234 if (node->get_name() == "Font") {
235 state.push_back (font_node_state (node, standard));
236 } else if (node->get_name() == "Subtitle") {
237 state.push_back (subtitle_node_state (node, tcr));
238 } else if (node->get_name() == "Text") {
239 state.push_back (text_node_state (node));
240 } else if (node->get_name() == "SubtitleList") {
241 state.push_back (ParseState ());
243 throw XMLError ("unexpected node " + node->get_name());
246 xmlpp::Node::NodeList c = node->get_children ();
247 for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
248 xmlpp::ContentNode const * v = dynamic_cast<xmlpp::ContentNode const *> (*i);
250 maybe_add_subtitle (v->get_content(), state);
252 xmlpp::Element const * e = dynamic_cast<xmlpp::Element const *> (*i);
254 parse_subtitles (e, state, tcr, standard);
262 SubtitleAsset::maybe_add_subtitle (string text, list<ParseState> const & parse_state)
264 if (empty_or_white_space (text)) {
269 BOOST_FOREACH (ParseState const & i, parse_state) {
271 ps.font_id = i.font_id.get();
274 ps.size = i.size.get();
276 if (i.aspect_adjust) {
277 ps.aspect_adjust = i.aspect_adjust.get();
280 ps.italic = i.italic.get();
283 ps.bold = i.bold.get();
286 ps.underline = i.underline.get();
289 ps.colour = i.colour.get();
292 ps.effect = i.effect.get();
294 if (i.effect_colour) {
295 ps.effect_colour = i.effect_colour.get();
298 ps.h_position = i.h_position.get();
301 ps.h_align = i.h_align.get();
304 ps.v_position = i.v_position.get();
307 ps.v_align = i.v_align.get();
310 ps.direction = i.direction.get();
316 ps.out = i.out.get();
318 if (i.fade_up_time) {
319 ps.fade_up_time = i.fade_up_time.get();
321 if (i.fade_down_time) {
322 ps.fade_down_time = i.fade_down_time.get();
326 if (!ps.in || !ps.out) {
327 /* We're not in a <Text> node; just ignore this content */
331 _subtitles.push_back (
334 ps.italic.get_value_or (false),
335 ps.bold.get_value_or (false),
336 ps.underline.get_value_or (false),
337 ps.colour.get_value_or (dcp::Colour (255, 255, 255)),
338 ps.size.get_value_or (42),
339 ps.aspect_adjust.get_value_or (1.0),
342 ps.h_position.get_value_or(0),
343 ps.h_align.get_value_or(HALIGN_CENTER),
344 ps.v_position.get_value_or(0),
345 ps.v_align.get_value_or(VALIGN_CENTER),
346 ps.direction.get_value_or (DIRECTION_LTR),
348 ps.effect.get_value_or (NONE),
349 ps.effect_colour.get_value_or (dcp::Colour (255, 255, 255)),
350 ps.fade_up_time.get_value_or(Time()),
351 ps.fade_down_time.get_value_or(Time())
357 SubtitleAsset::subtitles_during (Time from, Time to, bool starting) const
359 list<SubtitleString> s;
360 BOOST_FOREACH (SubtitleString const & i, _subtitles) {
361 if ((starting && from <= i.in() && i.in() < to) || (!starting && i.out() >= from && i.in() <= to)) {
370 SubtitleAsset::add (SubtitleString s)
372 _subtitles.push_back (s);
376 SubtitleAsset::latest_subtitle_out () const
379 BOOST_FOREACH (SubtitleString const & i, _subtitles) {
389 SubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
391 if (!Asset::equals (other_asset, options, note)) {
395 shared_ptr<const SubtitleAsset> other = dynamic_pointer_cast<const SubtitleAsset> (other_asset);
400 if (_subtitles != other->_subtitles) {
401 note (DCP_ERROR, "subtitles differ");
408 struct SubtitleSorter
410 bool operator() (SubtitleString const & a, SubtitleString const & b) {
411 if (a.in() != b.in()) {
412 return a.in() < b.in();
414 return a.v_position() < b.v_position();
419 SubtitleAsset::pull_fonts (shared_ptr<order::Part> part)
421 if (part->children.empty ()) {
425 /* Pull up from children */
426 BOOST_FOREACH (shared_ptr<order::Part> i, part->children) {
431 /* Establish the common font features that each of part's children have;
432 these features go into part's font.
434 part->font = part->children.front()->font;
435 BOOST_FOREACH (shared_ptr<order::Part> i, part->children) {
436 part->font.take_intersection (i->font);
439 /* Remove common values from part's children's fonts */
440 BOOST_FOREACH (shared_ptr<order::Part> i, part->children) {
441 i->font.take_difference (part->font);
445 /* Merge adjacent children with the same font */
446 list<shared_ptr<order::Part> >::const_iterator i = part->children.begin();
447 list<shared_ptr<order::Part> > merged;
449 while (i != part->children.end()) {
451 if ((*i)->font.empty ()) {
452 merged.push_back (*i);
455 list<shared_ptr<order::Part> >::const_iterator j = i;
457 while (j != part->children.end() && (*i)->font == (*j)->font) {
460 if (distance (i, j) == 1) {
461 merged.push_back (*i);
464 shared_ptr<order::Part> group (new order::Part (part, (*i)->font));
465 for (list<shared_ptr<order::Part> >::const_iterator k = i; k != j; ++k) {
467 group->children.push_back (*k);
469 merged.push_back (group);
475 part->children = merged;
478 /** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child
479 * class because the differences between the two are fairly subtle.
482 SubtitleAsset::subtitles_as_xml (xmlpp::Element* xml_root, int time_code_rate, Standard standard) const
484 list<SubtitleString> sorted = _subtitles;
485 sorted.sort (SubtitleSorter ());
487 /* Gather our subtitles into a hierarchy of Subtitle/Text/String objects, writing
488 font information into the bottom level (String) objects.
491 shared_ptr<order::Part> root (new order::Part (shared_ptr<order::Part> ()));
492 shared_ptr<order::Subtitle> subtitle;
493 shared_ptr<order::Text> text;
497 Time last_fade_up_time;
498 Time last_fade_down_time;
500 float last_h_position;
502 float last_v_position;
503 Direction last_direction;
505 BOOST_FOREACH (SubtitleString const & i, sorted) {
507 (last_in != i.in() ||
508 last_out != i.out() ||
509 last_fade_up_time != i.fade_up_time() ||
510 last_fade_down_time != i.fade_down_time())
513 subtitle.reset (new order::Subtitle (root, i.in(), i.out(), i.fade_up_time(), i.fade_down_time()));
514 root->children.push_back (subtitle);
518 last_fade_up_time = i.fade_up_time ();
519 last_fade_down_time = i.fade_down_time ();
524 last_h_align != i.h_align() ||
525 fabs(last_h_position - i.h_position()) > ALIGN_EPSILON ||
526 last_v_align != i.v_align() ||
527 fabs(last_v_position - i.v_position()) > ALIGN_EPSILON ||
528 last_direction != i.direction()
531 text.reset (new order::Text (subtitle, i.h_align(), i.h_position(), i.v_align(), i.v_position(), i.direction()));
532 subtitle->children.push_back (text);
534 last_h_align = i.h_align ();
535 last_h_position = i.h_position ();
536 last_v_align = i.v_align ();
537 last_v_position = i.v_position ();
538 last_direction = i.direction ();
541 text->children.push_back (shared_ptr<order::String> (new order::String (text, order::Font (i, standard), i.text())));
544 /* Pull font changes as high up the hierarchy as we can */
550 order::Context context;
551 context.time_code_rate = time_code_rate;
552 context.standard = standard;
553 context.spot_number = 1;
555 root->write_xml (xml_root, context);
559 SubtitleAsset::fonts_with_load_ids () const
561 map<string, Data> out;
562 BOOST_FOREACH (Font const & i, _fonts) {
563 out[i.load_id] = i.data;