Adds text-based subtitle support to the internal video player.

Such subtitles are provided in external files with extensions
like '.srt', '.txt', or '.sub'. The implementation uses a parser 
taken from Xine to read in the subtitles.

Following formats are supported by the parser: MicroDVD, SubRip, 
SubViewer, SAMI, VPlayer, RT, Sub Station Alpha, PJS, MPSUB, 
AQTitle, JacobSub, Subviewer2, Subrip09, and Mplayer sub 2.

Index: libs/libmythtv/NuppelVideoPlayer.cpp
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.cpp.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/NuppelVideoPlayer.cpp	2006-04-30 13:25:36.000000000 +0300
@@ -119,7 +119,7 @@
 uint track_type_to_display_mode[kTrackTypeCount+2] =
 {
     kDisplayNone,
-    kDisplaySubtitle,
+    kDisplayAVSubtitle,
     kDisplayCC608,
     kDisplayCC708,
     kDisplayTeletextCaptions,
@@ -287,7 +287,7 @@
     if (weMadeBuffer)
         delete ringBuffer;
 
-    if (osdHasSubtitles || nonDisplayedSubtitles.size() > 0)
+    if (osdHasSubtitles || nonDisplayedAVSubtitles.size() > 0)
         ClearSubtitles();
 
     if (osd)
@@ -1011,6 +1011,8 @@
             deleteIter = deleteMap.begin();
         }
     }
+
+
   
     // need this til proper DVD bookmarking is implemented
     if (!ringBuffer->isDVD())
@@ -1423,11 +1425,15 @@
 
             DisableTeletext();
         }
-        if (kDisplaySubtitle & mode)
+        if (kDisplayAVSubtitle & mode)
         {
             msg += decoder->GetTrackDesc(kTrackTypeSubtitle,
                                          GetTrack(kTrackTypeSubtitle));
         }
+        if (kDisplayTextSubtitle & mode)
+        {
+	    msg += QObject::tr("Text subtitles");
+        }
         if (kDisplayCC608 & mode)
         {
             msg += decoder->GetTrackDesc(kTrackTypeCC608,
@@ -1450,11 +1456,15 @@
 void NuppelVideoPlayer::EnableCaptions(uint mode)
 {
     QString msg = "";
-    if (kDisplaySubtitle & mode)
+    if (kDisplayAVSubtitle & mode)
     {
         msg += decoder->GetTrackDesc(kTrackTypeSubtitle,
                                      GetTrack(kTrackTypeSubtitle));
     }
+    if (kDisplayTextSubtitle & mode)
+    {
+	msg += QObject::tr("Text Subtitles");
+    }
     if (kDisplayNUVTeletextCaptions & mode)
         msg += QObject::tr("TXT") + QString(" %1").arg(ttPageNum, 3, 16);
     if (kDisplayCC608 & mode)
@@ -1544,6 +1554,7 @@
 bool NuppelVideoPlayer::ToggleCaptions(uint type)
 {
     uint mode = track_type_to_display_mode[type];
+
     uint origMode = textDisplayMode;
 
     if (ringBuffer->isDVD() && GetCaptionMode() > 0)
@@ -1564,8 +1575,11 @@
     if (kDisplayCC708 & mode)
         EnableCaptions(kDisplayCC708);
 
-    if (kDisplaySubtitle & mode)
-        EnableCaptions(kDisplaySubtitle);
+    if (kDisplayAVSubtitle & mode)
+        EnableCaptions(kDisplayAVSubtitle);
+
+    if (kDisplayTextSubtitle & mode)
+        EnableCaptions(kDisplayTextSubtitle);
 
     if (kDisplayTeletextCaptions & mode)
         EnableCaptions(kDisplayTeletextCaptions);
@@ -1588,7 +1602,9 @@
     // figure out which text type to enable..
     bool captions_found = true;
     if (decoder->GetTrackCount(kTrackTypeSubtitle))
-        EnableCaptions(kDisplaySubtitle);
+	EnableCaptions(kDisplayAVSubtitle);
+    else if (textSubtitles.SubtitleCount() > 0)
+	EnableCaptions(kDisplayTextSubtitle);
     else if (decoder->GetTrackCount(kTrackTypeCC708))
         EnableCaptions(kDisplayCC708);
     else if (decoder->GetTrackCount(kTrackTypeTeletextCaptions))
@@ -2379,10 +2395,12 @@
     if (textDisplayMode & kDisplayNUVCaptions)
         ShowText();
 
-    // handle with DVB/DVD subtitles
-    if (textDisplayMode & kDisplaySubtitle)
-        DisplaySubtitles();
-    else if (osdHasSubtitles)
+    // handle DVB/DVD subtitles decoded by ffmpeg (in AVSubtitle format)
+    if (textDisplayMode & kDisplayAVSubtitle) 
+	DisplayAVSubtitles();
+    else if (textDisplayMode & kDisplayTextSubtitle)
+	DisplayTextSubtitles();
+    else if (osdHasSubtitles) 
         ClearSubtitles();
 
     // handle scan type changes
@@ -2616,7 +2634,7 @@
     if (fftime <= 0)
         fftime = (int)(seconds * video_frame_rate);
 
-    if (osdHasSubtitles || nonDisplayedSubtitles.size() > 0)
+    if (osdHasSubtitles || nonDisplayedAVSubtitles.size() > 0)
        ClearSubtitles();
 
     return fftime > CalcMaxFFTime(fftime, false);
@@ -2630,7 +2648,7 @@
     if (rewindtime <= 0)
         rewindtime = (int)(seconds * video_frame_rate);
 
-    if (osdHasSubtitles || nonDisplayedSubtitles.size() > 0)
+    if (osdHasSubtitles || nonDisplayedAVSubtitles.size() > 0)
        ClearSubtitles();
 
     return rewindtime >= framesPlayed;
@@ -5557,7 +5575,7 @@
     if (kTrackTypeSubtitle == type)
     {
         DisableCaptions(textDisplayMode, false);
-        EnableCaptions(kDisplaySubtitle);
+        EnableCaptions(kDisplayAVSubtitle);
     }
     else if (kTrackTypeCC708 == type)
     {
@@ -5707,7 +5725,7 @@
                 SetTrack(kTrackTypeSubtitle, 0);
         }
     }
-    else if ((textDisplayMode & kDisplaySubtitle) &&
+    else if ((textDisplayMode & kDisplayAVSubtitle) &&
              (vbimode == VBIMode::PAL_TT))
     {
         if ((uint)GetTrack(kTrackTypeSubtitle) + 1 <
@@ -5767,7 +5785,7 @@
         else
             SetCaptionsEnabled(false);
     }
-    else if ((textDisplayMode & kDisplaySubtitle) &&
+    else if ((textDisplayMode & kDisplayAVSubtitle) &&
              (vbimode == VBIMode::NTSC_CC))
     {
         if ((uint)GetTrack(kTrackTypeSubtitle) + 1 <
@@ -5780,8 +5798,14 @@
     }
 }
 
-// updates new subtitles to the screen and clears old ones
-void NuppelVideoPlayer::DisplaySubtitles()
+#define MAX_SUBTITLE_DISPLAY_TIME_MS 4500
+/** \fn NuppelVideoPlayer::DisplayAVSubtitles(void)
+ *  \brief Displays new subtitles and removes old ones. 
+ *
+ *  This version is for AVSubtitles which are added with AddAVSubtitles()
+ *  when found in the input stream.
+ */
+void NuppelVideoPlayer::DisplayAVSubtitles()
 {
     OSDSet *subtitleOSD;
     bool setVisible = false;
@@ -5805,14 +5829,14 @@
         osdHasSubtitles = false;
     }
 
-    while (nonDisplayedSubtitles.size() > 0)
+    while (nonDisplayedAVSubtitles.size() > 0)
     {
-        const AVSubtitle subtitlePage = nonDisplayedSubtitles.front();
+        const AVSubtitle subtitlePage = nonDisplayedAVSubtitles.front();
 
         if (subtitlePage.start_display_time > currentFrame->timecode)
             break;
 
-        nonDisplayedSubtitles.pop_front();
+        nonDisplayedAVSubtitles.pop_front();
 
         // clear old subtitles
         if (osdHasSubtitles) 
@@ -5828,8 +5852,8 @@
             AVSubtitleRect* rect = &subtitlePage.rects[i];
 
             bool displaysub = true;
-            if (nonDisplayedSubtitles.size() > 0 &&
-                nonDisplayedSubtitles.front().start_display_time < 
+            if (nonDisplayedAVSubtitles.size() > 0 &&
+                nonDisplayedAVSubtitles.front().start_display_time < 
                 currentFrame->timecode)
             {
                 displaysub = false;
@@ -5879,10 +5903,11 @@
                 if (subtitlePage.end_display_time <= 
                     subtitlePage.start_display_time)
                 {
-                    if (nonDisplayedSubtitles.size() > 0)
-                        osdSubtitlesExpireAt = nonDisplayedSubtitles.front().start_display_time;
+                    if (nonDisplayedAVSubtitles.size() > 0)
+                        osdSubtitlesExpireAt = 
+			    nonDisplayedAVSubtitles.front().start_display_time;
                     else
-                        osdSubtitlesExpireAt += 4500;
+                        osdSubtitlesExpireAt += MAX_SUBTITLE_DISPLAY_TIME_MS;
                 }
 
                 setVisible = true;
@@ -5909,14 +5934,85 @@
     }
 }
 
-// hide subtitles and free the undisplayed subtitles
+/** \fn NuppelVideoPlayer::DisplayTextSubtitles(void)
+ *  \brief Displays subtitles in textual format.
+ *
+ *  This version is for subtitles that are loaded from an external subtitle
+ *  file by using the LoadExternalSubtitles() method. Subtitles are not 
+ *  deleted after displaying so they can be displayed again after seeking.
+ */
+void NuppelVideoPlayer::DisplayTextSubtitles()
+{
+    VideoFrame *currentFrame = videoOutput->GetLastShownFrame();
+
+    if (!osd || !currentFrame) {
+        VERBOSE(VB_PLAYBACK, "osd or current video frame not found");
+        return;
+    }
+
+    QMutexLocker locker(&subtitleLock);
+
+    // frame time code in frames shown or millisecs from the start
+    // depending on the timing type of the subtitles
+    uint64_t playPos = 0;
+    if (textSubtitles.IsFrameBasedTiming()) {
+	// frame based subtitles get out of synch after running mythcommflag
+	// for the file, i.e., the following number is wrong and does not
+	// match the subtitle frame numbers:
+	playPos = currentFrame->frameNumber;
+    } else {
+	playPos = currentFrame->timecode;
+    }
+    
+    // check if we are showing a subtitle already and need to keep showing it
+    if (textSubtitles.SubtitlePosition() != textSubtitles.Subtitles().end()) {
+	text_subtitle_t& currentSub = *textSubtitles.SubtitlePosition();
+	if (currentSub.start <= playPos && currentSub.end >= playPos) {
+	    return;
+	}
+    } else {
+	// haven't shown any subtitles yet or shown all subtitles,
+	// start looking from the beginning
+	textSubtitles.SubtitlePosition() = textSubtitles.Subtitles().begin();
+    }
+
+    osd->ClearTextSubtitles();
+    osdHasSubtitles = false;
+
+    TextSubtitleList::iterator nextSub = textSubtitles.SubtitlePosition();
+    do {    
+	if ((*nextSub).start <= playPos && (*nextSub).end >= playPos)
+	    break; // found a sub to display
+
+	if ((*nextSub).start > playPos) {
+	    if (nextSub == textSubtitles.Subtitles().begin())
+		return; // no sub to display at the moment		
+	    --nextSub; 
+	    if ((*nextSub).end < playPos) 
+		return; // no sub to display at the moment	    
+	} else {
+	    ++nextSub;
+	    if ((*nextSub).start > playPos) 
+		return; // no sub to display at the moment
+	}
+    } while (nextSub != textSubtitles.Subtitles().end());
+
+    text_subtitle_t& newSub = *nextSub;
+    QString subText;
+    textSubtitles.SubtitlePosition() = nextSub;
+    osd->SetTextSubtitles(newSub.textLines);
+    osdHasSubtitles = true;
+}
+
+
+// hide and free the undisplayed AVSubtitles
 void NuppelVideoPlayer::ClearSubtitles() 
 {
     subtitleLock.lock();
 
-    while (nonDisplayedSubtitles.size() > 0) 
+    while (nonDisplayedAVSubtitles.size() > 0) 
     {
-        AVSubtitle& subtitle = nonDisplayedSubtitles.front();
+        AVSubtitle& subtitle = nonDisplayedAVSubtitles.front();
 
         // Because the subtitles were not displayed, OSDSet does not
         // free the OSDTypeImages in ClearAll(), so we have to free
@@ -5931,7 +6027,7 @@
         if (subtitle.num_rects > 0)
             av_free(subtitle.rects);
 
-        nonDisplayedSubtitles.pop_front();
+        nonDisplayedAVSubtitles.pop_front();
     }
 
     subtitleLock.unlock();
@@ -5948,12 +6044,13 @@
     }
 }
 
-// adds a new subtitle to be shown
+// adds a new AVSubtitle to be shown, assumes the subtitles are pushed in
+// the order they should be shown
 // FIXME: Need to fix subtitles to use a 64bit timestamp
-void NuppelVideoPlayer::AddSubtitle(const AVSubtitle &subtitle) 
+void NuppelVideoPlayer::AddAVSubtitle(const AVSubtitle &subtitle) 
 {
     subtitleLock.lock();
-    nonDisplayedSubtitles.push_back(subtitle);
+    nonDisplayedAVSubtitles.push_back(subtitle);
     subtitleLock.unlock();
 }
 
@@ -5973,6 +6070,19 @@
     }
 }
 
+/** \fn NuppelVideoPlayer::LoadExternalSubtitles(QString)
+ *  \brief Loads subtitles from an external file.
+ *
+ *  \return True in case the subtitle file format was detected and subtitles
+ *  were loaded successfully, false otherwise.
+ */
+bool NuppelVideoPlayer::LoadExternalSubtitles(QString subtitleFileName)
+{
+    QMutexLocker locker(&subtitleLock);
+    textSubtitles.Clear();
+    return TextSubtitleParser::LoadSubtitles(subtitleFileName, textSubtitles);
+}
+
 void NuppelVideoPlayer::ChangeDVDTrack(bool ffw)
 {
     if (!ringBuffer->isDVD())
Index: libs/libmythtv/avformatdecoder.cpp
===================================================================
--- libs/libmythtv/avformatdecoder.cpp.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/avformatdecoder.cpp	2006-04-30 13:24:27.000000000 +0300
@@ -2960,7 +2960,7 @@
                     {
                         subtitle.start_display_time += pts;
                         subtitle.end_display_time += pts;
-                        GetNVP()->AddSubtitle(subtitle);
+                        GetNVP()->AddAVSubtitle(subtitle);
                     }
 
                     break;
Index: libs/libmythtv/libmythtv.pro
===================================================================
--- libs/libmythtv/libmythtv.pro.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/libmythtv.pro	2006-04-30 13:24:27.000000000 +0300
@@ -199,6 +199,10 @@
     SOURCES += tv_play.cpp              NuppelVideoPlayer.cpp
     SOURCES += DVDRingBuffer.cpp
 
+    # Text subtitle parser
+    HEADERS += textsubtitleparser.h     xine_demux_sputext.h
+    SOURCES += textsubtitleparser.cpp   xine_demux_sputext.c
+
     # A/V decoders
     HEADERS += decoderbase.h
     HEADERS += nuppeldecoder.h          avformatdecoder.h
Index: libs/libmythtv/osdtypes.h
===================================================================
--- libs/libmythtv/osdtypes.h.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/osdtypes.h	2006-04-30 13:24:27.000000000 +0300
@@ -283,7 +283,7 @@
     mutable uint    m_draw_info_len;
     mutable vector<DrawInfo> m_draw_info;
 };
-    
+   
 class OSDTypeImage : public OSDType
 {
   public:
Index: libs/libmythtv/tv_play.h
===================================================================
--- libs/libmythtv/tv_play.h.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/tv_play.h	2006-04-30 13:24:27.000000000 +0300
@@ -352,6 +352,8 @@
 
     void ITVRestart(bool isLive);
 
+    void FindAndLoadExternalSubs(NuppelVideoPlayer& target, QString videoFile);
+
     static QStringList GetValidRecorderList(uint chanid);
     static QStringList GetValidRecorderList(const QString &channum);
     static QStringList GetValidRecorderList(uint, const QString&);
Index: libs/libmythtv/NuppelVideoPlayer.h
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.h.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/NuppelVideoPlayer.h	2006-04-30 13:24:27.000000000 +0300
@@ -14,6 +14,7 @@
 #include "recordingprofile.h"
 #include "videooutbase.h"
 #include "teletextdecoder.h"
+#include "textsubtitleparser.h"
 #include "tv_play.h"
 #include "yuv2rgb.h"
 #include "cc608decoder.h"
@@ -70,12 +71,11 @@
 enum
 {
     kTrackTypeAudio = 0,
-    kTrackTypeSubtitle,
+    kTrackTypeSubtitle, // subtitle track from ffmpeg
     kTrackTypeCC608,
     kTrackTypeCC708,
     kTrackTypeTeletextCaptions,
     kTrackTypeCount,
-
     kTrackTypeTeletextMenu,
 };
 QString track_type_to_string(uint type);
@@ -87,12 +87,13 @@
     kDisplayNone                = 0x00,
     kDisplayNUVTeletextCaptions = 0x01,
     kDisplayTeletextCaptions    = 0x02,
-    kDisplaySubtitle            = 0x04,
+    kDisplayAVSubtitle          = 0x04,
     kDisplayCC608               = 0x08,
     kDisplayCC708               = 0x10,
     kDisplayNUVCaptions         = kDisplayNUVTeletextCaptions | kDisplayCC608,
-    kDisplayAllCaptions         = 0x1f,
-    kDisplayTeletextMenu        = 0x20,
+    kDisplayTextSubtitle        = 0x20,
+    kDisplayAllCaptions         = 0x3f,
+    kDisplayTeletextMenu        = 0x40,
 };
 
 class NuppelVideoPlayer : public CC608Reader, public CC708Reader
@@ -277,7 +278,7 @@
                       long long timecode);
     void AddTextData(unsigned char *buffer, int len,
                      long long timecode, char type);
-    void AddSubtitle(const AVSubtitle& subtitle);
+    void AddAVSubtitle(const AVSubtitle& subtitle);
 
     // Closed caption and teletext stuff
     uint GetCaptionMode(void) const { return textDisplayMode; }
@@ -287,6 +288,7 @@
     bool ToggleCaptions(void);
     bool ToggleCaptions(uint mode);
     void SetCaptionsEnabled(bool);
+    bool LoadExternalSubtitles(QString subtitleFileName);
 
     // Teletext Menu and non-NUV teletext decoder
     void EnableTeletext(void);
@@ -477,7 +479,8 @@
     void  UpdateCC(unsigned char *inpos);
 
     // Private subtitle stuff
-    void  DisplaySubtitles(void);
+    void  DisplayAVSubtitles(void);
+    void  DisplayTextSubtitles(void);
     void  ClearSubtitles(void);
 
     // Private LiveTV stuff
@@ -611,7 +614,18 @@
     bool      textDesired;
     bool      osdHasSubtitles;
     long long osdSubtitlesExpireAt;
-    MythDeque<AVSubtitle> nonDisplayedSubtitles;
+
+    /// Subtitles loaded from the video stream by libavcodec.
+    /// This should contain only undisplayed subtitles, old
+    /// ones are deleted after displayed.
+    MythDeque<AVSubtitle> nonDisplayedAVSubtitles;
+
+    /// Subtitles loaded from an external subtitle file.
+    /// This contains all subtitles in textual format. No
+    /// subtitles are deleted after displaying (so they can
+    /// be displayed again after seeking). The list is ordered
+    /// by the subtitle display start time.
+    TextSubtitles textSubtitles;
 
     CC708Service CC708services[64];
     QString    osdfontname;
Index: libs/libmythtv/textsubtitleparser.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ libs/libmythtv/textsubtitleparser.h	2006-04-30 13:24:27.000000000 +0300
@@ -0,0 +1,56 @@
+// -*- Mode: c++ -*-
+
+#ifndef TEXT_SUBTITLE_PARSER_H
+#define TEXT_SUBTITLE_PARSER_H
+
+#include <qstring.h>
+#include <qvaluelist.h>
+
+#define MAX_TEXT_SUBTITLE_LINES 5
+
+struct text_subtitle_t {
+    
+    uint64_t start; ///< Starting time in msec or starting frame
+    uint64_t end;   ///< Ending time in msec or ending frame
+    
+    QValueList<QString> textLines;
+
+    text_subtitle_t(long start_, long end_) : start(start_), end(end_) {}
+    
+    // this constructor is needed for Qt's containers
+    text_subtitle_t() : start(0), end(0) {}
+};
+
+typedef QValueList<text_subtitle_t> TextSubtitleList;
+
+class TextSubtitles {
+public:
+    TextSubtitles() : m_subtitles(), m_frameBasedTiming(false), 
+		      m_subtitlePosition(m_subtitles.end()) {};
+    virtual ~TextSubtitles() {};
+
+    bool IsFrameBasedTiming() { return m_frameBasedTiming; }
+    void SetFrameBasedTiming(bool frameBasedTiming) 
+	{ m_frameBasedTiming = frameBasedTiming; }
+
+    void AddSubtitle(const text_subtitle_t& newSub);
+    void Clear();
+
+    TextSubtitleList::iterator& SubtitlePosition() 
+	{ return m_subtitlePosition; }
+    TextSubtitleList& Subtitles() { return m_subtitles; }
+    std::size_t SubtitleCount() { return m_subtitles.size(); }
+
+private:
+    TextSubtitleList m_subtitles;
+    bool m_frameBasedTiming;
+    TextSubtitleList::iterator m_subtitlePosition;
+};
+
+class TextSubtitleParser {
+public:
+    static bool LoadSubtitles(QString fileName, TextSubtitles& target);
+};
+
+#endif
+
Index: libs/libmythtv/osd.h
===================================================================
--- libs/libmythtv/osd.h.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/osd.h	2006-04-30 13:24:27.000000000 +0300
@@ -40,6 +40,7 @@
 class OSDTypeImage;
 class OSDTypePositionIndicator;
 class OSDSurface;
+class OSDTypeText;
 class TV;
 class UDPNotifyOSDSet;
 class OSDListTreeType;
@@ -49,6 +50,7 @@
 class CC708Service;
 class TeletextViewer;
 
+
 class OSD : public QObject
 {
     Q_OBJECT
@@ -151,12 +153,15 @@
     bool HasSet(const QString &name);
     QRect GetSubtitleBounds();
 
+    void SetTextSubtitles(const QStringList& lines);
+    void ClearTextSubtitles();
+
  private:
     bool InitDefaults(void);
     bool InitCC608(void);
     bool InitCC708(void);
     bool InitTeletext(void);
-    bool InitDVBSub(void);
+    bool InitSubtitles(void);
     bool InitMenu(void);
     bool InitInteractiveTV(void);
 
Index: libs/libmythtv/xine_demux_sputext.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ libs/libmythtv/xine_demux_sputext.c	2006-04-30 13:24:27.000000000 +0300
@@ -0,0 +1,1182 @@
+/*
+ * Copyright (C) 2000-2003 the xine project
+ * 
+ * This file is part of xine, a free video player.
+ * 
+ * xine 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.
+ * 
+ * xine 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id: demux_sputext.c,v 1.47 2006/02/14 18:44:08 dsalt Exp $
+ *
+ * code based on old libsputext/xine_decoder.c
+ *
+ * code based on mplayer module:
+ *
+ * Subtitle reader with format autodetection
+ *
+ * Written by laaz
+ * Some code cleanup & realloc() by A'rpi/ESP-team
+ * dunnowhat sub format by szabi
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "xine_demux_sputext.h"
+
+#define LOG_MODULE "demux_sputext"
+#define LOG_VERBOSE
+/*
+#define LOG
+*/
+
+#define ERR           (void *)-1
+#define LINE_LEN      1000
+#define LINE_LEN_QUOT "1000"
+
+/*
+ * Demuxer code start
+ */
+
+#define FORMAT_UNKNOWN   -1
+#define FORMAT_MICRODVD   0
+#define FORMAT_SUBRIP     1
+#define FORMAT_SUBVIEWER  2
+#define FORMAT_SAMI       3
+#define FORMAT_VPLAYER    4
+#define FORMAT_RT         5
+#define FORMAT_SSA        6 /* Sub Station Alpha */
+#define FORMAT_PJS	  7
+#define FORMAT_MPSUB      8 
+#define FORMAT_AQTITLE    9 
+#define FORMAT_JACOBSUB   10
+#define FORMAT_SUBVIEWER2 11
+#define FORMAT_SUBRIP09   12
+#define FORMAT_MPL2       13 /*Mplayer sub 2 ?*/
+
+static int eol(char p) {
+  return (p=='\r' || p=='\n' || p=='\0');
+}
+
+static inline void trail_space(char *s) {
+  int i;
+  while (isspace(*s)) {
+    char *copy = s;
+    do {
+      copy[0] = copy[1];
+      copy++;
+    } while(*copy);
+  }
+  i = strlen(s) - 1;
+  while (i > 0 && isspace(s[i])) 
+    s[i--] = '\0';
+}
+
+/*
+ * Reimplementation of fgets() using the input->read() method.
+ */
+static char *read_line_from_input(demux_sputext_t *this, char *line, off_t len) {
+  off_t nread = 0;
+  char *s;
+  int linelen;
+  
+  if ((len - this->buflen) > 512) {
+    if((nread = fread(
+	    &this->buf[this->buflen], 1, 
+	    len - this->buflen, this->file_ptr)) < 0) {
+      printf("read failed.\n");
+      return NULL;
+    }
+  }
+  
+  this->buflen += nread;
+  this->buf[this->buflen] = '\0';
+
+  s = strchr(this->buf, '\n');
+
+  if (line && (s || this->buflen)) {
+    
+    linelen = s ? (s - this->buf) + 1 : this->buflen;
+    
+    memcpy(line, this->buf, linelen);
+    line[linelen] = '\0';
+
+    memmove(this->buf, &this->buf[linelen], SUB_BUFSIZE - linelen);
+    this->buflen -= linelen;
+
+    return line;
+  }
+
+  return NULL;
+}
+
+
+static subtitle_t *sub_read_line_sami(demux_sputext_t *this, subtitle_t *current) {
+
+  static char line[LINE_LEN + 1];
+  static char *s = NULL;
+  char text[LINE_LEN + 1], *p, *q;
+  int state;
+
+  p = NULL;
+  current->lines = current->start = 0;
+  current->end = -1;
+  state = 0;
+  
+  /* read the first line */
+  if (!s)
+    if (!(s = read_line_from_input(this, line, LINE_LEN))) return 0;
+  
+  do {
+    switch (state) {
+      
+    case 0: /* find "START=" */
+      s = strstr (s, "Start=");
+      if (s) {
+	current->start = strtol (s + 6, &s, 0) / 10;
+	state = 1; continue;
+      }
+      break;
+      
+    case 1: /* find "<P" */
+      if ((s = strstr (s, "<P"))) { s += 2; state = 2; continue; }
+      break;
+      
+    case 2: /* find ">" */
+      if ((s = strchr (s, '>'))) { s++; state = 3; p = text; continue; }
+      break;
+      
+    case 3: /* get all text until '<' appears */
+      if (*s == '\0') { break; }
+      else if (*s == '<') { state = 4; }
+      else if (!strncasecmp (s, "&nbsp;", 6)) { *p++ = ' '; s += 6; }
+      else if (*s == '\r') { s++; }
+      else if (!strncasecmp (s, "<br>", 4) || *s == '\n') {
+	*p = '\0'; p = text; trail_space (text);
+	if (text[0] != '\0')
+	  current->text[current->lines++] = strdup (text);
+	if (*s == '\n') s++; else s += 4;
+      }
+      else *p++ = *s++;
+      continue;
+      
+    case 4: /* get current->end or skip <TAG> */
+      q = strstr (s, "Start=");
+      if (q) {
+	current->end = strtol (q + 6, &q, 0) / 10 - 1;
+	*p = '\0'; trail_space (text);
+	if (text[0] != '\0')
+	  current->text[current->lines++] = strdup (text);
+	if (current->lines > 0) { state = 99; break; }
+	state = 0; continue;
+      }
+      s = strchr (s, '>');
+      if (s) { s++; state = 3; continue; }
+      break;
+    }
+    
+    /* read next line */
+    if (state != 99 && !(s = read_line_from_input (this, line, LINE_LEN))) 
+      return 0;
+    
+  } while (state != 99);
+  
+  return current;
+}
+
+
+static char *sub_readtext(char *source, char **dest) {
+  int len=0;
+  char *p=source;
+  
+  while ( !eol(*p) && *p!= '|' ) {
+    p++,len++;
+  }
+  
+  *dest= (char *)malloc (len+1);
+  if (!dest) 
+    return ERR;
+  
+  strncpy(*dest, source, len);
+  (*dest)[len]=0;
+  
+  while (*p=='\r' || *p=='\n' || *p=='|')
+    p++;
+  
+  if (*p)  return p;  /* not-last text field */
+  else return NULL;   /* last text field     */
+}
+
+static subtitle_t *sub_read_line_microdvd(demux_sputext_t *this, subtitle_t *current) {
+
+  char line[LINE_LEN + 1];
+  char line2[LINE_LEN + 1];
+  char *p, *next;
+  int i;
+  
+  memset (current, 0, sizeof(subtitle_t));
+  
+  current->end=-1;
+  do {
+    if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+  } while ((sscanf (line, "{%ld}{}%" LINE_LEN_QUOT "[^\r\n]", &(current->start), line2) !=2) &&
+           (sscanf (line, "{%ld}{%ld}%" LINE_LEN_QUOT "[^\r\n]", &(current->start), &(current->end),line2) !=3)
+	  );
+  
+  p=line2;
+  
+  next=p, i=0;
+  while ((next =sub_readtext (next, &(current->text[i])))) {
+    if (current->text[i]==ERR) return ERR;
+    i++;
+    if (i>=SUB_MAX_TEXT) { 
+      printf ("Too many lines in a subtitle\n");
+      current->lines=i;
+      return current;
+    }
+  }
+  current->lines= ++i;
+  
+  return current;
+}
+
+static subtitle_t *sub_read_line_subviewer(demux_sputext_t *this, subtitle_t *current) {
+
+  char line[LINE_LEN + 1];
+  int a1,a2,a3,a4,b1,b2,b3,b4;
+  char *p=NULL, *q=NULL;
+  int len;
+  
+  memset (current, 0, sizeof(subtitle_t));
+  
+  while (1) {
+    if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+    if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4) < 8) {
+      if (sscanf (line, "%d:%d:%d,%d,%d:%d:%d,%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4) < 8)
+        continue;
+    }
+    current->start = a1*360000+a2*6000+a3*100+a4;
+    current->end   = b1*360000+b2*6000+b3*100+b4;
+    
+    if (!read_line_from_input(this, line, LINE_LEN))
+      return NULL;
+    
+    p=q=line;
+    for (current->lines=1; current->lines <= SUB_MAX_TEXT; current->lines++) {
+      for (q=p,len=0; *p && *p!='\r' && *p!='\n' && *p!='|' && strncasecmp(p,"[br]",4); p++,len++);
+      current->text[current->lines-1]=(char *)malloc (len+1);
+      if (!current->text[current->lines-1]) return ERR;
+      strncpy (current->text[current->lines-1], q, len);
+      current->text[current->lines-1][len]='\0';
+      if (!*p || *p=='\r' || *p=='\n') break;
+      if (*p=='[') while (*p++!=']');
+      if (*p=='|') p++;
+    }
+    if (current->lines > SUB_MAX_TEXT) current->lines = SUB_MAX_TEXT;
+    break;
+  }
+  return current;
+}
+
+static subtitle_t *sub_read_line_subrip(demux_sputext_t *this,subtitle_t *current) {
+  char line[LINE_LEN + 1];
+  int a1,a2,a3,a4,b1,b2,b3,b4;
+  int i,end_sub;
+  
+  memset(current,0,sizeof(subtitle_t));
+  do {
+    if(!read_line_from_input(this,line,LINE_LEN))
+      return NULL;
+    i = sscanf(line,"%d:%d:%d%*[,.]%d --> %d:%d:%d%*[,.]%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4);
+  } while(i < 8);
+  current->start = a1*360000+a2*6000+a3*100+a4/10;
+  current->end   = b1*360000+b2*6000+b3*100+b4/10;
+  i=0;
+  end_sub=0;
+  do {
+    char *p; /* pointer to the curently read char */
+    char temp_line[SUB_BUFSIZE]; /* subtitle line that will be transfered to current->text[i] */
+    int temp_index; /* ... and its index wich 'points' to the first EMPTY place -> last read char is at temp_index-1 if temp_index>0 */
+    temp_line[SUB_BUFSIZE-1]='\0'; /* just in case... */
+    if(!read_line_from_input(this,line,LINE_LEN)) {
+      if(i)
+        break; /* if something was read, transmit it */
+      else
+        return NULL; /* if not, repport EOF */
+    }
+    for(temp_index=0,p=line;*p!='\0' && !end_sub && temp_index<SUB_BUFSIZE && i<SUB_MAX_TEXT;p++) {
+      switch(*p) {
+        case '\\':
+          if(*(p+1)=='N' || *(p+1)=='n') {
+            temp_line[temp_index++]='\0'; /* end of curent line */
+            p++;
+          } else
+            temp_line[temp_index++]=*p;
+          break;
+        case '{':
+#if 0 /* italic not implemented in renderer, ignore them for now */
+          if(!strncmp(p,"{\\i1}",5) && temp_index+3<SUB_BUFSIZE) {
+            temp_line[temp_index++]='<';
+            temp_line[temp_index++]='i';
+            temp_line[temp_index++]='>';
+#else
+          if(!strncmp(p,"{\\i1}",5)) {
+#endif
+            p+=4;
+          }
+#if 0 /* italic not implemented in renderer, ignore them for now */
+          else if(!strncmp(p,"{\\i0}",5) && temp_index+4<SUB_BUFSIZE) {
+            temp_line[temp_index++]='<';
+            temp_line[temp_index++]='/';
+            temp_line[temp_index++]='i';
+            temp_line[temp_index++]='>';
+#else
+          else if(!strncmp(p,"{\\i0}",5)) {
+#endif
+            p+=4;
+          }
+          else
+            temp_line[temp_index++]=*p;
+          break;
+        case '\r': /* just ignore '\r's */
+          break;
+        case '\n':
+          temp_line[temp_index++]='\0';
+          break;
+        default:
+          temp_line[temp_index++]=*p;
+          break;
+      }
+      if(temp_index>0) {
+        if(temp_index==SUB_BUFSIZE)
+          printf("Too many characters in a subtitle line\n");
+        if(temp_line[temp_index-1]=='\0' || temp_index==SUB_BUFSIZE) {
+          if(temp_index>1) { /* more than 1 char (including '\0') -> that is a valid one */
+            current->text[i]=(char *)malloc(temp_index);
+            if(!current->text[i])
+              return ERR;
+            strncpy(current->text[i],temp_line,temp_index); /* temp_index<=SUB_BUFSIZE is always true here */
+            i++;
+            temp_index=0;
+          } else
+            end_sub=1;
+        }
+      }
+    }
+  } while(i<SUB_MAX_TEXT && !end_sub);
+  if(i>=SUB_MAX_TEXT)
+    printf("Too many lines in a subtitle\n");
+  current->lines=i;
+  return current;
+}
+
+static subtitle_t *sub_read_line_vplayer(demux_sputext_t *this,subtitle_t *current) {
+  char line[LINE_LEN + 1];
+  int a1,a2,a3,b1,b2,b3;
+  char *p=NULL, *next, *p2;
+  int i;
+  
+  memset (current, 0, sizeof(subtitle_t));
+    
+  while (!current->text[0]) {
+    if( this->next_line[0] == '\0' ) { /* if the buffer is empty.... */
+      if( !read_line_from_input(this, line, LINE_LEN) ) return NULL;
+    } else {
+      /* ... get the current line from buffer. */
+      strncpy( line, this->next_line, LINE_LEN);
+      line[LINE_LEN] = '\0'; /* I'm scared. This makes me feel better. */
+      this->next_line[0] = '\0'; /* mark the buffer as empty. */
+    }
+    /* Initialize buffer with next line */
+    if( ! read_line_from_input( this, this->next_line, LINE_LEN) ) {
+      this->next_line[0] = '\0';
+      return NULL;
+    }
+    if( (sscanf( line,            "%d:%d:%d:", &a1, &a2, &a3) < 3) ||
+        (sscanf( this->next_line, "%d:%d:%d:", &b1, &b2, &b3) < 3) )
+      continue;
+    current->start = a1*360000+a2*6000+a3*100;
+    current->end   = b1*360000+b2*6000+b3*100;
+    if ((current->end - current->start) > LINE_LEN) 
+      current->end = current->start + LINE_LEN; /* not too long though.  */
+    /* teraz czas na wkopiowanie stringu */
+    p=line;
+    /* finds the body of the subtitle_t */
+    for (i=0; i<3; i++){              
+      p2=strchr( p, ':');
+      if( p2 == NULL ) break;
+      p=p2+1;
+    } 
+      
+    next=p;
+    i=0;
+    while( (next = sub_readtext( next, &(current->text[i]))) ) {
+      if (current->text[i]==ERR) 
+        return ERR;
+      i++;
+      if (i>=SUB_MAX_TEXT) { 
+        printf("Too many lines in a subtitle\n");
+        current->lines=i;
+        return current;
+      }
+    }
+    current->lines=++i;
+  }
+  return current;
+}
+
+static subtitle_t *sub_read_line_rt(demux_sputext_t *this,subtitle_t *current) {
+  /*
+   * TODO: This format uses quite rich (sub/super)set of xhtml 
+   * I couldn't check it since DTD is not included.
+   * WARNING: full XML parses can be required for proper parsing 
+   */
+  char line[LINE_LEN + 1];
+  int a1,a2,a3,a4,b1,b2,b3,b4;
+  char *p=NULL,*next=NULL;
+  int i,len,plen;
+  
+  memset (current, 0, sizeof(subtitle_t));
+  
+  while (!current->text[0]) {
+    if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+    /*
+     * TODO: it seems that format of time is not easily determined, it may be 1:12, 1:12.0 or 0:1:12.0
+     * to describe the same moment in time. Maybe there are even more formats in use.
+     */
+    if ((len=sscanf (line, "<Time Begin=\"%d:%d:%d.%d\" End=\"%d:%d:%d.%d\"",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4)) < 8)
+     
+      plen=a1=a2=a3=a4=b1=b2=b3=b4=0;
+    if (
+	((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d\" %*[Ee]nd=\"%d:%d\"%*[^<]<clear/>%n",&a2,&a3,&b2,&b3,&plen)) < 4) &&
+	((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d\" %*[Ee]nd=\"%d:%d.%d\"%*[^<]<clear/>%n",&a2,&a3,&b2,&b3,&b4,&plen)) < 5) &&
+	/*	((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d.%d\" %*[Ee]nd=\"%d:%d\"%*[^<]<clear/>%n",&a2,&a3,&a4,&b2,&b3,&plen)) < 5) && */
+	((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d.%d\" %*[Ee]nd=\"%d:%d.%d\"%*[^<]<clear/>%n",&a2,&a3,&a4,&b2,&b3,&b4,&plen)) < 6) &&
+	((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d:%d.%d\" %*[Ee]nd=\"%d:%d:%d.%d\"%*[^<]<clear/>%n",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4,&plen)) < 8) 
+	)
+      continue;
+    current->start = a1*360000+a2*6000+a3*100+a4/10;
+    current->end   = b1*360000+b2*6000+b3*100+b4/10;
+    p=line;	p+=plen;i=0;
+    /* TODO: I don't know what kind of convention is here for marking multiline subs, maybe <br/> like in xml? */
+    next = strstr(line,"<clear/>")+8;i=0;
+    while ((next =sub_readtext (next, &(current->text[i])))) {
+      if (current->text[i]==ERR) 
+	return ERR;
+      i++;
+      if (i>=SUB_MAX_TEXT) { 
+	printf("Too many lines in a subtitle\n");
+	current->lines=i;
+	return current;
+      }
+    }
+    current->lines=i+1;
+  }
+  return current;
+}
+
+static subtitle_t *sub_read_line_ssa(demux_sputext_t *this,subtitle_t *current) {
+  int comma;
+  static int max_comma = 32; /* let's use 32 for the case that the */
+  /*  amount of commas increase with newer SSA versions */
+  
+  int hour1, min1, sec1, hunsec1, hour2, min2, sec2, hunsec2, nothing;
+  int num;
+  char line[LINE_LEN + 1], line3[LINE_LEN + 1], *line2;
+  char *tmp;
+  
+  do {
+    if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+  } while (sscanf (line, "Dialogue: Marked=%d,%d:%d:%d.%d,%d:%d:%d.%d,"
+		   "%[^\n\r]", &nothing,
+		   &hour1, &min1, &sec1, &hunsec1, 
+		   &hour2, &min2, &sec2, &hunsec2,
+		   line3) < 9
+	   &&
+	   sscanf (line, "Dialogue: %d,%d:%d:%d.%d,%d:%d:%d.%d,"
+		   "%[^\n\r]", &nothing,
+		   &hour1, &min1, &sec1, &hunsec1, 
+		   &hour2, &min2, &sec2, &hunsec2,
+		   line3) < 9	    );
+  
+  line2=strchr(line3, ',');
+  
+  for (comma = 4; comma < max_comma; comma ++)
+    {
+      tmp = line2;
+      if(!(tmp=strchr(++tmp, ','))) break;
+      if(*(++tmp) == ' ') break; 
+      /* a space after a comma means we're already in a sentence */
+      line2 = tmp;
+    }
+  
+  if(comma < max_comma)max_comma = comma;
+  /* eliminate the trailing comma */
+  if(*line2 == ',') line2++;
+  
+  current->lines=0;num=0;
+  current->start = 360000*hour1 + 6000*min1 + 100*sec1 + hunsec1;
+  current->end   = 360000*hour2 + 6000*min2 + 100*sec2 + hunsec2;
+  
+  while (((tmp=strstr(line2, "\\n")) != NULL) || ((tmp=strstr(line2, "\\N")) != NULL) ){
+    current->text[num]=(char *)malloc(tmp-line2+1);
+    strncpy (current->text[num], line2, tmp-line2);
+    current->text[num][tmp-line2]='\0';
+    line2=tmp+2;
+    num++;
+    current->lines++;
+    if (current->lines >=  SUB_MAX_TEXT) return current;
+  }
+  
+  current->text[num]=strdup(line2);
+  current->lines++;
+  
+  return current;
+}
+
+/* Sylvain "Skarsnik" Colinet <scolinet@gmail.com>
+ * From MPlayer subreader.c :
+ *
+ * PJS subtitles reader.
+ * That's the "Phoenix Japanimation Society" format.
+ * I found some of them in http://www.scriptsclub.org/ (used for anime).
+ * The time is in tenths of second.
+ *
+ * by set, based on code by szabi (dunnowhat sub format ;-)
+ */
+
+static subtitle_t *sub_read_line_pjs (demux_sputext_t *this, subtitle_t *current) {
+  char line[LINE_LEN + 1];
+  char text[LINE_LEN + 1];
+  char *s, *d;
+  
+  memset (current, 0, sizeof(subtitle_t));
+  
+  if (!read_line_from_input(this, line, LINE_LEN))
+    return NULL;
+  for (s = line; *s && isspace(*s); s++);
+  if (*s == 0)
+    return NULL;
+  if (sscanf (line, "%ld,%ld,", &(current->start),
+	      &(current->end)) <2)
+    return ERR;
+  /* the files I have are in tenths of second */
+  current->start *= 10;
+  current->end *= 10;
+ 
+  /* walk to the beggining of the string */
+  for (; *s; s++) if (*s==',') break;
+  if (*s) {
+      for (s++; *s; s++) if (*s==',') break;
+      if (*s) s++;
+  }
+  if (*s!='"') {
+       return ERR;
+  }
+  /* copy the string to the text buffer */
+  for (s++, d=text; *s && *s!='"'; s++, d++)
+      *d=*s;
+  *d=0;
+  current->text[0] = strdup(text);
+  current->lines = 1;
+  
+  return current;
+}
+
+static subtitle_t *sub_read_line_mpsub (demux_sputext_t *this, subtitle_t *current) {
+  char line[LINE_LEN + 1];
+  float a,b;
+  int num=0;
+  char *p, *q;
+  
+  do {
+    if (!read_line_from_input(this, line, LINE_LEN)) 
+      return NULL;
+  } while (sscanf (line, "%f %f", &a, &b) !=2);
+
+  this->mpsub_position += (a*100.0);
+  current->start = (int) this->mpsub_position;
+  this->mpsub_position += (b*100.0);
+  current->end = (int) this->mpsub_position;
+  
+  while (num < SUB_MAX_TEXT) {
+    if (!read_line_from_input(this, line, LINE_LEN)) 
+      return NULL;
+
+    p=line;
+    while (isspace(*p)) 
+      p++;
+
+    if (eol(*p) && num > 0) 
+      return current;
+
+    if (eol(*p)) 
+      return NULL;
+    
+    for (q=p; !eol(*q); q++);
+    *q='\0';
+    if (strlen(p)) {
+      current->text[num]=strdup(p);
+      printf(">%s<\n",p);
+      current->lines = ++num;
+    } else {
+      if (num) 
+	return current;
+      else 
+	return NULL;
+    }
+  }
+
+  return NULL;
+}
+
+static subtitle_t *sub_read_line_aqt (demux_sputext_t *this, subtitle_t *current) {
+  char line[LINE_LEN + 1];
+
+  memset (current, 0, sizeof(subtitle_t));
+
+  while (1) {
+    /* try to locate next subtitle_t */
+    if (!read_line_from_input(this, line, LINE_LEN))
+      return NULL;
+    if (!(sscanf (line, "-->> %ld", &(current->start)) <1))
+      break;
+  }
+  
+  if (!read_line_from_input(this, line, LINE_LEN))
+    return NULL;
+  
+  sub_readtext((char *) &line,&current->text[0]);
+  current->lines = 1;
+  current->end = -1;
+  
+  if (!read_line_from_input(this, line, LINE_LEN))
+    return current;;
+  
+  sub_readtext((char *) &line,&current->text[1]);
+  current->lines = 2;
+  
+  if ((current->text[0]=="") && (current->text[1]=="")) {
+    return NULL;
+  }
+  
+  return current;
+}
+
+static subtitle_t *sub_read_line_jacobsub(demux_sputext_t *this, subtitle_t *current) {
+    char line1[LINE_LEN], line2[LINE_LEN], directive[LINE_LEN], *p, *q;
+    unsigned a1, a2, a3, a4, b1, b2, b3, b4, comment = 0;
+    static unsigned jacoTimeres = 30;
+    static int jacoShift = 0;
+
+    memset(current, 0, sizeof(subtitle_t));
+    memset(line1, 0, LINE_LEN);
+    memset(line2, 0, LINE_LEN);
+    memset(directive, 0, LINE_LEN);
+    while (!current->text[0]) {
+	if (!read_line_from_input(this, line1, LINE_LEN)) {
+	    return NULL;
+	}
+	if (sscanf
+	    (line1, "%u:%u:%u.%u %u:%u:%u.%u %" LINE_LEN_QUOT "[^\n\r]", &a1, &a2, &a3, &a4,
+	     &b1, &b2, &b3, &b4, line2) < 9) {
+	    if (sscanf(line1, "@%u @%u %" LINE_LEN_QUOT "[^\n\r]", &a4, &b4, line2) < 3) {
+		if (line1[0] == '#') {
+		    int hours = 0, minutes = 0, seconds, delta, inverter =
+			1;
+		    unsigned units = jacoShift;
+		    switch (toupper(line1[1])) {
+		    case 'S':
+			if (isalpha(line1[2])) {
+			    delta = 6;
+			} else {
+			    delta = 2;
+			}
+			if (sscanf(&line1[delta], "%d", &hours)) {
+			    if (hours < 0) {
+				hours *= -1;
+				inverter = -1;
+			    }
+			    if (sscanf(&line1[delta], "%*d:%d", &minutes)) {
+				if (sscanf
+				    (&line1[delta], "%*d:%*d:%d",
+				     &seconds)) {
+				    sscanf(&line1[delta], "%*d:%*d:%*d.%d",
+					   &units);
+				} else {
+				    hours = 0;
+				    sscanf(&line1[delta], "%d:%d.%d",
+					   &minutes, &seconds, &units);
+				    minutes *= inverter;
+				}
+			    } else {
+				hours = minutes = 0;
+				sscanf(&line1[delta], "%d.%d", &seconds,
+				       &units);
+				seconds *= inverter;
+			    }
+			    jacoShift =
+				((hours * 3600 + minutes * 60 +
+				  seconds) * jacoTimeres +
+				 units) * inverter;
+			}
+			break;
+		    case 'T':
+			if (isalpha(line1[2])) {
+			    delta = 8;
+			} else {
+			    delta = 2;
+			}
+			sscanf(&line1[delta], "%u", &jacoTimeres);
+			break;
+		    }
+		}
+		continue;
+	    } else {
+		current->start =
+		    (unsigned long) ((a4 + jacoShift) * 100.0 /
+				     jacoTimeres);
+		current->end =
+		    (unsigned long) ((b4 + jacoShift) * 100.0 /
+				     jacoTimeres);
+	    }
+	} else {
+	    current->start =
+		(unsigned
+		 long) (((a1 * 3600 + a2 * 60 + a3) * jacoTimeres + a4 +
+			 jacoShift) * 100.0 / jacoTimeres);
+	    current->end =
+		(unsigned
+		 long) (((b1 * 3600 + b2 * 60 + b3) * jacoTimeres + b4 +
+			 jacoShift) * 100.0 / jacoTimeres);
+	}
+	current->lines = 0;
+	p = line2;
+	while ((*p == ' ') || (*p == '\t')) {
+	    ++p;
+	}
+	if (isalpha(*p)||*p == '[') {
+	    int cont, jLength;
+
+	    if (sscanf(p, "%s %" LINE_LEN_QUOT "[^\n\r]", directive, line1) < 2)
+		return ERR;
+	    jLength = strlen(directive);
+	    for (cont = 0; cont < jLength; ++cont) {
+		if (isalpha(*(directive + cont)))
+		    *(directive + cont) = toupper(*(directive + cont));
+	    }
+	    if ((strstr(directive, "RDB") != NULL)
+		|| (strstr(directive, "RDC") != NULL)
+		|| (strstr(directive, "RLB") != NULL)
+		|| (strstr(directive, "RLG") != NULL)) {
+		continue;
+	    }
+	    /* no alignment */
+#if 0
+	    if (strstr(directive, "JL") != NULL) {
+		current->alignment = SUB_ALIGNMENT_HLEFT;
+	    } else if (strstr(directive, "JR") != NULL) {
+		current->alignment = SUB_ALIGNMENT_HRIGHT;
+	    } else {
+		current->alignment = SUB_ALIGNMENT_HCENTER;
+	    }
+#endif
+	    strcpy(line2, line1);
+	    p = line2;
+	}
+	for (q = line1; (!eol(*p)) && (current->lines < SUB_MAX_TEXT); ++p) {
+	    switch (*p) {
+	    case '{':
+		comment++;
+		break;
+	    case '}':
+		if (comment) {
+		    --comment;
+		    /* the next line to get rid of a blank after the comment */
+		    if ((*(p + 1)) == ' ')
+			p++;
+		}
+		break;
+	    case '~':
+		if (!comment) {
+		    *q = ' ';
+		    ++q;
+		}
+		break;
+	    case ' ':
+	    case '\t':
+		if ((*(p + 1) == ' ') || (*(p + 1) == '\t'))
+		    break;
+		if (!comment) {
+		    *q = ' ';
+		    ++q;
+		}
+		break;
+	    case '\\':
+		if (*(p + 1) == 'n') {
+		    *q = '\0';
+		    q = line1;
+		    current->text[current->lines++] = strdup(line1);
+		    ++p;
+		    break;
+		}
+		if ((toupper(*(p + 1)) == 'C')
+		    || (toupper(*(p + 1)) == 'F')) {
+		    ++p,++p;
+		    break;
+		}
+		if ((*(p + 1) == 'B') || (*(p + 1) == 'b') || 
+		    /* actually this means "insert current date here" */
+		    (*(p + 1) == 'D') || 
+		    (*(p + 1) == 'I') || (*(p + 1) == 'i') || 
+		    (*(p + 1) == 'N') || 
+		    /* actually this means "insert current time here" */
+		    (*(p + 1) == 'T') ||	
+		    (*(p + 1) == 'U') || (*(p + 1) == 'u')) {
+		    ++p;
+		    break;
+		}
+		if ((*(p + 1) == '\\') ||
+		    (*(p + 1) == '~') || (*(p + 1) == '{')) {
+		    ++p;
+		} else if (eol(*(p + 1))) {
+		    if (!read_line_from_input(this, directive, LINE_LEN))
+			return NULL;
+		    trail_space(directive);
+		    strncat(line2, directive,
+			    (LINE_LEN > 511) ? LINE_LEN : 511);
+		    break;
+		}
+	    default:
+		if (!comment) {
+		    *q = *p;
+		    ++q;
+		}
+	    }
+	}
+	*q = '\0';
+	current->text[current->lines] = strdup(line1);
+    }
+    current->lines++;
+    return current;
+}
+
+static subtitle_t *sub_read_line_subviewer2(demux_sputext_t *this, subtitle_t *current) {
+    char line[LINE_LEN+1];
+    int a1,a2,a3,a4;
+    char *p=NULL;
+    int i,len;
+   
+    while (!current->text[0]) {
+        if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+	if (line[0]!='{')
+	    continue;
+        if ((len=sscanf (line, "{T %d:%d:%d:%d",&a1,&a2,&a3,&a4)) < 4)
+            continue;
+        current->start = a1*360000+a2*6000+a3*100+a4/10;
+        for (i=0; i<SUB_MAX_TEXT;) {
+            if (!read_line_from_input(this, line, LINE_LEN)) break;
+            if (line[0]=='}') break;
+            len=0;
+            for (p=line; *p!='\n' && *p!='\r' && *p; ++p,++len);
+            if (len) {
+                current->text[i]=(char *)malloc (len+1);
+                if (!current->text[i]) return ERR;
+                strncpy (current->text[i], line, len); current->text[i][len]='\0';
+                ++i;
+            } else {
+                break;
+            }
+        }
+        current->lines=i;
+    }
+    return current;
+}
+
+static subtitle_t *sub_read_line_subrip09 (demux_sputext_t *this, subtitle_t *current) {
+  char line[LINE_LEN + 1];
+  char *next;
+  int h, m, s;
+  int i;
+  
+  memset (current, 0, sizeof(subtitle_t));
+  
+  do {
+    if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+  } while (sscanf (line, "[%d:%d:%d]", &h, &m, &s) != 3);
+
+  if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+
+  current->start = 360000 * h + 6000 * m + 100 * s;
+  current->end = -1;
+
+  next=line;
+  i=0;
+  while ((next = sub_readtext (next, &(current->text[i])))) {
+    if (current->text[i]==ERR) return ERR;
+    i++;
+    if (i>=SUB_MAX_TEXT) { 
+      printf("Too many lines in a subtitle\n");
+      current->lines=i;
+      return current;
+    }
+  }
+  current->lines= ++i;
+  
+  return current;
+}
+
+/* Code from subreader.c of MPlayer
+** Sylvain "Skarsnik" Colinet <scolinet@gmail.com>
+*/
+
+static subtitle_t *sub_read_line_mpl2(demux_sputext_t *this, subtitle_t *current) {
+  char line[LINE_LEN+1];
+  char line2[LINE_LEN+1];
+  char *p, *next;
+  int i;
+
+  memset (current, 0, sizeof(subtitle_t));
+  do {
+     if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+  } while ((sscanf (line,
+		      "[%ld][%ld]%[^\r\n]",
+		      &(current->start), &(current->end), line2) < 3));
+  current->start *= 10;
+  current->end *= 10;
+  p=line2;
+
+  next=p, i=0;
+  while ((next = sub_readtext (next, &(current->text[i])))) {
+      if (current->text[i] == ERR) {return ERR;}
+      i++;
+      if (i >= SUB_MAX_TEXT) {
+        printf("Too many lines in a subtitle\n");
+	current->lines = i;
+	return current;
+      }
+    }
+  current->lines= ++i;
+
+  return current;
+}
+
+
+static int sub_autodetect (demux_sputext_t *this) {
+
+  char line[LINE_LEN + 1];
+  int  i, j=0;
+  char p;
+  
+  while (j < 100) {
+    j++;
+    if (!read_line_from_input(this, line, LINE_LEN))
+      return FORMAT_UNKNOWN;
+
+    if ((sscanf (line, "{%d}{}", &i)==1) ||
+        (sscanf (line, "{%d}{%d}", &i, &i)==2)) {
+      this->uses_time=0;
+      return FORMAT_MICRODVD;
+    }
+
+    if (sscanf (line, "%d:%d:%d%*[,.]%d --> %d:%d:%d%*[,.]%d", &i, &i, &i, &i, &i, &i, &i, &i)==8) {
+      this->uses_time=1;
+      return FORMAT_SUBRIP;
+    }
+
+    if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d",     &i, &i, &i, &i, &i, &i, &i, &i)==8){
+      this->uses_time=1;
+      return FORMAT_SUBVIEWER;
+    }
+
+    if (sscanf (line, "%d:%d:%d,%d,%d:%d:%d,%d",     &i, &i, &i, &i, &i, &i, &i, &i)==8){
+      this->uses_time=1;
+      return FORMAT_SUBVIEWER;
+    }
+
+    if (strstr (line, "<SAMI>")) {
+      this->uses_time=1; 
+      return FORMAT_SAMI;
+    }
+    if (sscanf (line, "%d:%d:%d:",     &i, &i, &i )==3) {
+      this->uses_time=1;
+      return FORMAT_VPLAYER;
+    }
+    /*
+     * A RealText format is a markup language, starts with <window> tag,
+     * options (behaviour modifiers) are possible.
+     */
+    if ( !strcasecmp(line, "<window") ) {
+      this->uses_time=1;
+      return FORMAT_RT;
+    }
+    if ((!memcmp(line, "Dialogue: Marked", 16)) || (!memcmp(line, "Dialogue: ", 10))) {
+      this->uses_time=1; 
+      return FORMAT_SSA;
+    }
+    if (sscanf (line, "%d,%d,\"%c", &i, &i, (char *) &i) == 3) {
+      this->uses_time=0;
+      return FORMAT_PJS;
+    }
+    if (sscanf (line, "FORMAT=%d", &i) == 1) {
+      this->uses_time=0; 
+      return FORMAT_MPSUB;
+    }
+    if (sscanf (line, "FORMAT=TIM%c", &p)==1 && p=='E') {
+      this->uses_time=1; 
+      return FORMAT_MPSUB;
+    }
+    if (strstr (line, "-->>")) {
+      this->uses_time=0; 
+      return FORMAT_AQTITLE;
+    }
+    if (sscanf(line, "@%d @%d", &i, &i) == 2 ||
+	sscanf(line, "%d:%d:%d.%d %d:%d:%d.%d", &i, &i, &i, &i, &i, &i, &i, &i) == 8) {
+      this->uses_time = 1;
+      return FORMAT_JACOBSUB;
+    }
+    if (sscanf(line, "{T %d:%d:%d:%d",&i, &i, &i, &i) == 4) {
+      this->uses_time = 1;
+      return FORMAT_SUBVIEWER2;
+    }
+    if (sscanf(line, "[%d:%d:%d]", &i, &i, &i) == 3) {
+      this->uses_time = 1;
+      return FORMAT_SUBRIP09;
+    }
+  
+    if (sscanf (line, "[%d][%d]", &i, &i) == 2) {
+      this->uses_time = 1;
+      return FORMAT_MPL2;
+    }
+  }
+  return FORMAT_UNKNOWN;  /* too many bad lines */
+}
+
+subtitle_t *sub_read_file (demux_sputext_t *this) {
+
+  int n_max;
+  int timeout;
+  subtitle_t *first;
+  subtitle_t * (*func[])(demux_sputext_t *this,subtitle_t *dest)=
+  {
+    sub_read_line_microdvd,
+    sub_read_line_subrip,
+    sub_read_line_subviewer,
+    sub_read_line_sami,
+    sub_read_line_vplayer,
+    sub_read_line_rt,
+    sub_read_line_ssa,
+    sub_read_line_pjs,
+    sub_read_line_mpsub,
+    sub_read_line_aqt,
+    sub_read_line_jacobsub,
+    sub_read_line_subviewer2,
+    sub_read_line_subrip09,
+    sub_read_line_mpl2,
+  };
+
+  /* Rewind (sub_autodetect() needs to read input from the beginning) */
+  if(fseek(this->file_ptr, 0, SEEK_SET) == -1) {
+    printf("seek failed.\n");
+    return NULL;
+  }
+  this->buflen = 0;
+
+  this->format=sub_autodetect (this);
+  if (this->format==FORMAT_UNKNOWN) {
+    return NULL;
+  }
+
+  /*printf("Detected subtitle file format: %d\n", this->format);*/
+    
+  /* Rewind */
+  if(fseek(this->file_ptr, 0, SEEK_SET) == -1) {
+    printf("seek failed.\n");
+    return NULL;
+  }
+  this->buflen = 0;
+
+  this->num=0;n_max=32;
+  first = (subtitle_t *) malloc(n_max*sizeof(subtitle_t));
+  if(!first) return NULL;
+  timeout = MAX_TIMEOUT;
+
+  if (this->uses_time) timeout *= 100;
+  else timeout *= 10;
+
+  while(1) {
+    subtitle_t *sub;
+
+    if(this->num>=n_max){
+      n_max+=16;
+      first=realloc(first,n_max*sizeof(subtitle_t));
+    }
+
+    sub = func[this->format] (this, &first[this->num]);
+
+    if (!sub) 
+      break;   /* EOF */
+
+    if (sub==ERR) 
+      ++this->errs; 
+    else {
+      if (this->num > 0 && first[this->num-1].end == -1) {
+	/* end time not defined in the subtitle */
+	if (timeout > 0) { 
+	  /* timeout */
+	  if (timeout > sub->start - first[this->num-1].start) {
+	    first[this->num-1].end = sub->start;
+	  } else
+	    first[this->num-1].end = first[this->num-1].start + timeout;
+	} else {
+	  /* no timeout */
+	  first[this->num-1].end = sub->start;
+	}
+      }
+      ++this->num; /* Error vs. Valid */
+    }
+  }
+  /* timeout of last subtitle */
+  if (this->num > 0 && first[this->num-1].end == -1)
+    if (timeout > 0) {
+      first[this->num-1].end = first[this->num-1].start + timeout;
+    }
+
+#ifdef DEBUX_XINE_DEMUX_SPUTEXT
+  {
+    char buffer[1024];
+
+    sprintf(buffer, "Read %i subtitles", this->num);
+
+    if(this->errs) 
+      sprintf(buffer + strlen(buffer), ", %i bad line(s).\n", this->errs);
+    else
+      strcat(buffer, "\n");
+    
+    printf("%s", buffer);
+  }
+#endif
+  
+  return first;
+}
+
Index: libs/libmythtv/xine_demux_sputext.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ libs/libmythtv/xine_demux_sputext.h	2006-04-30 13:24:27.000000000 +0300
@@ -0,0 +1,55 @@
+#ifndef XINE_DEMUX_SPUTEXT_H
+#define XINE_DEMUX_SPUTEXT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+
+#define SUB_BUFSIZE   1024
+#define SUB_MAX_TEXT  5
+#define MAX_TIMEOUT 4
+
+#define DEBUG_XINE_DEMUX_SPUTEXT 1
+
+typedef struct {
+
+    int lines; ///< Count of text lines in this subtitle set.
+
+    long start; ///< Starting time in msec or starting frame
+    long end;   ///< Ending time in msec or starting frame
+    
+    char *text[SUB_MAX_TEXT]; ///< The subtitle text lines.
+} subtitle_t;
+
+typedef struct {
+
+  FILE*              file_ptr;
+
+  int                status;
+
+  char               buf[SUB_BUFSIZE];
+  off_t              buflen;
+
+  float              mpsub_position;  
+
+  int                uses_time;  
+  int                errs;  
+  subtitle_t        *subtitles;
+  int                num;            /* number of subtitle structs */
+  int                cur;            /* current subtitle           */
+  int                format;         /* constants see below        */
+  char               next_line[SUB_BUFSIZE]; /* a buffer for next line read from file */
+
+} demux_sputext_t;
+
+
+subtitle_t *sub_read_file (demux_sputext_t*);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: libs/libmythtv/tv_play.cpp
===================================================================
--- libs/libmythtv/tv_play.cpp.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/tv_play.cpp	2006-04-30 13:24:27.000000000 +0300
@@ -8,6 +8,7 @@
 #include <qregexp.h>
 #include <qfile.h>
 #include <qtimer.h>
+#include <qdir.h>
 
 #include "mythdbcon.h"
 #include "tv.h"
@@ -1157,6 +1158,10 @@
         nvp->ResetCaptions();
         nvp->ResetTeletext();
 
+	/// external subtitle file hack start
+
+	/// external subtitle file hack end
+
         activenvp = nvp;
         activerbuffer = prbuffer;
         StartOSD();
@@ -1279,6 +1284,7 @@
     nvp->SetLength(playbackLen);
     nvp->SetExactSeeks(gContext->GetNumSetting("ExactSeeking", 0));
     nvp->SetAutoCommercialSkip(autoCommercialSkip);
+    FindAndLoadExternalSubs(*nvp, prbuffer->GetFilename());
     nvp->SetLiveTVChain(tvchain);
 
     nvp->SetAudioStretchFactor(normal_speed);
@@ -6624,7 +6630,7 @@
         typeStr = "SUBTITLE";
         selStr  = "SELECTSUBTITLE_";
         grpStr  = "SUBTITLEGROUP";
-        sel     = activenvp->GetCaptionMode() & kDisplaySubtitle;
+        sel     = activenvp->GetCaptionMode() & kDisplayAVSubtitle;
     }
     else if (kTrackTypeCC608 == type)
     {
@@ -7078,4 +7084,46 @@
     nvp->ITVRestart(chanid, cardid, isLive);
 }
 
+/* \fn TV::FindAndLoadExternalSubs(NuppelVideoPlayer&, QString)
+ * \brief Tries to find an external subtitle file in the same directory
+ *  in which the video file is. Tries to parse each found candidate file
+ *  until one is parsed succesfully.
+ */
+void TV::FindAndLoadExternalSubs(NuppelVideoPlayer& target, QString videoFile)
+{
+    if (videoFile.length() < 1)
+	return;
+
+    QString baseName;
+
+    int suffixPos = videoFile.findRev(QChar('.'));
+    if (suffixPos > 0) {
+	baseName = videoFile.left(suffixPos);
+    } else {
+	baseName = videoFile;
+    }
+
+    // the detection does not work if the filaname has the following chars
+    baseName = 
+	baseName.replace("[", "?").replace("]", "?").replace("(", "?")
+	.replace(")", "?");
+
+    // try to find files with the same base name, but ending with
+    // '.srt', '.sub', or '.txt'
+    QDir dir;    
+    QStringList candidates = dir.entryList(
+	baseName + "*.srt; " + baseName + "*.sub; " + baseName + "*.txt;",
+	QDir::Files);
+
+    for (int i = 0; i < candidates.size(); ++i) {
+	if (nvp->LoadExternalSubtitles(candidates[i])) {
+	    VERBOSE(
+		VB_PLAYBACK, 
+		LOC + QString("Loaded text subtitles from %1.").
+		arg(candidates[i]));
+	    return;
+	}
+    }
+}
+
 /* vim: set expandtab tabstop=4 shiftwidth=4: */
Index: libs/libmythtv/textsubtitleparser.cpp
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ libs/libmythtv/textsubtitleparser.cpp	2006-04-30 13:26:04.000000000 +0300
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <qtextcodec.h>
+
+#include "textsubtitleparser.h"
+#include "xine_demux_sputext.h"
+
+void TextSubtitles::AddSubtitle(const text_subtitle_t& newSub) 
+{
+    m_subtitles.push_back(newSub); 
+    m_subtitlePosition = m_subtitles.end(); 
+}
+
+void TextSubtitles::Clear() 
+{
+    m_subtitles.clear(); 
+    m_subtitlePosition = m_subtitles.end(); 
+}
+
+bool TextSubtitleParser::LoadSubtitles(QString fileName, TextSubtitles& target) 
+{
+    demux_sputext_t sub_data;
+    if ((sub_data.file_ptr = fopen(fileName, "r")) == NULL) 
+    {
+	return false;
+    }
+
+    subtitle_t* loaded_subs = NULL;
+    if ((loaded_subs = sub_read_file(&sub_data)) == NULL) 
+    {
+	return false;
+    }
+    target.SetFrameBasedTiming(!sub_data.uses_time);
+
+    // convert the subtitles to our own format and free the original 
+    // structures
+    for (int sub_i = 0; sub_i < sub_data.num; ++sub_i) {
+	subtitle_t* sub = &loaded_subs[sub_i];
+	text_subtitle_t newsub(sub->start, sub->end);
+
+	if (!target.IsFrameBasedTiming()) {
+	    newsub.start *= 10; // convert from csec to msec
+	    newsub.end *= 10;
+	}
+	for (int line = 0; line < sub->lines; ++line) {
+	    newsub.textLines.push_back(QString(sub->text[line]));	    
+	    free(sub->text[line]);
+	}
+	target.AddSubtitle(newsub);
+    }
+    free(loaded_subs);
+    fclose(sub_data.file_ptr);
+    return true;
+}
Index: libs/libmythtv/osd.cpp
===================================================================
--- libs/libmythtv/osd.cpp.orig	2006-04-29 17:26:15.000000000 +0300
+++ libs/libmythtv/osd.cpp	2006-04-30 13:27:09.000000000 +0300
@@ -23,6 +23,7 @@
 #include "osdtypes.h"
 #include "osdsurface.h"
 #include "mythcontext.h"
+#include "textsubtitleparser.h"
 #include "libmyth/oldsettings.h"
 #include "udpnotify.h"
 
@@ -135,7 +136,7 @@
     ok &= InitCC708();
     ok &= InitTeletext();
     ok &= InitMenu();
-    ok &= InitDVBSub();
+    ok &= InitSubtitles();
     ok &= InitInteractiveTV();
     return ok;
 }
@@ -274,6 +275,91 @@
     return true;
 }   
 
+void OSD::SetTextSubtitles(const QStringList& lines) 
+{
+    const uint8_t SUBTITLE_FONT_SIZE = 18;
+    const uint8_t SUBTITLE_LINE_HEIGHT = 22;
+    const uint8_t MAX_CHARACTERS_PER_ROW = 50;
+
+    OSDSet* subtitleSet = GetSet("subtitles");
+    if (subtitleSet == NULL)
+	return;
+
+
+    QString subText = "";
+    int subLines = 0;
+    for (std::size_t i = 0; i < lines.size(); ++i) {
+
+	const QString& line = lines[i];
+	if (line.length() > MAX_CHARACTERS_PER_ROW) {
+	    // wrap long lines at word spaces
+	    QStringList words = QStringList::split(" ", line);
+	    QString newString = "";
+	    do {
+		QString word = words.first();
+		words.pop_front();
+
+		if (newString.length() + word.length() + 1 > 
+		    MAX_CHARACTERS_PER_ROW) {
+		    // next word won't fit anymore, create a new line
+		    subText.append(newString + "\n");
+		    ++subLines;
+		    newString = "";
+		}
+		newString.append(word + " ");
+	    } while(words.size() > 0);
+
+	    subText.append(newString);
+	} else {
+	    subText.append(line);
+	}
+	subText.append("\n");
+	++subLines;
+    }
+
+
+    ClearAll("subtitles");
+
+    QString name = "text_subtitles";
+
+    QRect area(0, displayheight - subLines * SUBTITLE_LINE_HEIGHT,
+	       displaywidth, displayheight);
+
+    QString fontname = "text_subtitle_font";
+    TTFFont *font = GetFont(fontname);
+    if (!font)
+    {
+	
+        font = LoadFont(gContext->GetSetting("OSDCCFont"), SUBTITLE_FONT_SIZE);
+
+        if (font) {
+	    // set outline so we can see the font in white background video
+	    font->setOutline(true);
+            fontMap[fontname] = font;
+	} else {
+	    VERBOSE(VB_IMPORTANT, "Cannot load font for text subtitles.");
+	}
+    }
+
+    OSDTypeText *text = new OSDTypeText(
+        name, font, "", area, wmult, hmult);
+  
+    text->SetCentered(true);
+    text->SetMultiLine(true);
+    text->SetSelected(false);
+    text->SetText(subText);
+    text->SetSelected(false);
+    subtitleSet->AddType(text);
+
+    SetVisible(subtitleSet, 0);
+}   
+
+void OSD::ClearTextSubtitles() 
+{
+    HideSet("subtitles");
+    ClearAll("subtitles");
+}
+
 bool OSD::InitMenu(void)
 {
     if (GetSet("menu"))
@@ -330,9 +416,9 @@
     return true;
 }
 
-bool OSD::InitDVBSub(void)
+bool OSD::InitSubtitles(void)
 {
-    // Create container for subtitles
+    // Create container for subtitles (DVB, DVD and external subs)
     if (GetSet("subtitles"))
         return true;
 
@@ -341,6 +427,7 @@
         new OSDSet(name, true,
                    osdBounds.width(), osdBounds.height(),
                    wmult, hmult, frameint);
+
     container->SetPriority(30);
     AddSet(container, name);
     return true;
@@ -458,6 +545,7 @@
         return font;
 
     delete font;
+    font = NULL;
     fullname = gContext->GetShareDir() + name;
 
     font = new TTFFont((char *)fullname.ascii(), size,
@@ -467,6 +555,7 @@
         return font;
 
     delete font;
+    font = NULL;
     if (themepath != "")
     {
         fullname = themepath + "/" + name;
@@ -477,6 +566,7 @@
     }
 
     delete font;
+    font = NULL;
 
     fullname = name;
     font = new TTFFont((char *)fullname.ascii(), size,
@@ -489,6 +579,7 @@
                                   "No OSD will be displayed.").arg(name));
 
     delete font;
+    font = NULL;
     return NULL;
 }
 
