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 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
20 #include <QTextCodec>
21 #elif QT_VERSION < QT_VERSION_CHECK(6,3,0)
22 #include <QStringConverter>
23 #endif
24 
25 // MythTV
29 #include "libmythbase/remotefile.h"
31 #include "libmyth/mythaverror.h"
32 
35 
36 static constexpr uint32_t IO_BUFFER_SIZE { 32768 };
37 
38 // This background thread helper class is adapted from the
39 // RebuildSaver class in mythcommflagplayer.cpp.
40 class SubtitleLoadHelper : public QRunnable
41 {
42  public:
44  TextSubtitles *target)
45 
46  : m_parent(parent), m_target(target)
47  {
48  QMutexLocker locker(&s_lock);
50  }
51 
52  void run(void) override // QRunnable
53  {
54  m_parent->LoadSubtitles(false);
55 
56  QMutexLocker locker(&s_lock);
58  if (!s_loading[m_target])
59  s_wait.wakeAll();
60  }
61 
62  static bool IsLoading(TextSubtitles *target)
63  {
64  QMutexLocker locker(&s_lock);
65  return s_loading[target] != 0U;
66  }
67 
68  static void Wait(TextSubtitles *target)
69  {
70  QMutexLocker locker(&s_lock);
71  if (!s_loading[target])
72  return;
73  while (s_wait.wait(&s_lock))
74  {
75  if (!s_loading[target])
76  return;
77  }
78  }
79 
80  private:
82  TextSubtitles *m_target { nullptr };
83 
84  static QMutex s_lock;
85  static QWaitCondition s_wait;
86  static QHash<TextSubtitles*,uint> s_loading;
87 };
89 QWaitCondition SubtitleLoadHelper::s_wait;
90 QHash<TextSubtitles*,uint> SubtitleLoadHelper::s_loading;
91 
92 // Work around the fact that RemoteFile doesn't work when the target
93 // file is actually local.
95 {
96 public:
97  explicit RemoteFileWrapper(const QString &filename) {
98  // This test stolen from FileRingBuffer::OpenFile()
99  bool is_local =
100  (!filename.startsWith("/dev")) &&
101  ((filename.startsWith("/")) || QFile::exists(filename));
102  m_isRemote = !is_local;
103  if (m_isRemote)
104  {
105  m_localFile = nullptr;
106  m_remoteFile = new RemoteFile(filename, false, false, 0s);
107  }
108  else
109  {
110  m_remoteFile = nullptr;
111  m_localFile = new QFile(filename);
112  if (!m_localFile->open(QIODevice::ReadOnly))
113  {
114  delete m_localFile;
115  m_localFile = nullptr;
116  }
117  }
118  }
120  delete m_remoteFile;
121  delete m_localFile;
122  }
123 
124  RemoteFileWrapper(const RemoteFileWrapper &) = delete; // not copyable
125  RemoteFileWrapper &operator=(const RemoteFileWrapper &) = delete; // not copyable
126 
127  bool isOpen(void) const {
128  if (m_isRemote)
129  return m_remoteFile->isOpen();
130  return m_localFile;
131  }
132  long long GetFileSize(void) const {
133  if (m_isRemote)
134  return m_remoteFile->GetFileSize();
135  if (m_localFile)
136  return m_localFile->size();
137  return 0;
138  }
139  int Read(void *data, int size) {
140  if (m_isRemote)
141  return m_remoteFile->Read(data, size);
142  if (m_localFile)
143  {
144  QDataStream stream(m_localFile);
145  return stream.readRawData(static_cast<char*>(data), size);
146  }
147  return 0;
148  }
149 private:
152  QFile *m_localFile;
153 };
154 
156 {
158 }
159 
161 {
162  emit TextSubtitlesUpdated();
163  QMutexLocker locker(&m_lock);
164  m_lastLoaded = QDateTime::currentDateTimeUtc();
165 }
166 
172 };
173 
175  : m_parent(parent), m_target(target), m_fileName(std::move(fileName))
176 {
177  m_pkt = av_packet_alloc();
178 }
179 
181 {
182  avcodec_free_context(&m_decCtx);
183  avformat_free_context(m_fmtCtx);
184  av_packet_free(&m_pkt);
185  m_stream = nullptr;
186  delete m_loadHelper;
187 }
188 
190 int TextSubtitleParser::read_packet(void *opaque, uint8_t *buf, int buf_size)
191 {
192  auto *bd = (struct local_buffer_t *)opaque;
193 
194  /* copy internal buffer data to buf */
195  off_t remaining = bd->rbuffer_len - bd->rbuffer_cur;
196  if (remaining <= 0)
197  return AVERROR_EOF;
198  buf_size = FFMIN(buf_size, remaining);
199  memcpy(buf, bd->rbuffer_text + bd->rbuffer_cur, buf_size);
200  bd->rbuffer_cur += buf_size;
201 
202  return buf_size;
203 }
204 
206 int64_t TextSubtitleParser::seek_packet(void *opaque, int64_t offset, int whence)
207 {
208  auto *bd = (struct local_buffer_t *)opaque;
209 
210  switch (whence)
211  {
212  case SEEK_CUR:
213  offset = bd->rbuffer_cur + offset;
214  break;
215 
216  case SEEK_END:
217  offset = bd->rbuffer_len - offset;
218  break;
219 
220  case SEEK_SET:
221  break;
222 
223  default:
224  return -1;
225  }
226 
227  if ((offset < 0) || (offset > bd->rbuffer_len))
228  return -1;
229  bd->rbuffer_cur = offset;
230  return 0;
231 }
232 
241 {
242 
243  int ret = av_read_frame(m_fmtCtx, m_pkt);
244  if (ret < 0)
245  return ret;
246 
247  AVSubtitle sub {};
248  int got_sub_ptr {0};
249  ret = avcodec_decode_subtitle2(m_decCtx, &sub, &got_sub_ptr, m_pkt);
250  if (ret < 0)
251  return ret;
252  if (!got_sub_ptr)
253  return -1;
254 
255  sub.start_display_time = av_q2d(m_stream->time_base) * m_pkt->dts * 1000;
256  sub.end_display_time = av_q2d(m_stream->time_base) * (m_pkt->dts + m_pkt->duration) * 1000;
257 
258  m_parent->AddAVSubtitle(sub, m_decCtx->codec_id == AV_CODEC_ID_XSUB, false, false, true);
259  return ret;
260 }
261 
262 void TextSubtitleParser::LoadSubtitles(bool inBackground)
263 {
264  std::string errbuf;
265 
266  if (inBackground)
267  {
269  {
272  start(m_loadHelper, "SubtitleLoadHelper");
273  }
274  return;
275  }
276 
277  // External subtitles are now presented as AV Subtitles.
278  m_parent->EnableAVSubtitles(false);
281 
282  local_buffer_t sub_data {};
283  RemoteFileWrapper rfile(m_fileName/*, false, false, 0*/);
284 
285  LOG(VB_VBI, LOG_INFO,
286  QString("Preparing to load subtitle file %1").arg(m_fileName));
287  if (!rfile.isOpen())
288  {
289  LOG(VB_VBI, LOG_INFO,
290  QString("Failed to load subtitle file %1").arg(m_fileName));
291  return;
292  }
293  m_target->SetHasSubtitles(true);
295 
296  // Only reload if rfile.GetFileSize() has changed.
297  // RemoteFile::GetFileSize can return -1 on error.
298  off_t new_len = rfile.GetFileSize();
299  if (new_len < 0)
300  {
301  LOG(VB_VBI, LOG_INFO,
302  QString("Failed to get file size for %1").arg(m_fileName));
303  return;
304  }
305 
306  if (m_target->GetByteCount() == new_len)
307  {
308  LOG(VB_VBI, LOG_INFO,
309  QString("Filesize unchanged (%1), not reloading subs (%2)")
310  .arg(new_len).arg(m_fileName));
312  return;
313  }
314  LOG(VB_VBI, LOG_INFO,
315  QString("Preparing to read %1 subtitle bytes from %2")
316  .arg(new_len).arg(m_fileName));
317  m_target->SetByteCount(new_len);
318  sub_data.rbuffer_len = new_len;
319  sub_data.rbuffer_text = new char[sub_data.rbuffer_len + 1];
320  sub_data.rbuffer_cur = 0;
321 
322  // Slurp the entire file into a buffer.
323  int numread = rfile.Read(sub_data.rbuffer_text, sub_data.rbuffer_len);
324  LOG(VB_VBI, LOG_INFO,
325  QString("Finished reading %1 subtitle bytes (requested %2)")
326  .arg(numread).arg(new_len));
327  bool isUtf8 {false};
328  auto qba = QByteArray::fromRawData(sub_data.rbuffer_text,
329  sub_data.rbuffer_len);
330 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
331  QTextCodec *textCodec = QTextCodec::codecForUtfText(qba, nullptr);
332  isUtf8 = (textCodec != nullptr);
333 #elif QT_VERSION < QT_VERSION_CHECK(6,3,0)
334  auto qba_encoding = QStringConverter::encodingForData(qba);
335  isUtf8 = qba_encoding.has_value()
336  && (qba_encoding.value() == QStringConverter::Utf8);
337 #else
338  isUtf8 = qba.isValidUtf8();
339 #endif
340 
341  // Create a format context and tie it to the file buffer.
342  m_fmtCtx = avformat_alloc_context();
343  if (m_fmtCtx == nullptr) {
344  LOG(VB_VBI, LOG_INFO, "Couldn't allocate format context");
345  return;
346  }
347  auto *avio_ctx_buffer = (uint8_t*)av_malloc(IO_BUFFER_SIZE);
348  if (avio_ctx_buffer == nullptr)
349  {
350  LOG(VB_VBI, LOG_INFO, "Couldn't allocate memory for avio context");
351  avformat_free_context(m_fmtCtx);
352  return;
353  }
354  m_fmtCtx->pb = avio_alloc_context(avio_ctx_buffer, IO_BUFFER_SIZE,
355  0, &sub_data,
356  &read_packet, nullptr, &seek_packet);
357  if (int ret = avformat_open_input(&m_fmtCtx, nullptr, nullptr, nullptr); ret < 0) {
358  LOG(VB_VBI, LOG_INFO, QString("Couldn't open input context %1")
359  .arg(av_make_error_stdstring(errbuf,ret)));
360  // FFmpeg frees context on error.
361  return;
362  }
363 
364  // Find the subtitle stream and its context.
365  QString encoding {"utf-8"};
366  if (!m_decCtx)
367  {
368  const AVCodec *codec {nullptr};
369  int stream_num = av_find_best_stream(m_fmtCtx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &codec, 0);
370  if (stream_num < 0) {
371  LOG(VB_VBI, LOG_INFO, QString("Couldn't find subtitle stream. %1")
372  .arg(av_make_error_stdstring(errbuf,stream_num)));
373  avformat_free_context(m_fmtCtx);
374  return;
375  }
376  m_stream = m_fmtCtx->streams[stream_num];
377  if (m_stream == nullptr) {
378  LOG(VB_VBI, LOG_INFO, QString("Stream %1 is null").arg(stream_num));
379  avformat_free_context(m_fmtCtx);
380  return;
381  }
382 
383  // Create a decoder for this subtitle stream context.
384  m_decCtx = avcodec_alloc_context3(codec);
385  if (!m_decCtx) {
386  LOG(VB_VBI, LOG_INFO, QString("Couldn't allocate decoder context"));
387  avformat_free_context(m_fmtCtx);
388  return;
389  }
390 
391  // Ask FFmpeg to convert subtitles to utf-8.
392  AVDictionary *dict = nullptr;
393  if (!isUtf8)
394  {
395  encoding = gCoreContext->GetSetting("SubtitleCodec", "utf-8");
396  if (encoding != "utf-8")
397  {
398  LOG(VB_VBI, LOG_INFO,
399  QString("Converting from %1 to utf-8.").arg(encoding));
400  av_dict_set(&dict, "sub_charenc", qPrintable(encoding), 0);
401  }
402  }
403  if (avcodec_open2(m_decCtx, codec, &dict) < 0) {
404  LOG(VB_VBI, LOG_INFO,
405  QString("Couldn't open decoder context for encoding %1").arg(encoding));
406  avcodec_free_context(&m_decCtx);
407  avformat_free_context(m_fmtCtx);
408  return;
409  }
410  }
411 
412  LOG(VB_GENERAL, LOG_INFO, QString("Loaded %2 '%3' subtitles from %4")
413  .arg(encoding, m_decCtx->codec->long_name, m_fileName));
415 }
416 
418 {
419  if (nullptr == m_decCtx)
420  return {};
421  return { reinterpret_cast<char*>(m_decCtx->subtitle_header),
422  m_decCtx->subtitle_header_size };
423 }
424 
425 void TextSubtitleParser::SeekFrame(int64_t ts, int flags)
426 {
427  if (av_seek_frame(m_fmtCtx, -1, ts, flags) < 0)
428  {
429  LOG(VB_PLAYBACK, LOG_INFO,
430  QString("TextSubtitleParser av_seek_frame(fmtCtx, -1, %1, %2) -- error")
431  .arg(ts).arg(flags));
432  }
433 }
TextSubtitleParser::seek_packet
static int64_t seek_packet(void *opaque, int64_t offset, int whence)
Seek in the file buffer.
Definition: textsubtitleparser.cpp:206
TextSubtitleParser::m_parent
SubtitleReader * m_parent
Definition: textsubtitleparser.h:101
RemoteFileWrapper::GetFileSize
long long GetFileSize(void) const
Definition: textsubtitleparser.cpp:132
RemoteFileWrapper::m_remoteFile
RemoteFile * m_remoteFile
Definition: textsubtitleparser.cpp:151
TextSubtitleParser
Definition: textsubtitleparser.h:86
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
SubtitleReader::AddAVSubtitle
bool AddAVSubtitle(AVSubtitle &subtitle, bool fix_position, bool is_selected_forced_track, bool allow_forced, bool isExternal)
Definition: subtitlereader.cpp:58
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:108
SubtitleReader
Definition: subtitlereader.h:42
TextSubtitleParser::SeekFrame
void SeekFrame(int64_t ts, int flags)
Definition: textsubtitleparser.cpp:425
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:33
TextSubtitles::~TextSubtitles
~TextSubtitles() override
Definition: textsubtitleparser.cpp:155
hardwareprofile.software.dict
dictionary dict
Definition: software.py:55
RemoteFileWrapper::m_isRemote
bool m_isRemote
Definition: textsubtitleparser.cpp:150
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:190
SubtitleLoadHelper
Definition: textsubtitleparser.cpp:40
mythlogging.h
remotefile.h
local_buffer_t::rbuffer_cur
off_t rbuffer_cur
Definition: textsubtitleparser.cpp:171
SubtitleReader::EnableTextSubtitles
void EnableTextSubtitles(bool enable)
Definition: subtitlereader.cpp:38
RemoteFileWrapper::m_localFile
QFile * m_localFile
Definition: textsubtitleparser.cpp:152
RemoteFileWrapper
Definition: textsubtitleparser.cpp:94
SubtitleLoadHelper::s_wait
static QWaitCondition s_wait
Definition: textsubtitleparser.cpp:85
TextSubtitleParser::m_fileName
QString m_fileName
Definition: textsubtitleparser.h:104
TextSubtitles
Definition: textsubtitleparser.h:41
TextSubtitleParser::TextSubtitleParser
TextSubtitleParser(SubtitleReader *parent, QString fileName, TextSubtitles *target)
Definition: textsubtitleparser.cpp:174
TextSubtitleParser::m_target
TextSubtitles * m_target
Definition: textsubtitleparser.h:103
SubtitleReader::EnableRawTextSubtitles
void EnableRawTextSubtitles(bool enable)
Definition: subtitlereader.cpp:43
TextSubtitleParser::m_decCtx
AVCodecContext * m_decCtx
Definition: textsubtitleparser.h:107
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
mythmediabuffer.h
SubtitleLoadHelper::s_lock
static QMutex s_lock
Definition: textsubtitleparser.cpp:84
SubtitleLoadHelper::s_loading
static QHash< TextSubtitles *, uint > s_loading
Definition: textsubtitleparser.cpp:86
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:139
local_buffer_t::rbuffer_len
off_t rbuffer_len
Definition: textsubtitleparser.cpp:170
SubtitleLoadHelper::IsLoading
static bool IsLoading(TextSubtitles *target)
Definition: textsubtitleparser.cpp:62
SubtitleLoadHelper::m_parent
TextSubtitleParser * m_parent
Definition: textsubtitleparser.cpp:81
TextSubtitles::GetByteCount
off_t GetByteCount(void) const
Definition: textsubtitleparser.h:61
RemoteFileWrapper::~RemoteFileWrapper
~RemoteFileWrapper()
Definition: textsubtitleparser.cpp:119
mythcorecontext.h
local_buffer_t
A local buffer that the entire file is slurped into.
Definition: textsubtitleparser.cpp:168
SubtitleLoadHelper::SubtitleLoadHelper
SubtitleLoadHelper(TextSubtitleParser *parent, TextSubtitles *target)
Definition: textsubtitleparser.cpp:43
std
Definition: mythchrono.h:23
TextSubtitleParser::~TextSubtitleParser
~TextSubtitleParser()
Definition: textsubtitleparser.cpp:180
SubtitleLoadHelper::run
void run(void) override
Definition: textsubtitleparser.cpp:52
TextSubtitles::SetLastLoaded
void SetLastLoaded(void)
Definition: textsubtitleparser.cpp:160
RemoteFileWrapper::isOpen
bool isOpen(void) const
Definition: textsubtitleparser.cpp:127
RemoteFileWrapper::RemoteFileWrapper
RemoteFileWrapper(const QString &filename)
Definition: textsubtitleparser.cpp:97
TextSubtitleParser::m_loadHelper
SubtitleLoadHelper * m_loadHelper
Definition: textsubtitleparser.h:102
TextSubtitles::TextSubtitlesUpdated
void TextSubtitlesUpdated()
TextSubtitleParser::m_fmtCtx
AVFormatContext * m_fmtCtx
Definition: textsubtitleparser.h:106
TextSubtitles::SetFilename
void SetFilename(const QString &fileName)
Definition: textsubtitleparser.h:51
IO_BUFFER_SIZE
static constexpr uint32_t IO_BUFFER_SIZE
TextSubtitles Copyright (c) 2006 by Pekka Jääskeläinen Distributed as part of MythTV under GPL v2 and...
Definition: textsubtitleparser.cpp:36
TextSubtitleParser::ReadNextSubtitle
int ReadNextSubtitle(void)
Read the next subtitle in the AV stream.
Definition: textsubtitleparser.cpp:240
SubtitleLoadHelper::m_target
TextSubtitles * m_target
Definition: textsubtitleparser.cpp:82
TextSubtitleParser::m_pkt
AVPacket * m_pkt
Definition: textsubtitleparser.h:109
build_compdb.filename
filename
Definition: build_compdb.py:21
MThreadPool::globalInstance
static MThreadPool * globalInstance(void)
Definition: mthreadpool.cpp:307
TextSubtitleParser::LoadSubtitles
void LoadSubtitles(bool inBackground)
Definition: textsubtitleparser.cpp:262
SubtitleLoadHelper::Wait
static void Wait(TextSubtitles *target)
Definition: textsubtitleparser.cpp:68
mythaverror.h
TextSubtitleParser::GetSubHeader
QByteArray GetSubHeader()
Definition: textsubtitleparser.cpp:417
av_make_error_stdstring
char * av_make_error_stdstring(std::string &errbuf, int errnum)
Definition: mythaverror.cpp:41
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:898
RemoteFile::Read
int Read(void *data, int size)
Definition: remotefile.cpp:929
local_buffer_t::rbuffer_text
char * rbuffer_text
Definition: textsubtitleparser.cpp:169
TextSubtitles::m_lock
QRecursiveMutex m_lock
Definition: textsubtitleparser.h:80