MythTV  master
textsubtitleparser.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
7 // ANSI C
8 #include <cstdio>
9 #include <cstring>
10 #include <climits>
11 
12 // C++
13 #include <algorithm>
14 
15 // Qt
16 #include <QRunnable>
17 #include <QFile>
18 #include <QDataStream>
19 
20 // MythTV
24 #include "libmythbase/remotefile.h"
26 #include "libmyth/mythaverror.h"
27 
30 
31 #define IO_BUFFER_SIZE 32768
32 
33 // This background thread helper class is adapted from the
34 // RebuildSaver class in mythcommflagplayer.cpp.
35 class SubtitleLoadHelper : public QRunnable
36 {
37  public:
39  TextSubtitles *target)
40 
41  : m_parent(parent), m_target(target)
42  {
43  QMutexLocker locker(&s_lock);
45  }
46 
47  void run(void) override // QRunnable
48  {
49  m_parent->LoadSubtitles(false);
50 
51  QMutexLocker locker(&s_lock);
53  if (!s_loading[m_target])
54  s_wait.wakeAll();
55  }
56 
57  static bool IsLoading(TextSubtitles *target)
58  {
59  QMutexLocker locker(&s_lock);
60  return s_loading[target] != 0U;
61  }
62 
63  static void Wait(TextSubtitles *target)
64  {
65  QMutexLocker locker(&s_lock);
66  if (!s_loading[target])
67  return;
68  while (s_wait.wait(&s_lock))
69  {
70  if (!s_loading[target])
71  return;
72  }
73  }
74 
75  private:
77  TextSubtitles *m_target { nullptr };
78 
79  static QMutex s_lock;
80  static QWaitCondition s_wait;
81  static QHash<TextSubtitles*,uint> s_loading;
82 };
84 QWaitCondition SubtitleLoadHelper::s_wait;
85 QHash<TextSubtitles*,uint> SubtitleLoadHelper::s_loading;
86 
87 // Work around the fact that RemoteFile doesn't work when the target
88 // file is actually local.
90 {
91 public:
92  explicit RemoteFileWrapper(const QString &filename) {
93  // This test stolen from FileRingBuffer::OpenFile()
94  bool is_local =
95  (!filename.startsWith("/dev")) &&
96  ((filename.startsWith("/")) || QFile::exists(filename));
97  m_isRemote = !is_local;
98  if (m_isRemote)
99  {
100  m_localFile = nullptr;
101  m_remoteFile = new RemoteFile(filename, false, false, 0s);
102  }
103  else
104  {
105  m_remoteFile = nullptr;
106  m_localFile = new QFile(filename);
107  if (!m_localFile->open(QIODevice::ReadOnly))
108  {
109  delete m_localFile;
110  m_localFile = nullptr;
111  }
112  }
113  }
115  delete m_remoteFile;
116  delete m_localFile;
117  }
118 
119  RemoteFileWrapper(const RemoteFileWrapper &) = delete; // not copyable
120  RemoteFileWrapper &operator=(const RemoteFileWrapper &) = delete; // not copyable
121 
122  bool isOpen(void) const {
123  if (m_isRemote)
124  return m_remoteFile->isOpen();
125  return m_localFile;
126  }
127  long long GetFileSize(void) const {
128  if (m_isRemote)
129  return m_remoteFile->GetFileSize();
130  if (m_localFile)
131  return m_localFile->size();
132  return 0;
133  }
134  int Read(void *data, int size) {
135  if (m_isRemote)
136  return m_remoteFile->Read(data, size);
137  if (m_localFile)
138  {
139  QDataStream stream(m_localFile);
140  return stream.readRawData(static_cast<char*>(data), size);
141  }
142  return 0;
143  }
144 private:
147  QFile *m_localFile;
148 };
149 
151 {
153 }
154 
156 {
157  emit TextSubtitlesUpdated();
158  QMutexLocker locker(&m_lock);
159  m_lastLoaded = QDateTime::currentDateTimeUtc();
160 }
161 
167 };
168 
170 {
171  avcodec_free_context(&m_decCtx);
172  delete m_loadHelper;
173 }
174 
176 int TextSubtitleParser::read_packet(void *opaque, uint8_t *buf, int buf_size)
177 {
178  auto *bd = (struct local_buffer_t *)opaque;
179 
180  /* copy internal buffer data to buf */
181  off_t remaining = bd->rbuffer_len - bd->rbuffer_cur;
182  if (remaining <= 0)
183  return AVERROR_EOF;
184  buf_size = FFMIN(buf_size, remaining);
185  memcpy(buf, bd->rbuffer_text + bd->rbuffer_cur, buf_size);
186  bd->rbuffer_cur += buf_size;
187 
188  return buf_size;
189 }
190 
192 int64_t TextSubtitleParser::seek_packet(void *opaque, int64_t offset, int whence)
193 {
194  auto *bd = (struct local_buffer_t *)opaque;
195 
196  switch (whence)
197  {
198  case SEEK_CUR:
199  offset = bd->rbuffer_cur + offset;
200  break;
201 
202  case SEEK_END:
203  offset = bd->rbuffer_len - offset;
204  break;
205 
206  case SEEK_SET:
207  break;
208 
209  default:
210  return -1;
211  }
212 
213  if ((offset < 0) || (offset > bd->rbuffer_len))
214  return -1;
215  bd->rbuffer_cur = offset;
216  return 0;
217 }
218 
226 int TextSubtitleParser::decode(AVPacket *pkt)
227 {
228  AVSubtitle sub {};
229  int got_sub_ptr {0};
230 
231  int ret = avcodec_decode_subtitle2(m_decCtx, &sub, &got_sub_ptr, pkt);
232  if (ret < 0)
233  return ret;
234  if (!got_sub_ptr)
235  return -1;
236 
237  sub.start_display_time = av_q2d(m_stream->time_base) * pkt->dts * 1000;
238  sub.end_display_time = av_q2d(m_stream->time_base) * (pkt->dts + pkt->duration) * 1000;
239 
240  m_parent->AddAVSubtitle(sub, m_decCtx->codec_id == AV_CODEC_ID_XSUB, false);
241  m_count += 1;
242  return ret;
243 }
244 
245 void TextSubtitleParser::LoadSubtitles(bool inBackground)
246 {
247  std::string errbuf;
248 
249  if (inBackground)
250  {
252  {
255  start(m_loadHelper, "SubtitleLoadHelper");
256  }
257  return;
258  }
259 
260  // External subtitles are now presented as AV Subtitles.
264 
265  local_buffer_t sub_data {};
266  RemoteFileWrapper rfile(m_fileName/*, false, false, 0*/);
267 
268  LOG(VB_VBI, LOG_INFO,
269  QString("Preparing to load subtitle file %1").arg(m_fileName));
270  if (!rfile.isOpen())
271  {
272  LOG(VB_VBI, LOG_INFO,
273  QString("Failed to load subtitle file %1").arg(m_fileName));
274  return;
275  }
276  m_target->SetHasSubtitles(true);
278 
279  // Only reload if rfile.GetFileSize() has changed.
280  // RemoteFile::GetFileSize can return -1 on error.
281  off_t new_len = rfile.GetFileSize();
282  if (new_len < 0)
283  {
284  LOG(VB_VBI, LOG_INFO,
285  QString("Failed to get file size for %1").arg(m_fileName));
286  return;
287  }
288 
289  if (m_target->GetByteCount() == new_len)
290  {
291  LOG(VB_VBI, LOG_INFO,
292  QString("Filesize unchanged (%1), not reloading subs (%2)")
293  .arg(new_len).arg(m_fileName));
295  return;
296  }
297  LOG(VB_VBI, LOG_INFO,
298  QString("Preparing to read %1 subtitle bytes from %2")
299  .arg(new_len).arg(m_fileName));
300  m_target->SetByteCount(new_len);
301  sub_data.rbuffer_len = new_len;
302  sub_data.rbuffer_text = new char[sub_data.rbuffer_len + 1];
303  sub_data.rbuffer_cur = 0;
304 
305  // Slurp the entire file into a buffer.
306  int numread = rfile.Read(sub_data.rbuffer_text, sub_data.rbuffer_len);
307  LOG(VB_VBI, LOG_INFO,
308  QString("Finished reading %1 subtitle bytes (requested %2)")
309  .arg(numread).arg(new_len));
310 
311  // Create a format context and tie it to the file buffer.
312  AVFormatContext *fmt_ctx = avformat_alloc_context();
313  if (fmt_ctx == nullptr) {
314  LOG(VB_VBI, LOG_INFO, "Couldn't allocate format context");
315  return;
316  }
317  auto *avio_ctx_buffer = (uint8_t*)av_malloc(IO_BUFFER_SIZE);
318  if (avio_ctx_buffer == nullptr)
319  {
320  LOG(VB_VBI, LOG_INFO, "Couldn't allocate mamory for avio context");
321  avformat_free_context(fmt_ctx);
322  return;
323  }
324  fmt_ctx->pb = avio_alloc_context(avio_ctx_buffer, IO_BUFFER_SIZE,
325  0, &sub_data,
326  &read_packet, nullptr, &seek_packet);
327  if(int ret = avformat_open_input(&fmt_ctx, nullptr, nullptr, nullptr); ret < 0) {
328  LOG(VB_VBI, LOG_INFO, QString("Couldn't open input context %1")
329  .arg(av_make_error_stdstring(errbuf,ret)));
330  // FFmpeg frees context on error.
331  return;
332  }
333 
334  // Find the subtitle stream and its context.
335  if (!m_decCtx)
336  {
337  const AVCodec *codec {nullptr};
338  int stream_num = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &codec, 0);
339  if (stream_num < 0) {
340  LOG(VB_VBI, LOG_INFO, QString("Couldn't find subtitle stream. %1")
341  .arg(av_make_error_stdstring(errbuf,stream_num)));
342  avformat_free_context(fmt_ctx);
343  return;
344  }
345  m_stream = fmt_ctx->streams[stream_num];
346  if (m_stream == nullptr) {
347  LOG(VB_VBI, LOG_INFO, QString("Stream %1 is null").arg(stream_num));
348  avformat_free_context(fmt_ctx);
349  return;
350  }
351 
352  // Create a decoder for this subtitle stream context.
353  m_decCtx = avcodec_alloc_context3(codec);
354  if (!m_decCtx) {
355  LOG(VB_VBI, LOG_INFO, QString("Couldn't allocate decoder context"));
356  avformat_free_context(fmt_ctx);
357  return;
358  }
359  if (avcodec_open2(m_decCtx, codec, nullptr) < 0) {
360  LOG(VB_VBI, LOG_INFO, QString("Couldn't open decoder context"));
361  avcodec_free_context(&m_decCtx);
362  avformat_free_context(fmt_ctx);
363  return;
364  }
365  }
366 
367  /* decode until eof */
368  AVPacket *pkt = av_packet_alloc();
369  av_new_packet(pkt, 4096);
370  while (av_read_frame(fmt_ctx, pkt) >= 0)
371  {
372  int bytes {0};
373  while ((bytes = decode(pkt)) >= 0)
374  {
375  pkt->data += bytes;
376  pkt->size -= bytes;
377  }
378 
379  // reset buffer for next packet
380  pkt->data = pkt->buf->data;
381  pkt->size = pkt->buf->size;
382  }
383 
384  /* flush the decoder */
385  pkt->data = nullptr;
386  pkt->size = 0;
387  while (decode(pkt) >= 0)
388  {
389  }
390 
391  LOG(VB_GENERAL, LOG_INFO, QString("Loaded %1 %2 subtitles from '%3'")
392  .arg(m_count)
393  .arg(m_decCtx->codec->long_name, m_fileName));
395 
396  av_packet_free(&pkt);
397  m_stream = nullptr;
398  avformat_free_context(fmt_ctx);
399 }
400 
402 {
403  if (nullptr == m_decCtx)
404  return {};
405  return { reinterpret_cast<char*>(m_decCtx->subtitle_header),
406  m_decCtx->subtitle_header_size };
407 }
TextSubtitleParser::seek_packet
static int64_t seek_packet(void *opaque, int64_t offset, int whence)
Seek in the file buffer.
Definition: textsubtitleparser.cpp:192
TextSubtitleParser::decode
int decode(AVPacket *pkt)
Decode a single packet worth of data.
Definition: textsubtitleparser.cpp:226
TextSubtitleParser::m_parent
SubtitleReader * m_parent
Definition: textsubtitleparser.h:104
RemoteFileWrapper::GetFileSize
long long GetFileSize(void) const
Definition: textsubtitleparser.cpp:127
RemoteFileWrapper::m_remoteFile
RemoteFile * m_remoteFile
Definition: textsubtitleparser.cpp:146
TextSubtitleParser
Definition: textsubtitleparser.h:90
RemoteFile::GetFileSize
long long GetFileSize(void) const
GetFileSize: returns the remote file's size at the time it was first opened Will query the server in ...
Definition: remotefile.cpp:1084
textsubtitleparser.h
TextSubtitles::m_lastLoaded
QDateTime m_lastLoaded
Definition: textsubtitleparser.h:74
RemoteFileWrapper::operator=
RemoteFileWrapper & operator=(const RemoteFileWrapper &)=delete
TextSubtitles::SetByteCount
void SetByteCount(off_t count)
Definition: textsubtitleparser.h:57
RemoteFile
Definition: remotefile.h:17
TextSubtitleParser::m_stream
AVStream * m_stream
Definition: textsubtitleparser.h:110
SubtitleReader::AddAVSubtitle
bool AddAVSubtitle(AVSubtitle &subtitle, bool fix_position, bool allow_forced)
Definition: subtitlereader.cpp:36
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
RemoteFile::isOpen
bool isOpen(void) const
Definition: remotefile.cpp:238
subtitlereader.h
SubtitleReader::EnableAVSubtitles
void EnableAVSubtitles(bool enable)
Definition: subtitlereader.cpp:21
TextSubtitles::~TextSubtitles
~TextSubtitles() override
Definition: textsubtitleparser.cpp:150
RemoteFileWrapper::m_isRemote
bool m_isRemote
Definition: textsubtitleparser.cpp:145
TextSubtitleParser::read_packet
static int read_packet(void *opaque, uint8_t *buf, int buf_size)
Read data from the file buffer into the avio context buffer.
Definition: textsubtitleparser.cpp:176
TextSubtitleParser::m_count
uint32_t m_count
Definition: textsubtitleparser.h:112
SubtitleLoadHelper
Definition: textsubtitleparser.cpp:35
mythlogging.h
remotefile.h
local_buffer_t::rbuffer_cur
off_t rbuffer_cur
Definition: textsubtitleparser.cpp:166
SubtitleReader::EnableTextSubtitles
void EnableTextSubtitles(bool enable)
Definition: subtitlereader.cpp:26
RemoteFileWrapper::m_localFile
QFile * m_localFile
Definition: textsubtitleparser.cpp:147
RemoteFileWrapper
Definition: textsubtitleparser.cpp:89
SubtitleLoadHelper::s_wait
static QWaitCondition s_wait
Definition: textsubtitleparser.cpp:80
TextSubtitleParser::m_fileName
QString m_fileName
Definition: textsubtitleparser.h:107
TextSubtitles
Definition: textsubtitleparser.h:41
TextSubtitleParser::m_target
TextSubtitles * m_target
Definition: textsubtitleparser.h:106
SubtitleReader::EnableRawTextSubtitles
void EnableRawTextSubtitles(bool enable)
Definition: subtitlereader.cpp:31
TextSubtitleParser::m_decCtx
AVCodecContext * m_decCtx
Definition: textsubtitleparser.h:109
mythmediabuffer.h
SubtitleLoadHelper::s_lock
static QMutex s_lock
Definition: textsubtitleparser.cpp:79
SubtitleLoadHelper::s_loading
static QHash< TextSubtitles *, uint > s_loading
Definition: textsubtitleparser.cpp:81
off_t
#define off_t
Definition: mythiowrapper.cpp:241
mthreadpool.h
TextSubtitles::SetHasSubtitles
void SetHasSubtitles(bool hasSubs)
Definition: textsubtitleparser.h:66
RemoteFileWrapper::Read
int Read(void *data, int size)
Definition: textsubtitleparser.cpp:134
local_buffer_t::rbuffer_len
off_t rbuffer_len
Definition: textsubtitleparser.cpp:165
SubtitleLoadHelper::IsLoading
static bool IsLoading(TextSubtitles *target)
Definition: textsubtitleparser.cpp:57
SubtitleLoadHelper::m_parent
TextSubtitleParser * m_parent
Definition: textsubtitleparser.cpp:76
TextSubtitles::GetByteCount
off_t GetByteCount(void) const
Definition: textsubtitleparser.h:61
RemoteFileWrapper::~RemoteFileWrapper
~RemoteFileWrapper()
Definition: textsubtitleparser.cpp:114
musicbrainzngs.compat.bytes
bytes
Definition: compat.py:49
mythcorecontext.h
local_buffer_t
A local buffer that the entire file is slurped into.
Definition: textsubtitleparser.cpp:163
SubtitleLoadHelper::SubtitleLoadHelper
SubtitleLoadHelper(TextSubtitleParser *parent, TextSubtitles *target)
Definition: textsubtitleparser.cpp:38
TextSubtitleParser::~TextSubtitleParser
~TextSubtitleParser()
Definition: textsubtitleparser.cpp:169
SubtitleLoadHelper::run
void run(void) override
Definition: textsubtitleparser.cpp:47
TextSubtitles::SetLastLoaded
void SetLastLoaded(void)
Definition: textsubtitleparser.cpp:155
RemoteFileWrapper::isOpen
bool isOpen(void) const
Definition: textsubtitleparser.cpp:122
RemoteFileWrapper::RemoteFileWrapper
RemoteFileWrapper(const QString &filename)
Definition: textsubtitleparser.cpp:92
TextSubtitleParser::m_loadHelper
SubtitleLoadHelper * m_loadHelper
Definition: textsubtitleparser.h:105
IO_BUFFER_SIZE
#define IO_BUFFER_SIZE
TextSubtitles Copyright (c) 2006 by Pekka Jääskeläinen Distributed as part of MythTV under GPL v2 and...
Definition: textsubtitleparser.cpp:31
TextSubtitles::TextSubtitlesUpdated
void TextSubtitlesUpdated()
TextSubtitles::SetFilename
void SetFilename(const QString &fileName)
Definition: textsubtitleparser.h:51
SubtitleLoadHelper::m_target
TextSubtitles * m_target
Definition: textsubtitleparser.cpp:77
build_compdb.filename
filename
Definition: build_compdb.py:21
MThreadPool::globalInstance
static MThreadPool * globalInstance(void)
Definition: mthreadpool.cpp:317
TextSubtitleParser::LoadSubtitles
void LoadSubtitles(bool inBackground)
Definition: textsubtitleparser.cpp:245
SubtitleLoadHelper::Wait
static void Wait(TextSubtitles *target)
Definition: textsubtitleparser.cpp:63
mythaverror.h
TextSubtitleParser::GetSubHeader
QByteArray GetSubHeader()
Definition: textsubtitleparser.cpp:401
av_make_error_stdstring
char * av_make_error_stdstring(std::string &errbuf, int errnum)
Definition: mythaverror.cpp:41
RemoteFile::Read
int Read(void *data, int size)
Definition: remotefile.cpp:929
local_buffer_t::rbuffer_text
char * rbuffer_text
Definition: textsubtitleparser.cpp:164
TextSubtitles::m_lock
QRecursiveMutex m_lock
Definition: textsubtitleparser.h:83