X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fsubtitle_asset.cc;h=b9f6336c594928d09c6990aeaf035e2f37c936dd;hb=9486066c29b91a8d9ac25be1c596cad62387208f;hp=b7586207c9ae59a32c63d33b8b037ed3e975c2a7;hpb=8411002d2c768dfaaa4b89cf6a2b12b3967f1f69;p=libdcp.git diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index b7586207..b9f6336c 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -1,87 +1,132 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2015 Carl Hetherington - This program is free software; you can redistribute it and/or modify + This file is part of libdcp. + + libdcp is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - This program is distributed in the hope that it will be useful, + libdcp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - + along with libdcp. If not, see . + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. */ -#include +#include "raw_convert.h" #include "subtitle_asset.h" #include "util.h" - -using namespace std; -using namespace boost; -using namespace libdcp; - -SubtitleAsset::SubtitleAsset (string directory, string xml) - : Asset (directory, xml) - , XMLFile (path().string(), "DCSubtitle") +#include "xml.h" +#include "font_node.h" +#include "text_node.h" +#include "subtitle_string.h" +#include "dcp_assert.h" +#include +#include +#include +#include +#include +#include + +using std::string; +using std::list; +using std::cout; +using std::cerr; +using std::map; +using boost::shared_ptr; +using boost::shared_array; +using boost::optional; +using boost::dynamic_pointer_cast; +using namespace dcp; + +SubtitleAsset::SubtitleAsset () { - _subtitle_id = string_node ("SubtitleID"); - _movie_title = string_node ("MovieTitle"); - _reel_number = int64_node ("ReelNumber"); - _language = string_node ("Language"); - ignore_node ("LoadFont"); +} - list > font_nodes = sub_nodes ("Font"); - _load_font_nodes = sub_nodes ("LoadFont"); +SubtitleAsset::SubtitleAsset (boost::filesystem::path file) + : Asset (file) +{ - /* Now make Subtitle objects to represent the raw XML nodes - in a sane way. - */ +} +void +SubtitleAsset::parse_subtitles ( + shared_ptr xml, + list > font_nodes, + list > subtitle_nodes + ) +{ + /* Make Subtitle objects to represent the raw XML nodes in a sane way */ ParseState parse_state; - examine_font_nodes (font_nodes, parse_state); + examine_nodes (xml, font_nodes, parse_state); + examine_nodes (xml, subtitle_nodes, parse_state); } void -SubtitleAsset::examine_font_nodes ( - list > const & font_nodes, +SubtitleAsset::examine_nodes ( + shared_ptr xml, + list > const & subtitle_nodes, ParseState& parse_state ) { - for (list >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) { + BOOST_FOREACH (shared_ptr i, subtitle_nodes) { + parse_state.subtitle_nodes.push_back (i); + examine_nodes (xml, i->text_nodes, parse_state); + examine_nodes (xml, i->font_nodes, parse_state); + parse_state.subtitle_nodes.pop_back (); + } +} - parse_state.font_nodes.push_back (*i); - maybe_add_subtitle ((*i)->text, parse_state); +void +SubtitleAsset::examine_nodes ( + shared_ptr xml, + list > const & font_nodes, + ParseState& parse_state + ) +{ + BOOST_FOREACH (shared_ptr i, font_nodes) { + + parse_state.font_nodes.push_back (i); + maybe_add_subtitle (i->text, parse_state); + + examine_nodes (xml, i->subtitle_nodes, parse_state); + examine_nodes (xml, i->font_nodes, parse_state); + examine_nodes (xml, i->text_nodes, parse_state); - for (list >::iterator j = (*i)->subtitle_nodes.begin(); j != (*i)->subtitle_nodes.end(); ++j) { - parse_state.subtitle_nodes.push_back (*j); - examine_text_nodes ((*j)->text_nodes, parse_state); - examine_font_nodes ((*j)->font_nodes, parse_state); - parse_state.subtitle_nodes.pop_back (); - } - - examine_font_nodes ((*i)->font_nodes, parse_state); - examine_text_nodes ((*i)->text_nodes, parse_state); - parse_state.font_nodes.pop_back (); } } void -SubtitleAsset::examine_text_nodes ( - list > const & text_nodes, +SubtitleAsset::examine_nodes ( + shared_ptr xml, + list > const & text_nodes, ParseState& parse_state ) { - for (list >::const_iterator i = text_nodes.begin(); i != text_nodes.end(); ++i) { - parse_state.text_nodes.push_back (*i); - maybe_add_subtitle ((*i)->text, parse_state); - examine_font_nodes ((*i)->font_nodes, parse_state); + BOOST_FOREACH (shared_ptr i, text_nodes) { + parse_state.text_nodes.push_back (i); + maybe_add_subtitle (i->text, parse_state); + examine_nodes (xml, i->font_nodes, parse_state); parse_state.text_nodes.pop_back (); } } @@ -92,260 +137,271 @@ SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state) if (empty_or_white_space (text)) { return; } - + if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) { return; } - assert (!parse_state.text_nodes.empty ()); - assert (!parse_state.subtitle_nodes.empty ()); - - FontNode effective_font (parse_state.font_nodes); - TextNode effective_text (*parse_state.text_nodes.back ()); - SubtitleNode effective_subtitle (*parse_state.subtitle_nodes.back ()); + DCP_ASSERT (!parse_state.text_nodes.empty ()); + DCP_ASSERT (!parse_state.subtitle_nodes.empty ()); + + dcp::FontNode effective_font (parse_state.font_nodes); + dcp::TextNode effective_text (*parse_state.text_nodes.back ()); + dcp::SubtitleNode effective_subtitle (*parse_state.subtitle_nodes.back ()); _subtitles.push_back ( - shared_ptr ( - new Subtitle ( - font_id_to_name (effective_font.id), - effective_font.italic.get(), - effective_font.color.get(), - effective_font.size, - effective_subtitle.in, - effective_subtitle.out, - effective_text.v_position, - effective_text.v_align, - text, - effective_font.effect.get(), - effective_font.effect_color.get(), - effective_subtitle.fade_up_time, - effective_subtitle.fade_down_time - ) + SubtitleString ( + effective_font.id, + effective_font.italic.get_value_or (false), + effective_font.bold.get_value_or (false), + effective_font.underline.get_value_or (false), + effective_font.colour.get_value_or (dcp::Colour (255, 255, 255)), + effective_font.size, + effective_font.aspect_adjust.get_value_or (1.0), + effective_subtitle.in, + effective_subtitle.out, + effective_text.h_position, + effective_text.h_align, + effective_text.v_position, + effective_text.v_align, + effective_text.direction, + text, + effective_font.effect.get_value_or (NONE), + effective_font.effect_colour.get_value_or (dcp::Colour (0, 0, 0)), + effective_subtitle.fade_up_time, + effective_subtitle.fade_down_time ) ); } -FontNode::FontNode (xmlpp::Node const * node) - : XMLNode (node) +list +SubtitleAsset::subtitles_during (Time from, Time to, bool starting) const { - text = content (); - - id = optional_string_attribute ("Id"); - size = optional_int64_attribute ("Size"); - italic = optional_bool_attribute ("Italic"); - color = optional_color_attribute ("Color"); - string const e = optional_string_attribute ("Effect"); - if (e == "none") { - effect = NONE; - } else if (e == "border") { - effect = BORDER; - } else if (e == "shadow") { - effect = SHADOW; - } else if (!e.empty ()) { - throw DCPReadError ("unknown subtitle effect type"); - } - effect_color = optional_color_attribute ("EffectColor"); - subtitle_nodes = sub_nodes ("Subtitle"); - font_nodes = sub_nodes ("Font"); - text_nodes = sub_nodes ("Text"); -} - -FontNode::FontNode (list > const & font_nodes) - : size (0) - , italic (false) - , color ("FFFFFFFF") - , effect_color ("FFFFFFFF") -{ - for (list >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) { - if (!(*i)->id.empty ()) { - id = (*i)->id; - } - if ((*i)->size != 0) { - size = (*i)->size; - } - if ((*i)->italic) { - italic = (*i)->italic.get (); - } - if ((*i)->color) { - color = (*i)->color.get (); - } - if ((*i)->effect) { - effect = (*i)->effect.get (); - } - if ((*i)->effect_color) { - effect_color = (*i)->effect_color.get (); + list s; + BOOST_FOREACH (SubtitleString const & i, _subtitles) { + if ((starting && from <= i.in() && i.in() < to) || (!starting && i.out() >= from && i.in() <= to)) { + s.push_back (i); } } -} -LoadFontNode::LoadFontNode (xmlpp::Node const * node) - : XMLNode (node) -{ - id = string_attribute ("Id"); - uri = string_attribute ("URI"); + return s; } - -SubtitleNode::SubtitleNode (xmlpp::Node const * node) - : XMLNode (node) +void +SubtitleAsset::add (SubtitleString s) { - in = time_attribute ("TimeIn"); - out = time_attribute ("TimeOut"); - font_nodes = sub_nodes ("Font"); - text_nodes = sub_nodes ("Text"); - fade_up_time = fade_time ("FadeUpTime"); - fade_down_time = fade_time ("FadeDownTime"); + _subtitles.push_back (s); } Time -SubtitleNode::fade_time (string name) +SubtitleAsset::latest_subtitle_out () const { - string const u = optional_string_attribute (name); Time t; - - if (u.empty ()) { - t = Time (0, 0, 0, 20); - } else if (u.find (":") != string::npos) { - t = Time (u); - } else { - t = Time (0, 0, 0, lexical_cast (u)); - } - - if (t > Time (0, 0, 8, 0)) { - t = Time (0, 0, 8, 0); + BOOST_FOREACH (SubtitleString const & i, _subtitles) { + if (i.out() > t) { + t = i.out (); + } } return t; } -TextNode::TextNode (xmlpp::Node const * node) - : XMLNode (node) - , v_align (CENTER) +bool +SubtitleAsset::equals (shared_ptr other_asset, EqualityOptions options, NoteHandler note) const { - text = content (); - v_position = float_attribute ("VPosition"); - string const v = optional_string_attribute ("VAlign"); - if (v == "top") { - v_align = TOP; - } else if (v == "center") { - v_align = CENTER; - } else if (v == "bottom") { - v_align = BOTTOM; + if (!Asset::equals (other_asset, options, note)) { + return false; } - font_nodes = sub_nodes ("Font"); -} + shared_ptr other = dynamic_pointer_cast (other_asset); + if (!other) { + return false; + } -list > -SubtitleAsset::subtitles_at (Time t) const -{ - list > s; - for (list >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { - if ((*i)->in() <= t && t <= (*i)->out ()) { - s.push_back (*i); - } + if (_subtitles != other->_subtitles) { + note (DCP_ERROR, "subtitles differ"); + return false; } - return s; + return true; } -std::string -SubtitleAsset::font_id_to_name (string id) const -{ - list >::const_iterator i = _load_font_nodes.begin(); - while (i != _load_font_nodes.end() && (*i)->id != id) { - ++i; +struct SubtitleSorter { + bool operator() (SubtitleString const & a, SubtitleString const & b) { + if (a.in() != b.in()) { + return a.in() < b.in(); + } + return a.v_position() < b.v_position(); } +}; - if (i == _load_font_nodes.end ()) { - return ""; - } +/** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child + * class because the differences between the two are fairly subtle. + */ +void +SubtitleAsset::subtitles_as_xml (xmlpp::Element* root, int time_code_rate, Standard standard) const +{ + list sorted = _subtitles; + sorted.sort (SubtitleSorter ()); + + string const xmlns = standard == SMPTE ? "dcst" : ""; + + /* XXX: script not supported */ + + optional font; + bool italic = false; + bool bold = false; + bool underline = false; + Colour colour; + int size = 0; + float aspect_adjust = 1.0; + Effect effect = NONE; + Colour effect_colour; + int spot_number = 1; + Time last_in; + Time last_out; + Time last_fade_up_time; + Time last_fade_down_time; + + xmlpp::Element* font_element = 0; + xmlpp::Element* subtitle_element = 0; + + BOOST_FOREACH (SubtitleString const & i, sorted) { + + /* We will start a new ... whenever some font property changes. + I suppose we should really make an optimal hierarchy of tags, but + that seems hard. + */ + + bool const font_changed = + font != i.font() || + italic != i.italic() || + bold != i.bold() || + underline != i.underline() || + colour != i.colour() || + size != i.size() || + fabs (aspect_adjust - i.aspect_adjust()) > ASPECT_ADJUST_EPSILON || + effect != i.effect() || + effect_colour != i.effect_colour(); + + if (font_changed) { + font = i.font (); + italic = i.italic (); + bold = i.bold (); + underline = i.underline (); + colour = i.colour (); + size = i.size (); + aspect_adjust = i.aspect_adjust (); + effect = i.effect (); + effect_colour = i.effect_colour (); + } - if ((*i)->uri == "arial.ttf") { - return "Arial"; - } + if (!font_element || font_changed) { + font_element = root->add_child ("Font", xmlns); + if (font) { + if (standard == SMPTE) { + font_element->set_attribute ("ID", font.get ()); + } else { + font_element->set_attribute ("Id", font.get ()); + } + } + font_element->set_attribute ("Italic", italic ? "yes" : "no"); + font_element->set_attribute ("Color", colour.to_argb_string()); + font_element->set_attribute ("Size", raw_convert (size)); + if (fabs (aspect_adjust - 1.0) > ASPECT_ADJUST_EPSILON) { + font_element->set_attribute ("AspectAdjust", raw_convert (aspect_adjust)); + } + font_element->set_attribute ("Effect", effect_to_string (effect)); + font_element->set_attribute ("EffectColor", effect_colour.to_argb_string()); + font_element->set_attribute ("Script", "normal"); + if (standard == SMPTE) { + font_element->set_attribute ("Underline", underline ? "yes" : "no"); + } else { + font_element->set_attribute ("Underlined", underline ? "yes" : "no"); + } + font_element->set_attribute ("Weight", bold ? "bold" : "normal"); + } - return ""; -} + if (!subtitle_element || font_changed || + (last_in != i.in() || + last_out != i.out() || + last_fade_up_time != i.fade_up_time() || + last_fade_down_time != i.fade_down_time() + )) { + + subtitle_element = font_element->add_child ("Subtitle", xmlns); + subtitle_element->set_attribute ("SpotNumber", raw_convert (spot_number++)); + subtitle_element->set_attribute ("TimeIn", i.in().rebase(time_code_rate).as_string(standard)); + subtitle_element->set_attribute ("TimeOut", i.out().rebase(time_code_rate).as_string(standard)); + if (standard == SMPTE) { + subtitle_element->set_attribute ("FadeUpTime", i.fade_up_time().rebase(time_code_rate).as_string(standard)); + subtitle_element->set_attribute ("FadeDownTime", i.fade_down_time().rebase(time_code_rate).as_string(standard)); + } else { + subtitle_element->set_attribute ("FadeUpTime", raw_convert (i.fade_up_time().as_editable_units(time_code_rate))); + subtitle_element->set_attribute ("FadeDownTime", raw_convert (i.fade_down_time().as_editable_units(time_code_rate))); + } + + last_in = i.in (); + last_out = i.out (); + last_fade_up_time = i.fade_up_time (); + last_fade_down_time = i.fade_down_time (); + } -Subtitle::Subtitle ( - string font, - bool italic, - Color color, - int size, - Time in, - Time out, - float v_position, - VAlign v_align, - string text, - Effect effect, - Color effect_color, - Time fade_up_time, - Time fade_down_time - ) - : _font (font) - , _italic (italic) - , _color (color) - , _size (size) - , _in (in) - , _out (out) - , _v_position (v_position) - , _v_align (v_align) - , _text (text) - , _effect (effect) - , _effect_color (effect_color) - , _fade_up_time (fade_up_time) - , _fade_down_time (fade_down_time) -{ + xmlpp::Element* text = subtitle_element->add_child ("Text", xmlns); -} + if (i.h_align() != HALIGN_CENTER) { + if (standard == SMPTE) { + text->set_attribute ("Halign", halign_to_string (i.h_align ())); + } else { + text->set_attribute ("HAlign", halign_to_string (i.h_align ())); + } + } -int -Subtitle::size_in_pixels (int screen_height) const -{ - /* Size in the subtitle file is given in points as if the screen - height is 11 inches, so a 72pt font would be 1/11th of the screen - height. - */ - - return _size * screen_height / (11 * 72); -} + if (i.h_position() > ALIGN_EPSILON) { + if (standard == SMPTE) { + text->set_attribute ("Hposition", raw_convert (i.h_position() * 100, 6)); + } else { + text->set_attribute ("HPosition", raw_convert (i.h_position() * 100, 6)); + } + } -bool -libdcp::operator== (Subtitle const & a, Subtitle const & b) -{ - return ( - a.font() == b.font() && - a.italic() == b.italic() && - a.color() == b.color() && - a.size() == b.size() && - a.in() == b.in() && - a.out() == b.out() && - a.v_position() == b.v_position() && - a.v_align() == b.v_align() && - a.text() == b.text() && - a.effect() == b.effect() && - a.effect_color() == b.effect_color() && - a.fade_up_time() == b.fade_up_time() && - a.fade_down_time() == b.fade_down_time() - ); + if (standard == SMPTE) { + text->set_attribute ("Valign", valign_to_string (i.v_align())); + } else { + text->set_attribute ("VAlign", valign_to_string (i.v_align())); + } + + if (i.v_position() > ALIGN_EPSILON) { + if (standard == SMPTE) { + text->set_attribute ("Vposition", raw_convert (i.v_position() * 100, 6)); + } else { + text->set_attribute ("VPosition", raw_convert (i.v_position() * 100, 6)); + } + } else { + if (standard == SMPTE) { + text->set_attribute ("Vposition", "0"); + } else { + text->set_attribute ("VPosition", "0"); + } + } + + /* Interop only supports "horizontal" or "vertical" for direction, so only write this + for SMPTE. + */ + if (i.direction() != DIRECTION_LTR && standard == SMPTE) { + text->set_attribute ("Direction", direction_to_string (i.direction ())); + } + + text->add_child_text (i.text()); + } } -ostream& -libdcp::operator<< (ostream& s, Subtitle const & sub) +map +SubtitleAsset::fonts_with_load_ids () const { - s << "\n`" << sub.text() << "' from " << sub.in() << " to " << sub.out() << ";\n" - << "fade up " << sub.fade_up_time() << ", fade down " << sub.fade_down_time() << ";\n" - << "font " << sub.font() << ", "; - - if (sub.italic()) { - s << "italic"; - } else { - s << "non-italic"; + map out; + BOOST_FOREACH (Font const & i, _fonts) { + out[i.load_id] = i.data; } - - s << ", size " << sub.size() << ", color " << sub.color() << ", vpos " << sub.v_position() << ", valign " << ((int) sub.v_align()) << ";\n" - << "effect " << ((int) sub.effect()) << ", effect color " << sub.effect_color(); - - return s; + return out; }