diff options
| author | Carl Hetherington <cth@carlh.net> | 2014-10-06 13:31:45 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2014-10-06 13:31:45 +0100 |
| commit | 630a56ad9e7ecfb42b1d761098820f9e492f9c4e (patch) | |
| tree | 18080ecfc5b716d22ae041908356b11350c87e0b | |
| parent | 152b14ab5509678f3d68b52a1ed275cfdc38191b (diff) | |
Use libsub for subrip decoding; improve default position of srt subs.
| -rw-r--r-- | cscript | 3 | ||||
| -rw-r--r-- | src/lib/subrip.cc | 189 | ||||
| -rw-r--r-- | src/lib/subrip.h | 14 | ||||
| -rw-r--r-- | src/lib/subrip_decoder.cc | 58 | ||||
| -rw-r--r-- | src/lib/wscript | 2 | ||||
| -rw-r--r-- | test/subrip_test.cc | 162 | ||||
| -rw-r--r-- | wscript | 14 |
7 files changed, 57 insertions, 385 deletions
@@ -157,7 +157,8 @@ def make_control(debian_version, bits, filename, debug): def dependencies(target): return (('ffmpeg-cdist', '2dffa11'), - ('libdcp', '1.0')) + ('libdcp', '1.0'), + ('libsub', None)) def build(target, options): cmd = './waf configure --prefix=%s' % target.directory diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc index 6c9cef916..8a7423698 100644 --- a/src/lib/subrip.cc +++ b/src/lib/subrip.cc @@ -17,23 +17,17 @@ */ -#include <boost/algorithm/string.hpp> -#include <boost/lexical_cast.hpp> #include "subrip.h" -#include "subrip_content.h" -#include "subrip_subtitle.h" #include "cross.h" #include "exceptions.h" +#include "subrip_content.h" +#include <libsub/subrip_reader.h> +#include <libsub/collect.h> #include "i18n.h" -using std::string; -using std::list; using std::vector; -using std::cout; using boost::shared_ptr; -using boost::lexical_cast; -using boost::algorithm::trim; SubRip::SubRip (shared_ptr<const SubRipContent> content) { @@ -42,179 +36,8 @@ SubRip::SubRip (shared_ptr<const SubRipContent> content) throw OpenFileError (content->path (0)); } - enum { - COUNTER, - METADATA, - CONTENT - } state = COUNTER; - - char buffer[256]; - - boost::optional<SubRipSubtitle> current; - list<string> lines; - - while (!feof (f)) { - fgets (buffer, sizeof (buffer), f); - if (feof (f)) { - break; - } - - string line (buffer); - trim_right_if (line, boost::is_any_of ("\n\r")); - - switch (state) { - case COUNTER: - { - if (line.empty ()) { - /* a blank line at the start is ok */ - break; - } - - state = METADATA; - current = SubRipSubtitle (); - } - break; - case METADATA: - { - vector<string> p; - boost::algorithm::split (p, line, boost::algorithm::is_any_of (" ")); - if (p.size() != 3 && p.size() != 7) { - throw SubRipError (line, _("a time/position line"), content->path (0)); - } - - current->period = ContentTimePeriod (convert_time (p[0]), convert_time (p[2])); - - if (p.size() > 3) { - current->x1 = convert_coordinate (p[3]); - current->x2 = convert_coordinate (p[4]); - current->y1 = convert_coordinate (p[5]); - current->y2 = convert_coordinate (p[6]); - } - state = CONTENT; - break; - } - case CONTENT: - if (line.empty ()) { - state = COUNTER; - current->pieces = convert_content (lines); - _subtitles.push_back (current.get ()); - current.reset (); - lines.clear (); - } else { - lines.push_back (line); - } - break; - } - } - - if (state == CONTENT) { - current->pieces = convert_content (lines); - _subtitles.push_back (current.get ()); - } - - fclose (f); -} - -ContentTime -SubRip::convert_time (string t) -{ - ContentTime r; - - vector<string> a; - boost::algorithm::split (a, t, boost::is_any_of (":")); - assert (a.size() == 3); - r += ContentTime::from_seconds (lexical_cast<int> (a[0]) * 60 * 60); - r += ContentTime::from_seconds (lexical_cast<int> (a[1]) * 60); - - vector<string> b; - boost::algorithm::split (b, a[2], boost::is_any_of (",")); - r += ContentTime::from_seconds (lexical_cast<int> (b[0])); - r += ContentTime::from_seconds (lexical_cast<double> (b[1]) / 1000); - - return r; -} - -int -SubRip::convert_coordinate (string t) -{ - vector<string> a; - boost::algorithm::split (a, t, boost::is_any_of (":")); - assert (a.size() == 2); - return lexical_cast<int> (a[1]); -} - -void -SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p) -{ - if (!p.text.empty ()) { - pieces.push_back (p); - p.text.clear (); - } -} - -list<SubRipSubtitlePiece> -SubRip::convert_content (list<string> t) -{ - list<SubRipSubtitlePiece> pieces; - - SubRipSubtitlePiece p; - - enum { - TEXT, - TAG - } state = TEXT; - - string tag; - - /* XXX: missing <font> support */ - /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might - not work, I think. - */ - - for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) { - for (size_t j = 0; j < i->size(); ++j) { - switch (state) { - case TEXT: - if ((*i)[j] == '<' || (*i)[j] == '{') { - state = TAG; - } else { - p.text += (*i)[j]; - } - break; - case TAG: - if ((*i)[j] == '>' || (*i)[j] == '}') { - if (tag == "b") { - maybe_content (pieces, p); - p.bold = true; - } else if (tag == "/b") { - maybe_content (pieces, p); - p.bold = false; - } else if (tag == "i") { - maybe_content (pieces, p); - p.italic = true; - } else if (tag == "/i") { - maybe_content (pieces, p); - p.italic = false; - } else if (tag == "u") { - maybe_content (pieces, p); - p.underline = true; - } else if (tag == "/u") { - maybe_content (pieces, p); - p.underline = false; - } - tag.clear (); - state = TEXT; - } else { - tag += (*i)[j]; - } - break; - } - } - } - - maybe_content (pieces, p); - - return pieces; + sub::SubripReader reader (f); + _subtitles = sub::collect<vector<sub::Subtitle> > (reader.subtitles ()); } ContentTime @@ -224,5 +47,5 @@ SubRip::length () const return ContentTime (); } - return _subtitles.back().period.to; + return ContentTime::from_seconds (_subtitles.back().to.metric().get().all_as_seconds ()); } diff --git a/src/lib/subrip.h b/src/lib/subrip.h index 7603a101d..14bc360c0 100644 --- a/src/lib/subrip.h +++ b/src/lib/subrip.h @@ -21,6 +21,7 @@ #define DCPOMATIC_SUBRIP_H #include "subrip_subtitle.h" +#include <libsub/subtitle.h> class SubRipContent; class subrip_time_test; @@ -36,18 +37,7 @@ public: ContentTime length () const; protected: - std::vector<SubRipSubtitle> _subtitles; - -private: - friend struct subrip_time_test; - friend struct subrip_coordinate_test; - friend struct subrip_content_test; - friend struct subrip_parse_test; - - static ContentTime convert_time (std::string); - static int convert_coordinate (std::string); - static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>); - static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &); + std::vector<sub::Subtitle> _subtitles; }; #endif diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc index 2ae285b5e..411e7542d 100644 --- a/src/lib/subrip_decoder.cc +++ b/src/lib/subrip_decoder.cc @@ -39,11 +39,9 @@ SubRipDecoder::seek (ContentTime time, bool accurate) SubtitleDecoder::seek (time, accurate); _next = 0; - list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); - while (i != _subtitles[_next].pieces.end() && _subtitles[_next].period.from < time) { - ++i; + while (_next < _subtitles.size() && ContentTime::from_seconds (_subtitles[_next].from.metric().get().all_as_seconds ()) < time) { + ++_next; } - } bool @@ -56,25 +54,27 @@ SubRipDecoder::pass () /* XXX: we are ignoring positioning specified in the file */ list<dcp::SubtitleString> out; - for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) { - out.push_back ( - dcp::SubtitleString ( - "Arial", - i->italic, - dcp::Color (255, 255, 255), - /* .srt files don't specify size, so this is an arbitrary value */ - 48, - dcp::Time (rint (_subtitles[_next].period.from.seconds() * 250)), - dcp::Time (rint (_subtitles[_next].period.to.seconds() * 250)), - 0.2, - dcp::BOTTOM, - i->text, - dcp::NONE, - dcp::Color (255, 255, 255), - 0, - 0 - ) - ); + for (list<sub::Line>::const_iterator i = _subtitles[_next].lines.begin(); i != _subtitles[_next].lines.end(); ++i) { + for (list<sub::Block>::const_iterator j = i->blocks.begin(); j != i->blocks.end(); ++j) { + out.push_back ( + dcp::SubtitleString ( + "Arial", + j->italic, + dcp::Color (255, 255, 255), + /* .srt files don't specify size, so this is an arbitrary value */ + 48, + dcp::Time (rint (_subtitles[_next].from.metric().get().all_as_milliseconds() / 4)), + dcp::Time (rint (_subtitles[_next].to.metric().get().all_as_milliseconds() / 4)), + i->vertical_position.line.get() * (1.5 / 22) + 0.8, + dcp::TOP, + j->text, + dcp::NONE, + dcp::Color (255, 255, 255), + 0, + 0 + ) + ); + } } text_subtitle (out); @@ -89,9 +89,15 @@ SubRipDecoder::subtitles_during (ContentTimePeriod p, bool starting) const list<ContentTimePeriod> d; - for (vector<SubRipSubtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { - if ((starting && p.contains (i->period.from)) || (!starting && p.overlaps (i->period))) { - d.push_back (i->period); + for (vector<sub::Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + + ContentTimePeriod t ( + ContentTime::from_seconds (i->from.metric().get().all_as_seconds()), + ContentTime::from_seconds (i->to.metric().get().all_as_seconds()) + ); + + if ((starting && p.contains (t.from)) || (!starting && p.overlaps (t))) { + d.push_back (t); } } diff --git a/src/lib/wscript b/src/lib/wscript index 4e62206f1..d62c22bae 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -106,7 +106,7 @@ def build(bld): AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++ - CURL ZIP QUICKMAIL PANGOMM CAIROMM XMLSEC + CURL ZIP QUICKMAIL PANGOMM CAIROMM XMLSEC SUB """ if bld.env.TARGET_OSX: diff --git a/test/subrip_test.cc b/test/subrip_test.cc index 0827005c1..e18ee2ea5 100644 --- a/test/subrip_test.cc +++ b/test/subrip_test.cc @@ -35,152 +35,6 @@ using std::string; using boost::shared_ptr; using boost::dynamic_pointer_cast; -/** Test SubRip::convert_time */ -BOOST_AUTO_TEST_CASE (subrip_time_test) -{ - BOOST_CHECK_EQUAL (SubRip::convert_time ("00:03:10,500"), ContentTime::from_seconds ((3 * 60) + 10 + 0.5)); - BOOST_CHECK_EQUAL (SubRip::convert_time ("04:19:51,782"), ContentTime::from_seconds ((4 * 3600) + (19 * 60) + 51 + 0.782)); -} - -/** Test SubRip::convert_coordinate */ -BOOST_AUTO_TEST_CASE (subrip_coordinate_test) -{ - BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("foo:42"), 42); - BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("X1:999"), 999); -} - -/** Test SubRip::convert_content */ -BOOST_AUTO_TEST_CASE (subrip_content_test) -{ - list<string> c; - list<SubRipSubtitlePiece> p; - - c.push_back ("Hello world"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 1); - BOOST_CHECK_EQUAL (p.front().text, "Hello world"); - c.clear (); - - c.push_back ("<b>Hello world</b>"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 1); - BOOST_CHECK_EQUAL (p.front().text, "Hello world"); - BOOST_CHECK_EQUAL (p.front().bold, true); - c.clear (); - - c.push_back ("<i>Hello world</i>"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 1); - BOOST_CHECK_EQUAL (p.front().text, "Hello world"); - BOOST_CHECK_EQUAL (p.front().italic, true); - c.clear (); - - c.push_back ("<u>Hello world</u>"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 1); - BOOST_CHECK_EQUAL (p.front().text, "Hello world"); - BOOST_CHECK_EQUAL (p.front().underline, true); - c.clear (); - - c.push_back ("{b}Hello world{/b}"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 1); - BOOST_CHECK_EQUAL (p.front().text, "Hello world"); - BOOST_CHECK_EQUAL (p.front().bold, true); - c.clear (); - - c.push_back ("{i}Hello world{/i}"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 1); - BOOST_CHECK_EQUAL (p.front().text, "Hello world"); - BOOST_CHECK_EQUAL (p.front().italic, true); - c.clear (); - - c.push_back ("{u}Hello world{/u}"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 1); - BOOST_CHECK_EQUAL (p.front().text, "Hello world"); - BOOST_CHECK_EQUAL (p.front().underline, true); - c.clear (); - - c.push_back ("<b>This is <i>nesting</i> of subtitles</b>"); - p = SubRip::convert_content (c); - BOOST_CHECK_EQUAL (p.size(), 3); - list<SubRipSubtitlePiece>::iterator i = p.begin (); - BOOST_CHECK_EQUAL (i->text, "This is "); - BOOST_CHECK_EQUAL (i->bold, true); - BOOST_CHECK_EQUAL (i->italic, false); - ++i; - BOOST_CHECK_EQUAL (i->text, "nesting"); - BOOST_CHECK_EQUAL (i->bold, true); - BOOST_CHECK_EQUAL (i->italic, true); - ++i; - BOOST_CHECK_EQUAL (i->text, " of subtitles"); - BOOST_CHECK_EQUAL (i->bold, true); - BOOST_CHECK_EQUAL (i->italic, false); - ++i; - c.clear (); -} - -/** Test parsing of full SubRip file content */ -BOOST_AUTO_TEST_CASE (subrip_parse_test) -{ - shared_ptr<Film> film = new_test_film ("subrip_parse_test"); - shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip.srt")); - content->examine (shared_ptr<Job> ()); - BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471)); - - SubRip s (content); - - vector<SubRipSubtitle>::const_iterator i = s._subtitles.begin(); - - BOOST_CHECK (i != s._subtitles.end ()); - BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 49.200)); - BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 52.351)); - BOOST_CHECK_EQUAL (i->pieces.size(), 1); - BOOST_CHECK_EQUAL (i->pieces.front().text, "This is a subtitle, and it goes over two lines."); - - ++i; - BOOST_CHECK (i != s._subtitles.end ()); - BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 52.440)); - BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 54.351)); - BOOST_CHECK_EQUAL (i->pieces.size(), 1); - BOOST_CHECK_EQUAL (i->pieces.front().text, "We have emboldened this"); - BOOST_CHECK_EQUAL (i->pieces.front().bold, true); - - ++i; - BOOST_CHECK (i != s._subtitles.end ()); - BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 54.440)); - BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 56.590)); - BOOST_CHECK_EQUAL (i->pieces.size(), 1); - BOOST_CHECK_EQUAL (i->pieces.front().text, "And italicised this."); - BOOST_CHECK_EQUAL (i->pieces.front().italic, true); - - ++i; - BOOST_CHECK (i != s._subtitles.end ()); - BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 56.680)); - BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 58.955)); - BOOST_CHECK_EQUAL (i->pieces.size(), 1); - BOOST_CHECK_EQUAL (i->pieces.front().text, "Shall I compare thee to a summers' day?"); - - ++i; - BOOST_CHECK (i != s._subtitles.end ()); - BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((2 * 60) + 0.840)); - BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((2 * 60) + 3.400)); - BOOST_CHECK_EQUAL (i->pieces.size(), 1); - BOOST_CHECK_EQUAL (i->pieces.front().text, "Is this a dagger I see before me?"); - - ++i; - BOOST_CHECK (i != s._subtitles.end ()); - BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((3 * 60) + 54.560)); - BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((3 * 60) + 56.471)); - BOOST_CHECK_EQUAL (i->pieces.size(), 1); - BOOST_CHECK_EQUAL (i->pieces.front().text, "Hello world."); - - ++i; - BOOST_CHECK (i == s._subtitles.end ()); -} - /** Test rendering of a SubRip subtitle */ BOOST_AUTO_TEST_CASE (subrip_render_test) { @@ -201,19 +55,3 @@ BOOST_AUTO_TEST_CASE (subrip_render_test) write_image (image.image, "build/test/subrip_render_test.png"); check_file ("build/test/subrip_render_test.png", "test/data/subrip_render_test.png"); } - -static void -test (boost::filesystem::path p) -{ - shared_ptr<Film> film = new_test_film ("subrip_read_test_" + p.string ()); - boost::filesystem::path t = private_data / p; - shared_ptr<SubRipContent> s (new SubRipContent (film, t)); - s->examine (shared_ptr<Job> ()); -} - -/** Test of reading some typical .srt files */ -BOOST_AUTO_TEST_CASE (subrip_read_test) -{ - test ("sintel_en.srt"); - test ("Fight.Club.1999.720p.BRRip.x264-x0r.srt"); -} @@ -57,6 +57,11 @@ def dynamic_openjpeg(conf): conf.check_cfg(package='libopenjpeg', args='--cflags --libs', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True) conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.2', mandatory=True) +def static_sub(conf): + conf.check_cfg(package='libsub', atleast_version='0.01.0', args='--cflags', uselib_store='SUB', mandatory=True) + conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB] + conf.env.STLIB_SUB = ['sub'] + def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh): conf.check_cfg(package='libdcp-1.0', atleast_version='0.96', args='--cflags', uselib_store='DCP', mandatory=True) conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP] @@ -87,6 +92,10 @@ def dynamic_dcp(conf): conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True) conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP] +def dynamic_sub(conf): + conf.check_cfg(package='libsub', atleast_version='0.01.0', args='--cflags --libs', uselib_store='SUB', mandatory=True) + conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB] + def dynamic_ssh(conf): conf.check_cc(fragment=""" #include <libssh/libssh.h>\n @@ -262,6 +271,7 @@ def configure(conf): conf.env.STLIB_QUICKMAIL = ['quickmail'] static_ffmpeg(conf) static_openjpeg(conf) + static_sub(conf) static_dcp(conf, False, False, False, False) dynamic_boost(conf, boost_lib_suffix, boost_thread) @@ -276,6 +286,7 @@ def configure(conf): conf.env.LIB_QUICKMAIL = ['ssh2', 'idn'] static_ffmpeg(conf) static_openjpeg(conf) + static_sub(conf) static_dcp(conf, True, True, True, True) static_boost(conf, boost_lib_suffix) @@ -290,6 +301,7 @@ def configure(conf): conf.env.LIB_XMLSEC = ['ltdl'] static_ffmpeg(conf) static_openjpeg(conf) + static_sub(conf) static_dcp(conf, False, True, True, True) dynamic_boost(conf, boost_lib_suffix, boost_thread) @@ -308,6 +320,7 @@ def configure(conf): dynamic_ffmpeg(conf) dynamic_openjpeg(conf) dynamic_dcp(conf) + dynamic_sub(conf) dynamic_ssh(conf) # Not packaging; just a straight build @@ -319,6 +332,7 @@ def configure(conf): dynamic_boost(conf, boost_lib_suffix, boost_thread) dynamic_ffmpeg(conf) dynamic_dcp(conf) + dynamic_sub(conf) dynamic_openjpeg(conf) dynamic_ssh(conf) |
