X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fsubtitle_asset.cc;h=25eebf95718c4cce04cd73f623447b3a4a82b22a;hb=b511420d55c99fb72cf1ca5cd7dedf53010e8941;hp=9a3e3bbc9929b1302ab1d9ac8a6542a5eb5096a9;hpb=df6ed597b720399f02e7b75a7cf448d0956c89a1;p=libdcp.git diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 9a3e3bbc..25eebf95 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -17,91 +17,155 @@ */ +#include +#include #include "subtitle_asset.h" +#include "util.h" -using namespace std; -using namespace boost; +using std::string; +using std::list; +using std::ostream; +using std::ofstream; +using std::stringstream; +using boost::shared_ptr; +using boost::lexical_cast; using namespace libdcp; -SubtitleAsset::SubtitleAsset (string directory, string xml) - : Asset (directory, xml) - , XMLFile (path().string(), "DCSubtitle") +SubtitleAsset::SubtitleAsset (string directory, string xml_file) + : Asset (directory, xml_file) { - _subtitle_id = string_node ("SubtitleID"); - _movie_title = string_node ("MovieTitle"); - _reel_number = int64_node ("ReelNumber"); - _language = string_node ("Language"); + read_xml (xml_file); +} + +SubtitleAsset::SubtitleAsset (string directory, string movie_title, string language) + : Asset (directory) + , _movie_title (movie_title) + , _reel_number ("1") + , _language (language) +{ + +} + +void +SubtitleAsset::read_xml (string xml_file) +{ + shared_ptr xml (new XMLFile (xml_file, "DCSubtitle")); + + _uuid = xml->string_child ("SubtitleID"); + _movie_title = xml->string_child ("MovieTitle"); + _reel_number = xml->string_child ("ReelNumber"); + _language = xml->string_child ("Language"); - ignore_node ("LoadFont"); + xml->ignore_child ("LoadFont"); - list > font_nodes = sub_nodes ("Font"); - _load_font_nodes = sub_nodes ("LoadFont"); + list > font_nodes = xml->type_children ("Font"); + _load_font_nodes = xml->type_children ("LoadFont"); /* Now make Subtitle objects to represent the raw XML nodes in a sane way. */ - list > current_font_nodes; - for (list >::iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) { - examine_font_node (*i, current_font_nodes); - } + ParseState parse_state; + examine_font_nodes (xml, font_nodes, parse_state); } void -SubtitleAsset::examine_font_node (shared_ptr font_node, list >& current_font_nodes) -{ - current_font_nodes.push_back (font_node); - - for (list >::iterator j = font_node->subtitle_nodes.begin(); j != font_node->subtitle_nodes.end(); ++j) { - for (list >::iterator k = (*j)->text_nodes.begin(); k != (*j)->text_nodes.end(); ++k) { - FontNode effective (current_font_nodes); - _subtitles.push_back ( - shared_ptr ( - new Subtitle ( - font_id_to_name (effective.id), - effective.italic.get(), - effective.color.get(), - effective.size, - (*j)->in, - (*j)->out, - (*k)->v_position, - (*k)->v_align, - (*k)->text, - effective.effect.get(), - effective.effect_color.get() - ) - ) - ); +SubtitleAsset::examine_font_nodes ( + shared_ptr xml, + list > const & font_nodes, + ParseState& parse_state + ) +{ + for (list >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) { + + parse_state.font_nodes.push_back (*i); + maybe_add_subtitle ((*i)->text, 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 (xml, (*j)->text_nodes, parse_state); + examine_font_nodes (xml, (*j)->font_nodes, parse_state); + parse_state.subtitle_nodes.pop_back (); } + + examine_font_nodes (xml, (*i)->font_nodes, parse_state); + examine_text_nodes (xml, (*i)->text_nodes, parse_state); + + parse_state.font_nodes.pop_back (); } +} - for (list >::iterator j = font_node->font_nodes.begin(); j != font_node->font_nodes.end(); ++j) { - examine_font_node (*j, current_font_nodes); +void +SubtitleAsset::examine_text_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 (xml, (*i)->font_nodes, parse_state); + parse_state.text_nodes.pop_back (); } +} - current_font_nodes.pop_back (); +void +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 ()); + + _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 ? effective_font.effect.get() : NONE, + effective_font.effect_color.get(), + effective_subtitle.fade_up_time, + effective_subtitle.fade_down_time + ) + ) + ); } FontNode::FontNode (xmlpp::Node const * node) : XMLNode (node) { + 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"); + if (!e.empty ()) { + effect = string_to_effect (e); } effect_color = optional_color_attribute ("EffectColor"); - subtitle_nodes = sub_nodes ("Subtitle"); - font_nodes = sub_nodes ("Font"); + subtitle_nodes = type_children ("Subtitle"); + font_nodes = type_children ("Font"); + text_nodes = type_children ("Text"); } FontNode::FontNode (list > const & font_nodes) @@ -145,7 +209,31 @@ SubtitleNode::SubtitleNode (xmlpp::Node const * node) { in = time_attribute ("TimeIn"); out = time_attribute ("TimeOut"); - text_nodes = sub_nodes ("Text"); + font_nodes = type_children ("Font"); + text_nodes = type_children ("Text"); + fade_up_time = fade_time ("FadeUpTime"); + fade_down_time = fade_time ("FadeDownTime"); +} + +Time +SubtitleNode::fade_time (string name) +{ + 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); + } + + return t; } TextNode::TextNode (xmlpp::Node const * node) @@ -155,13 +243,11 @@ TextNode::TextNode (xmlpp::Node const * node) 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 (!v.empty ()) { + v_align = string_to_valign (v); } + + font_nodes = type_children ("Font"); } list > @@ -207,7 +293,9 @@ Subtitle::Subtitle ( VAlign v_align, string text, Effect effect, - Color effect_color + Color effect_color, + Time fade_up_time, + Time fade_down_time ) : _font (font) , _italic (italic) @@ -220,6 +308,8 @@ Subtitle::Subtitle ( , _text (text) , _effect (effect) , _effect_color (effect_color) + , _fade_up_time (fade_up_time) + , _fade_down_time (fade_down_time) { } @@ -234,3 +324,178 @@ Subtitle::size_in_pixels (int screen_height) const return _size * screen_height / (11 * 72); } + +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() + ); +} + +ostream& +libdcp::operator<< (ostream& s, Subtitle const & sub) +{ + 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"; + } + + 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; +} + +void +SubtitleAsset::add (shared_ptr s) +{ + _subtitles.push_back (s); +} + +void +SubtitleAsset::write_to_cpl (ostream& s) const +{ + /* XXX: should EditRate, Duration and IntrinsicDuration be in here? */ + + s << " \n" + << " urn:uuid:" << _uuid << "\n" + << " " << _file_name << "\n" + << " 0\n" + << " \n"; +} + +struct SubtitleSorter { + bool operator() (shared_ptr a, shared_ptr b) { + if (a->in() != b->in()) { + return a->in() < b->in(); + } + return a->v_position() < b->v_position(); + } +}; + +void +SubtitleAsset::write_xml () +{ + ofstream f (path().string().c_str()); + write_xml (f); +} + +void +SubtitleAsset::write_xml (ostream& s) +{ + s << "\n" + << "\n" + << " " << _uuid << "\n" + << " " << _movie_title << "\n" + << " " << _reel_number << "\n" + << " " << _language << "\n" + << " \n"; + + _subtitles.sort (SubtitleSorter ()); + + /* XXX: multiple fonts not supported */ + /* XXX: script, underlined, weight not supported */ + + bool first = true; + bool italic = false; + Color color; + int size = 0; + Effect effect = NONE; + Color effect_color; + int spot_number = 1; + Time last_in; + Time last_out; + Time last_fade_up_time; + Time last_fade_down_time; + + for (list >::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + + /* We will start a new ... whenever some font property changes. + I suppose should really make an optimal hierarchy of tags, but + that seems hard. + */ + + bool const font_changed = first || + italic != (*i)->italic() || + color != (*i)->color() || + size != (*i)->size() || + effect != (*i)->effect() || + effect_color != (*i)->effect_color(); + + stringstream a; + if (font_changed) { + italic = (*i)->italic (); + a << "Italic=\"" << (italic ? "yes" : "no") << "\" "; + color = (*i)->color (); + a << "Color=\"" << color.to_argb_string() << "\" "; + size = (*i)->size (); + a << "Size=\"" << size << "\" "; + effect = (*i)->effect (); + a << "Effect=\"" << effect_to_string(effect) << "\" "; + effect_color = (*i)->effect_color (); + a << "EffectColor=\"" << effect_color.to_argb_string() << "\" "; + a << "Script=\"normal\" Underlined=\"no\" Weight=\"normal\""; + } + + if (first || + (last_in != (*i)->in() || + last_out != (*i)->out() || + last_fade_up_time != (*i)->fade_up_time() || + last_fade_down_time != (*i)->fade_down_time() + )) { + + if (!first) { + s << " \n"; + } + + if (font_changed) { + if (!first) { + s << " \n"; + } + s << " \n"; + } + + s << " in().to_string() << "\" " + << "TimeOut=\"" << (*i)->out().to_string() << "\" " + << "FadeUpTime=\"" << (*i)->fade_up_time().to_ticks() << "\" " + << "FadeDownTime=\"" << (*i)->fade_down_time().to_ticks() << "\"" + << ">\n"; + + last_in = (*i)->in (); + last_out = (*i)->out (); + last_fade_up_time = (*i)->fade_up_time (); + last_fade_down_time = (*i)->fade_down_time (); + } + + s << " v_align()) << "\" " + << "VPosition=\"" << (*i)->v_position() << "\"" + << ">" << (*i)->text() << "\n"; + + first = false; + } + + s << " \n"; + s << " \n"; + s << "\n"; +}