diff options
| author | Carl Hetherington <cth@carlh.net> | 2016-06-06 08:59:53 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2016-06-06 08:59:53 +0100 |
| commit | aba52dd5cc2fa9736fd9b007962923d4dae8c4a6 (patch) | |
| tree | aa206963eaf22e3be47d9a10f83553cfb57879fc | |
| parent | a06e701131b813bc42ae81b9c5e8c7dbbfea7eb9 (diff) | |
Hacks.libssa
| -rw-r--r-- | src/ssa_reader.cc | 299 | ||||
| -rw-r--r-- | src/ssa_reader.h | 5 | ||||
| -rw-r--r-- | src/sub_time.cc | 25 | ||||
| -rw-r--r-- | src/sub_time.h | 5 | ||||
| -rw-r--r-- | src/wscript | 2 | ||||
| -rw-r--r-- | test/data/test.ssa | 20 | ||||
| -rw-r--r-- | test/ssa_reader_test.cc | 75 | ||||
| -rw-r--r-- | wscript | 4 |
8 files changed, 133 insertions, 302 deletions
diff --git a/src/ssa_reader.cc b/src/ssa_reader.cc index c76a78d..a4daf24 100644 --- a/src/ssa_reader.cc +++ b/src/ssa_reader.cc @@ -22,6 +22,7 @@ #include "sub_assert.h" #include "raw_convert.h" #include "subtitle.h" +#include <ass/ass.h> #include <boost/algorithm/string.hpp> #include <boost/bind.hpp> #include <boost/foreach.hpp> @@ -43,281 +44,77 @@ using namespace sub; /** @param s Subtitle string encoded in UTF-8 */ SSAReader::SSAReader (string const & s) { - stringstream str (s); - this->read (boost::bind (&get_line_stringstream, &str)); + char* buffer = new char[s.length() + 1]; + strcpy (buffer, s.c_str()); + read (buffer, s.length() + 1); } /** @param f Subtitle file encoded in UTF-8 */ SSAReader::SSAReader (FILE* f) { - this->read (boost::bind (&get_line_file, f)); -} - -class Style -{ -public: - Style () - : font_size (24) - , primary_colour (255, 255, 255) - , bold (false) - , italic (false) - {} - - Style (string format_line, string style_line) - : font_size (24) - , primary_colour (255, 255, 255) - , bold (false) - , italic (false) - { - vector<string> keys; - split (keys, format_line, is_any_of (",")); - vector<string> style; - split (style, style_line, is_any_of (",")); - - SUB_ASSERT (!keys.empty()); - SUB_ASSERT (!style.empty()); - SUB_ASSERT (keys.size() == style.size()); - - for (size_t i = 0; i < style.size(); ++i) { - trim (keys[i]); - trim (style[i]); - if (keys[i] == "Name") { - name = style[i]; - } else if (keys[i] == "Fontname") { - font_name = style[i]; - } else if (keys[i] == "Fontsize") { - font_size = raw_convert<int> (style[i]); - } else if (keys[i] == "PrimaryColour") { - primary_colour = colour (raw_convert<int> (style[i])); - } else if (keys[i] == "BackColour") { - back_colour = colour (raw_convert<int> (style[i])); - } else if (keys[i] == "Bold") { - bold = style[i] == "-1"; - } else if (keys[i] == "Italic") { - italic = style[i] == "-1"; - } else if (keys[i] == "BorderStyle") { - if (style[i] == "1") { - effect = SHADOW; - } - } - } - } - - string name; - optional<string> font_name; - int font_size; - Colour primary_colour; - /** outline colour */ - optional<Colour> back_colour; - bool bold; - bool italic; - optional<Effect> effect; + fseek (f, 0, SEEK_END); + long const size = ftell (f); + fseek (f, 0, SEEK_SET); + char* buffer = new char[size]; + fread (buffer, size, 1, f); -private: - Colour colour (int c) const - { - return Colour ( - ((c & 0x0000ff) >> 0) / 255.0, - ((c & 0x00ff00) >> 8) / 255.0, - ((c & 0xff0000) >> 16) / 255.0 - ); - } -}; - -Time -SSAReader::parse_time (string t) const -{ - vector<string> bits; - split (bits, t, is_any_of (":.")); - SUB_ASSERT (bits.size() == 4); - return Time::from_hms ( - raw_convert<int> (bits[0]), - raw_convert<int> (bits[1]), - raw_convert<int> (bits[2]), - raw_convert<int> (bits[3]) * 10 - ); + read (buffer, size); } -/** @param base RawSubtitle filled in with any required common values. - * @param line SSA line string. - * @return List of RawSubtitles to represent line with vertical reference TOP_OF_SUBTITLE. - */ -list<RawSubtitle> -SSAReader::parse_line (RawSubtitle base, string line) +void +SSAReader::read (char* buffer, int size) { - enum { - TEXT, - STYLE, - BACKSLASH - } state = TEXT; - - list<RawSubtitle> subs; - RawSubtitle current = base; - string style; - - current.vertical_position.line = 0; - /* XXX: arbitrary */ - current.vertical_position.lines = 32; - current.vertical_position.reference = TOP_OF_SUBTITLE; - - for (size_t i = 0; i < line.length(); ++i) { - char const c = line[i]; - switch (state) { - case TEXT: - if (c == '{') { - state = STYLE; - } else if (c == '\\') { - state = BACKSLASH; - } else if (c != '\r' && c != '\n') { - current.text += c; - } - break; - case STYLE: - if (c == '}') { - if (!current.text.empty ()) { - subs.push_back (current); - current.text = ""; - } - if (style == "i1") { - current.italic = true; - } else if (style == "i0") { - current.italic = false; - } - style = ""; - state = TEXT; - } else { - style += c; - } - break; - case BACKSLASH: - if ((c == 'n' || c == 'N') && !current.text.empty ()) { - subs.push_back (current); - current.text = ""; - current.vertical_position.line = current.vertical_position.line.get() + 1; - } - state = TEXT; - break; - } + ASS_Library* library = ass_library_init (); + if (!library) { + delete[] buffer; + throw std::runtime_error ("Could not initialise libass"); } - if (!current.text.empty ()) { - subs.push_back (current); - } + char encoding[] = "UTF-8"; - return subs; -} + ASS_Track* track = ass_read_memory (library, buffer, size, encoding); + delete[] buffer; -void -SSAReader::read (function<optional<string> ()> get_line) -{ - enum { - INFO, - STYLES, - EVENTS - } part = INFO; + for (int i = 0; i < track->n_events; ++i) { + sub::RawSubtitle sub; - map<string, Style> styles; - string style_format_line; - vector<string> event_format; + ASS_Event const & event = track->events[i]; + ASS_Style const & style = track->styles[event.Style]; - while (true) { - optional<string> line = get_line (); - if (!line) { - break; - } + sub.text = event.Text; + sub.font = style.FontName; + sub.font_size.set_proportional (style.FontSize / track->PlayResY); - trim (*line); - remove_unicode_bom (line); + /* XXX: effect, effect_colour */ - if (starts_with (*line, ";") || line->empty ()) { - continue; - } + sub.bold = style.Bold; + sub.italic = style.Italic; + sub.underline = style.Underline; - if (starts_with (*line, "[")) { - /* Section heading */ - if (line.get() == "[Script Info]") { - part = INFO; - } else if (line.get() == "[V4 Styles]") { - part = STYLES; - } else if (line.get() == "[Events]") { - part = EVENTS; - } - continue; - } + cout << "align " << style.Alignment << "\n"; - size_t const colon = line->find (":"); - SUB_ASSERT (colon != string::npos); - SUB_ASSERT (line->length() > colon + 1); - string const type = line->substr (0, colon); - string const body = line->substr (colon + 2); + int valign = style.Alignment & 12; - switch (part) { - case INFO: + switch (valign) { + case VALIGN_SUB: + sub.vertical_position.reference = BOTTOM_OF_SCREEN; break; - case STYLES: - if (type == "Format") { - style_format_line = body; - } else if (type == "Style") { - SUB_ASSERT (!style_format_line.empty ()); - Style s (style_format_line, body); - styles[s.name] = s; - } + case VALIGN_CENTER: + sub.vertical_position.reference = CENTRE_OF_SCREEN; break; - case EVENTS: - if (type == "Format") { - split (event_format, body, is_any_of (",")); - BOOST_FOREACH (string& i, event_format) { - trim (i); - } - } else if (type == "Dialogue") { - SUB_ASSERT (!event_format.empty ()); - vector<string> event; - split (event, body, is_any_of (",")); - - /* There may be commas in the subtitle part; reassemble any extra parts - from when we just split it. - */ - while (event.size() > event_format.size()) { - string const ex = event.back (); - event.pop_back (); - event.back() += "," + ex; - } - - SUB_ASSERT (!event.empty()); - SUB_ASSERT (event_format.size() == event.size()); - - RawSubtitle sub; - - for (size_t i = 0; i < event.size(); ++i) { - trim (event[i]); - if (event_format[i] == "Start") { - sub.from = parse_time (event[i]); - } else if (event_format[i] == "End") { - sub.to = parse_time (event[i]); - } else if (event_format[i] == "Style") { - SUB_ASSERT (styles.find(event[i]) != styles.end()); - Style style = styles[event[i]]; - sub.font = style.font_name; - sub.font_size = FontSize::from_points (style.font_size); - sub.colour = style.primary_colour; - sub.effect_colour = style.back_colour; - sub.bold = style.bold; - sub.italic = style.italic; - sub.effect = style.effect; + case VALIGN_TOP: + sub.vertical_position.reference = TOP_OF_SCREEN; + break; + } - /* XXX: arbitrary */ - sub.vertical_position.lines = 32; - sub.vertical_position.reference = TOP_OF_SUBTITLE; - sub.vertical_position.line = 0; + sub.vertical_position.proportional = style.MarginV / track->PlayResY; - } else if (event_format[i] == "Text") { - BOOST_FOREACH (sub::RawSubtitle j, parse_line (sub, event[i])) { - _subs.push_back (j); - } - } - } - } - } + sub.from = Time::from_milliseconds (event.Start); + sub.to = sub.from + Time::from_milliseconds (event.Duration); + _subs.push_back (sub); } + + ass_free_track (track); + ass_library_done (library); } diff --git a/src/ssa_reader.h b/src/ssa_reader.h index 1fe64f3..b80aec4 100644 --- a/src/ssa_reader.h +++ b/src/ssa_reader.h @@ -35,11 +35,8 @@ public: SSAReader (FILE* f); SSAReader (std::string const & subs); - static std::list<RawSubtitle> parse_line (RawSubtitle base, std::string line); - private: - void read (boost::function<boost::optional<std::string> ()> get_line); - Time parse_time (std::string t) const; + void read (char* buffer, int size); }; } diff --git a/src/sub_time.cc b/src/sub_time.cc index c4e00ba..1c7d294 100644 --- a/src/sub_time.cc +++ b/src/sub_time.cc @@ -155,6 +155,12 @@ Time::from_hms (int h, int m, int s, int ms) return Time (h * 3600 + m * 60 + s, ms, Rational (1000, 1)); } +Time +Time::from_milliseconds (int64_t m) +{ + return Time (m / 1000, m % 1000, Rational (1000, 1)); +} + /** Create a Time from a number of frames. * rate must be integer. */ @@ -172,11 +178,9 @@ Time::all_as_seconds () const return _seconds + double(milliseconds ()) / 1000; } -/** Add a time to this one. This time must have an integer _rate - * and t must have the same rate. - */ -void -Time::add (Time t) +/** This time must have an integer _rate and t must have the same rate */ +Time & +Time::operator+= (Time const & t) { SUB_ASSERT (_rate); @@ -190,6 +194,17 @@ Time::add (Time t) _frames -= _rate.get().integer_fraction(); ++_seconds; } + + return *this; +} + +/** This time must have an integer _rate and t must have the same rate */ +Time +Time::operator+ (Time const & t) +{ + Time r = *this; + r += t; + return r; } void diff --git a/src/sub_time.h b/src/sub_time.h index 93088cb..f5d15d7 100644 --- a/src/sub_time.h +++ b/src/sub_time.h @@ -42,13 +42,16 @@ public: double all_as_seconds () const; - void add (Time t); void scale (float f); static Time from_hmsf (int h, int m, int s, int f, boost::optional<Rational> rate = boost::optional<Rational> ()); static Time from_hms (int h, int m, int s, int ms); + static Time from_milliseconds (int64_t ms); static Time from_frames (int frames, Rational rate); + Time operator+ (Time const & other); + Time& operator+= (Time const & other); + private: friend bool operator< (Time const & a, Time const & b); friend bool operator> (Time const & a, Time const & b); diff --git a/src/wscript b/src/wscript index 9136ea1..e493a07 100644 --- a/src/wscript +++ b/src/wscript @@ -8,7 +8,7 @@ def build(bld): obj.name = 'libsub%s' % bld.env.API_VERSION obj.target = 'sub%s' % bld.env.API_VERSION - obj.uselib = 'CXML DCP BOOST_FILESYSTEM BOOST_LOCALE BOOST_REGEX ASDCPLIB_CTH' + obj.uselib = 'CXML DCP BOOST_FILESYSTEM BOOST_LOCALE BOOST_REGEX ASDCPLIB_CTH LIBASS' obj.use = 'libkumu-libsub%s libasdcp-libsub%s' % (bld.env.API_VERSION, bld.env.API_VERSION) obj.export_includes = ['.'] obj.source = """ diff --git a/test/data/test.ssa b/test/data/test.ssa new file mode 100644 index 0000000..578c3d4 --- /dev/null +++ b/test/data/test.ssa @@ -0,0 +1,20 @@ +[Script Info] +Title: libsub test +ScriptType: v4.00 +WrapStyle: 0 +ScaledBorderAndShadow: yes +PlayResX: 1920 +PlayResY: 1080 + +[V4 Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding +Style: Default,Arial,20,16777215,255,0,0,0,0,1,2,2,2,10,10,10,0,1 + +[Fonts] + +[Graphics] + +[Events] +Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: Marked=0,0:00:01.23,0:00:04.55,Default,,0,0,0,,Hello world +Dialogue: Marked=0,0:00:05.74,0:00:11.00,Default,,0,0,900,,This is vertically moved diff --git a/test/ssa_reader_test.cc b/test/ssa_reader_test.cc index 76043a7..9a6dd56 100644 --- a/test/ssa_reader_test.cc +++ b/test/ssa_reader_test.cc @@ -42,11 +42,13 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test) BOOST_CHECK_EQUAL (i->to, sub::Time::from_hms (0, 2, 41, 790)); list<sub::Line>::iterator j = i->lines.begin(); BOOST_REQUIRE (j != i->lines.end ()); + BOOST_CHECK_EQUAL (j->vertical_position.proportional.get(), 0); + BOOST_CHECK_EQUAL (j->vertical_position.reference.get(), sub::BOTTOM_OF_SCREEN); BOOST_REQUIRE_EQUAL (j->blocks.size(), 1); sub::Block b = j->blocks.front (); BOOST_CHECK_EQUAL (b.text, "Et les enregistrements de ses ondes delta ?"); BOOST_CHECK_EQUAL (b.font.get(), "Wolf_Rain"); - BOOST_CHECK_EQUAL (b.font_size.points().get(), 56); + BOOST_CHECK_EQUAL (b.font_size.proportional().get(), 56.0 / 1024); BOOST_CHECK_EQUAL (b.bold, false); BOOST_CHECK_EQUAL (b.italic, false); BOOST_CHECK_EQUAL (b.underline, false); @@ -61,7 +63,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test) b = j->blocks.front (); BOOST_CHECK_EQUAL (b.text, "Toujours rien."); BOOST_CHECK_EQUAL (b.font.get(), "Wolf_Rain"); - BOOST_CHECK_EQUAL (b.font_size.points().get(), 56); + BOOST_CHECK_EQUAL (b.font_size.proportional().get(), 56.0 / 1024); BOOST_CHECK_EQUAL (b.bold, false); BOOST_CHECK_EQUAL (b.italic, false); BOOST_CHECK_EQUAL (b.underline, false); @@ -70,44 +72,6 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test) BOOST_CHECK (i == subs.end()); } -BOOST_AUTO_TEST_CASE (ssa_reader_line_test1) -{ - sub::RawSubtitle base; - list<sub::RawSubtitle> r = sub::SSAReader::parse_line (base, "This is a line with some {i1}italics{i0} and then\\nthere is a new line."); - - list<sub::RawSubtitle>::const_iterator i = r.begin (); - BOOST_CHECK_EQUAL (i->text, "This is a line with some "); - BOOST_CHECK_EQUAL (i->italic, false); - ++i; - BOOST_REQUIRE (i != r.end ()); - - BOOST_CHECK_EQUAL (i->text, "italics"); - BOOST_CHECK_EQUAL (i->italic, true); - ++i; - BOOST_REQUIRE (i != r.end ()); - - BOOST_CHECK_EQUAL (i->text, " and then"); - BOOST_CHECK_EQUAL (i->italic, false); - ++i; - BOOST_REQUIRE (i != r.end ()); - - BOOST_CHECK_EQUAL (i->text, "there is a new line."); - ++i; - BOOST_REQUIRE (i == r.end ()); -} - -BOOST_AUTO_TEST_CASE (ssa_reader_line_test2) -{ - sub::RawSubtitle base; - list<sub::RawSubtitle> r = sub::SSAReader::parse_line (base, "{i1}It's all just italics{i0}"); - - list<sub::RawSubtitle>::const_iterator i = r.begin (); - BOOST_CHECK_EQUAL (i->text, "It's all just italics"); - BOOST_CHECK_EQUAL (i->italic, true); - ++i; - BOOST_REQUIRE (i == r.end ()); -} - static void test (boost::filesystem::path p) { @@ -123,3 +87,34 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test2) { test ("DKH_UT_EN20160601def.ssa"); } + +/** Test a file with some bits to exercise the parser */ +BOOST_AUTO_TEST_CASE (ssa_reader_test3) +{ + boost::filesystem::path p = "test/data/test.ssa"; + FILE* f = fopen (p.string().c_str(), "r"); + sub::SSAReader reader (f); + fclose (f); + list<sub::Subtitle> subs = sub::collect<std::list<sub::Subtitle> > (reader.subtitles ()); + + BOOST_REQUIRE_EQUAL (subs.size(), 2); + + list<sub::Subtitle>::const_iterator i = subs.begin (); + BOOST_CHECK_EQUAL (i->from, sub::Time::from_hms (0, 0, 1, 230)); + BOOST_CHECK_EQUAL (i->to, sub::Time::from_hms (0, 0, 4, 550)); + BOOST_REQUIRE_EQUAL (i->lines.size(), 1); + list<sub::Line>::const_iterator j = i->lines.begin(); + BOOST_REQUIRE_EQUAL (j->blocks.size(), 1); + sub::Block b = j->blocks.front (); + BOOST_CHECK_EQUAL (b.text, "Hello world"); + ++i; + + BOOST_CHECK_EQUAL (i->from, sub::Time::from_hms (0, 0, 5, 740)); + BOOST_CHECK_EQUAL (i->to, sub::Time::from_hms (0, 0, 11, 0)); + BOOST_REQUIRE_EQUAL (i->lines.size(), 1); + j = i->lines.begin(); + BOOST_REQUIRE_EQUAL (j->blocks.size(), 1); + b = j->blocks.front (); + BOOST_CHECK_EQUAL (b.text, "This is vertically moved"); + ++i; +} @@ -18,6 +18,9 @@ def configure(conf): conf.env.append_value('CXXFLAGS', ['-Wall', '-Wextra', '-D_FILE_OFFSET_BITS=64', '-D__STDC_FORMAT_MACROS']) conf.env.append_value('CXXFLAGS', ['-DLIBSUB_VERSION="%s"' % VERSION]) + # Disable libxml++ deprecation warnings for now + conf.env.append_value('CXXFLAGS', ['-Wno-deprecated-declarations']) + conf.env.ENABLE_DEBUG = conf.options.enable_debug conf.env.STATIC = conf.options.static conf.env.TARGET_WINDOWS = conf.options.target_windows @@ -30,6 +33,7 @@ def configure(conf): conf.env.append_value('CXXFLAGS', '-O3') conf.check_cfg(package='openssl', args='--cflags --libs', uselib_store='OPENSSL', mandatory=True) + conf.check_cfg(package='libass', args='--cflags --libs', uselib_store='LIBASS', mandatory=True) if conf.options.static: conf.env.HAVE_CXML = 1 |
