X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fsubtitle_asset.cc;h=866a6d253706401f96746527bf9eec329782ccc0;hb=afeea0415dd56a3106a4c71df2e4a6ccc2d72e74;hp=2b200a2f319cb58be7d37a3256edfb23039722ed;hpb=b56b008e2ad86bd2c29a42390891a32ae658d6c4;p=libdcp.git diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 2b200a2f..866a6d25 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -17,135 +17,144 @@ */ +#include +#include +#include +#include #include "subtitle_asset.h" - -using namespace std; -using namespace boost; +#include "parse/subtitle.h" +#include "util.h" +#include "xml.h" + +using std::string; +using std::list; +using std::ostream; +using std::ofstream; +using std::stringstream; +using boost::shared_ptr; +using boost::lexical_cast; +using boost::optional; 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) + , _need_sort (false) { - _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"); + read_xml (path().string()); +} - /* Now make Subtitle objects to represent the raw XML nodes - in a sane way. - */ +SubtitleAsset::SubtitleAsset (string directory, string movie_title, string language) + : Asset (directory) + , _movie_title (movie_title) + , _reel_number ("1") + , _language (language) + , _need_sort (false) +{ - list > current_font_nodes; - for (list >::iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) { - examine_font_node (*i, current_font_nodes); - } } 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) { - _subtitles.push_back ( - shared_ptr ( - new Subtitle ( - font_id_to_name (id_from_font_nodes (current_font_nodes)), - italic_from_font_nodes (current_font_nodes), - size_from_font_nodes (current_font_nodes), - (*j)->in, - (*j)->out, - (*k)->v_position, - (*k)->text - ) - ) - ); - } - } +SubtitleAsset::read_xml (string xml_file) +{ + shared_ptr xml (new cxml::Document ("DCSubtitle")); + xml->read_file (xml_file); + + _uuid = xml->string_child ("SubtitleID"); + _movie_title = xml->string_child ("MovieTitle"); + _reel_number = xml->string_child ("ReelNumber"); + _language = xml->string_child ("Language"); - for (list >::iterator j = font_node->font_nodes.begin(); j != font_node->font_nodes.end(); ++j) { - examine_font_node (*j, current_font_nodes); - } + xml->ignore_child ("LoadFont"); - current_font_nodes.pop_back (); -} + list > font_nodes = type_children (xml, "Font"); + _load_font_nodes = type_children (xml, "LoadFont"); -string -SubtitleAsset::id_from_font_nodes (list > const & font_nodes) const -{ - for (list >::const_reverse_iterator i = font_nodes.rbegin(); i != font_nodes.rend(); ++i) { - if (!(*i)->id.empty ()) { - return (*i)->id; - } - } + /* Now make Subtitle objects to represent the raw XML nodes + in a sane way. + */ - return ""; + ParseState parse_state; + examine_font_nodes (xml, font_nodes, parse_state); } -int -SubtitleAsset::size_from_font_nodes (list > const & font_nodes) const +void +SubtitleAsset::examine_font_nodes ( + shared_ptr xml, + list > const & font_nodes, + ParseState& parse_state + ) { - for (list >::const_reverse_iterator i = font_nodes.rbegin(); i != font_nodes.rend(); ++i) { - if ((*i)->size != 0) { - return (*i)->size; - } - } + for (list >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) { - return 0; + parse_state.font_nodes.push_back (*i); + maybe_add_subtitle ((*i)->text, parse_state); -} - -bool -SubtitleAsset::italic_from_font_nodes (list > const & font_nodes) const -{ - for (list >::const_reverse_iterator i = font_nodes.rbegin(); i != font_nodes.rend(); ++i) { - if ((*i)->italic) { - return (*i)->italic.get (); + 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 (); } - - return false; - } -FontNode::FontNode (xmlpp::Node const * node) - : XMLNode (node) +void +SubtitleAsset::examine_text_nodes ( + shared_ptr xml, + list > const & text_nodes, + ParseState& parse_state + ) { - id = string_attribute ("Id"); - size = optional_int64_attribute ("Size"); - italic = optional_bool_attribute ("Italic"); - subtitle_nodes = sub_nodes ("Subtitle"); - font_nodes = sub_nodes ("Font"); + 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 (); + } } -LoadFontNode::LoadFontNode (xmlpp::Node const * node) - : XMLNode (node) +void +SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state) { - id = string_attribute ("Id"); - uri = string_attribute ("URI"); -} + if (empty_or_white_space (text)) { + return; + } + if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) { + return; + } -SubtitleNode::SubtitleNode (xmlpp::Node const * node) - : XMLNode (node) -{ - in = time_attribute ("TimeIn"); - out = time_attribute ("TimeOut"); - text_nodes = sub_nodes ("Text"); -} - -TextNode::TextNode (xmlpp::Node const * node) - : XMLNode (node) -{ - text = content (); - v_position = float_attribute ("VPosition"); + assert (!parse_state.text_nodes.empty ()); + assert (!parse_state.subtitle_nodes.empty ()); + + libdcp::parse::Font effective_font (parse_state.font_nodes); + libdcp::parse::Text effective_text (*parse_state.text_nodes.back ()); + libdcp::parse::Subtitle 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 + ) + ) + ); } list > @@ -164,7 +173,7 @@ SubtitleAsset::subtitles_at (Time t) const std::string SubtitleAsset::font_id_to_name (string id) const { - list >::const_iterator i = _load_font_nodes.begin(); + list >::const_iterator i = _load_font_nodes.begin(); while (i != _load_font_nodes.end() && (*i)->id != id) { ++i; } @@ -181,21 +190,33 @@ SubtitleAsset::font_id_to_name (string id) const } Subtitle::Subtitle ( - std::string font, + string font, bool italic, + Color color, int size, Time in, Time out, float v_position, - std::string text + 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) { } @@ -210,3 +231,190 @@ 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); + _need_sort = true; +} + +void +SubtitleAsset::write_to_cpl (xmlpp::Element* node, bool) const +{ + /* XXX: should EditRate, Duration and IntrinsicDuration be in here? */ + + xmlpp::Node* ms = node->add_child ("MainSubtitle"); + ms->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + ms->add_child("AnnotationText")->add_child_text (_file_name); + /* XXX */ + ms->add_child("EntryPoint")->add_child_text ("0"); +} + +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 () const +{ + ofstream s (path().string().c_str()); + write_xml (s); +} + +void +SubtitleAsset::write_xml (ostream& s) const +{ + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("DCSubtitle"); + root->set_attribute ("Version", "1.0"); + + root->add_child("SubtitleID")->add_child_text (_uuid); + root->add_child("MovieTitle")->add_child_text (_movie_title); + root->add_child("ReelNumber")->add_child_text (lexical_cast (_reel_number)); + root->add_child("Language")->add_child_text (_language); + + if (_load_font_nodes.size() > 1) { + boost::throw_exception (MiscError ("multiple LoadFont nodes not supported")); + } + + if (!_load_font_nodes.empty ()) { + xmlpp::Element* load_font = root->add_child("LoadFont"); + load_font->set_attribute("Id", _load_font_nodes.front()->id); + load_font->set_attribute("URI", _load_font_nodes.front()->uri); + } + + list > sorted = _subtitles; + if (_need_sort) { + sorted.sort (SubtitleSorter ()); + } + + /* XXX: multiple fonts not supported */ + /* XXX: script, underlined, weight not supported */ + + 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; + + xmlpp::Element* font = 0; + xmlpp::Element* subtitle = 0; + + for (list >::iterator i = sorted.begin(); i != sorted.end(); ++i) { + + /* 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 = + italic != (*i)->italic() || + color != (*i)->color() || + size != (*i)->size() || + effect != (*i)->effect() || + effect_color != (*i)->effect_color(); + + if (font_changed) { + italic = (*i)->italic (); + color = (*i)->color (); + size = (*i)->size (); + effect = (*i)->effect (); + effect_color = (*i)->effect_color (); + } + + if (!font || font_changed) { + font = root->add_child ("Font"); + string id = "theFontId"; + if (!_load_font_nodes.empty()) { + id = _load_font_nodes.front()->id; + } + font->set_attribute ("Id", id); + font->set_attribute ("Italic", italic ? "yes" : "no"); + font->set_attribute ("Color", color.to_argb_string()); + font->set_attribute ("Size", lexical_cast (size)); + font->set_attribute ("Effect", effect_to_string (effect)); + font->set_attribute ("EffectColor", effect_color.to_argb_string()); + font->set_attribute ("Script", "normal"); + font->set_attribute ("Underlined", "no"); + font->set_attribute ("Weight", "normal"); + } + + if (!subtitle || 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 = font->add_child ("Subtitle"); + subtitle->set_attribute ("SpotNumber", lexical_cast (spot_number++)); + subtitle->set_attribute ("TimeIn", (*i)->in().to_string()); + subtitle->set_attribute ("TimeOut", (*i)->out().to_string()); + subtitle->set_attribute ("FadeUpTime", lexical_cast ((*i)->fade_up_time().to_ticks())); + subtitle->set_attribute ("FadeDownTime", lexical_cast ((*i)->fade_down_time().to_ticks())); + + last_in = (*i)->in (); + last_out = (*i)->out (); + last_fade_up_time = (*i)->fade_up_time (); + last_fade_down_time = (*i)->fade_down_time (); + } + + xmlpp::Element* text = subtitle->add_child ("Text"); + text->set_attribute ("VAlign", valign_to_string ((*i)->v_align())); + text->set_attribute ("VPosition", lexical_cast ((*i)->v_position())); + text->add_child_text ((*i)->text()); + } + + doc.write_to_stream_formatted (s, "UTF-8"); +} +