diff options
| author | Carl Hetherington <cth@carlh.net> | 2012-10-19 19:42:26 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2012-10-19 19:42:26 +0100 |
| commit | af9b2212f0d8f8c1f53aaf29e520b812dfdc321c (patch) | |
| tree | 82d12ed02b489d4e0f978a333cc130f4c427ea60 /src | |
| parent | 13c6d55d16337abbd8d8969d7062a515c1b26995 (diff) | |
Initial work on subtitle writing.
Diffstat (limited to 'src')
| -rw-r--r-- | src/asset.cc | 4 | ||||
| -rw-r--r-- | src/asset.h | 4 | ||||
| -rw-r--r-- | src/dcp.cc | 3 | ||||
| -rw-r--r-- | src/dcp.h | 1 | ||||
| -rw-r--r-- | src/dcp_time.cc | 21 | ||||
| -rw-r--r-- | src/dcp_time.h | 4 | ||||
| -rw-r--r-- | src/subtitle_asset.cc | 194 | ||||
| -rw-r--r-- | src/subtitle_asset.h | 16 | ||||
| -rw-r--r-- | src/types.cc | 81 | ||||
| -rw-r--r-- | src/types.h | 9 | ||||
| -rw-r--r-- | src/xml.h | 1 |
11 files changed, 295 insertions, 43 deletions
diff --git a/src/asset.cc b/src/asset.cc index a7e28fd4..5ecc2e9f 100644 --- a/src/asset.cc +++ b/src/asset.cc @@ -39,7 +39,9 @@ Asset::Asset (string directory, string file_name) , _file_name (file_name) , _uuid (make_uuid ()) { - + if (_file_name.empty ()) { + _file_name = _uuid + ".xml"; + } } void diff --git a/src/asset.h b/src/asset.h index 32fd93ae..d518a5b9 100644 --- a/src/asset.h +++ b/src/asset.h @@ -45,9 +45,9 @@ class Asset public: /** Construct an Asset. * @param directory Directory where our XML or MXF file is. - * @param file_name Name of our file within directory. + * @param file_name Name of our file within directory, or empty to make one up based on UUID. */ - Asset (std::string directory, std::string file_name); + Asset (std::string directory, std::string file_name = ""); /** Write details of the asset to a CPL stream. * @param s Stream. @@ -216,8 +216,6 @@ DCP::read (bool require_mxfs) } else { throw DCPReadError ("duplicate PKLs found"); } - } else if (root == "DCSubtitle") { - files.subtitles.push_back (t.string()); } } @@ -242,7 +240,6 @@ DCP::read (bool require_mxfs) for (list<string>::iterator i = files.cpls.begin(); i != files.cpls.end(); ++i) { _cpls.push_back (shared_ptr<CPL> (new CPL (_directory, *i, asset_map, require_mxfs))); } - } bool @@ -177,7 +177,6 @@ private: std::list<std::string> cpls; std::string pkl; std::string asset_map; - std::list<std::string> subtitles; }; /** the directory that we are writing to */ diff --git a/src/dcp_time.cc b/src/dcp_time.cc index bc15c367..c9cd751a 100644 --- a/src/dcp_time.cc +++ b/src/dcp_time.cc @@ -75,6 +75,12 @@ libdcp::operator== (Time const & a, Time const & b) } bool +libdcp::operator!= (Time const & a, Time const & b) +{ + return !(a == b); +} + +bool libdcp::operator<= (Time const & a, Time const & b) { if (a.h != b.h) { @@ -210,3 +216,18 @@ libdcp::operator/ (Time a, Time const & b) int64_t const bt = b.h * 3600 * 250 + b.m * 60 * 250 + b.s * 250 + b.t; return float (at) / bt; } + +string +Time::to_string () const +{ + stringstream str; + str << h << ":" << m << ":" << s << ":" << t; + return str.str (); +} + +int64_t +Time::to_ticks () const +{ + return t + s * 25 + m * 60 * 25 + h * 60 * 60 * 25; +} + diff --git a/src/dcp_time.h b/src/dcp_time.h index 0bbf5510..8033348b 100644 --- a/src/dcp_time.h +++ b/src/dcp_time.h @@ -53,9 +53,13 @@ public: int m; ///< minutes int s; ///< seconds int t; ///< `ticks', where 1 tick is 4 milliseconds + + std::string to_string () const; + int64_t to_ticks () const; }; extern bool operator== (Time const & a, Time const & b); +extern bool operator!= (Time const & a, Time const & b); extern bool operator<= (Time const & a, Time const & b); extern bool operator< (Time const & a, Time const & b); extern bool operator> (Time const & a, Time const & b); diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 1df4496a..8e15ba1a 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -17,6 +17,7 @@ */ +#include <fstream> #include <boost/lexical_cast.hpp> #include "subtitle_asset.h" #include "util.h" @@ -25,30 +26,41 @@ using namespace std; using namespace boost; 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_child ("SubtitleID"); - _movie_title = string_child ("MovieTitle"); - _reel_number = string_child ("ReelNumber"); - _language = string_child ("Language"); + shared_ptr<XMLFile> xml (new XMLFile (path().string(), "DCSubtitle")); + + _uuid = xml->string_child ("SubtitleID"); + _movie_title = xml->string_child ("MovieTitle"); + _reel_number = xml->string_child ("ReelNumber"); + _language = xml->string_child ("Language"); - ignore_child ("LoadFont"); + xml->ignore_child ("LoadFont"); - list<shared_ptr<FontNode> > font_nodes = type_children<FontNode> ("Font"); - _load_font_nodes = type_children<LoadFontNode> ("LoadFont"); + list<shared_ptr<FontNode> > font_nodes = xml->type_children<FontNode> ("Font"); + _load_font_nodes = xml->type_children<LoadFontNode> ("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); +} + +SubtitleAsset::SubtitleAsset (string directory, string movie_title, string language) + : Asset (directory) + , _movie_title (movie_title) + , _reel_number ("1") + , _language (language) +{ + } void SubtitleAsset::examine_font_nodes ( + shared_ptr<XMLFile> xml, list<shared_ptr<FontNode> > const & font_nodes, ParseState& parse_state ) @@ -60,13 +72,13 @@ SubtitleAsset::examine_font_nodes ( for (list<shared_ptr<SubtitleNode> >::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,6 +86,7 @@ SubtitleAsset::examine_font_nodes ( void SubtitleAsset::examine_text_nodes ( + shared_ptr<XMLFile> xml, list<shared_ptr<TextNode> > const & text_nodes, ParseState& parse_state ) @@ -81,7 +94,7 @@ SubtitleAsset::examine_text_nodes ( for (list<shared_ptr<TextNode> >::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 (); } } @@ -135,14 +148,8 @@ FontNode::FontNode (xmlpp::Node const * node) 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 = type_children<SubtitleNode> ("Subtitle"); @@ -225,12 +232,8 @@ 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<FontNode> ("Font"); @@ -349,3 +352,134 @@ libdcp::operator<< (ostream& s, Subtitle const & sub) return s; } + +void +SubtitleAsset::add (shared_ptr<Subtitle> s) +{ + _subtitles.push_back (s); +} + +void +SubtitleAsset::write_to_cpl (ostream& s) const +{ + /* XXX: should EditRate, Duration and IntrinsicDuration be in here? */ + + s << " <MainSubtitle>\n" + << " <Id>urn:uuid:" << _uuid << "</Id>\n" + << " <AnnotationText>" << _file_name << "</AnnotationText>\n" + << " <EntryPoint>0</EntryPoint>\n" + << " </MainSubtitle>\n"; +} + +struct SubtitleSorter { + bool operator() (shared_ptr<Subtitle> a, shared_ptr<Subtitle> b) { + return a->in() < b->in(); + } +}; + +void +SubtitleAsset::write_xml () +{ + ofstream f (path().string().c_str()); + + f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + << "<DCSubtitle Version=\"1.0\">\n" + << " <SubtitleID>" << _uuid << "</SubtitleID>\n" + << " <MovieTitle>" << _movie_title << "</MovieTitle>\n" + << " <ReelNumber>" << _reel_number << "</ReelNumber>\n" + << " <Language>" << _language << "</Language>\n" + << " <LoadFont Id=\"theFontId\" URI=\"arial.ttf\"/>"; + + _subtitles.sort (SubtitleSorter ()); + + /* XXX: multiple fonts not supported */ + /* XXX: script, underlined, weight not supported */ + + bool first = true; + bool italic; + 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<shared_ptr<Subtitle> >::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + + stringstream a; + if (first || italic != (*i)->italic()) { + italic = (*i)->italic (); + a << "Italic=\"" << (italic ? "yes" : "no") << "\" "; + } + + if (first || color != (*i)->color()) { + color = (*i)->color (); + a << "Color=\"" << color.to_argb_string() << "\" "; + } + + if (size || size != (*i)->size()) { + size = (*i)->size (); + a << "Size=\"" << size << "\" "; + } + + if (first || effect != (*i)->effect()) { + effect = (*i)->effect (); + a << "Effect=\"" << effect_to_string(effect) << "\" "; + } + + if (first || effect_color != (*i)->effect_color()) { + effect_color = (*i)->effect_color (); + a << "EffectColor=\"" << effect_color.to_argb_string() << "\" "; + } + + if (first) { + a << "Script=\"normal\" Underlined=\"no\" Weight=\"normal\">"; + } + + if (!a.str().empty()) { + if (!first) { + f << " </Font>\n"; + } else { + f << " <Font Id=\"theFontId\" " << a << ">\n"; + } + } + + 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) { + f << " </Subtitle>\n"; + } + + f << " <Subtitle " + << "SpotNumber=\"" << spot_number++ << "\" " + << "TimeIn=" << (*i)->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 (); + } + + f << " <Text " + << "VAlign=\"" << valign_to_string ((*i)->v_align()) << "\" " + << "VPosition=\"" << (*i)->v_position() << "\" " + << ">" << (*i)->text() << "</Text>\n"; + + first = false; + } + + f << " </Subtitle>\n"; + f << "</Font>\n"; +} diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h index 66f75cbe..1b834522 100644 --- a/src/subtitle_asset.h +++ b/src/subtitle_asset.h @@ -177,12 +177,13 @@ private: bool operator== (Subtitle const & a, Subtitle const & b); std::ostream& operator<< (std::ostream& s, Subtitle const & sub); -class SubtitleAsset : public Asset, public XMLFile +class SubtitleAsset : public Asset { public: - SubtitleAsset (std::string directory, std::string xml); + SubtitleAsset (std::string directory, std::string xml_file); + SubtitleAsset (std::string directory, std::string movie_title, std::string language); - void write_to_cpl (std::ostream&) const {} + void write_to_cpl (std::ostream&) const; virtual bool equals (boost::shared_ptr<const Asset>, EqualityOptions, std::list<std::string>& notes) const { /* XXX */ notes.push_back ("subtitle assets not compared yet"); @@ -198,6 +199,10 @@ public: return _subtitles; } + void add (boost::shared_ptr<Subtitle>); + + void write_xml (); + private: std::string font_id_to_name (std::string id) const; @@ -210,16 +215,17 @@ private: void maybe_add_subtitle (std::string text, ParseState const & parse_state); void examine_font_nodes ( + boost::shared_ptr<XMLFile> xml, std::list<boost::shared_ptr<FontNode> > const & font_nodes, ParseState& parse_state ); void examine_text_nodes ( + boost::shared_ptr<XMLFile> xml, std::list<boost::shared_ptr<TextNode> > const & text_nodes, ParseState& parse_state ); - - std::string _subtitle_id; + std::string _movie_title; /* strangely, this is sometimes a string */ std::string _reel_number; diff --git a/src/types.cc b/src/types.cc index 50aee2a2..a00e8261 100644 --- a/src/types.cc +++ b/src/types.cc @@ -1,5 +1,6 @@ #include <vector> #include <cstdio> +#include <iomanip> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> #include "types.h" @@ -56,6 +57,20 @@ Color::Color (string argb_hex) } } +string +Color::to_argb_string () const +{ + stringstream s; + s << "FF"; + s << hex + << setw(2) << setfill('0') << r + << setw(2) << setfill('0') << g + << setw(2) << setfill('0') << b; + + string t = s.str(); + to_upper (t); + return t; +} bool libdcp::operator== (Color const & a, Color const & b) @@ -63,9 +78,75 @@ libdcp::operator== (Color const & a, Color const & b) return (a.r == b.r && a.g == b.g && a.b == b.b); } +bool +libdcp::operator!= (Color const & a, Color const & b) +{ + return !(a == b); +} + ostream & libdcp::operator<< (ostream& s, Color const & c) { s << "(" << c.r << ", " << c.g << ", " << c.b << ")"; return s; } + +string +libdcp::effect_to_string (Effect e) +{ + switch (e) { + case NONE: + return "none"; + case BORDER: + return "border"; + case SHADOW: + return "shadow"; + } + + throw MiscError ("unknown effect type"); +} + +Effect +libdcp::string_to_effect (string s) +{ + if (s == "none") { + return NONE; + } else if (s == "border") { + return BORDER; + } else if (s == "shadow") { + return SHADOW; + } + + throw DCPReadError ("unknown subtitle effect type"); +} + +string +libdcp::valign_to_string (VAlign v) +{ + switch (v) { + case TOP: + return "top"; + case CENTER: + return "center"; + case BOTTOM: + return "bottom"; + } + + throw MiscError ("unknown valign type"); +} + +VAlign +libdcp::string_to_valign (string s) +{ + if (s == "top") { + return TOP; + } else if (s == "center") { + return CENTER; + } else if (s == "bottom") { + return BOTTOM; + } + + throw DCPReadError ("unknown subtitle valign type"); +} + + diff --git a/src/types.h b/src/types.h index 3acded36..133c229d 100644 --- a/src/types.h +++ b/src/types.h @@ -58,6 +58,9 @@ enum Effect SHADOW }; +extern std::string effect_to_string (Effect e); +extern Effect string_to_effect (std::string s); + enum VAlign { TOP, @@ -65,6 +68,9 @@ enum VAlign BOTTOM }; +extern std::string valign_to_string (VAlign a); +extern VAlign string_to_valign (std::string s); + enum Eye { EYE_LEFT, @@ -107,9 +113,12 @@ public: int r; int g; int b; + + std::string to_argb_string () const; }; extern bool operator== (Color const & a, Color const & b); +extern bool operator!= (Color const & a, Color const & b); extern std::ostream & operator<< (std::ostream & s, Color const & c); } @@ -24,7 +24,6 @@ public: XMLNode (); XMLNode (xmlpp::Node const * node); -protected: std::string string_child (std::string); std::string optional_string_child (std::string); ContentKind kind_child (std::string); |
