Permalink
Browse files

Adds mythccextractor, which extracts closed caption and subtitle stre…

…ams from DVB and ATSC recordings.

This is modeled on the functionality of ccextractor, but currently extacts all streams in one go rather than having you select the streams you want. It places text based captions in SRT files and places image based ones in png's one subdirectory for each language.

I've tested that this doesn't break 608, 708 and DVB teletext and subtitle parsing within the player for the test files I have and does successfully extract all caption and subtitle streams.

One deficiency I noticed was that the primary caption/teletext stream seems to have multiple lines added to the SRT files for each piece of text, perhaps this has to do with text being flushed to the screen more often in these files than in secondary streams, but in any case the secondary streams appear to read better.

This will currently only work with completed recordings. My hope is that limitation will be addressed in a future commit.

This is a code contribution from Digital Nirvana, Inc.
  • Loading branch information...
1 parent bfacfb9 commit 48079bbda6e274754e3db72df8aca159d02aeb8e @daniel-kristjansson daniel-kristjansson committed Jul 19, 2011
View
1 mythtv/libs/libmyth/programtypes.cpp
@@ -16,6 +16,7 @@ const char *kFlaggerInUseID = "flagger";
const char *kTranscoderInUseID = "transcoder";
const char *kPreviewGeneratorInUseID = "preview_generator";
const char *kJobQueueInUseID = "jobqueue";
+const char *kCCExtractorInUseID = "ccextractor";
QString toString(MarkTypes type)
{
View
1 mythtv/libs/libmyth/programtypes.h
@@ -31,6 +31,7 @@ MPUBLIC extern const char *kFlaggerInUseID;
MPUBLIC extern const char *kTranscoderInUseID;
MPUBLIC extern const char *kPreviewGeneratorInUseID;
MPUBLIC extern const char *kJobQueueInUseID;
+MPUBLIC extern const char *kCCExtractorInUseID;
typedef QHash<QString,QString> InfoMap;
View
1 mythtv/libs/libmythbase/mythcorecontext.h
@@ -18,6 +18,7 @@
#define MYTH_APPNAME_MYTHTV_SETUP "mythtv-setup"
#define MYTH_APPNAME_MYTHFILLDATABASE "mythfilldatabase"
#define MYTH_APPNAME_MYTHCOMMFLAG "mythcommflag"
+#define MYTH_APPNAME_MYTHCCEXTRACTOR "mythccextractor"
#define MYTH_APPNAME_MYTHPREVIEWGEN "mythpreviewgen"
#define MYTH_APPNAME_MYTHTRANSCODE "mythtranscode"
#define MYTH_APPNAME_MYTHWELCOME "mythwelcome"
View
35 mythtv/libs/libmythtv/avformatdecoder.cpp
@@ -292,7 +292,6 @@ AvFormatDecoder::AvFormatDecoder(MythPlayer *parent,
ccd608(new CC608Decoder(parent->GetCC608Reader())),
ccd708(new CC708Decoder(parent->GetCC708Reader())),
ttd(new TeletextDecoder(parent->GetTeletextReader())),
- subReader(parent->GetSubReader()),
// Interactive TV
itv(NULL),
// Audio
@@ -741,7 +740,10 @@ void AvFormatDecoder::SeekReset(long long newKey, uint skipFrames,
{
GetFrame(kDecodeVideo);
if (decoded_video_frame)
+ {
m_parent->DiscardVideoFrame(decoded_video_frame);
+ decoded_video_frame = NULL;
+ }
}
if (doflush)
@@ -1003,7 +1005,7 @@ int AvFormatDecoder::OpenFile(RingBuffer *rbuffer, bool novideo,
#endif // USING_MHEG
// Try to get a position map from the recorder if we don't have one yet.
- if (!recordingHasPositionMap)
+ if (!recordingHasPositionMap && !is_db_ignored)
{
if ((m_playbackinfo) || livetv || watchingrecording)
{
@@ -3042,7 +3044,13 @@ bool AvFormatDecoder::ProcessVideoFrame(AVStream *stream, AVFrame *mpa_pic)
VideoFrame *picframe = (VideoFrame *)(mpa_pic->opaque);
- if (!directrendering)
+ if (special_decode & kAVSpecialDecode_NoDecode)
+ {
+ // Do nothing, we just want the pts, captions, subtites, etc.
+ // So we can release the unconverted blank video frame to the
+ // display queue.
+ }
+ else if (!directrendering)
{
AVPicture tmppicture;
@@ -3301,7 +3309,7 @@ void AvFormatDecoder::ProcessDSMCCPacket(
bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt)
{
- if (!subReader)
+ if (!m_parent->GetSubReader(pkt->stream_index))
return true;
long long pts = 0;
@@ -3334,7 +3342,7 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt)
}
}
}
- else if (pkt->stream_index == subIdx)
+ else if (decodeAllSubtitles || pkt->stream_index == subIdx)
{
QMutexLocker locker(avcodeclock);
avcodec_decode_subtitle2(curstream->codec, &subtitle, &gotSubtitles,
@@ -3351,30 +3359,33 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt)
.arg(subtitle.start_display_time)
.arg(subtitle.end_display_time));
- if (subReader)
- {
- subReader->AddAVSubtitle(subtitle,
- curstream->codec->codec_id == CODEC_ID_XSUB);
- }
+ m_parent->GetSubReader(pkt->stream_index)->AddAVSubtitle(
+ subtitle, curstream->codec->codec_id == CODEC_ID_XSUB);
}
return true;
}
bool AvFormatDecoder::ProcessRawTextPacket(AVPacket *pkt)
{
- if (!subReader ||
+ if (!decodeAllSubtitles ||
selectedTrack[kTrackTypeRawText].av_stream_index != pkt->stream_index)
{
return false;
}
+ if (!m_parent->GetSubReader(pkt->stream_index+0x2000))
+ return false;
+
QTextCodec *codec = QTextCodec::codecForName("utf-8");
QTextDecoder *dec = codec->makeDecoder();
QString text = dec->toUnicode((const char*)pkt->data, pkt->size);
QStringList list = text.split('\n', QString::SkipEmptyParts);
delete dec;
- subReader->AddRawTextSubtitle(list, pkt->convergence_duration);
+
+ m_parent->GetSubReader(pkt->stream_index+0x2000)->
+ AddRawTextSubtitle(list, pkt->convergence_duration);
+
return true;
}
View
1 mythtv/libs/libmythtv/avformatdecoder.h
@@ -314,7 +314,6 @@ class AvFormatDecoder : public DecoderBase
CC608Decoder *ccd608;
CC708Decoder *ccd708;
TeletextDecoder *ttd;
- SubtitleReader *subReader;
int cc608_parity_table[256];
/// Lookup table for whether a stream was seen in the PMT
/// entries 0-3 correspond to CEA-608 CC1 through CC4, while
View
173 mythtv/libs/libmythtv/cc608reader.cpp
@@ -2,14 +2,16 @@ extern "C" {
#include "vbitext/vbi.h"
}
+#include <algorithm>
+using namespace std;
+
#include "mythplayer.h"
#include "cc608reader.h"
CC608Reader::CC608Reader(MythPlayer *parent)
: m_parent(parent), m_enabled(false), m_readPosition(0),
m_writePosition(0), m_maxTextSize(0), m_ccMode(CC_CC1),
- m_ccPageNum(0x888), m_outputText(""), m_outputCol(0),
- m_outputRow(0), m_changed(true)
+ m_ccPageNum(0x888)
{
memset(&m_inputBuffers, 0, sizeof(m_inputBuffers));
m_maxTextSize = 8 * (sizeof(teletextsubtitle) + VT_WIDTH);
@@ -36,8 +38,28 @@ void CC608Reader::FlushTxtBuffers(void)
m_readPosition = m_writePosition;
}
-CC608Buffer* CC608Reader::GetOutputText(bool &changed)
+CC608Buffer *CC608Reader::GetOutputText(bool &changed)
+{
+ bool last_changed = true;
+ while (last_changed)
+ {
+ last_changed = false;
+ int streamIdx = -1;
+ CC608Buffer *tmp = GetOutputText(last_changed, streamIdx);
+ if (last_changed && (streamIdx == m_ccMode))
+ {
+ changed = true;
+ return tmp;
+ }
+ }
+
+ return NULL;
+}
+
+CC608Buffer *CC608Reader::GetOutputText(bool &changed, int &streamIdx)
{
+ streamIdx = -1;
+
if (!m_enabled || !m_parent)
return NULL;
@@ -50,6 +72,8 @@ CC608Buffer* CC608Reader::GetOutputText(bool &changed)
{
if (m_inputBuffers[m_writePosition].type == 'T')
{
+ streamIdx = MAXOUTBUFFERS - 1;
+
// display full page of teletext
//
// all formatting is always defined in the page itself,
@@ -65,7 +89,7 @@ CC608Buffer* CC608Reader::GetOutputText(bool &changed)
if (pagenr == (m_ccPageNum<<16))
{
// show teletext subtitles
- ClearBuffers(false, true);
+ ClearBuffers(false, true, streamIdx);
(*inpos)++;
while (*inpos)
{
@@ -75,17 +99,19 @@ CC608Buffer* CC608Reader::GetOutputText(bool &changed)
CC608Text *cc = new CC608Text(QString((const char*) inpos),
st.row, st.col, st.fg, true);
- m_outputBuffers.lock.lock();
- m_outputBuffers.buffers.push_back(cc);
- m_outputBuffers.lock.unlock();
+
+ m_state[streamIdx].m_output.lock.lock();
+ m_state[streamIdx].m_output.buffers.push_back(cc);
+ m_state[streamIdx].m_output.lock.unlock();
+
inpos += st.len;
}
changed = true;
}
}
else if (m_inputBuffers[m_writePosition].type == 'C')
{
- Update(m_inputBuffers[m_writePosition].buffer);
+ streamIdx = Update(m_inputBuffers[m_writePosition].buffer);
changed = true;
}
@@ -94,29 +120,35 @@ CC608Buffer* CC608Reader::GetOutputText(bool &changed)
m_writePosition = (m_writePosition + 1) % MAXTBUFFER;
}
- m_changed = false;
- return &m_outputBuffers;
+ if (streamIdx >= 0)
+ {
+ m_state[streamIdx].m_changed = false;
+ return &m_state[streamIdx].m_output;
+ }
+ else
+ {
+ return &m_state[MAXOUTBUFFERS - 1].m_output;
+ }
}
void CC608Reader::SetMode(int mode)
{
- int oldmode = m_ccMode;
+ // TODO why was the clearing code removed?
+ //int oldmode = m_ccMode;
m_ccMode = (mode <= 2) ? ((mode == 1) ? CC_CC1 : CC_CC2) :
((mode == 3) ? CC_CC3 : CC_CC4);
- if (oldmode != m_ccMode)
- ClearBuffers(true, true);
+ //if (oldmode != m_ccMode)
+ // ClearBuffers(true, true);
}
-void CC608Reader::Update(unsigned char *inpos)
+int CC608Reader::Update(unsigned char *inpos)
{
struct ccsubtitle subtitle;
memcpy(&subtitle, inpos, sizeof(subtitle));
inpos += sizeof(ccsubtitle);
- // skip undisplayed streams
- if ((subtitle.resumetext & CC_MODE_MASK) != m_ccMode)
- return;
+ const int streamIdx = (subtitle.resumetext & CC_MODE_MASK) >> 4;
if (subtitle.row == 0)
subtitle.row = 1;
@@ -126,9 +158,9 @@ void CC608Reader::Update(unsigned char *inpos)
#if 0
LOG(VB_GENERAL, LOG_DEBUG, "erase displayed memory");
#endif
- ClearBuffers(false, true);
+ ClearBuffers(false, true, streamIdx);
if (!subtitle.len)
- return;
+ return streamIdx;
}
// if (subtitle.len || !subtitle.clr)
@@ -160,22 +192,26 @@ void CC608Reader::Update(unsigned char *inpos)
inpos++;
}
if (bscnt)
- m_outputText.remove(m_outputText.length() - bscnt, bscnt);
+ {
+ m_state[streamIdx].m_outputText.remove(
+ m_state[streamIdx].m_outputText.length() - bscnt,
+ bscnt);
+ }
}
else
{
// new line: count spaces to calculate column position
row++;
- m_outputCol = 0;
- m_outputText = "";
+ m_state[streamIdx].m_outputCol = 0;
+ m_state[streamIdx].m_outputText = "";
while ((inpos < end) && *inpos != 0 && (char)*inpos == ' ')
{
inpos++;
- m_outputCol++;
+ m_state[streamIdx].m_outputCol++;
}
}
- m_outputRow = subtitle.row;
+ m_state[streamIdx].m_outputRow = subtitle.row;
unsigned char *cur = inpos;
//null terminate at EOL
@@ -186,11 +222,19 @@ void CC608Reader::Update(unsigned char *inpos)
if (*inpos != 0 || linecont)
{
if (linecont)
- m_outputText += QString::fromUtf8((const char *)inpos, -1);
+ {
+ m_state[streamIdx].m_outputText +=
+ QString::fromUtf8((const char *)inpos, -1);
+ }
else
- m_outputText = QString::fromUtf8((const char *)inpos, -1);
- tmpcc = new CC608Text(m_outputText, m_outputCol, m_outputRow,
- 0, false);
+ {
+ m_state[streamIdx].m_outputText =
+ QString::fromUtf8((const char *)inpos, -1);
+ }
+ tmpcc = new CC608Text(
+ m_state[streamIdx].m_outputText,
+ m_state[streamIdx].m_outputCol,
+ m_state[streamIdx].m_outputRow, 0, false);
ccbuf->push_back(tmpcc);
#if 0
if (ccbuf->size() > 4)
@@ -210,10 +254,10 @@ void CC608Reader::Update(unsigned char *inpos)
// TXT mode
// - can use entire 15 rows
// - scroll up when reaching bottom
- if (m_outputRow > 15)
+ if (m_state[streamIdx].m_outputRow > 15)
{
if (row)
- scroll = m_outputRow - 15;
+ scroll = m_state[streamIdx].m_outputRow - 15;
if (tmpcc)
tmpcc->y = 15;
}
@@ -222,13 +266,13 @@ void CC608Reader::Update(unsigned char *inpos)
{
// multi-line text
// - fix display of old (badly-encoded) files
- if (m_outputRow > 15)
+ if (m_state[streamIdx].m_outputRow > 15)
{
ccp = ccbuf->begin();
for (; ccp != ccbuf->end(); ccp++)
{
tmpcc = *ccp;
- tmpcc->y -= (m_outputRow - 15);
+ tmpcc->y -= (m_state[streamIdx].m_outputRow - 15);
}
}
}
@@ -241,25 +285,28 @@ void CC608Reader::Update(unsigned char *inpos)
// - if caption is at top, row address is for first row (?)
if (subtitle.rowcount > 4)
subtitle.rowcount = 4;
- if (m_outputRow < subtitle.rowcount)
+ if (m_state[streamIdx].m_outputRow < subtitle.rowcount)
{
- m_outputRow = subtitle.rowcount;
+ m_state[streamIdx].m_outputRow = subtitle.rowcount;
if (tmpcc)
- tmpcc->y = m_outputRow;
+ tmpcc->y = m_state[streamIdx].m_outputRow;
}
if (row)
{
scroll = row;
scroll_prsv = true;
- scroll_yoff = m_outputRow - subtitle.rowcount;
- scroll_ymax = m_outputRow;
+ scroll_yoff =
+ m_state[streamIdx].m_outputRow - subtitle.rowcount;
+ scroll_ymax = m_state[streamIdx].m_outputRow;
}
}
Update608Text(ccbuf, replace, scroll,
- scroll_prsv, scroll_yoff, scroll_ymax);
+ scroll_prsv, scroll_yoff, scroll_ymax, streamIdx);
delete ccbuf;
}
+
+ return streamIdx;
}
void CC608Reader::TranscodeWriteText(void (*func)
@@ -286,9 +333,9 @@ void CC608Reader::TranscodeWriteText(void (*func)
}
}
-void CC608Reader::Update608Text(vector<CC608Text*> *ccbuf,
- int replace, int scroll, bool scroll_prsv,
- int scroll_yoff, int scroll_ymax)
+void CC608Reader::Update608Text(
+ vector<CC608Text*> *ccbuf, int replace, int scroll, bool scroll_prsv,
+ int scroll_yoff, int scroll_ymax, int streamIdx)
// ccbuf : new text
// replace : replace last lines
// scroll : scroll amount
@@ -299,14 +346,14 @@ void CC608Reader::Update608Text(vector<CC608Text*> *ccbuf,
vector<CC608Text*>::iterator i;
int visible = 0;
- m_outputBuffers.lock.lock();
- if (m_outputBuffers.buffers.size() && (scroll || replace))
+ m_state[streamIdx].m_output.lock.lock();
+ if (m_state[streamIdx].m_output.buffers.size() && (scroll || replace))
{
CC608Text *cc;
// get last row
int ylast = 0;
- i = m_outputBuffers.buffers.end() - 1;
+ i = m_state[streamIdx].m_output.buffers.end() - 1;
cc = *i;
if (cc)
ylast = cc->y;
@@ -322,21 +369,21 @@ void CC608Reader::Update608Text(vector<CC608Text*> *ccbuf,
ykeep += ymove;
}
- i = m_outputBuffers.buffers.begin();
- while (i < m_outputBuffers.buffers.end())
+ i = m_state[streamIdx].m_output.buffers.begin();
+ while (i < m_state[streamIdx].m_output.buffers.end())
{
cc = (*i);
if (!cc)
{
- i = m_outputBuffers.buffers.erase(i);
+ i = m_state[streamIdx].m_output.buffers.erase(i);
continue;
}
if (cc->y > (ylast - replace))
{
// delete last lines
delete cc;
- i = m_outputBuffers.buffers.erase(i);
+ i = m_state[streamIdx].m_output.buffers.erase(i);
}
else if (scroll)
{
@@ -349,7 +396,7 @@ void CC608Reader::Update608Text(vector<CC608Text*> *ccbuf,
else
{
// delete lines outside scroll window
- i = m_outputBuffers.buffers.erase(i);
+ i = m_state[streamIdx].m_output.buffers.erase(i);
delete cc;
}
}
@@ -358,7 +405,7 @@ void CC608Reader::Update608Text(vector<CC608Text*> *ccbuf,
}
}
- visible += m_outputBuffers.buffers.size();
+ visible += m_state[streamIdx].m_output.buffers.size();
if (ccbuf)
{
@@ -369,18 +416,17 @@ void CC608Reader::Update608Text(vector<CC608Text*> *ccbuf,
if (*i)
{
visible++;
- m_outputBuffers.buffers.push_back(*i);
+ m_state[streamIdx].m_output.buffers.push_back(*i);
}
i++;
}
}
- m_changed = visible;
- m_outputBuffers.lock.unlock();
+ m_state[streamIdx].m_changed = visible;
+ m_state[streamIdx].m_output.lock.unlock();
}
-void CC608Reader::ClearBuffers(bool input, bool output)
+void CC608Reader::ClearBuffers(bool input, bool output, int outputStreamIdx)
{
-
if (input)
{
for (int i = 0; i < MAXTBUFFER; i++)
@@ -395,13 +441,16 @@ void CC608Reader::ClearBuffers(bool input, bool output)
m_writePosition = 0;
}
- if (output)
+ if (output && outputStreamIdx < 0)
+ {
+ for (int i = 0; i < MAXOUTBUFFERS; ++i)
+ m_state[i].Clear();
+ }
+
+ if (output && outputStreamIdx >= 0)
{
- m_outputText = "";
- m_outputCol = 0;
- m_outputRow = 0;
- m_outputBuffers.Clear();
- m_changed = true;
+ outputStreamIdx = min(outputStreamIdx, MAXOUTBUFFERS - 1);
+ m_state[outputStreamIdx].Clear();
}
}
@@ -432,7 +481,7 @@ void CC608Reader::AddTextData(unsigned char *buffer, int len,
if (!m_enabled)
return;
- if (!(MAXTBUFFER - NumInputBuffers() - 1))
+ if (NumInputBuffers() >= MAXTBUFFER - 1)
{
LOG(VB_GENERAL, LOG_ERR, "AddTextData(): Text buffer overflow");
return;
View
39 mythtv/libs/libmythtv/cc608reader.h
@@ -10,6 +10,7 @@
#include "mythtvexp.h"
#define MAXTBUFFER 60
+#define MAXOUTBUFFERS (16 + 1)
class CC608Text
{
@@ -53,6 +54,30 @@ class CC608Buffer
vector<CC608Text*> buffers;
};
+class CC608StateTracker
+{
+ public:
+ CC608StateTracker() :
+ m_outputText(""), m_outputCol(0), m_outputRow(0), m_changed(true)
+ {
+ }
+
+ void Clear(void)
+ {
+ m_outputText = "";
+ m_outputCol = 0;
+ m_outputRow = 0;
+ m_changed = true;
+ m_output.Clear();
+ }
+
+ QString m_outputText;
+ int m_outputCol;
+ int m_outputRow;
+ bool m_changed;
+ CC608Buffer m_output;
+};
+
class MythPlayer;
class MTV_PUBLIC CC608Reader : public CC608Input
@@ -64,21 +89,23 @@ class MTV_PUBLIC CC608Reader : public CC608Input
void SetTTPageNum(int page) { m_ccPageNum = page; }
void SetEnabled(bool enable) { m_enabled = enable; }
void FlushTxtBuffers(void);
+ CC608Buffer *GetOutputText(bool &changed, int &streamIdx);
CC608Buffer* GetOutputText(bool &changed);
void SetMode(int mode);
- void ClearBuffers(bool input, bool output);
+ void ClearBuffers(bool input, bool output, int outputStreamIdx = -1);
void AddTextData(unsigned char *buf, int len,
int64_t timecode, char type);
void TranscodeWriteText(void (*func)
(void *, unsigned char *, int, int, int),
void * ptr);
private:
- void Update(unsigned char *inpos);
+ int Update(unsigned char *inpos);
void Update608Text(vector<CC608Text*> *ccbuf,
int replace = 0, int scroll = 0,
bool scroll_prsv = false,
- int scroll_yoff = 0, int scroll_ymax = 15);
+ int scroll_yoff = 0, int scroll_ymax = 15,
+ int streamIdx = CC_CC1);
int NumInputBuffers(bool need_to_lock = true);
MythPlayer *m_parent;
@@ -92,11 +119,7 @@ class MTV_PUBLIC CC608Reader : public CC608Input
int m_ccMode;
int m_ccPageNum;
// Output buffers
- QString m_outputText;
- int m_outputCol;
- int m_outputRow;
- bool m_changed;
- CC608Buffer m_outputBuffers;
+ CC608StateTracker m_state[MAXOUTBUFFERS];
};
#endif // CC608READER_H
View
4 mythtv/libs/libmythtv/cc708window.h
@@ -11,6 +11,8 @@ using namespace std;
#include <QMutex>
#include <QColor>
+#include "mythtvexp.h"
+
class CC708CharacterAttribute
{
public:
@@ -113,7 +115,7 @@ class CC708String
CC708CharacterAttribute attr;
};
-class CC708Window
+class MTV_PUBLIC CC708Window
{
public:
CC708Window();
View
1 mythtv/libs/libmythtv/decoderbase.cpp
@@ -44,6 +44,7 @@ DecoderBase::DecoderBase(MythPlayer *parent, const ProgramInfo &pginfo)
getrawframes(false), getrawvideo(false),
errored(false), waitingForChange(false), readAdjust(0),
justAfterChange(false),
+ decodeAllSubtitles(false),
// language preference
languagePreference(iso639_get_language_key_list())
{
View
2 mythtv/libs/libmythtv/decoderbase.h
@@ -176,6 +176,7 @@ class DecoderBase
void UpdateBDFramesPlayed(void);
// Audio/Subtitle/EIA-608/EIA-708 stream selection
+ void SetDecodeAllSubtitles(bool val) { decodeAllSubtitles = val; }
virtual QStringList GetTracks(uint type) const;
virtual uint GetTrackCount(uint type) const
{ return tracks[type].size(); }
@@ -276,6 +277,7 @@ class DecoderBase
bool justAfterChange;
// Audio/Subtitle/EIA-608/EIA-708 stream selection
+ bool decodeAllSubtitles;
int currentTrack[kTrackTypeCount];
sinfo_vec_t tracks[kTrackTypeCount];
StreamInfo wantedTrack[kTrackTypeCount];
View
2 mythtv/libs/libmythtv/libmythtv.pro
@@ -263,13 +263,15 @@ using_frontend {
# Video playback
HEADERS += tv_play.h mythplayer.h
HEADERS += mythdvdplayer.h audioplayer.h
+ HEADERS += mythccextractorplayer.h teletextextractorreader.h
HEADERS += playercontext.h
HEADERS += tv_play_win.h deletemap.h
HEADERS += mythcommflagplayer.h commbreakmap.h
HEADERS += mythbdplayer.h
HEADERS += mythiowrapper.h tvbrowsehelper.h
SOURCES += tv_play.cpp mythplayer.cpp
SOURCES += mythdvdplayer.cpp audioplayer.cpp
+ SOURCES += mythccextractorplayer.cpp teletextextractorreader.cpp
SOURCES += playercontext.cpp
SOURCES += tv_play_win.cpp deletemap.cpp
SOURCES += mythcommflagplayer.cpp commbreakmap.cpp
View
714 mythtv/libs/libmythtv/mythccextractorplayer.cpp
@@ -0,0 +1,714 @@
+// -*- Mode: c++ -*-
+
+/*
+ * Class MythCCExtractorPlayer
+ *
+ * Copyright (C) Digital Nirvana, Inc. 2010
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <iostream>
+using namespace std;
+
+#include <QPainter>
+
+#include "mythccextractorplayer.h"
+
+MythCCExtractorPlayer::~MythCCExtractorPlayer()
+{
+ QHash<uint, CC708Reader*>::iterator it7 = cc708.begin();
+ for (; it7 != cc708.end(); ++it7)
+ delete *it7;
+ cc708.clear();
+ //
+ QHash<uint, CC608Reader*>::iterator it6 = cc608.begin();
+ for (; it6 != cc608.end(); ++it6)
+ delete *it6;
+ cc608.clear();
+ //
+ QHash<uint, SubtitleReader*>::iterator its = subReader.begin();
+ for (; its != subReader.end(); ++its)
+ delete *its;
+ subReader.clear();
+ //
+ QHash<uint, TeletextExtractorReader *>::iterator itt = ttxReader.begin();
+ for (; itt != ttxReader.end(); ++itt)
+ delete *itt;
+ ttxReader.clear();
+}
+
+/**
+ * Call it when you got new video frame to process subtitles if any.
+ */
+
+void MythCCExtractorPlayer::OnGotNewFrame(void)
+{
+ m_myFramesPlayed = decoder->GetFramesRead();
+
+ videoOutput->StartDisplayingFrame();
+ VideoFrame *frame = videoOutput->GetLastShownFrame();
+ videoOutput->DoneDisplayingFrame(frame);
+
+ if (m_curTimeShift < 0)
+ m_curTimeShift = frame->timecode;
+
+ m_curTime = frame->timecode - m_curTimeShift;
+
+ ProcessTTXSubtitles();
+ Process608Subtitles();
+ Process708Subtitles();
+ ProcessDVBSubtitles();
+}
+
+/**
+ * Call it when you finished reading of video to finish subtitles processing.
+ */
+
+void MythCCExtractorPlayer::OnDecodingFinished(void)
+{
+ FinishTTXSubtitlesProcess();
+ Finish608SubtitlesProcess();
+ Finish708SubtitlesProcess();
+ FinishDVBSubtitlesProcess();
+}
+
+static QString progress_string(
+ MythTimer &flagTime, uint64_t m_myFramesPlayed, uint64_t totalFrames)
+{
+ if (totalFrames == 0ULL)
+ {
+ return QString("%1 frames processed \r")
+ .arg(m_myFramesPlayed,7);
+ }
+
+ double elapsed = flagTime.elapsed() * 0.001;
+ double flagFPS = (elapsed > 0.0) ? (m_myFramesPlayed / elapsed) : 0;
+
+ double percentage = m_myFramesPlayed * 100.0 / totalFrames;
+ percentage = (percentage > 100.0 && percentage < 101.0) ?
+ 100.0 : percentage;
+
+ if (flagFPS < 10.0)
+ {
+ return QString("%1 fps %2% \r")
+ .arg(flagFPS,4,'f',1).arg(percentage,4,'f',1);
+ }
+ else
+ {
+ return QString("%1 fps %2% \r")
+ .arg(flagFPS,4,'f',0).arg(percentage,4,'f',1);
+ }
+}
+
+bool MythCCExtractorPlayer::run(void)
+{
+ m_myFramesPlayed = 0;
+
+ killdecoder = false;
+ framesPlayed = 0;
+ using_null_videoout = true;
+
+ if (OpenFile() < 0)
+ return false;
+
+ decoder->SetDecodeAllSubtitles(true);
+
+ SetPlaying(true);
+
+ if (!InitVideo())
+ {
+ LOG(VB_GENERAL, LOG_ERR, "Unable to initialize video");
+ SetPlaying(false);
+ return false;
+ }
+
+ ClearAfterSeek();
+
+ MythTimer flagTime, ui_timer, inuse_timer, save_timer;
+ flagTime.start();
+ ui_timer.start();
+ inuse_timer.start();
+ save_timer.start();
+
+ m_curTime = 0;
+
+ if (DecoderGetFrame(kDecodeVideo))
+ OnGotNewFrame();
+
+ if (m_showProgress)
+ cout << "\r \r" << flush;
+
+ while (!GetEof())
+ {
+ if (inuse_timer.elapsed() > 2534)
+ {
+ inuse_timer.restart();
+ player_ctx->LockPlayingInfo(__FILE__, __LINE__);
+ if (player_ctx->playingInfo)
+ player_ctx->playingInfo->UpdateInUseMark();
+ player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
+ }
+
+ if (m_showProgress && (ui_timer.elapsed() > 98 * 4))
+ {
+ ui_timer.restart();
+ QString str = progress_string(
+ flagTime, m_myFramesPlayed, totalFrames);
+ cout << qPrintable(str) << '\r' << flush;
+ }
+
+ if (DecoderGetFrame(kDecodeVideo))
+ {
+ OnGotNewFrame();
+ }
+ }
+
+ if (m_showProgress)
+ {
+ if ((m_myFramesPlayed < totalFrames) &&
+ ((m_myFramesPlayed + 30) > totalFrames))
+ {
+ m_myFramesPlayed = totalFrames;
+ }
+ QString str = progress_string(flagTime, m_myFramesPlayed, totalFrames);
+ cout << qPrintable(str) << endl;
+ }
+
+ OnDecodingFinished();
+
+ SetPlaying(false);
+ killdecoder = true;
+
+ return true;
+}
+
+/**
+ * Processes a new subtitle.
+ */
+
+void MythCCExtractorPlayer::GotNew608Subtitle(uint streamId, int ccIdx,
+ CC608Buffer *subtitles)
+{
+ QStringList content;
+ for (int i = 0; i < (int) subtitles->buffers.size(); ++i)
+ {
+ content << subtitles->buffers[i]->text;
+ }
+
+ // Some workaround to eliminate 'HE' in English subtitles.
+ if (content.size() > 0 && content.first() == "HE")
+ content.removeFirst();
+
+ QList<OneSubtitle> &list = cc608Subtitles[streamId][ccIdx];
+
+ ProcessNewSubtitle(content, list);
+}
+
+/**
+ * Processes new subtitles if any from all CC608 streams.
+ */
+
+void MythCCExtractorPlayer::Process608Subtitles(void)
+{
+ foreach (uint streamId, GetCC608StreamsList())
+ {
+ const int ccStreams[] = {
+ CC_CC1 >> 4, CC_CC2 >> 4, CC_CC3 >> 4, CC_CC4 >> 4
+ };
+ CC608Reader *subReader = GetCC608Reader(streamId);
+
+ while (true)
+ {
+ bool changed = false;
+ int streamRawIdx = -1;
+ CC608Buffer *subtitles = subReader->GetOutputText(
+ changed, streamRawIdx);
+
+ if (!changed)
+ break;
+
+ const int ccIdx = distance(
+ ccStreams, find(ccStreams, ccStreams + 4, streamRawIdx));
+
+ if (ccIdx >= 4)
+ continue;
+
+ GotNew608Subtitle(streamId, ccIdx, subtitles);
+ }
+ }
+}
+
+/**
+ * Checks for latest teletext changes.
+ */
+
+void MythCCExtractorPlayer::ProcessTTXSubtitles(void)
+{
+ foreach (uint stream_id, GetTTXStreamsList())
+ {
+ TeletextExtractorReader *reader = ttxReader.value(stream_id, NULL);
+
+ if (reader && !reader->GetUpdatedPages().isEmpty())
+ {
+ typedef QPair<int, int> qpii;
+ foreach (qpii subpageIdc, reader->GetUpdatedPages())
+ {
+ reader->SetPage(subpageIdc.first, subpageIdc.second);
+ TeletextSubPage *subPage = reader->FindSubPage();
+
+ if (subPage)
+ {
+ subPage->pagenum = subpageIdc.first; //just in case
+ GotNewTTXPage(stream_id, *subPage);
+ }
+ }
+ }
+
+ if (reader)
+ reader->ClearUpdatedPages();
+ }
+}
+
+/**
+ * Process last cc608 changes (last in video).
+ * Call it after video will be finished.
+ */
+
+void MythCCExtractorPlayer::Finish608SubtitlesProcess(void)
+{
+ foreach (uint stream_id, GetCC608StreamsList())
+ {
+ CC608Reader *reader = cc608.value(stream_id, NULL);
+
+ if (reader)
+ {
+ foreach (int ccIdx, GetCC608Subtitles().keys())
+ {
+ CC608Buffer subtitles;
+ GotNew608Subtitle(stream_id, ccIdx, &subtitles);
+ }
+ }
+ }
+}
+
+/**
+ * Process last teletext changes (last in video).
+ * Call it after video will be finished.
+ */
+
+void MythCCExtractorPlayer::FinishTTXSubtitlesProcess(void)
+{
+ foreach (uint stream_id, GetTTXStreamsList())
+ {
+ TeletextExtractorReader *reader = ttxReader.value(stream_id, NULL);
+
+ if (reader)
+ {
+ foreach (int page, GetTTXSubtitles().keys())
+ {
+ TeletextSubPage subPage;
+ memset(&subPage, 0, sizeof(subPage));
+ subPage.pagenum = page;
+ GotNewTTXPage(stream_id, subPage);
+ }
+ }
+ }
+}
+
+/**
+ * Adds new subtitle, finishes last if needed.
+ * @param content Text content of new subtitle (may be empty).
+ * @param list Queue of subtitles we modify.
+ */
+
+void MythCCExtractorPlayer::ProcessNewSubtitle(QStringList content,
+ QList<OneSubtitle> &list)
+{
+ OneSubtitle last_one = list.isEmpty() ? OneSubtitle() : list.back();
+
+ bool update_last = !list.isEmpty()
+ && m_curTime == last_one.start_time
+ && !content.isEmpty();
+
+ if (update_last)
+ {
+ //update text only (need for cc608)
+ list.back().text = content;
+
+ }
+ else if (content != last_one.text || last_one.length >= 0)
+ {
+ // Finish previous subtitle.
+ if (!last_one.text.isEmpty() && last_one.length < 0)
+ {
+ list.back().length = m_curTime - last_one.start_time;
+ }
+
+ // Put new one if it isn't empty.
+ if (!content.isEmpty())
+ {
+ OneSubtitle new_one;
+ new_one.start_time = m_curTime;
+ new_one.text = content;
+
+ list.push_back(new_one);
+ }
+ }
+}
+
+/**
+ * Adds new subtitle, finishes last if needed.
+ * This is a version for DVB graphic subtitles only.
+ * @param content Content of the new subtitle (may be empty).
+ * We're going to use it's img & start_time fields.
+ * @param list Queue of subtitles we modify.
+ */
+
+void MythCCExtractorPlayer::ProcessNewSubtitle(OneSubtitle content,
+ QList<OneSubtitle> &list)
+{
+ OneSubtitle last_one = list.isEmpty() ? OneSubtitle() : list.back();
+
+ bool update_last = !list.isEmpty() &&
+ content.start_time == last_one.start_time &&
+ !content.img.isNull();
+
+ if (update_last)
+ {
+ //update image only
+ list.back().img = content.img;
+ }
+ else if (content.img != last_one.img || last_one.length >= 0)
+ {
+ // Finish previous subtitle.
+ if (!last_one.img.isNull() && last_one.length < 0)
+ {
+ list.back().length = content.start_time - last_one.start_time;
+ }
+
+ // Put new one if it isn't empty.
+ if (!content.img.isNull())
+ {
+ OneSubtitle new_one;
+ new_one.start_time = content.start_time;
+ new_one.img = content.img;
+
+ list.push_back(new_one);
+ }
+ }
+}
+
+/**
+ * Puts new page to appropriate collection, organizes stream of pages to
+ * the stream of subtitles in ttxSubtitles.
+ * Note: be sure that subPage.pagenum is set properly.
+ *
+ * @param stream_id Id of teletext stream where subPage from.
+ * @param subPage Subpage of teletext we got.
+ */
+
+void MythCCExtractorPlayer::GotNewTTXPage(
+ uint stream_id, const TeletextSubPage &subPage)
+{
+ if (!subPage.subtitle)
+ return;
+
+ QStringList content = ConvertTTXPage(subPage);
+ QList<OneSubtitle> &list = ttxSubtitles[stream_id][subPage.pagenum];
+ ProcessNewSubtitle(content, list);
+}
+
+/**
+ * Extracts text subtitles from the teletext page.
+ */
+
+QStringList MythCCExtractorPlayer::ConvertTTXPage(
+ const TeletextSubPage &subPage)
+{
+ QStringList content;
+
+ for (int i = 0; i < 25; ++i)
+ {
+ QString str = decode_teletext(subPage.lang, subPage.data[i]);
+ str = str.trimmed();
+
+ if (!str.isEmpty())
+ {
+ content << str;
+ }
+ }
+
+ return content;
+}
+
+/**
+ * Processes new subtitles if any from all CC708 streams.
+ */
+
+void MythCCExtractorPlayer::Process708Subtitles(void)
+{
+ foreach (uint streamId, GetCC708StreamsList())
+ {
+ CC708Reader *subReader = GetCC708Reader(streamId);
+
+ for (int serviceIdx = 1; serviceIdx < 64; ++serviceIdx)
+ {
+ CC708Service *service = subReader->GetService(serviceIdx);
+ for (int windowIdx = 0; windowIdx < 8; ++windowIdx)
+ {
+ CC708Window &win = service->windows[windowIdx];
+ if (win.changed && win.visible)
+ {
+ vector<CC708String *> strings = win.GetStrings();
+
+ GotNew708Subtitle(streamId, serviceIdx, windowIdx, strings);
+
+ service->windows[windowIdx].changed = false;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Finishes pending CC608 subtitles.
+ */
+
+void MythCCExtractorPlayer::Finish708SubtitlesProcess(void)
+{
+ foreach (uint streamId, GetCC708StreamsList())
+ {
+ //CC708Reader *subReader = GetCC708Reader(streamId);
+
+ for (int serviceIdx = 1; serviceIdx < 64; ++serviceIdx)
+ {
+ //CC708Service *service = subReader->GetService(serviceIdx);
+ for (int windowIdx = 0; windowIdx < 8; ++windowIdx)
+ {
+ //if (service->windows[windowIdx].changed)
+ {
+ vector<CC708String *> strings; // Empty content.
+ GotNew708Subtitle(streamId, serviceIdx, windowIdx, strings);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Processes new CC708 subtitle.
+ */
+
+void MythCCExtractorPlayer::GotNew708Subtitle(
+ uint streamId, int serviceIdx,
+ int windowIdx, const vector<CC708String *> &content)
+{
+ bool empty = true;
+ QStringList myContent;
+ foreach (CC708String *str, content)
+ {
+ empty = empty && str->str.trimmed().isEmpty();
+
+ QString t = str->str.trimmed();
+ if (!t.isEmpty())
+ myContent << t;
+ }
+
+ cc708windows[qMakePair(streamId, serviceIdx)][windowIdx] = myContent;
+ QStringList myContent2;
+ foreach (QStringList list, cc708windows[qMakePair(streamId, serviceIdx)])
+ {
+ myContent2.append(list);
+ }
+
+ QList<OneSubtitle> &list = cc708Subtitles[streamId][serviceIdx];
+
+ ProcessNewSubtitle(myContent2, list);
+}
+
+/**
+ * Processes new subtitles if any from all dbv streams.
+ */
+
+void MythCCExtractorPlayer::ProcessDVBSubtitles(void)
+{
+ foreach (uint streamId, GetSubtitleStreamsList())
+ {
+ SubtitleReader *subReader = GetSubReader(streamId);
+ OneSubtitle cur_sub;
+
+ if (subReader->HasTextSubtitles())
+ {
+ LOG(VB_VBI, LOG_DEBUG,
+ "There are unhandled text dvb subtitles");
+ }
+
+ uint64_t duration;
+ const QStringList rawSubs = subReader->GetRawTextSubtitles(duration);
+ if (!rawSubs.isEmpty())
+ {
+ LOG(VB_VBI, LOG_DEBUG,
+ QString("There are also %1 raw text subtitles with duration %2")
+ .arg(rawSubs.size()).arg(duration));
+ }
+
+ AVSubtitles *subtitles = subReader->GetAVSubtitles();
+ foreach (const AVSubtitle &buf, subtitles->buffers)
+ {
+ const QSize v_size = GetVideoSize();
+ QImage sub_pict(v_size, QImage::Format_ARGB32);
+ sub_pict.fill(0);
+
+ int min_x = v_size.width();
+ int min_y = v_size.height();
+ int max_x = 0;
+ int max_y = 0;
+
+ QPainter painter(&sub_pict);
+
+ for (int i = 0; i < (int) buf.num_rects; ++i)
+ {
+ AVSubtitleRect *rect = buf.rects[i];
+
+ if (buf.rects[i]->type == SUBTITLE_BITMAP)
+ {
+ const int x = rect->x;
+ const int y = rect->y;
+ const int w = rect->w;
+ const int h = rect->h;
+ const int cc = rect->nb_colors;
+ const uchar *data = rect->pict.data[0];
+ const QRgb *palette = (QRgb *) rect->pict.data[1];
+
+ QImage img(data, w, h, QImage::Format_Indexed8);
+ img.setColorCount(cc);
+ for (int i = 0; i < cc; ++i)
+ {
+ img.setColor(i, palette[i]);
+ }
+
+ painter.drawImage(x, y, img);
+
+ min_x = min(min_x, x);
+ min_y = min(min_y, y);
+ max_x = max(max_x, x + w);
+ max_y = max(max_y, y + h);
+ }
+ }
+
+ OneSubtitle sub;
+ sub.start_time = buf.start_display_time - m_curTimeShift;
+ sub.length = buf.end_display_time - buf.start_display_time;
+
+ if (min_x < max_x && min_y < max_y)
+ {
+ sub.img_shift = QPoint(min_x, min_y);
+ sub.img = sub_pict.copy(min_x, min_y, max_x - min_x, max_y - min_y);
+
+ }
+ else
+ {
+ // Empty subtitle, do nothing.
+ }
+
+ //?
+ cur_sub = sub;
+ break;
+ }
+
+// if (!cur_sub.img.isNull() || !cur_sub.text.isEmpty()) {
+// dvbSubtitles[streamId].append(cur_sub);
+// }
+
+ QList<OneSubtitle> &list = dvbSubtitles[streamId];
+ ProcessNewSubtitle(cur_sub, list);
+
+ subReader->ClearAVSubtitles();
+ subReader->ClearRawTextSubtitles();
+ }
+ }
+
+/**
+ * Finishes pending DVB subtitles.
+ */
+
+void MythCCExtractorPlayer::FinishDVBSubtitlesProcess(void)
+{
+ // Do nothing.
+}
+
+CC708Reader *MythCCExtractorPlayer::GetCC708Reader(uint id)
+{
+ QHash<uint, CC708Reader*>::iterator it = cc708.find(id);
+ if (it != cc708.end())
+ return *it;
+
+ //printf("Make CC708Reader for %u\n", id);
+
+ // TODO FIXME ? safe to pass "this" ?
+ CC708Reader *n = new CC708Reader(this);
+ n->SetEnabled(true);
+
+ cc708.insert(id, n);
+ return n;
+}
+
+CC608Reader *MythCCExtractorPlayer::GetCC608Reader(uint id)
+{
+ QHash<uint, CC608Reader*>::iterator it = cc608.find(id);
+ if (it != cc608.end())
+ return *it;
+
+ //printf("Make CC608Reader for %u\n", id);
+
+ // TODO FIXME ? safe to pass "this" ?
+ CC608Reader *n = new CC608Reader(this);
+ n->SetEnabled(true);
+
+ cc608.insert(id, n);
+ return n;
+}
+
+SubtitleReader *MythCCExtractorPlayer::GetSubReader(uint id)
+{
+ QHash<uint, SubtitleReader*>::iterator it = subReader.find(id);
+ if (it != subReader.end())
+ return *it;
+
+ //printf("Make SubReader for %u\n", id);
+
+ SubtitleReader *n = new SubtitleReader();
+
+ n->EnableAVSubtitles(true);
+ n->EnableTextSubtitles(true);
+ n->EnableRawTextSubtitles(true);
+
+ subReader.insert(id, n);
+ return n;
+}
+
+TeletextReader *MythCCExtractorPlayer::GetTeletextReader(uint id)
+{
+ QHash<uint, TeletextExtractorReader *>::iterator it = ttxReader.find(id);
+ if (it != ttxReader.end())
+ return *it;
+
+ //printf("Make TeletextReader for %u\n", id);
+
+ TeletextExtractorReader *n = new TeletextExtractorReader();
+ ttxReader.insert(id, n);
+ return n;
+}
View
184 mythtv/libs/libmythtv/mythccextractorplayer.h
@@ -0,0 +1,184 @@
+// -*- Mode: c++ -*-
+
+#ifndef MYTHCCEXTRACTORPLAYER_H
+#define MYTHCCEXTRACTORPLAYER_H
+
+#include <stdint.h>
+
+#include <QStringList>
+#include <QImage>
+#include <QPoint>
+#include <QHash>
+
+#include "mythplayer.h"
+#include "teletextextractorreader.h"
+
+/**
+ * Represents one subtitle record.
+ */
+
+struct OneSubtitle
+{
+ /// Time we have to start showing subtitle, msec.
+ int64_t start_time;
+ /// Time we have to show subtitle, msec.
+ int64_t length;
+ /// Is this a text subtitle.
+ bool is_text;
+ /// Lines of text of subtitles.
+ QStringList text;
+ /// Image of subtitle.
+ QImage img;
+ /// Shift of image on the screen.
+ QPoint img_shift;
+
+ OneSubtitle() :
+ start_time(),
+ length(-1),
+ is_text(true),
+ text(),
+ img_shift(0, 0)
+ {}
+
+ bool operator==(const OneSubtitle &x) const
+ {
+ if (is_text != x.is_text)
+ return false;
+ if (is_text && text != x.text)
+ return false;
+ if (!is_text && img != x.img)
+ return false;
+ if (!is_text && img_shift != x.img_shift)
+ return false;
+ if (start_time != x.start_time)
+ return false;
+ if (length != x.length)
+ return false;
+
+ return true;
+ }
+};
+
+class MTV_PUBLIC MythCCExtractorPlayer : public MythPlayer
+{
+ public:
+ /// Key is a page number, values are the subtitles in chrono order.
+ typedef QHash<int, QList<OneSubtitle> > TTXStreamType;
+ /// Key is a CC number (1-4), values are the subtitles in chrono order.
+ typedef QHash<int, QList<OneSubtitle> > CC608StreamType;
+ /// Key is a CC service (1-63), values are the subtitles in chrono order.
+ typedef QHash<int, QList<OneSubtitle> > CC708StreamType;
+ /// Subtitles in chrono order.
+ typedef QList<OneSubtitle> DVBStreamType;
+
+ MythCCExtractorPlayer(bool showProgress) :
+ MythPlayer(true /*muted*/),
+ m_curTime(0),
+ m_curTimeShift(-1),
+ m_myFramesPlayed(0),
+ m_showProgress(showProgress)
+ {
+ }
+
+ ~MythCCExtractorPlayer();
+
+ bool run(void);
+
+ virtual CC708Reader *GetCC708Reader(uint id=0);
+ virtual CC608Reader *GetCC608Reader(uint id=0);
+ virtual SubtitleReader *GetSubReader(uint id=0);
+ virtual TeletextReader *GetTeletextReader(uint id=0);
+
+ QList<uint> GetCC708StreamsList(void) const
+ {
+ return cc708.keys();
+ }
+
+ QList<uint> GetCC608StreamsList(void) const
+ {
+ return cc608.keys();
+ }
+
+ QList<uint> GetSubtitleStreamsList(void) const
+ {
+ return subReader.keys();
+ }
+
+ QList<uint> GetTTXStreamsList(void) const
+ {
+ return ttxReader.keys();
+ }
+
+ TTXStreamType GetTTXSubtitles(uint stream_id = 0) const
+ {
+ return ttxSubtitles.value(stream_id);
+ }
+
+ CC608StreamType GetCC608Subtitles(uint stream_id = 0) const
+ {
+ return cc608Subtitles.value(stream_id);
+ }
+
+ CC708StreamType GetCC708Subtitles(uint stream_id = 0) const
+ {
+ return cc708Subtitles.value(stream_id);
+ }
+
+ DVBStreamType GetDVBSubtitles(uint stream_id = 0) const
+ {
+ return dvbSubtitles.value(stream_id);
+ }
+
+ private:
+ void ProcessNewSubtitle(QStringList content, QList<OneSubtitle> &list);
+ void ProcessNewSubtitle(OneSubtitle content, QList<OneSubtitle> &list);
+
+ void Process608Subtitles(void);
+ void GotNew608Subtitle(uint streamId, int ccIdx, CC608Buffer *subtitles);
+ void Finish608SubtitlesProcess(void);
+
+ void ProcessTTXSubtitles(void);
+ void GotNewTTXPage(uint stream_id, const TeletextSubPage &subPage);
+ void FinishTTXSubtitlesProcess(void);
+
+ static QStringList ConvertTTXPage(const TeletextSubPage &subPage);
+
+ void Process708Subtitles(void);
+ void GotNew708Subtitle(uint streamId, int serviceIdx, int windowIdx,
+ const std::vector<CC708String *> &content);
+ void Finish708SubtitlesProcess(void);
+
+ void ProcessDVBSubtitles(void);
+ void FinishDVBSubtitlesProcess(void);
+
+ void OnGotNewFrame(void);
+ void OnDecodingFinished(void);
+
+ QHash<uint, CC708Reader*> cc708;
+ QHash<uint, CC608Reader*> cc608;
+ QHash<uint, SubtitleReader*> subReader;
+ QHash<uint, TeletextExtractorReader*> ttxReader;
+
+ /// Keeps read subtitles from teletext for each ttx stream.
+ QHash<uint, TTXStreamType> ttxSubtitles;
+ /// Keeps read cc608 subtitles from for each cc608 stream.
+ QHash<uint, CC608StreamType> cc608Subtitles;
+ /// Keeps read cc708 subtitles from for each cc608 stream.
+ QHash<uint, CC708StreamType> cc708Subtitles;
+ /// Keeps read DVB subtitles
+ QHash<uint, DVBStreamType> dvbSubtitles;
+
+ /// Keeps cc708 windows (1-8) for all streams & services
+ /// (which ids are the keys).
+ QHash<QPair<uint, int>, QHash<int, QStringList> > cc708windows;
+
+ /// Keeps track for decoding time to make timestamps for subtitles.
+ int64_t m_curTime;
+ int64_t m_curTimeShift;
+ uint64_t m_myFramesPlayed;
+ bool m_showProgress;
+};
+
+#endif // MYTHCCEXTRACTORPLAYER_H
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
View
9 mythtv/libs/libmythtv/mythplayer.cpp
@@ -995,9 +995,12 @@ int MythPlayer::OpenFile(uint retries, bool allow_libmpeg2)
deleteMap.TrackerReset(bookmarkseek, totalFrames);
deleteMap.TrackerWantsToJump(bookmarkseek, totalFrames, bookmarkseek);
- if (player_ctx->playingInfo->QueryAutoExpire() == kLiveTVAutoExpire)
- gCoreContext->SaveSetting("DefaultChanid",
- player_ctx->playingInfo->GetChanID());
+ if (!gCoreContext->IsDatabaseIgnored() &&
+ player_ctx->playingInfo->QueryAutoExpire() == kLiveTVAutoExpire)
+ {
+ gCoreContext->SaveSetting(
+ "DefaultChanid", player_ctx->playingInfo->GetChanID());
+ }
return IsErrored() ? -1 : 0;
}
View
10 mythtv/libs/libmythtv/mythplayer.h
@@ -114,7 +114,7 @@ class MTV_PUBLIC MythPlayer
public:
MythPlayer(bool muted = false);
- ~MythPlayer();
+ virtual ~MythPlayer();
// Initialisation
virtual int OpenFile(uint retries = 4, bool allow_libmpeg2 = true);
@@ -248,10 +248,10 @@ class MTV_PUBLIC MythPlayer
// Public Closed caption and teletext stuff
uint GetCaptionMode(void) const { return textDisplayMode; }
- CC708Reader* GetCC708Reader(void) { return &cc708; }
- CC608Reader* GetCC608Reader(void) { return &cc608; }
- SubtitleReader* GetSubReader(void) { return &subReader; }
- TeletextReader* GetTeletextReader(void) { return &ttxReader; }
+ virtual CC708Reader *GetCC708Reader(uint id=0) { return &cc708; }
+ virtual CC608Reader *GetCC608Reader(uint id=0) { return &cc608; }
+ virtual SubtitleReader *GetSubReader(uint id=0) { return &subReader; }
+ virtual TeletextReader *GetTeletextReader(uint id=0) { return &ttxReader; }
// Public Audio/Subtitle/EIA-608/EIA-708 stream selection - thread safe
void TracksChanged(uint trackType);
View
245 mythtv/libs/libmythtv/teletextextractorreader.cpp
@@ -0,0 +1,245 @@
+// -*- Mode: c++ -*-
+
+#include "teletextextractorreader.h"
+
+void TeletextExtractorReader::PageUpdated(int page, int subpage)
+{
+ m_updated_pages.insert(qMakePair(page, subpage));
+ TeletextReader::PageUpdated(page, subpage);
+}
+
+void TeletextExtractorReader::HeaderUpdated(
+ int page, int subpage, uint8_t *page_ptr, int lang)
+{
+ m_updated_pages.insert(qMakePair(page, subpage));
+ TeletextReader::HeaderUpdated(page, subpage, page_ptr, lang);
+}
+
+/************************************************************************
+ * Everything below this message in this file is based on some VLC
+ * teletext code which was in turn based on some ProjectX teletext code.
+ ************************************************************************/
+
+/*****************************************************************************
+ * telx.c : Minimalistic Teletext subtitles decoder
+ *****************************************************************************
+ * Copyright (C) 2007 Vincent Penne
+ * Some code converted from ProjectX java dvb decoder (c) 2001-2005 by dvb.matt
+ * $Id: 2b01e6a460b7c3693bccd690e3dbc018832d2777 $
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*
+ * My doc only mentions 13 national characters, but experiments show there
+ * are more, in france for example I already found two more (0x9 and 0xb).
+ *
+ * Conversion is in this order :
+ *
+ * 0x23 0x24 0x40 0x5b 0x5c 0x5d 0x5e 0x5f 0x60 0x7b 0x7c 0x7d 0x7e
+ * (these are the standard ones)
+ * 0x08 0x09 0x0a 0x0b 0x0c 0x0d (apparently a control character) 0x0e 0x0f
+ */
+
+static const uint16_t ppi_national_subsets[][20] =
+{
+ { 0x00a3, 0x0024, 0x0040, 0x00ab, 0x00bd, 0x00bb, 0x005e, 0x0023,
+ 0x002d, 0x00bc, 0x00a6, 0x00be, 0x00f7 }, /* english, 000 */
+
+ { 0x00e9, 0x00ef, 0x00e0, 0x00eb, 0x00ea, 0x00f9, 0x00ee, 0x0023,
+ 0x00e8, 0x00e2, 0x00f4, 0x00fb, 0x00e7, 0, 0x00eb, 0, 0x00ef
+ }, /* french, 001 */
+
+ { 0x0023, 0x00a4, 0x00c9, 0x00c4, 0x00d6, 0x00c5, 0x00dc, 0x005f,
+ 0x00e9, 0x00e4, 0x00f6, 0x00e5, 0x00fc
+ }, /* swedish, finnish, hungarian, 010 */
+
+ { 0x0023, 0x016f, 0x010d, 0x0165, 0x017e, 0x00fd, 0x00ed, 0x0159,
+ 0x00e9, 0x00e1, 0x011b, 0x00fa, 0x0161 }, /* czech, slovak, 011 */
+
+ { 0x0023, 0x0024, 0x00a7, 0x00c4, 0x00d6, 0x00dc, 0x005e, 0x005f,
+ 0x00b0, 0x00e4, 0x00f6, 0x00fc, 0x00df }, /* german, 100 */
+
+ { 0x00e7, 0x0024, 0x00a1, 0x00e1, 0x00e9, 0x00ed, 0x00f3, 0x00fa,
+ 0x00bf, 0x00fc, 0x00f1, 0x00e8, 0x00e0 }, /* portuguese, spanish, 101 */
+
+ { 0x00a3, 0x0024, 0x00e9, 0x00b0, 0x00e7, 0x00bb, 0x005e, 0x0023,
+ 0x00f9, 0x00e0, 0x00f2, 0x00e8, 0x00ec }, /* italian, 110 */
+
+ { 0x0023, 0x00a4, 0x0162, 0x00c2, 0x015e, 0x0102, 0x00ce, 0x0131,
+ 0x0163, 0x00e2, 0x015f, 0x0103, 0x00ee }, /* rumanian, 111 */
+
+ /* I have these tables too, but I don't know how they can be triggered */
+ { 0x0023, 0x0024, 0x0160, 0x0117, 0x0119, 0x017d, 0x010d, 0x016b,
+ 0x0161, 0x0105, 0x0173, 0x017e, 0x012f }, /* lettish, lithuanian, 1000 */
+
+ { 0x0023, 0x0144, 0x0105, 0x005a, 0x015a, 0x0141, 0x0107, 0x00f3,
+ 0x0119, 0x017c, 0x015b, 0x0142, 0x017a }, /* polish, 1001 */
+
+ { 0x0023, 0x00cb, 0x010c, 0x0106, 0x017d, 0x0110, 0x0160, 0x00eb,
+ 0x010d, 0x0107, 0x017e, 0x0111, 0x0161
+ }, /* serbian, croatian, slovenian, 1010 */
+
+ { 0x0023, 0x00f5, 0x0160, 0x00c4, 0x00d6, 0x017e, 0x00dc, 0x00d5,
+ 0x0161, 0x00e4, 0x00f6, 0x017e, 0x00fc }, /* estonian, 1011 */
+
+ { 0x0054, 0x011f, 0x0130, 0x015e, 0x00d6, 0x00c7, 0x00dc, 0x011e,
+ 0x0131, 0x015f, 0x00f6, 0x00e7, 0x00fc }, /* turkish, 1100 */
+};
+
+// utc-2 --> utf-8
+// this is not a general function, but it's enough for what we do here
+// the result buffer need to be at least 4 bytes long
+static void to_utf8(char *res, uint16_t ch)
+{
+ if(ch >= 0x80)
+ {
+ if(ch >= 0x800)
+ {
+ res[0] = (ch >> 12) | 0xE0;
+ res[1] = ((ch >> 6) & 0x3F) | 0x80;
+ res[2] = (ch & 0x3F) | 0x80;
+ res[3] = 0;
+ }
+ else
+ {
+ res[0] = (ch >> 6) | 0xC0;
+ res[1] = (ch & 0x3F) | 0x80;
+ res[2] = 0;
+ }
+ }
+ else
+ {
+ res[0] = ch;
+ res[1] = 0;
+ }
+}
+
+/**
+ * Get decoded ttx as a string.
+ */
+
+QString decode_teletext(int codePage, const uint8_t data[40])
+{
+ QString res;
+ char utf8[7];
+
+ const uint16_t *pi_active_national_set = ppi_national_subsets[codePage];
+
+ for (int i = 0; i < 40; ++i)
+ {
+ //int in = bytereverse(data[i]) & 0x7f;
+ int in = data[i] & 0x7f;
+ uint16_t out = 32;
+
+ switch (in)
+ {
+ /* special national characters */
+ case 0x23:
+ out = pi_active_national_set[0];
+ break;
+ case 0x24:
+ out = pi_active_national_set[1];
+ break;
+ case 0x40:
+ out = pi_active_national_set[2];
+ break;
+ case 0x5b:
+ out = pi_active_national_set[3];
+ break;
+ case 0x5c:
+ out = pi_active_national_set[4];
+ break;
+ case 0x5d:
+ out = pi_active_national_set[5];
+ break;
+ case 0x5e:
+ out = pi_active_national_set[6];
+ break;
+ case 0x5f:
+ out = pi_active_national_set[7];
+ break;
+ case 0x60:
+ out = pi_active_national_set[8];
+ break;
+ case 0x7b:
+ out = pi_active_national_set[9];
+ break;
+ case 0x7c:
+ out = pi_active_national_set[10];
+ break;
+ case 0x7d:
+ out = pi_active_national_set[11];
+ break;
+ case 0x7e:
+ out = pi_active_national_set[12];
+ break;
+
+ case 0x0a:
+ case 0x0b:
+ case 0x0d:
+ //wtf? looks like some kind of garbage for me
+ out = 32;
+ break;
+
+ default:
+ /* non documented national range 0x08 - 0x0f */
+ if (in >= 0x08 && in <= 0x0f)
+ {
+ out = pi_active_national_set[13 + in - 8];
+ break;
+ }
+
+ /* normal ascii */
+ if (in > 32 && in < 0x7f)
+ out = in;
+ }
+
+ /* handle undefined national characters */
+ if (out == 0)
+ out = '?'; //' ' or '?' ?
+
+ /* convert to utf-8 */
+ to_utf8(utf8, out);
+ res += QString::fromUtf8(utf8, strlen(utf8));
+ }
+
+ return res;
+}
+
+//QString DechiperTtxFlags(int flags) {
+// QString res;
+
+// if (flags & TP_SUPPRESS_HEADER)
+// res += "TP_SUPPRESS_HEADER ";
+// if (flags & TP_UPDATE_INDICATOR)
+// res += "TP_UPDATE_INDICATOR ";
+// if (flags & TP_INTERRUPTED_SEQ)
+// res += "TP_INTERRUPTED_SEQ ";
+// if (flags & TP_INHIBIT_DISPLAY)
+// res += "TP_INHIBIT_DISPLAY ";
+// if (flags & TP_MAGAZINE_SERIAL)
+// res += "TP_MAGAZINE_SERIAL ";
+// if (flags & TP_ERASE_PAGE)
+// res += "TP_ERASE_PAGE ";
+// if (flags & TP_NEWSFLASH)
+// res += "TP_NEWSFLASH ";
+// if (flags & TP_SUBTITLE)
+// res += "TP_SUBTITLE ";
+
+// return res.trimmed();
+//}
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
View
40 mythtv/libs/libmythtv/teletextextractorreader.h
@@ -0,0 +1,40 @@
+// -*- Mode: c++ -*-
+
+#ifndef TELETEXTEXTRACTORREADER_H
+#define TELETEXTEXTRACTORREADER_H
+
+#include <QString>
+#include <QMutex>
+#include <QPair>
+#include <QSet>
+
+#include "mythtvexp.h"
+#include "teletextreader.h"
+
+QString decode_teletext(int codePage, const uint8_t data[40]);
+
+class MTV_PUBLIC TeletextExtractorReader : public TeletextReader
+{
+ public:
+ QSet<QPair<int, int> > GetUpdatedPages(void) const
+ {
+ return m_updated_pages;
+ }
+
+ void ClearUpdatedPages(void)
+ {
+ m_updated_pages.clear();
+ }
+
+ protected:
+ virtual void PageUpdated(int page, int subpage);
+ virtual void HeaderUpdated(
+ int page, int subpage, uint8_t *page_ptr, int lang);
+
+ private:
+ QSet<QPair<int, int> > m_updated_pages;
+};
+
+#endif // TELETEXTEXTRACTORREADER_H
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
View
16 mythtv/libs/libmythtv/teletextreader.cpp
@@ -1,4 +1,5 @@
#include "teletextreader.h"
+#include "mythlogging.h"
#include <string.h>
#include "vbilut.h"
@@ -300,6 +301,9 @@ void TeletextReader::Reset(void)
void TeletextReader::AddPageHeader(int page, int subpage, const uint8_t *buf,
int vbimode, int lang, int flags)
{
+ //LOG(VB_GENERAL, LOG_ERR, QString("AddPageHeader(p %1, sp %2, lang %3)")
+ // .arg(page).arg(subpage).arg(lang));
+
int magazine = MAGAZINE(page);
if (magazine < 1 || magazine > 8)
return;
@@ -366,13 +370,16 @@ void TeletextReader::AddPageHeader(int page, int subpage, const uint8_t *buf,
if ( !(ttpage->flags & TP_INTERRUPTED_SEQ))
{
memcpy(m_header, ttpage->data[0], 40);
- HeaderUpdated(ttpage->data[0],ttpage->lang);
+ HeaderUpdated(page, subpage, ttpage->data[0],ttpage->lang);
}
}
void TeletextReader::AddTeletextData(int magazine, int row,
const uint8_t* buf, int vbimode)
{
+ //LOG(VB_GENERAL, LOG_ERR, QString("AddTeletextData(%1, %2)")
+ // .arg(magazine).arg(row));
+
int b1, b2, b3, err = 0;
if (magazine < 1 || magazine > 8)
@@ -483,11 +490,14 @@ void TeletextReader::PageUpdated(int page, int subpage)
m_page_changed = true;
}
-void TeletextReader::HeaderUpdated(uint8_t * page, int lang)
+void TeletextReader::HeaderUpdated(
+ int page, int subpage, uint8_t *page_ptr, int lang)
{
+ (void)page;
+ (void)subpage;
(void)lang;
- if (page == NULL)
+ if (page_ptr == NULL)
return;
if (m_curpage_showheader == false)
View
10 mythtv/libs/libmythtv/teletextreader.h
@@ -70,7 +70,7 @@ class TeletextReader
{
public:
TeletextReader();
- ~TeletextReader();
+ virtual ~TeletextReader();
// OSD/Player methods
void Reset(void);
@@ -97,11 +97,11 @@ class TeletextReader
void AddTeletextData(int magazine, int row,
const uint8_t* buf, int vbimode);
-
- private:
+ protected:
void NewsFlash(void) {};
- void PageUpdated(int page, int subpage);
- void HeaderUpdated(uint8_t *page, int lang);
+ virtual void PageUpdated(int page, int subpage);
+ virtual void HeaderUpdated(
+ int page, int subpage, uint8_t *page_ptr, int lang);
const TeletextSubPage *FindSubPage(int page, int subpage, int dir=0) const
{ return FindSubPageInternal(page, subpage, dir); }
View
1 mythtv/programs/mythccextractor/.gitignore
@@ -0,0 +1 @@
+mythccextractor
View
26 mythtv/programs/mythccextractor/commandlineparser.cpp
@@ -0,0 +1,26 @@
+#include "commandlineparser.h"
+#include "mythcorecontext.h"
+
+MythCCExtractorCommandLineParser::MythCCExtractorCommandLineParser() :
+ MythCommandLineParser(MYTH_APPNAME_MYTHCCEXTRACTOR)
+{
+ LoadArguments();
+}
+
+void MythCCExtractorCommandLineParser::LoadArguments(void)
+{
+ addHelp();
+ addSettingsOverride();
+ addVersion();
+ addLogging();
+
+ add(QStringList( QStringList() << "-i" << "--infile" ), "inputfile", "",
+ "input file", "");
+}
+
+QString MythCCExtractorCommandLineParser::GetHelpHeader(void) const
+{
+ return
+ "This is a command for generating srt files for\n"
+ "DVB and ATSC recordings.";
+}
View
19 mythtv/programs/mythccextractor/commandlineparser.h
@@ -0,0 +1,19 @@
+// -*- Mode: c++ -*-
+
+#ifndef _MYTH_CCEXTRACTOR_COMMAND_LINE_PARSER_H_
+#define _MYTH_CCEXTRACTOR_COMMAND_LINE_PARSER_H_
+
+#include "mythcommandlineparser.h"
+
+class MythCCExtractorCommandLineParser : public MythCommandLineParser
+{
+ public:
+ MythCCExtractorCommandLineParser();
+ void LoadArguments(void);
+ protected:
+ QString GetHelpHeader(void) const;
+};
+
+#endif // _MYTH_CCEXTRACTOR_COMMAND_LINE_PARSER_H_
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
View
345 mythtv/programs/mythccextractor/main.cpp
@@ -0,0 +1,345 @@
+// -*- Mode: c++ -*-
+
+// C++ headers
+#include <iostream>
+using namespace std;
+
+// Qt headers
+#include <QCoreApplication>
+#include <QString>
+#include <QtCore>
+#include <QtGui>
+
+// MythTV headers
+#include "mythccextractorplayer.h"
+#include "commandlineparser.h"
+#include "programinfo.h"
+#include "mythcontext.h"
+#include "mythversion.h"
+#include "ringbuffer.h"
+#include "exitcodes.h"
+
+namespace {
+ void cleanup()
+ {
+ delete gContext;
+ gContext = NULL;
+
+ }
+
+ class CleanupGuard
+ {
+ public:
+ typedef void (*CleanupFunc)();
+
+ public:
+ CleanupGuard(CleanupFunc cleanFunction) :
+ m_cleanFunction(cleanFunction) {}
+
+ ~CleanupGuard()
+ {
+ m_cleanFunction();
+ }
+
+ private:
+ CleanupFunc m_cleanFunction;
+ };
+}
+
+/**
+ * Class to write SubRip files.
+ */
+
+class SrtWriter
+{
+ public:
+ SrtWriter(const QString &fileName) :
+ m_outFile(fileName),
+ m_outStream(&m_outFile),
+ m_srtCounter(0)
+ {
+ }
+
+ bool init()
+ {
+ return m_outFile.open(QFile::WriteOnly);
+ }
+
+ /**
+ * Adds next subtitle.
+ */
+ void addSubtitle(const OneSubtitle &sub)
+ {
+ const char *endl = "\r\n";
+ m_outStream << ++m_srtCounter << endl;
+
+ m_outStream << formatTimeForSrt(sub.start_time) << " --> ";
+ m_outStream << formatTimeForSrt(sub.start_time + sub.length) << endl;
+
+ foreach (QString line, sub.text)
+ m_outStream << line << endl;
+
+ m_outStream << endl;
+ }
+
+ private:
+ /**
+ * Formats time to format appropriate to SubRip file.
+ */
+ static QString formatTimeForSrt(qint64 time_in_msec)
+ {
+ qint64 hh, mm, ss, msec;
+
+ msec = time_in_msec % 1000;
+ time_in_msec /= 1000;
+
+ ss = time_in_msec % 60;
+ time_in_msec /= 60;
+
+ mm = time_in_msec % 60;
+ time_in_msec /= 60;
+
+ hh = time_in_msec;
+
+ QString res;
+ QTextStream stream(&res);
+
+ stream << qSetPadChar('0') << qSetFieldWidth(2) << hh;
+ stream << qSetFieldWidth(0) << ':';
+ stream << qSetPadChar('0') << qSetFieldWidth(2) << mm;
+ stream << qSetFieldWidth(0) << ':';
+ stream << qSetPadChar('0') << qSetFieldWidth(2) << ss;
+ stream << qSetFieldWidth(0) << ',';
+ stream << qSetPadChar('0') << qSetFieldWidth(3) << msec;
+
+ return res;
+ }
+
+ /// Output file.
+ QFile m_outFile;
+ /// Output stream associated with m_outFile.
+ QTextStream m_outStream;
+
+ /// Count of subtitles we already have written.
+ int m_srtCounter;
+};
+
+/**
+ * Sumps all subtitle information to SubRip files.
+ */
+
+static void DumpSubtitleInformation(
+ const MythCCExtractorPlayer *player, const QString &fileName)
+{
+ // Determine where we will put extracted info.
+ QStringList comps = QFileInfo(fileName).fileName().split(".");
+ if (!comps.empty())
+ comps.removeLast();
+
+ QDir working_dir(QFileInfo(fileName).path());
+ const QString base_name = comps.join(".");
+
+ // Process (DVB) subtitle streams.
+ foreach (uint streamId, player->GetSubtitleStreamsList())
+ {
+ MythCCExtractorPlayer::DVBStreamType subs =
+ player->GetDVBSubtitles(streamId);
+
+ const QString dir_name = QString(base_name + ".dvb-%1").arg(streamId);
+ if (!working_dir.mkdir(dir_name))
+ {
+ qDebug() << "Can't make dir " << dir_name;
+ }
+
+ const QDir stream_dir(working_dir.filePath(dir_name));
+
+ int cnt = 0;
+ foreach (OneSubtitle cur_sub, subs)
+ {
+ const QString file_name = stream_dir.filePath(
+ QString("%1_%2-to-%3.png")
+ .arg(cnt)
+ .arg(cur_sub.start_time)
+ .arg(cur_sub.start_time + cur_sub.length)
+ );
+
+ if (!cur_sub.img.save(file_name))
+ qDebug() << "Can't write file " << file_name;
+
+ ++cnt;
+ }
+ }
+
+ // Process CC608.
+ foreach (uint streamId, player->GetCC608StreamsList())
+ {
+ MythCCExtractorPlayer::CC608StreamType subs =
+ player->GetCC608Subtitles(streamId);
+
+ foreach (int idx, subs.keys())
+ {
+ if (subs.value(idx).isEmpty())
+ continue; // Skip empty subtitle streams.
+
+ SrtWriter srtWriter(
+ working_dir.filePath(
+ base_name +
+ QString(".cc608-cc%1.srt").arg(idx + 1)));
+
+ if (srtWriter.init())
+ {
+ foreach (const OneSubtitle &sub, subs.value(idx))
+ {
+ srtWriter.addSubtitle(sub);
+ }
+ }
+ }
+ }
+
+ // Process CC708.
+ foreach (uint streamId, player->GetCC708StreamsList())
+ {
+ MythCCExtractorPlayer::CC708StreamType subs =
+ player->GetCC708Subtitles(streamId);
+
+ foreach (int idx, subs.keys())
+ {
+ if (subs.value(idx).isEmpty())
+ continue; // Skip empty subtitle streams.
+
+ SrtWriter srtWriter(working_dir.filePath(
+ base_name
+ + QString(".cc708-service%1.srt")
+ .arg(idx, 2, 10, QChar('0'))));
+ if (srtWriter.init())
+ {
+ foreach (const OneSubtitle &sub, subs.value(idx))
+ {
+ srtWriter.addSubtitle(sub);
+ }
+ }
+ }
+ }
+
+ // Process teletext.
+ foreach (uint streamId, player->GetTTXStreamsList())
+ {
+ MythCCExtractorPlayer::TTXStreamType subs =
+ player->GetTTXSubtitles(streamId);
+
+ foreach (int page, subs.keys())
+ {