diff options
| author | Carl Hetherington <cth@carlh.net> | 2014-12-16 00:23:24 +0000 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2014-12-16 00:23:24 +0000 |
| commit | 19b5ac5e09e204a9c22daff460f3cbb2137d265b (patch) | |
| tree | dade81e56e3ad9934686c650eef23f92d3111211 | |
| parent | a49e1c2df1568391651344ae368c53f76d504571 (diff) | |
Use libdcp for DCP subtitle parsing so that we get support for MXF-wrapped SMPTE subtitles.
| -rw-r--r-- | src/dcp_reader.cc | 368 | ||||
| -rw-r--r-- | src/dcp_reader.h | 30 | ||||
| -rw-r--r-- | src/reader_factory.cc | 15 | ||||
| -rw-r--r-- | src/reader_factory.h | 3 | ||||
| -rw-r--r-- | src/wscript | 2 | ||||
| -rw-r--r-- | test/dcp_reader_test.cc | 6 | ||||
| -rw-r--r-- | test/dcp_to_stl_binary_test.cc | 40 | ||||
| -rw-r--r-- | wscript | 8 |
8 files changed, 99 insertions, 373 deletions
diff --git a/src/dcp_reader.cc b/src/dcp_reader.cc index 3634d93..b192b76 100644 --- a/src/dcp_reader.cc +++ b/src/dcp_reader.cc @@ -21,345 +21,71 @@ #include "vertical_reference.h" #include "xml.h" #include <libcxml/cxml.h> -#include <boost/algorithm/string.hpp> -#include <boost/lexical_cast.hpp> +#include <libdcp/subtitle_asset.h> -using std::string; using std::list; -using std::vector; -using std::istream; using std::cout; using boost::shared_ptr; -using boost::optional; -using boost::lexical_cast; -using boost::is_any_of; using namespace sub; -namespace sub { - -class DCPFont; - -/** @class DCPText - * @brief A DCP subtitle <Text> node. - */ -class DCPText -{ -public: - DCPText () - : v_position (0) - , v_align (TOP_OF_SCREEN) - {} - - DCPText (shared_ptr<const cxml::Node> node) - : v_align (CENTRE_OF_SCREEN) - { - text = node->content (); - v_position = node->number_attribute<float> ("VPosition"); - optional<string> v = node->optional_string_attribute ("VAlign"); - if (v) { - v_align = string_to_vertical_reference (v.get ()); - } - - font_nodes = type_children<DCPFont> (node, "Font"); - } - - float v_position; - VerticalReference v_align; - string text; - shared_ptr<DCPFont> foo; - list<shared_ptr<DCPFont> > font_nodes; -}; - -/** @class DCPSubtitle - * @brief A DCP subtitle <Subtitle> node. - */ -class DCPSubtitle +static MetricTime +dcp_to_metric (libdcp::Time t) { -public: - DCPSubtitle () {} - DCPSubtitle (shared_ptr<const cxml::Node> node) - { - in = MetricTime (time (node->string_attribute ("TimeIn"))); - out = MetricTime (time (node->string_attribute ("TimeOut"))); - font_nodes = type_children<DCPFont> (node, "Font"); - text_nodes = type_children<DCPText> (node, "Text"); - fade_up_time = fade_time (node, "FadeUpTime"); - fade_down_time = fade_time (node, "FadeDownTime"); - } - - MetricTime in; - MetricTime out; - MetricTime fade_up_time; - MetricTime fade_down_time; - list<shared_ptr<DCPFont> > font_nodes; - list<shared_ptr<DCPText> > text_nodes; - -private: - static MetricTime time (std::string time) - { - vector<string> b; - split (b, time, is_any_of (":")); - if (b.size() != 4) { - boost::throw_exception (XMLError ("unrecognised time specification")); - } - - return MetricTime (lexical_cast<int>(b[0]), lexical_cast<int> (b[1]), lexical_cast<int> (b[2]), lexical_cast<int> (b[3]) * 4); - } - - MetricTime fade_time (shared_ptr<const cxml::Node> node, string name) - { - string const u = node->optional_string_attribute (name).get_value_or (""); - MetricTime t; - - if (u.empty ()) { - t = MetricTime (0, 0, 0, 80); - } else if (u.find (":") != string::npos) { - t = time (u); - } else { - t = MetricTime (0, 0, 0, lexical_cast<int>(u) * 4); - } - - if (t > MetricTime (0, 0, 8, 0)) { - t = MetricTime (0, 0, 8, 0); - } - - return t; - } -}; - -/** @class DCPFont - * @brief A DCP subtitle <Font> node. - */ -class DCPFont -{ -public: - DCPFont () - : size (0) - {} - - DCPFont (shared_ptr<const cxml::Node> node) - { - text = node->content (); - - id = node->optional_string_attribute ("Id").get_value_or (""); - size = node->optional_number_attribute<int64_t> ("Size").get_value_or (0); - italic = node->optional_bool_attribute ("Italic"); - optional<string> c = node->optional_string_attribute ("Color"); - if (c) { - colour = Colour (c.get ()); - } - optional<string> const e = node->optional_string_attribute ("Effect"); - if (e) { - effect = string_to_effect (e.get ()); - } - c = node->optional_string_attribute ( "EffectColor"); - if (c) { - effect_colour = Colour (c.get ()); - } - subtitle_nodes = type_children<DCPSubtitle> (node, "Subtitle"); - font_nodes = type_children<DCPFont> (node, "Font"); - text_nodes = type_children<DCPText> (node, "Text"); - } - - DCPFont (list<shared_ptr<DCPFont> > const & font_nodes) - : size (0) - , italic (false) - , colour ("FFFFFFFF") - , effect_colour ("FFFFFFFF") - { - for (list<shared_ptr<DCPFont> >::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)->colour) { - colour = (*i)->colour.get (); - } - if ((*i)->effect) { - effect = (*i)->effect.get (); - } - if ((*i)->effect_colour) { - effect_colour = (*i)->effect_colour.get (); - } - } - } - - string text; - string id; - int size; - optional<bool> italic; - optional<Colour> colour; - optional<Effect> effect; - optional<Colour> effect_colour; - - list<shared_ptr<DCPSubtitle> > subtitle_nodes; - list<shared_ptr<DCPFont> > font_nodes; - list<shared_ptr<DCPText> > text_nodes; -}; - -/** @class DCPLoadFont - * @brief A DCP subtitle <LoadFont> node. - */ -class DCPLoadFont -{ -public: - DCPLoadFont () {} - DCPLoadFont (shared_ptr<const cxml::Node> node) - { - id = node->string_attribute ("Id"); - uri = node->string_attribute ("URI"); - } - - string id; - string uri; -}; - -/** @class DCPReader::ParseState - * @brief Holder of state for use while reading DCP subtitles. - */ -struct DCPReader::ParseState { - list<shared_ptr<DCPFont> > font_nodes; - list<shared_ptr<DCPText> > text_nodes; - list<shared_ptr<DCPSubtitle> > subtitle_nodes; -}; - + return MetricTime (t.h, t.m, t.s, t.t * 4); } -/** @param s A string. - * @return true if the string contains only space, newline or tab characters, or is empty. - */ -static bool -empty_or_white_space (string s) +static Colour +dcp_to_colour (libdcp::Color c) { - for (size_t i = 0; i < s.length(); ++i) { - if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') { - return false; - } - } - - return true; -} - -string -DCPReader::font_id_to_name (string id) const -{ - list<shared_ptr<DCPLoadFont> >::const_iterator i = _load_font_nodes.begin(); - while (i != _load_font_nodes.end() && (*i)->id != id) { - ++i; - } - - if (i == _load_font_nodes.end ()) { - return ""; - } - - if ((*i)->uri == "arial.ttf" || (*i)->uri == "Arial.ttf") { - return "Arial"; - } - - return (*i)->uri; + return Colour (float (c.r) / 255, float (c.g) / 255, float (c.b) / 255); } /** @class DCPReader * @brief A class to read DCP subtitles. */ -DCPReader::DCPReader (istream& in) -{ - shared_ptr<cxml::Document> xml (new cxml::Document ("DCSubtitle")); - xml->read_stream (in); - - xml->ignore_child ("SubtitleID"); - xml->ignore_child ("MovieTitle"); - xml->ignore_child ("ReelNumber"); - xml->ignore_child ("Language"); - - list<shared_ptr<DCPFont> > font_nodes = type_children<DCPFont> (xml, "Font"); - _load_font_nodes = type_children<DCPLoadFont> (xml, "LoadFont"); - - /* Now make Subtitle objects to represent the raw XML nodes - in a sane way. - */ - - ParseState parse_state; - examine_font_nodes (xml, font_nodes, parse_state); -} - -void -DCPReader::examine_font_nodes ( - shared_ptr<const cxml::Node> xml, - list<shared_ptr<DCPFont> > const & font_nodes, - ParseState& parse_state - ) +DCPReader::DCPReader (boost::filesystem::path file) { - for (list<shared_ptr<DCPFont> >::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<shared_ptr<DCPSubtitle> >::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 (); + libdcp::SubtitleAsset asset (file.parent_path().string(), file.leaf().string()); + list<shared_ptr<libdcp::Subtitle> > subs = asset.subtitles (); + for (list<shared_ptr<libdcp::Subtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) { + RawSubtitle sub; + + sub.vertical_position.proportional = float ((*i)->v_position ()) / 100; + switch ((*i)->v_align ()) { + case libdcp::TOP: + sub.vertical_position.reference = TOP_OF_SCREEN; + break; + case libdcp::CENTER: + sub.vertical_position.reference = CENTRE_OF_SCREEN; + break; + case libdcp::BOTTOM: + sub.vertical_position.reference = BOTTOM_OF_SCREEN; + break; } - - examine_font_nodes (xml, (*i)->font_nodes, parse_state); - examine_text_nodes (xml, (*i)->text_nodes, parse_state); + + sub.from.set_metric (dcp_to_metric ((*i)->in ())); + sub.to.set_metric (dcp_to_metric ((*i)->out ())); + sub.fade_up = dcp_to_metric ((*i)->fade_up_time ()); + sub.fade_down = dcp_to_metric ((*i)->fade_down_time ()); - parse_state.font_nodes.pop_back (); - } -} - -void -DCPReader::examine_text_nodes ( - shared_ptr<const cxml::Node> xml, - list<shared_ptr<DCPText> > const & text_nodes, - ParseState& parse_state - ) -{ - for (list<shared_ptr<DCPText> >::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 (); - } -} - -void -DCPReader::maybe_add_subtitle (string text, ParseState& parse_state) -{ - if (empty_or_white_space (text)) { - return; - } - - if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) { - return; - } - - DCPFont effective_font (parse_state.font_nodes); - DCPText effective_text (*parse_state.text_nodes.back ()); - DCPSubtitle effective_subtitle (*parse_state.subtitle_nodes.back ()); - - RawSubtitle sub; + sub.text = (*i)->text (); + sub.font = (*i)->font (); + sub.font_size.set_proportional (float ((*i)->size ()) / (72 * 11)); + switch ((*i)->effect ()) { + case libdcp::NONE: + break; + case libdcp::BORDER: + sub.effect = BORDER; + break; + case libdcp::SHADOW: + sub.effect = SHADOW; + break; + } - sub.vertical_position.proportional = float (effective_text.v_position) / 100; - sub.vertical_position.reference = effective_text.v_align; - sub.from.set_metric (effective_subtitle.in); - sub.to.set_metric (effective_subtitle.out); - sub.fade_up = effective_subtitle.fade_up_time; - sub.fade_down = effective_subtitle.fade_down_time; + sub.effect_colour = dcp_to_colour ((*i)->effect_color ()); + sub.colour = dcp_to_colour ((*i)->color ()); + sub.italic = (*i)->italic (); - sub.text = text; - sub.font = font_id_to_name (effective_font.id); - sub.font_size.set_proportional (float (effective_font.size) / (72 * 11)); - sub.effect = effective_font.effect; - sub.effect_colour = effective_font.effect_colour; - sub.colour = effective_font.colour.get (); - sub.italic = effective_font.italic.get (); - - _subs.push_back (sub); + _subs.push_back (sub); + } } diff --git a/src/dcp_reader.h b/src/dcp_reader.h index c08d631..6d4fa71 100644 --- a/src/dcp_reader.h +++ b/src/dcp_reader.h @@ -22,6 +22,7 @@ #include "reader.h" #include <boost/shared_ptr.hpp> +#include <boost/filesystem.hpp> namespace cxml { class Node; @@ -29,40 +30,13 @@ namespace cxml { namespace sub { -class DCPFont; -class DCPText; -class DCPSubtitle; -class DCPLoadFont; - /** @class DCPReader * @brief A class which reads DCP subtitles. */ class DCPReader : public Reader { public: - DCPReader (std::istream &); - -private: - - struct ParseState; - - void maybe_add_subtitle (std::string text, ParseState& parse_state); - - void examine_font_nodes ( - boost::shared_ptr<const cxml::Node> xml, - std::list<boost::shared_ptr<DCPFont> > const & font_nodes, - ParseState& parse_state - ); - - void examine_text_nodes ( - boost::shared_ptr<const cxml::Node> xml, - std::list<boost::shared_ptr<DCPText> > const & text_nodes, - ParseState& parse_state - ); - - std::string font_id_to_name (std::string id) const; - - std::list<boost::shared_ptr<DCPLoadFont> > _load_font_nodes; + DCPReader (boost::filesystem::path file); }; } diff --git a/src/reader_factory.cc b/src/reader_factory.cc index fd19c0f..31a205b 100644 --- a/src/reader_factory.cc +++ b/src/reader_factory.cc @@ -31,19 +31,18 @@ using boost::shared_ptr; using namespace sub; shared_ptr<Reader> -sub::reader_factory (string file_name) +sub::reader_factory (boost::filesystem::path file_name) { - ifstream f (file_name.c_str ()); - if (!f.good ()) { - return shared_ptr<Reader> (); - } + string ext = file_name.extension().string(); + transform (ext.begin(), ext.end(), ext.begin(), ::tolower); - if (ends_with (file_name, ".xml") || ends_with (file_name, ".XML")) { - return shared_ptr<Reader> (new DCPReader (f)); + if (ext == ".xml" || ext == ".mxf") { + return shared_ptr<Reader> (new DCPReader (file_name)); } - if (ends_with (file_name, ".stl") || ends_with (file_name, ".STL")) { + if (ext == ".stl") { /* Check the start of the DFC */ + ifstream f (file_name.string().c_str ()); char buffer[11]; f.read (buffer, 11); f.seekg (0); diff --git a/src/reader_factory.h b/src/reader_factory.h index c12a475..e7c349b 100644 --- a/src/reader_factory.h +++ b/src/reader_factory.h @@ -18,12 +18,13 @@ */ #include <boost/shared_ptr.hpp> +#include <boost/filesystem.hpp> namespace sub { class Reader; extern boost::shared_ptr<Reader> -reader_factory (std::string); +reader_factory (boost::filesystem::path); } diff --git a/src/wscript b/src/wscript index 58434b2..599e2af 100644 --- a/src/wscript +++ b/src/wscript @@ -8,7 +8,7 @@ def build(bld): obj.name = 'libsub' obj.target = 'sub' - obj.uselib = 'CXML BOOST_FILESYSTEM BOOST_LOCALE' + obj.uselib = 'CXML DCP BOOST_FILESYSTEM BOOST_LOCALE' obj.export_includes = ['.'] obj.source = """ colour.cc diff --git a/test/dcp_reader_test.cc b/test/dcp_reader_test.cc index 5da9bf0..8d0fda1 100644 --- a/test/dcp_reader_test.cc +++ b/test/dcp_reader_test.cc @@ -30,8 +30,7 @@ using boost::shared_ptr; /* Test reading of a DCP XML file */ BOOST_AUTO_TEST_CASE (dcp_reader_test1) { - ifstream file ("test/data/test1.xml"); - sub::DCPReader reader (file); + sub::DCPReader reader ("test/data/test1.xml"); list<sub::Subtitle> subs = sub::collect<list<sub::Subtitle> > (reader.subtitles ()); list<sub::Subtitle>::iterator i = subs.begin (); @@ -156,8 +155,7 @@ BOOST_AUTO_TEST_CASE (dcp_reader_test1) /* And another one */ BOOST_AUTO_TEST_CASE (dcp_reader_test2) { - ifstream file ("test/data/test2.xml"); - sub::DCPReader reader (file); + sub::DCPReader reader ("test/data/test2.xml"); list<sub::Subtitle> subs = sub::collect<list<sub::Subtitle> > (reader.subtitles ()); list<sub::Subtitle>::iterator i = subs.begin (); diff --git a/test/dcp_to_stl_binary_test.cc b/test/dcp_to_stl_binary_test.cc index aa6d1fb..3643a95 100644 --- a/test/dcp_to_stl_binary_test.cc +++ b/test/dcp_to_stl_binary_test.cc @@ -34,9 +34,8 @@ BOOST_AUTO_TEST_CASE (dcp_to_stl_binary_test1) } boost::filesystem::path p = private_test / "fd586c30-6d38-48f2-8241-27359acf184c_sub.xml"; - ifstream f (p.string().c_str ()); sub::write_stl_binary ( - sub::collect<list<sub::Subtitle> > (sub::DCPReader(f).subtitles ()), + sub::collect<list<sub::Subtitle> > (sub::DCPReader(p).subtitles ()), 25, sub::LANGUAGE_FRENCH, "", "", @@ -62,9 +61,8 @@ BOOST_AUTO_TEST_CASE (dcp_to_stl_binary_test2) } boost::filesystem::path p = private_test / "93e8a6bf-499e-4d36-9350-a9bfa2e6758a_sub.xml"; - ifstream f (p.string().c_str ()); sub::write_stl_binary ( - sub::collect<list<sub::Subtitle> > (sub::DCPReader(f).subtitles ()), + sub::collect<list<sub::Subtitle> > (sub::DCPReader(p).subtitles ()), 25, sub::LANGUAGE_FRENCH, "", "", @@ -90,9 +88,8 @@ BOOST_AUTO_TEST_CASE (dcp_to_stl_binary_test3) } boost::filesystem::path p = private_test / "Paddington_FTR_Subs_DE-FR_24fps_R1.xml"; - ifstream f (p.string().c_str ()); sub::write_stl_binary ( - sub::collect<list<sub::Subtitle> > (sub::DCPReader(f).subtitles ()), + sub::collect<list<sub::Subtitle> > (sub::DCPReader(p).subtitles ()), 25, sub::LANGUAGE_FRENCH, "", "", @@ -113,10 +110,8 @@ BOOST_AUTO_TEST_CASE (dcp_to_stl_binary_test3) BOOST_AUTO_TEST_CASE (dcp_to_stl_binary_test4) { - ifstream file ("test/data/test1.xml"); - sub::write_stl_binary ( - sub::collect<list<sub::Subtitle> > (sub::DCPReader(file).subtitles ()), + sub::collect<list<sub::Subtitle> > (sub::DCPReader("test/data/test1.xml").subtitles ()), 25, sub::LANGUAGE_FRENCH, "", "", @@ -129,3 +124,30 @@ BOOST_AUTO_TEST_CASE (dcp_to_stl_binary_test4) "build/test/test1.stl" ); } + +BOOST_AUTO_TEST_CASE (dcp_to_stl_binary_test5) +{ + if (private_test.empty ()) { + return; + } + + boost::filesystem::path p = private_test / "065d39ff-6723-4dbf-a94f-849cde82f5e1_sub.mxf"; + sub::write_stl_binary ( + sub::collect<list<sub::Subtitle> > (sub::DCPReader(p).subtitles ()), + 25, + sub::LANGUAGE_FRENCH, + "", "", + "", "", + "", "", + "300514", "300514", 0, + "GBR", + "", + "", "", + "build/test/065d39ff-6723-4dbf-a94f-849cde82f5e1_sub.stl" + ); + + check_file ( + private_test / "065d39ff-6723-4dbf-a94f-849cde82f5e1_sub.stl", + "build/test/065d39ff-6723-4dbf-a94f-849cde82f5e1_sub.stl" + ); +} @@ -7,7 +7,7 @@ VERSION = '0.01.0devel' def options(opt): opt.load('compiler_cxx') opt.add_option('--enable-debug', action='store_true', default=False, help='build with debugging information and without optimisation') - opt.add_option('--static', action='store_true', default=False, help='build libsub statically and link statically to cxml') + opt.add_option('--static', action='store_true', default=False, help='build libsub statically and link statically to cxml and dcp') opt.add_option('--target-windows', action='store_true', default=False, help='set up to do a cross-compile to make a Windows package') opt.add_option('--disable-tests', action='store_true', default=False, help='disable building of tests') @@ -31,8 +31,14 @@ def configure(conf): conf.env.LIB_CXML = ['glibmm-2.4', 'glib-2.0', 'pcre', 'sigc-2.0', 'rt', 'xml++-2.6', 'xml2', 'pthread', 'lzma', 'dl', 'z'] conf.env.STLIB_CXML = ['cxml'] conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags', uselib_store='CXML', mandatory=True) + conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP] + conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp'] + conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt'] + conf.check_cfg(package='libdcp', atleast_version='0.98', args='--cflags', uselib_store='DCP', mandatory=True) else: conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags --libs', uselib_store='CXML', mandatory=True) + conf.check_cfg(package='libdcp', atleast_version='0.97.0', args='--cflags --libs', uselib_store='DCP', mandatory=True) + conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP] boost_lib_suffix = '' if conf.env.TARGET_WINDOWS: |
