Various work on SubRip.
authorCarl Hetherington <cth@carlh.net>
Sat, 11 Jan 2014 14:04:58 +0000 (14:04 +0000)
committerCarl Hetherington <cth@carlh.net>
Sat, 11 Jan 2014 14:04:58 +0000 (14:04 +0000)
src/lib/content_factory.cc
src/lib/exceptions.cc
src/lib/exceptions.h
src/lib/subrip.cc [new file with mode: 0644]
src/lib/subrip.h [new file with mode: 0644]
src/lib/subrip_content.cc [new file with mode: 0644]
src/lib/subrip_content.h [new file with mode: 0644]
src/lib/subrip_subtitle.h [new file with mode: 0644]
src/lib/wscript
test/subrip_test.cc [new file with mode: 0644]
test/wscript

index bab22b8eb19e14b6179fd27696710c6b630df70c..825c8049854ba3032519b7d27430560a18d9f8b8 100644 (file)
@@ -21,6 +21,7 @@
 #include "ffmpeg_content.h"
 #include "image_content.h"
 #include "sndfile_content.h"
+#include "subrip_content.h"
 #include "util.h"
 
 using std::string;
@@ -39,6 +40,8 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version)
                content.reset (new ImageContent (film, node, version));
        } else if (type == "Sndfile") {
                content.reset (new SndfileContent (film, node, version));
+       } else if (type == "SubRip") {
+               content.reset (new SubRipContent (film, node, version));
        }
 
        return content;
@@ -48,11 +51,16 @@ shared_ptr<Content>
 content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
 {
        shared_ptr<Content> content;
+
+       string ext = path.extension().string ();
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
                
        if (valid_image_file (path)) {
                content.reset (new ImageContent (film, path));
        } else if (SndfileContent::valid_file (path)) {
                content.reset (new SndfileContent (film, path));
+       } else if (ext == ".srt") {
+               content.reset (new SubRipContent (film, path));
        } else {
                content.reset (new FFmpegContent (film, path));
        }
index 8144f41b999690f526db309a4102547f4e69a05c..e05ac4ff04b3fdebb5617129f5c9a0eb6c9dc10e 100644 (file)
@@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s)
 
 }
 
-PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+PixelFormatError::PixelFormatError (string o, AVPixelFormat f)
        : StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
 {
 
 }
+
+SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f)
+       : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw, expecting), f)
+{
+
+}
index c1240538f3de2e2a10b6dfb052e17d2bbbbffc9e..61163c8d12403dc492251f3e85668b4fa9a1119e 100644 (file)
@@ -230,6 +230,13 @@ public:
        PixelFormatError (std::string o, AVPixelFormat f);
 };
 
+/** An error that occurs while parsing a SubRip file */
+class SubRipError : public FileError
+{
+public:
+       SubRipError (std::string, std::string, boost::filesystem::path);
+};
+
 /** A parent class for classes which have a need to catch and
  *  re-throw exceptions.  This is intended for classes
  *  which run their own thread; they should do something like
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
new file mode 100644 (file)
index 0000000..50931e1
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+    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 <boost/algorithm/string.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<SubRipContent> content)
+{
+       FILE* f = fopen_boost (content->path (0), "r");
+       if (!f) {
+               throw OpenFileError (content->path (0));
+       }
+
+       enum {
+               COUNTER,
+               METADATA,
+               CONTENT
+       } state = COUNTER;
+
+       char buffer[256];
+       int next_count = 1;
+
+       boost::optional<SubRipSubtitle> current;
+       list<string> lines;
+       
+       while (!feof (f)) {
+               fgets (buffer, sizeof (buffer), f);
+               string line (buffer);
+               trim (line);
+               
+               switch (state) {
+               case COUNTER:
+               {
+                       int x = 0;
+                       try {
+                               x = lexical_cast<int> (line);
+                       } catch (...) {
+
+                       }
+                       
+                       if (x == next_count) {
+                               state = METADATA;
+                               ++next_count;
+                               current = SubRipSubtitle ();
+                       } else {
+                               throw SubRipError (line, _("a subtitle count"), content->path (0));
+                       }
+               }
+               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->from = convert_time (p[0]);
+                       current->to = 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]);
+                       }
+                       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;
+               }
+       }
+
+       fclose (f);
+}
+
+Time
+SubRip::convert_time (string t)
+{
+       Time r = 0;
+
+       vector<string> a;
+       boost::algorithm::split (a, t, boost::is_any_of (":"));
+       assert (a.size() == 3);
+       r += lexical_cast<int> (a[0]) * 60 * 60 * TIME_HZ;
+       r += lexical_cast<int> (a[1]) * 60 * TIME_HZ;
+
+       vector<string> b;
+       boost::algorithm::split (b, a[2], boost::is_any_of (","));
+       r += lexical_cast<int> (b[0]) * TIME_HZ;
+       r += lexical_cast<int> (b[1]) * TIME_HZ / 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;
+}
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
new file mode 100644 (file)
index 0000000..a8d8104
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+    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_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+
+class SubRip
+{
+public:
+       SubRip (boost::shared_ptr<SubRipContent>);
+
+private:
+       friend class subrip_time_test;
+       friend class subrip_coordinate_test;
+       friend class subrip_content_test;
+       
+       static Time 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::list<SubRipSubtitle> _subtitles;
+};
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
new file mode 100644 (file)
index 0000000..79a1d49
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+    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_content.h"
+#include "util.h"
+
+#include "i18n.h"
+
+using std::stringstream;
+using std::string;
+using boost::shared_ptr;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+       : Content (film, path)
+       , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
+       : Content (film, node)
+       , SubtitleContent (film, node)
+{
+
+}
+
+void
+SubRipContent::examine (boost::shared_ptr<Job>)
+{
+
+}
+
+string
+SubRipContent::summary () const
+{
+       return path_summary() + " " + _("[subtitles]");
+}
+
+string
+SubRipContent::technical_summary () const
+{
+       return Content::technical_summary() + " - " + _("SubRip subtitles");
+}
+
+string
+SubRipContent::information () const
+{
+       
+}
+       
+void
+SubRipContent::as_xml (xmlpp::Node* node)
+{
+       node->add_child("Type")->add_child_text ("SubRip");
+       Content::as_xml (node);
+       SubtitleContent::as_xml (node);
+}
+
+Time
+SubRipContent::full_length () const
+{
+
+}
+
+string
+SubRipContent::identifier () const
+{
+       LocaleGuard lg;
+
+       stringstream s;
+       s << Content::identifier()
+         << "_" << subtitle_scale()
+         << "_" << subtitle_offset();
+
+       return s.str ();
+}
+
diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h
new file mode 100644 (file)
index 0000000..1551081
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+    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 "subtitle_content.h"
+
+class SubRipContent : public SubtitleContent
+{
+public:
+       SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       SubRipContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string technical_summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *);
+       Time full_length () const;
+       std::string identifier () const;
+};
diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h
new file mode 100644 (file)
index 0000000..933e0fc
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    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.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_SUBTITLE_H
+#define DCPOMATIC_SUBRIP_SUBTITLE_H
+
+#include <boost/optional.hpp>
+#include <libdcp/types.h>
+#include "types.h"
+
+struct SubRipSubtitlePiece
+{
+       SubRipSubtitlePiece ()
+               : bold (false)
+               , italic (false)
+               , underline (false)
+       {}
+       
+       std::string text;
+       bool bold;
+       bool italic;
+       bool underline;
+       libdcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+       SubRipSubtitle ()
+               : from (0)
+               , to (0)
+       {}
+       
+       Time from;
+       Time to;
+       boost::optional<int> x1;
+       boost::optional<int> x2;
+       boost::optional<int> y1;
+       boost::optional<int> y2;
+       std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
index 81a55a160bc42ab200931e9bec862d712ab7d965..284baa72561debbdbfa48a75b0665ca090879dd5 100644 (file)
@@ -50,6 +50,8 @@ sources = """
           sndfile_content.cc
           sndfile_decoder.cc
           sound_processor.cc
+          subrip.cc
+          subrip_content.cc
           subtitle_content.cc
           subtitle_decoder.cc
           timer.cc
diff --git a/test/subrip_test.cc b/test/subrip_test.cc
new file mode 100644 (file)
index 0000000..f23fc8a
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+    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 <boost/test/unit_test.hpp>
+#include "lib/subrip.h"
+#include "lib/subrip_content.h"
+
+using std::list;
+using std::string;
+using boost::shared_ptr;
+
+/** Test SubRip::convert_time */
+BOOST_AUTO_TEST_CASE (subrip_time_test)
+{
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("00:03:10,500"), rint (((3 * 60) + 10 + 0.5) * TIME_HZ));
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("04:19:51,782"), rint (((4 * 3600) + (19 * 60) + 51 + 0.782) * TIME_HZ));
+}
+
+/** 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)
+{
+       SubRipContent content (shared_ptr<Film> (), "test/data/subrip.srt");
+       content.examine (shared_ptr<Job> ());
+}
index 5aa95e3244493d4c9ae62f5a38e90ffd9a2cf070..834491e194eca5b6507e12badc03ad12f7ec3573 100644 (file)
@@ -40,6 +40,7 @@ def build(bld):
                  scaling_test.cc
                  silence_padding_test.cc
                  stream_test.cc
+                 subrip_test.cc
                  test.cc
                  threed_test.cc
                  util_test.cc