X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fsubtitle_asset.cc;h=866a6d253706401f96746527bf9eec329782ccc0;hb=cb92077b0a6730f1d71931300ad293e29185c47d;hp=0d89546e0bd6d9ea0691dea55508419f933dfbf2;hpb=c23087acc887289f04f927bf7ede352324f4081b;p=libdcp.git diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 0d89546e..866a6d25 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -17,56 +17,87 @@ */ +#include #include +#include +#include #include "subtitle_asset.h" +#include "parse/subtitle.h" #include "util.h" - -using namespace std; -using namespace boost; +#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_child ("SubtitleID"); - _movie_title = string_child ("MovieTitle"); - _reel_number = int64_child ("ReelNumber"); - _language = string_child ("Language"); + read_xml (path().string()); +} - ignore_child ("LoadFont"); +SubtitleAsset::SubtitleAsset (string directory, string movie_title, string language) + : Asset (directory) + , _movie_title (movie_title) + , _reel_number ("1") + , _language (language) + , _need_sort (false) +{ - list > font_nodes = type_children ("Font"); - _load_font_nodes = type_children ("LoadFont"); +} + +void +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"); + + xml->ignore_child ("LoadFont"); + + list > font_nodes = type_children (xml, "Font"); + _load_font_nodes = type_children (xml, "LoadFont"); /* Now make Subtitle objects to represent the raw XML nodes in a sane way. */ ParseState parse_state; - examine_font_nodes (font_nodes, parse_state); + examine_font_nodes (xml, font_nodes, parse_state); } void SubtitleAsset::examine_font_nodes ( - list > const & 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) { + 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) { + 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); + 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 ((*i)->font_nodes, parse_state); - examine_text_nodes ((*i)->text_nodes, parse_state); + examine_font_nodes (xml, (*i)->font_nodes, parse_state); + examine_text_nodes (xml, (*i)->text_nodes, parse_state); parse_state.font_nodes.pop_back (); } @@ -74,14 +105,15 @@ SubtitleAsset::examine_font_nodes ( void SubtitleAsset::examine_text_nodes ( - list > const & 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) { + 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); + examine_font_nodes (xml, (*i)->font_nodes, parse_state); parse_state.text_nodes.pop_back (); } } @@ -100,9 +132,9 @@ SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state) 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 ()); + 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 ( @@ -116,7 +148,7 @@ SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state) effective_text.v_position, effective_text.v_align, text, - effective_font.effect.get(), + effective_font.effect ? effective_font.effect.get() : NONE, effective_font.effect_color.get(), effective_subtitle.fade_up_time, effective_subtitle.fade_down_time @@ -125,117 +157,6 @@ SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state) ); } -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"); - } - effect_color = optional_color_attribute ("EffectColor"); - subtitle_nodes = type_children ("Subtitle"); - font_nodes = type_children ("Font"); - text_nodes = type_children ("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 (); - } - } -} - -LoadFontNode::LoadFontNode (xmlpp::Node const * node) - : XMLNode (node) -{ - id = string_attribute ("Id"); - uri = string_attribute ("URI"); -} - - -SubtitleNode::SubtitleNode (xmlpp::Node const * node) - : XMLNode (node) -{ - in = time_attribute ("TimeIn"); - out = time_attribute ("TimeOut"); - 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) - : XMLNode (node) - , v_align (CENTER) -{ - 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; - } - - font_nodes = type_children ("Font"); -} - list > SubtitleAsset::subtitles_at (Time t) const { @@ -252,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; } @@ -349,3 +270,151 @@ libdcp::operator<< (ostream& s, Subtitle const & sub) 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"); +} +