summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ssa_reader.cc299
-rw-r--r--src/ssa_reader.h5
-rw-r--r--src/sub_time.cc25
-rw-r--r--src/sub_time.h5
-rw-r--r--src/wscript2
-rw-r--r--test/data/test.ssa20
-rw-r--r--test/ssa_reader_test.cc75
-rw-r--r--wscript4
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;
+}
diff --git a/wscript b/wscript
index 96d5da7..4fc9e37 100644
--- a/wscript
+++ b/wscript
@@ -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