Use libsub for subrip decoding; improve default position of srt subs.
authorCarl Hetherington <cth@carlh.net>
Mon, 6 Oct 2014 12:31:45 +0000 (13:31 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 6 Oct 2014 12:31:45 +0000 (13:31 +0100)
cscript
src/lib/subrip.cc
src/lib/subrip.h
src/lib/subrip_decoder.cc
src/lib/wscript
test/subrip_test.cc
wscript

diff --git a/cscript b/cscript
index 180eca9417e615c43acf1ba6e3647f99ade0f6f9..603968a4d67daf02b924ff72fb1acef075f00780 100644 (file)
--- a/cscript
+++ b/cscript
@@ -157,7 +157,8 @@ def make_control(debian_version, bits, filename, debug):
 
 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
index 6c9cef91655c9fe83efce7724c781196ab6aff13..8a7423698d850268c7b927970831ab0a63c3029a 100644 (file)
 
 */
 
-#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)
 {
@@ -42,179 +36,8 @@ 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
@@ -224,5 +47,5 @@ SubRip::length () const
                return ContentTime ();
        }
 
-       return _subtitles.back().period.to;
+       return ContentTime::from_seconds (_subtitles.back().to.metric().get().all_as_seconds ());
 }
index 7603a101de4a07a69588da726e924a5f1ad1529e..14bc360c0763d057140ed8c593c9ad901939cdca 100644 (file)
@@ -21,6 +21,7 @@
 #define DCPOMATIC_SUBRIP_H
 
 #include "subrip_subtitle.h"
+#include <libsub/subtitle.h>
 
 class SubRipContent;
 class subrip_time_test;
@@ -36,18 +37,7 @@ public:
        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
index 2ae285b5ede17586143aa56728f37e2877a773b3..411e7542d26be7bddfd036f5a42c8144cef07e17 100644 (file)
@@ -39,11 +39,9 @@ SubRipDecoder::seek (ContentTime time, bool accurate)
        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
@@ -56,25 +54,27 @@ SubRipDecoder::pass ()
        /* 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);
@@ -89,9 +89,15 @@ SubRipDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
 
        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);
                }
        }
 
index 4e62206f129d0217fbc20e159c0909e329a016cd..d62c22bae9738c3963559aa6796dca45a976f057 100644 (file)
@@ -106,7 +106,7 @@ def build(bld):
                  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:
index 0827005c166050c1be1b212388c5184c59d1ba49..e18ee2ea564d20c19988659771b96dd4fbc68aea 100644 (file)
@@ -35,152 +35,6 @@ using std::string;
 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)
 {
@@ -201,19 +55,3 @@ 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");
-}
diff --git a/wscript b/wscript
index c81765935a6f447658c3c8a4ae9190049e9734ea..58df6ab77d5e81d877410199fc515b7936edf829 100644 (file)
--- a/wscript
+++ b/wscript
@@ -57,6 +57,11 @@ def dynamic_openjpeg(conf):
     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]
@@ -87,6 +92,10 @@ def dynamic_dcp(conf):
     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
@@ -262,6 +271,7 @@ def configure(conf):
         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)
 
@@ -276,6 +286,7 @@ def configure(conf):
         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)
 
@@ -290,6 +301,7 @@ def configure(conf):
         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)
 
@@ -308,6 +320,7 @@ def configure(conf):
         dynamic_ffmpeg(conf)
         dynamic_openjpeg(conf)
         dynamic_dcp(conf)
+        dynamic_sub(conf)
         dynamic_ssh(conf)
 
     # Not packaging; just a straight build
@@ -319,6 +332,7 @@ def configure(conf):
         dynamic_boost(conf, boost_lib_suffix, boost_thread)
         dynamic_ffmpeg(conf)
         dynamic_dcp(conf)
+        dynamic_sub(conf)
         dynamic_openjpeg(conf)
         dynamic_ssh(conf)