Skip to content

Commit

Permalink
Support .srt files for in-progress recordings. Refs #11618
Browse files Browse the repository at this point in the history
Allow a .srt file to be reloaded for an in-progress recording after
the last subtitle is reached.  The backend is queried at most one a
second for an updated .srt file.

Still to do:

1. Do the update check in a separate thread to avoid stuttering.

2. Currently the REC_STARTED_WRITING event is not sent when the HD-PVR
switches to a new program during live TV, so the third-party script
ends up not generating .srt files for subsequent programs in a live TV
session.

3. ccextractor buffers its output, so one needs to be at least 30
seconds behind real-time to get .srt captions.  A better solution
would be to add the required functionality to mythccextractor.
  • Loading branch information
stichnot committed Jun 23, 2013
1 parent c691ddf commit feb2e0f
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 41 deletions.
6 changes: 6 additions & 0 deletions mythtv/libs/libmythtv/mythplayer.cpp
Expand Up @@ -2672,6 +2672,12 @@ void MythPlayer::JumpToProgram(void)

player_ctx->buffer->OpenFile(
pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout);
QString subfn = player_ctx->buffer->GetSubtitleFilename();
TVState desiredState = player_ctx->GetState();
bool isInProgress =
desiredState == kState_WatchingRecording || kState_WatchingLiveTV;
if (!subfn.isEmpty() && GetSubReader())
GetSubReader()->LoadExternalSubtitles(subfn, isInProgress);

if (!player_ctx->buffer->IsOpen())
{
Expand Down
10 changes: 6 additions & 4 deletions mythtv/libs/libmythtv/playercontext.cpp
Expand Up @@ -408,21 +408,23 @@ bool PlayerContext::CreatePlayer(TV *tv, QWidget *widget,

player->SetVideoFilters((useNullVideo) ? "onefield" : "");

bool isWatchingRecording = (desiredState == kState_WatchingRecording);
player->SetWatchingRecording(isWatchingRecording);

if (!IsAudioNeeded())
audio->SetNoAudio();
else
{
QString subfn = buffer->GetSubtitleFilename();
bool isInProgress =
desiredState == kState_WatchingRecording || kState_WatchingLiveTV;
if (!subfn.isEmpty() && player->GetSubReader())
player->GetSubReader()->LoadExternalSubtitles(subfn);
player->GetSubReader()->LoadExternalSubtitles(subfn, isInProgress);
}

if (embed && !embedbounds.isNull())
player->EmbedInWidget(embedbounds);

bool isWatchingRecording = (desiredState == kState_WatchingRecording);
player->SetWatchingRecording(isWatchingRecording);

SetPlayer(player);

if (pipState == kPIPOff || pipState == kPBPLeft)
Expand Down
6 changes: 4 additions & 2 deletions mythtv/libs/libmythtv/subtitlereader.cpp
Expand Up @@ -97,15 +97,17 @@ void SubtitleReader::FreeAVSubtitle(const AVSubtitle &subtitle)
av_free(subtitle.rects);
}

bool SubtitleReader::LoadExternalSubtitles(const QString &subtitleFileName)
bool SubtitleReader::LoadExternalSubtitles(const QString &subtitleFileName,
bool isInProgress)
{
m_TextSubtitles.Clear();
m_TextSubtitles.SetInProgress(isInProgress);
return TextSubtitleParser::LoadSubtitles(subtitleFileName, m_TextSubtitles);
}

bool SubtitleReader::HasTextSubtitles(void)
{
return m_TextSubtitles.GetSubtitleCount() > 0;
return m_TextSubtitles.GetSubtitleCount() >= 0;
}

QStringList SubtitleReader::GetRawTextSubtitles(uint64_t &duration)
Expand Down
2 changes: 1 addition & 1 deletion mythtv/libs/libmythtv/subtitlereader.h
Expand Up @@ -45,7 +45,7 @@ class SubtitleReader

TextSubtitles* GetTextSubtitles(void) { return &m_TextSubtitles; }
bool HasTextSubtitles(void);
bool LoadExternalSubtitles(const QString &videoFile);
bool LoadExternalSubtitles(const QString &videoFile, bool isInProgress);

QStringList GetRawTextSubtitles(uint64_t &duration);
void AddRawTextSubtitle(QStringList list, uint64_t duration);
Expand Down
88 changes: 72 additions & 16 deletions mythtv/libs/libmythtv/textsubtitleparser.cpp
Expand Up @@ -18,9 +18,10 @@ using std::lower_bound;

// MythTV
#include "mythcorecontext.h"
#include "ringbuffer.h"
#include "remotefile.h"
#include "textsubtitleparser.h"
#include "xine_demux_sputext.h"
#include "mythlogging.h"

static bool operator<(const text_subtitle_t& left,
const text_subtitle_t& right)
Expand Down Expand Up @@ -51,10 +52,10 @@ bool TextSubtitles::HasSubtitleChanged(uint64_t timecode) const
* current video position.
* \return The subtitles as a list of strings.
*/
QStringList TextSubtitles::GetSubtitles(uint64_t timecode) const
QStringList TextSubtitles::GetSubtitles(uint64_t timecode)
{
QStringList list;
if (m_subtitles.empty())
if (!m_isInProgress && m_subtitles.empty())
return list;

text_subtitle_t searchTarget(timecode, timecode);
Expand Down Expand Up @@ -84,9 +85,27 @@ QStringList TextSubtitles::GetSubtitles(uint64_t timecode) const

if (nextSubPos == m_subtitles.end())
{
// at the end of video, the blank subtitle should last until
// forever
endCode = startCode + INT_MAX;
if (m_isInProgress)
{
const int maxReloadInterval = 1000; // ms
if (IsFrameBasedTiming())
// Assume conservative 24fps
endCode = startCode + maxReloadInterval / 24;
else
endCode = startCode + maxReloadInterval;
QDateTime now = QDateTime::currentDateTimeUtc();
if (!m_fileName.isEmpty() &&
m_lastLoaded.msecsTo(now) >= maxReloadInterval)
{
TextSubtitleParser::LoadSubtitles(m_fileName, *this);
}
}
else
{
// at the end of video, the blank subtitle should last
// until forever
endCode = startCode + INT_MAX;
}
}
else
{
Expand All @@ -104,35 +123,70 @@ QStringList TextSubtitles::GetSubtitles(uint64_t timecode) const

void TextSubtitles::AddSubtitle(const text_subtitle_t &newSub)
{
m_lock.lock();
QMutexLocker locker(&m_lock);
m_subtitles.push_back(newSub);
m_lock.unlock();
}

void TextSubtitles::Clear(void)
{
m_lock.lock();
QMutexLocker locker(&m_lock);
m_subtitles.clear();
m_lock.unlock();
}

bool TextSubtitleParser::LoadSubtitles(QString fileName, TextSubtitles &target)
void TextSubtitles::SetLastLoaded(void)
{
QMutexLocker locker(&m_lock);
m_lastLoaded = QDateTime::currentDateTimeUtc();
}

bool TextSubtitleParser::LoadSubtitles(const QString &fileName,
TextSubtitles &target)
{
demux_sputext_t sub_data;
sub_data.rbuffer = RingBuffer::Create(fileName, 0, false);
RemoteFile rfile(fileName, false, false, 0);

if (!sub_data.rbuffer)
LOG(VB_VBI, LOG_INFO,
QString("Preparing to load subtitle file (%1)").arg(fileName));
if (!rfile.Open())
{
LOG(VB_VBI, LOG_INFO,
QString("Failed to load subtitle file (%1)").arg(fileName));
return false;
}
target.SetHasSubtitles(true);
target.SetFilename(fileName);

// Only reload if rfile.GetRealFileSize() has changed.
off_t new_len = rfile.GetFileSize();
if (target.GetByteCount() == new_len)
{
LOG(VB_VBI, LOG_INFO,
QString("Filesize unchanged (%1), not reloading subs (%2)")
.arg(new_len).arg(fileName));
target.SetLastLoaded();
return new_len;
}
LOG(VB_VBI, LOG_INFO,
QString("Preparing to read %1 subtitle bytes from %2")
.arg(new_len).arg(fileName));
target.SetByteCount(new_len);
sub_data.rbuffer_len = new_len;
sub_data.rbuffer_text = new char[sub_data.rbuffer_len + 1];
sub_data.rbuffer_cur = 0;
sub_data.errs = 0;
int numread = rfile.Read(sub_data.rbuffer_text, sub_data.rbuffer_len);
LOG(VB_VBI, LOG_INFO,
QString("Finished reading %1 subtitle bytes (requested %2)")
.arg(numread).arg(new_len));
subtitle_t *loaded_subs = sub_read_file(&sub_data);
if (!loaded_subs)
{
delete sub_data.rbuffer;
delete sub_data.rbuffer_text;
return false;
}

target.SetFrameBasedTiming(!sub_data.uses_time);
target.Clear();

QTextCodec *textCodec = NULL;
QString codec = gCoreContext->GetSetting("SubtitleCodec", "");
Expand All @@ -142,7 +196,7 @@ bool TextSubtitleParser::LoadSubtitles(QString fileName, TextSubtitles &target)
textCodec = QTextCodec::codecForName("utf-8");
if (!textCodec)
{
delete sub_data.rbuffer;
delete sub_data.rbuffer_text;
return false;
}

Expand Down Expand Up @@ -175,7 +229,9 @@ bool TextSubtitleParser::LoadSubtitles(QString fileName, TextSubtitles &target)
// textCodec object is managed by Qt, do not delete...

free(loaded_subs);
delete sub_data.rbuffer;
delete sub_data.rbuffer_text;

target.SetLastLoaded();

return true;
}
46 changes: 39 additions & 7 deletions mythtv/libs/libmythtv/textsubtitleparser.h
Expand Up @@ -16,6 +16,7 @@ using namespace std;

// Qt headers
#include <QStringList>
#include <QDateTime>

class text_subtitle_t
{
Expand All @@ -40,7 +41,9 @@ typedef vector<text_subtitle_t> TextSubtitleList;
class TextSubtitles
{
public:
TextSubtitles() : m_frameBasedTiming(false)
TextSubtitles() : m_frameBasedTiming(false), m_byteCount(0),
m_isInProgress(false), m_hasSubtitles(false),
m_lock(QMutex::Recursive)
{
m_lastReturnedSubtitle.start = 0;
m_lastReturnedSubtitle.end = 0;
Expand All @@ -49,7 +52,7 @@ class TextSubtitles
virtual ~TextSubtitles() {}

bool HasSubtitleChanged(uint64_t timecode) const;
QStringList GetSubtitles(uint64_t timecode) const;
QStringList GetSubtitles(uint64_t timecode);

/** \fn TextSubtitles::IsFrameBasedTiming(void) const
* \brief Returns true in case the subtitle timing data is frame-based.
Expand All @@ -61,14 +64,33 @@ class TextSubtitles
bool IsFrameBasedTiming(void) const
{ return m_frameBasedTiming; }

void SetFrameBasedTiming(bool frameBasedTiming)
{ m_frameBasedTiming = frameBasedTiming; }
void SetFrameBasedTiming(bool frameBasedTiming) {
QMutexLocker locker(&m_lock);
m_frameBasedTiming = frameBasedTiming;
}

void SetFilename(const QString &fileName) {
QMutexLocker locker(&m_lock);
m_fileName = fileName;
}

void AddSubtitle(const text_subtitle_t& newSub);
void Clear(void);
void SetLastLoaded(void);
void SetByteCount(off_t count) {
QMutexLocker locker(&m_lock);
m_byteCount = count;
}
off_t GetByteCount(void) const { return m_byteCount; }
void SetInProgress(bool isInProgress) {
QMutexLocker locker(&m_lock);
m_isInProgress = isInProgress;
}
void SetHasSubtitles(bool hasSubs) { m_hasSubtitles = hasSubs; }

uint GetSubtitleCount(void) const
{ return m_subtitles.size(); }
// Returns -1 if there is no subtitle file.
int GetSubtitleCount(void) const
{ return m_hasSubtitles ? m_subtitles.size() : -1; }

void Lock(void) { m_lock.lock(); }
void Unlock(void) { m_lock.unlock(); }
Expand All @@ -77,13 +99,23 @@ class TextSubtitles
TextSubtitleList m_subtitles;
mutable text_subtitle_t m_lastReturnedSubtitle;
bool m_frameBasedTiming;
QString m_fileName;
QDateTime m_lastLoaded;
off_t m_byteCount;
// Note: m_isInProgress is overly conservative because it doesn't
// change from true to false after a recording completes.
bool m_isInProgress;
// It's possible to have zero subtitles at the start of playback
// because none have yet been written for an in-progress
// recording, so use m_hasSubtitles instead of m_subtitles.size().
bool m_hasSubtitles;
QMutex m_lock;
};

class TextSubtitleParser
{
public:
static bool LoadSubtitles(QString fileName, TextSubtitles &target);
static bool LoadSubtitles(const QString &fileName, TextSubtitles &target);
};

#endif
19 changes: 9 additions & 10 deletions mythtv/libs/libmythtv/xine_demux_sputext.cpp
Expand Up @@ -104,8 +104,13 @@ static char *read_line_from_input(demux_sputext_t *demuxstr, char *line, off_t l
// an error back, we check for empty reads so that we can stop reading
// when there is no more data to read
if (demuxstr->emptyReads == 0 && (len - demuxstr->buflen) > 512) {
nread = demuxstr->rbuffer->Read(
&demuxstr->buf[demuxstr->buflen], len - demuxstr->buflen);
nread = len - demuxstr->buflen;
if (nread > demuxstr->rbuffer_len - demuxstr->rbuffer_cur)
nread = demuxstr->rbuffer_len - demuxstr->rbuffer_cur;
memcpy(&demuxstr->buf[demuxstr->buflen],
&demuxstr->rbuffer_text[demuxstr->rbuffer_cur],
nread);
demuxstr->rbuffer_cur += nread;
if (nread < 0) {
printf("read failed.\n");
return NULL;
Expand Down Expand Up @@ -1111,10 +1116,7 @@ subtitle_t *sub_read_file (demux_sputext_t *demuxstr) {
};

/* Rewind (sub_autodetect() needs to read input from the beginning) */
if(demuxstr->rbuffer->Seek(0, SEEK_SET) == -1) {
printf("seek failed.\n");
return NULL;
}
demuxstr->rbuffer_cur = 0;
demuxstr->buflen = 0;
demuxstr->emptyReads = 0;

Expand All @@ -1126,10 +1128,7 @@ subtitle_t *sub_read_file (demux_sputext_t *demuxstr) {
/*printf("Detected subtitle file format: %d\n", demuxstr->format);*/

/* Rewind */
if(demuxstr->rbuffer->Seek(0, SEEK_SET) == -1) {
printf("seek failed.\n");
return NULL;
}
demuxstr->rbuffer_cur = 0;
demuxstr->buflen = 0;
demuxstr->emptyReads = 0;

Expand Down
4 changes: 3 additions & 1 deletion mythtv/libs/libmythtv/xine_demux_sputext.h
Expand Up @@ -21,7 +21,9 @@ typedef struct {

typedef struct {

RingBuffer* rbuffer;
char *rbuffer_text;
off_t rbuffer_len;
off_t rbuffer_cur;

int status;

Expand Down

0 comments on commit feb2e0f

Please sign in to comment.