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
*/
-#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)
{
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
return ContentTime ();
}
- return _subtitles.back().period.to;
+ return ContentTime::from_seconds (_subtitles.back().to.metric().get().all_as_seconds ());
}
#define DCPOMATIC_SUBRIP_H
#include "subrip_subtitle.h"
+#include <libsub/subtitle.h>
class SubRipContent;
class subrip_time_test;
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
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
/* 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);
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);
}
}
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:
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)
{
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");
-}
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]
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
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)
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)
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)
dynamic_ffmpeg(conf)
dynamic_openjpeg(conf)
dynamic_dcp(conf)
+ dynamic_sub(conf)
dynamic_ssh(conf)
# Not packaging; just a straight build
dynamic_boost(conf, boost_lib_suffix, boost_thread)
dynamic_ffmpeg(conf)
dynamic_dcp(conf)
+ dynamic_sub(conf)
dynamic_openjpeg(conf)
dynamic_ssh(conf)