diff options
| author | Carl Hetherington <cth@carlh.net> | 2014-10-06 00:58:02 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2014-10-06 00:58:02 +0100 |
| commit | d68bb2ae0e6a3a19c627f9005eed7aca206349cd (patch) | |
| tree | 4217b30f04131f6a4b14dbe6dbdc1edd9758c264 | |
| parent | 385a64deea15f8cf360d342fbc62c17ce86c2859 (diff) | |
Basic and scruffy Subrip read support.
| -rw-r--r-- | src/exceptions.h | 46 | ||||
| -rw-r--r-- | src/subrip_reader.cc | 191 | ||||
| -rw-r--r-- | src/subrip_reader.h | 36 | ||||
| -rw-r--r-- | src/time_pair.h | 10 | ||||
| -rw-r--r-- | src/wscript | 1 | ||||
| -rw-r--r-- | test/data/test.srt | 8 | ||||
| -rw-r--r-- | test/subrip_reader_test.cc | 154 | ||||
| -rw-r--r-- | test/test.cc | 1 | ||||
| -rw-r--r-- | test/wscript | 1 |
9 files changed, 431 insertions, 17 deletions
diff --git a/src/exceptions.h b/src/exceptions.h index f2f1246..cb49d86 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -22,14 +22,12 @@ namespace sub { -/** @class XMLError - * @brief An error raised when reading an XML file. - */ -class XMLError : public std::exception +class MessageError : public std::exception { public: - XMLError (std::string const & message) : _message (message) {} - ~XMLError () throw () {} + MessageError (std::string const & message) + : _message (message) {} + ~MessageError () throw () {} /** @return error message */ char const * what () const throw () { @@ -41,23 +39,37 @@ private: std::string _message; }; +/** @class XMLError + * @brief An error raised when reading an XML file. + */ +class XMLError : public MessageError +{ +public: + XMLError (std::string const & message) + : MessageError (message) + {} +}; + /** @class STLError * @brief An error raised when reading a binary STL file. */ -class STLError : public std::exception +class STLError : public MessageError { public: - STLError (std::string const & message) : _message (message) {} - ~STLError () throw () {} - - /** @return error message */ - char const * what () const throw () { - return _message.c_str (); - } + STLError (std::string const & message) + : MessageError (message) + {} +}; -private: - /** error message */ - std::string _message; +/** @class SubripError + * @brief An error raised when reading a Subrip file. + */ +class SubripError : public MessageError +{ +public: + SubripError (std::string saw, std::string expecting) + : MessageError ("Error in SubRip file: saw " + saw + " while expecting " + expecting) + {} }; } diff --git a/src/subrip_reader.cc b/src/subrip_reader.cc new file mode 100644 index 0000000..873a6f1 --- /dev/null +++ b/src/subrip_reader.cc @@ -0,0 +1,191 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "subrip_reader.h" +#include "exceptions.h" +#include <boost/algorithm/string.hpp> +#include <boost/lexical_cast.hpp> +#include <cstdio> +#include <vector> + +using std::string; +using std::vector; +using boost::lexical_cast; +using namespace sub; + +SubripReader::SubripReader (FILE* f) +{ + enum { + COUNTER, + METADATA, + CONTENT + } state = COUNTER; + + char buffer[256]; + + TimePair from; + TimePair to; + + string line; + int line_number = 0; + + while (!feof (f)) { + fgets (buffer, sizeof (buffer), f); + if (feof (f)) { + break; + } + + line = string (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; + } + 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"); + } + + from = convert_time (p[0]); + to = convert_time (p[2]); + + /* XXX: should not ignore coordinate specifications */ + + state = CONTENT; + break; + } + case CONTENT: + if (line.empty ()) { + state = COUNTER; + line_number = 0; + } else { + convert_line (line, line_number, from, to); + line_number++; + } + break; + } + } +} + +TimePair +SubripReader::convert_time (string t) +{ + vector<string> a; + boost::algorithm::split (a, t, boost::is_any_of (":")); + if (a.size() != 3) { + throw SubripError (t, "time in the format h:m:s,ms"); + } + + vector<string> b; + boost::algorithm::split (b, a[2], boost::is_any_of (",")); + + return TimePair ( + MetricTime ( + lexical_cast<int> (a[0]), + lexical_cast<int> (a[1]), + lexical_cast<int> (b[0]), + lexical_cast<int> (b[1]) + ) + ); +} + +void +SubripReader::convert_line (string t, int line_number, TimePair from, TimePair to) +{ + enum { + TEXT, + TAG + } state = TEXT; + + string tag; + + RawSubtitle p; + p.font = "Arial"; + p.font_size.set_points (48); + p.from = from; + p.to = to; + p.vertical_position.proportional = 0.7 + line_number * 0.1; + p.vertical_position.reference = TOP; + + /* 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 (size_t i = 0; i < t.size(); ++i) { + switch (state) { + case TEXT: + if (t[i] == '<' || t[i] == '{') { + state = TAG; + } else { + p.text += t[i]; + } + break; + case TAG: + if (t[i] == '>' || t[i] == '}') { + if (tag == "b") { + maybe_content (p); + p.bold = true; + } else if (tag == "/b") { + maybe_content (p); + p.bold = false; + } else if (tag == "i") { + maybe_content (p); + p.italic = true; + } else if (tag == "/i") { + maybe_content (p); + p.italic = false; + } else if (tag == "u") { + maybe_content (p); + p.underline = true; + } else if (tag == "/u") { + maybe_content (p); + p.underline = false; + } + tag.clear (); + state = TEXT; + } else { + tag += t[i]; + } + break; + } + } + + maybe_content (p); +} + +void +SubripReader::maybe_content (RawSubtitle& p) +{ + if (!p.text.empty ()) { + _subs.push_back (p); + p.text.clear (); + } +} diff --git a/src/subrip_reader.h b/src/subrip_reader.h new file mode 100644 index 0000000..2c69971 --- /dev/null +++ b/src/subrip_reader.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "reader.h" +#include "time_pair.h" + +namespace sub { + +class SubripReader : public Reader +{ +public: + SubripReader (FILE* f); + +private: + TimePair convert_time (std::string t); + void convert_line (std::string t, int line_number, TimePair from, TimePair to); + void maybe_content (RawSubtitle& p); +}; + +} diff --git a/src/time_pair.h b/src/time_pair.h index a9c1a09..d4b2c09 100644 --- a/src/time_pair.h +++ b/src/time_pair.h @@ -32,6 +32,16 @@ namespace sub { class TimePair { public: + TimePair () {} + + TimePair (FrameTime t) + : _frame (t) + {} + + TimePair (MetricTime t) + : _metric (t) + {} + void set_frame (FrameTime t) { _frame = t; _metric = boost::optional<MetricTime> (); diff --git a/src/wscript b/src/wscript index ef5c787..160277f 100644 --- a/src/wscript +++ b/src/wscript @@ -28,6 +28,7 @@ def build(bld): stl_text_reader.cc stl_util.cc time_pair.cc + subrip_reader.cc subtitle.cc vertical_reference.cc vertical_position.cc diff --git a/test/data/test.srt b/test/data/test.srt new file mode 100644 index 0000000..a3cfc30 --- /dev/null +++ b/test/data/test.srt @@ -0,0 +1,8 @@ +1 +00:00:41,090 --> 00:00:42,210 +This is a subtitle +and that's a line break + +2 +00:01:01,010 --> 00:01:02,100 +This is some <b>bold</b> and some <b><i>bold italic</b></i> and some <u>underlined</u>. diff --git a/test/subrip_reader_test.cc b/test/subrip_reader_test.cc new file mode 100644 index 0000000..fc6a6db --- /dev/null +++ b/test/subrip_reader_test.cc @@ -0,0 +1,154 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "subrip_reader.h" +#include "subtitle.h" +#include "collect.h" +#include <boost/test/unit_test.hpp> +#include <fstream> + +using std::list; +using std::ifstream; +using std::vector; + +/* Test reading of a Subrip file */ +BOOST_AUTO_TEST_CASE (subrip_reader_test) +{ + FILE* f = fopen ("test/data/test.srt", "r"); + sub::SubripReader reader (f); + fclose (f); + list<sub::Subtitle> subs = sub::collect (reader.subtitles ()); + + list<sub::Subtitle>::iterator i = subs.begin (); + + + /* First subtitle */ + + BOOST_CHECK (i != subs.end ()); + BOOST_CHECK_EQUAL (i->from.metric().get(), sub::MetricTime (0, 0, 41, 90)); + BOOST_CHECK_EQUAL (i->to.metric().get(), sub::MetricTime (0, 0, 42, 210)); + + list<sub::Line>::iterator j = i->lines.begin (); + BOOST_CHECK (j != i->lines.end ()); + BOOST_CHECK_EQUAL (j->blocks.size(), 1); + sub::Block b = j->blocks.front (); + BOOST_CHECK_EQUAL (b.text, "This is a subtitle"); + BOOST_CHECK_EQUAL (b.font, "Arial"); + BOOST_CHECK_EQUAL (b.font_size.points().get(), 48); + BOOST_CHECK_EQUAL (b.bold, false); + BOOST_CHECK_EQUAL (b.italic, false); + BOOST_CHECK_EQUAL (b.underline, false); + BOOST_CHECK_CLOSE (j->vertical_position.proportional.get(), 0.7, 1); + BOOST_CHECK_EQUAL (j->vertical_position.reference.get(), sub::TOP); + ++j; + + BOOST_CHECK (j != i->lines.end ()); + BOOST_CHECK_EQUAL (j->blocks.size(), 1); + b = j->blocks.front (); + BOOST_CHECK_EQUAL (b.text, "and that's a line break"); + BOOST_CHECK_EQUAL (b.font, "Arial"); + BOOST_CHECK_EQUAL (b.font_size.points().get(), 48); + BOOST_CHECK_EQUAL (b.bold, false); + BOOST_CHECK_EQUAL (b.italic, false); + BOOST_CHECK_EQUAL (b.underline, false); + BOOST_CHECK_CLOSE (j->vertical_position.proportional.get(), 0.8, 1); + BOOST_CHECK_EQUAL (j->vertical_position.reference.get(), sub::TOP); + ++i; + + + /* Second subtitle */ + + BOOST_CHECK (i != subs.end ()); + BOOST_CHECK_EQUAL (i->from.metric().get(), sub::MetricTime (0, 1, 1, 10)); + BOOST_CHECK_EQUAL (i->to.metric().get(), sub::MetricTime (0, 1, 2, 100)); + + BOOST_CHECK_EQUAL (i->lines.size(), 1); + sub::Line l = i->lines.front (); + BOOST_CHECK_EQUAL (l.blocks.size(), 7); + BOOST_CHECK_CLOSE (l.vertical_position.proportional.get(), 0.7, 1); + + list<sub::Block>::iterator k = l.blocks.begin (); + + BOOST_CHECK (k != l.blocks.end ()); + BOOST_CHECK_EQUAL (k->text, "This is some "); + BOOST_CHECK_EQUAL (k->font, "Arial"); + BOOST_CHECK_EQUAL (k->font_size.points().get(), 48); + BOOST_CHECK_EQUAL (k->bold, false); + BOOST_CHECK_EQUAL (k->italic, false); + BOOST_CHECK_EQUAL (k->underline, false); + ++k; + + BOOST_CHECK (k != l.blocks.end ()); + BOOST_CHECK_EQUAL (k->text, "bold"); + BOOST_CHECK_EQUAL (k->font, "Arial"); + BOOST_CHECK_EQUAL (k->font_size.points().get(), 48); + BOOST_CHECK_EQUAL (k->bold, true); + BOOST_CHECK_EQUAL (k->italic, false); + BOOST_CHECK_EQUAL (k->underline, false); + ++k; + + BOOST_CHECK (k != l.blocks.end ()); + BOOST_CHECK_EQUAL (k->text, " and some "); + BOOST_CHECK_EQUAL (k->font, "Arial"); + BOOST_CHECK_EQUAL (k->font_size.points().get(), 48); + BOOST_CHECK_EQUAL (k->bold, false); + BOOST_CHECK_EQUAL (k->italic, false); + BOOST_CHECK_EQUAL (k->underline, false); + ++k; + + BOOST_CHECK (k != l.blocks.end ()); + BOOST_CHECK_EQUAL (k->text, "bold italic"); + BOOST_CHECK_EQUAL (k->font, "Arial"); + BOOST_CHECK_EQUAL (k->font_size.points().get(), 48); + BOOST_CHECK_EQUAL (k->bold, true); + BOOST_CHECK_EQUAL (k->italic, true); + BOOST_CHECK_EQUAL (k->underline, false); + ++k; + + BOOST_CHECK (k != l.blocks.end ()); + BOOST_CHECK_EQUAL (k->text, " and some "); + BOOST_CHECK_EQUAL (k->font, "Arial"); + BOOST_CHECK_EQUAL (k->font_size.points().get(), 48); + BOOST_CHECK_EQUAL (k->bold, false); + BOOST_CHECK_EQUAL (k->italic, false); + BOOST_CHECK_EQUAL (k->underline, false); + ++k; + + BOOST_CHECK (k != l.blocks.end ()); + BOOST_CHECK_EQUAL (k->text, "underlined"); + BOOST_CHECK_EQUAL (k->font, "Arial"); + BOOST_CHECK_EQUAL (k->font_size.points().get(), 48); + BOOST_CHECK_EQUAL (k->bold, false); + BOOST_CHECK_EQUAL (k->italic, false); + BOOST_CHECK_EQUAL (k->underline, true); + ++k; + + BOOST_CHECK (k != l.blocks.end ()); + BOOST_CHECK_EQUAL (k->text, "."); + BOOST_CHECK_EQUAL (k->font, "Arial"); + BOOST_CHECK_EQUAL (k->font_size.points().get(), 48); + BOOST_CHECK_EQUAL (k->bold, false); + BOOST_CHECK_EQUAL (k->italic, false); + BOOST_CHECK_EQUAL (k->underline, false); + ++k; + + BOOST_CHECK (k == l.blocks.end ()); +} + + diff --git a/test/test.cc b/test/test.cc index 272c89a..4a1dbf4 100644 --- a/test/test.cc +++ b/test/test.cc @@ -27,6 +27,7 @@ using std::string; using std::cerr; +using std::cout; using std::min; using std::max; using std::ifstream; diff --git a/test/wscript b/test/wscript index 71ad5b2..9c3eb68 100644 --- a/test/wscript +++ b/test/wscript @@ -22,6 +22,7 @@ def build(bld): stl_binary_reader_test.cc stl_binary_writer_test.cc stl_text_reader_test.cc + subrip_reader_test.cc time_test.cc test.cc """ |
