Expand Up
@@ -16,6 +16,11 @@
#include < QRunnable>
#include < QFile>
#include < QDataStream>
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
#include < QTextCodec>
#elif QT_VERSION < QT_VERSION_CHECK(6,3,0)
#include < QStringConverter>
#endif
// MythTV
#include " libmythbase/mthreadpool.h"
Expand Down
Expand Up
@@ -166,9 +171,19 @@ struct local_buffer_t {
off_t rbuffer_cur;
};
TextSubtitleParser::TextSubtitleParser (SubtitleReader *parent, QString fileName, TextSubtitles *target)
: m_parent(parent), m_target(target), m_fileName(std::move(fileName))
{
m_pkt = av_packet_alloc ();
av_new_packet (m_pkt, 4096 );
}
TextSubtitleParser::~TextSubtitleParser ()
{
avcodec_free_context (&m_decCtx);
avformat_free_context (m_fmtCtx);
av_packet_free (&m_pkt);
m_stream = nullptr ;
delete m_loadHelper;
}
Expand Down
Expand Up
@@ -216,29 +231,35 @@ int64_t TextSubtitleParser::seek_packet(void *opaque, int64_t offset, int whence
return 0 ;
}
// / \brief Decode a single packet worth of data .
// / \brief Read the next subtitle in the AV stream .
// /
// / av_read_frame guarantees that pkt->pts, pkt->dts and pkt->duration
// / are always set to correct values in AVStream.time_base units (and
// / guessed if the format cannot provide them). pkt->pts can be
// / AV_NOPTS_VALUE if the video format has B-frames, so it is better to
// / rely on pkt->dts if you do not decompress the payload.
int TextSubtitleParser::decode (AVPacket *pkt )
int TextSubtitleParser::ReadNextSubtitle ( void )
{
// reset buffer
m_pkt->data = m_pkt->buf ->data ;
m_pkt->size = m_pkt->buf ->size ;
int ret = av_read_frame (m_fmtCtx, m_pkt);
if (ret < 0 )
return ret;
AVSubtitle sub {};
int got_sub_ptr {0 };
int ret = avcodec_decode_subtitle2 (m_decCtx, &sub, &got_sub_ptr, pkt);
ret = avcodec_decode_subtitle2 (m_decCtx, &sub, &got_sub_ptr, m_pkt);
if (ret < 0 )
return ret;
if (!got_sub_ptr)
return -1 ;
sub.start_display_time = av_q2d (m_stream->time_base ) * pkt ->dts * 1000 ;
sub.end_display_time = av_q2d (m_stream->time_base ) * (pkt ->dts + pkt ->duration ) * 1000 ;
sub.start_display_time = av_q2d (m_stream->time_base ) * m_pkt ->dts * 1000 ;
sub.end_display_time = av_q2d (m_stream->time_base ) * (m_pkt ->dts + m_pkt ->duration ) * 1000 ;
m_parent->AddAVSubtitle (sub, m_decCtx->codec_id == AV_CODEC_ID_XSUB, false );
m_count += 1 ;
return ret;
}
Expand Down
Expand Up
@@ -307,95 +328,94 @@ void TextSubtitleParser::LoadSubtitles(bool inBackground)
LOG (VB_VBI, LOG_INFO,
QString (" Finished reading %1 subtitle bytes (requested %2)" )
.arg (numread).arg (new_len));
bool isUtf8 {false };
auto qba = QByteArray::fromRawData (sub_data.rbuffer_text ,
sub_data.rbuffer_len );
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
QTextCodec *textCodec = QTextCodec::codecForUtfText (qba, nullptr );
isUtf8 = (textCodec != nullptr );
#elif QT_VERSION < QT_VERSION_CHECK(6,3,0)
auto qba_encoding = QStringConverter::encodingForData (qba);
isUtf8 = qba_encoding.has_value
&& (qba_encoding.value == QStringConverter::Utf8);
#else
isUtf8 = qba.isValidUtf8 ();
#endif
// Create a format context and tie it to the file buffer.
AVFormatContext *fmt_ctx = avformat_alloc_context ();
if (fmt_ctx == nullptr ) {
m_fmtCtx = avformat_alloc_context ();
if (m_fmtCtx == nullptr ) {
LOG (VB_VBI, LOG_INFO, " Couldn't allocate format context" );
return ;
}
auto *avio_ctx_buffer = (uint8_t *)av_malloc (IO_BUFFER_SIZE);
if (avio_ctx_buffer == nullptr )
{
LOG (VB_VBI, LOG_INFO, " Couldn't allocate mamory for avio context" );
avformat_free_context (fmt_ctx );
LOG (VB_VBI, LOG_INFO, " Couldn't allocate memory for avio context" );
avformat_free_context (m_fmtCtx );
return ;
}
fmt_ctx ->pb = avio_alloc_context (avio_ctx_buffer, IO_BUFFER_SIZE,
m_fmtCtx ->pb = avio_alloc_context (avio_ctx_buffer, IO_BUFFER_SIZE,
0 , &sub_data,
&read_packet, nullptr , &seek_packet);
if (int ret = avformat_open_input (&fmt_ctx , nullptr , nullptr , nullptr ); ret < 0 ) {
if (int ret = avformat_open_input (&m_fmtCtx , nullptr , nullptr , nullptr ); ret < 0 ) {
LOG (VB_VBI, LOG_INFO, QString (" Couldn't open input context %1" )
.arg (av_make_error_stdstring (errbuf,ret)));
// FFmpeg frees context on error.
return ;
}
// Find the subtitle stream and its context.
QString encoding {" utf-8" };
if (!m_decCtx)
{
const AVCodec *codec {nullptr };
int stream_num = av_find_best_stream (fmt_ctx , AVMEDIA_TYPE_SUBTITLE, -1 , -1 , &codec, 0 );
int stream_num = av_find_best_stream (m_fmtCtx , AVMEDIA_TYPE_SUBTITLE, -1 , -1 , &codec, 0 );
if (stream_num < 0 ) {
LOG (VB_VBI, LOG_INFO, QString (" Couldn't find subtitle stream. %1" )
.arg (av_make_error_stdstring (errbuf,stream_num)));
avformat_free_context (fmt_ctx );
avformat_free_context (m_fmtCtx );
return ;
}
m_stream = fmt_ctx ->streams [stream_num];
m_stream = m_fmtCtx ->streams [stream_num];
if (m_stream == nullptr ) {
LOG (VB_VBI, LOG_INFO, QString (" Stream %1 is null" ).arg (stream_num));
avformat_free_context (fmt_ctx );
avformat_free_context (m_fmtCtx );
return ;
}
// Create a decoder for this subtitle stream context.
m_decCtx = avcodec_alloc_context3 (codec);
if (!m_decCtx) {
LOG (VB_VBI, LOG_INFO, QString (" Couldn't allocate decoder context" ));
avformat_free_context (fmt_ctx);
return ;
}
if (avcodec_open2 (m_decCtx, codec, nullptr ) < 0 ) {
LOG (VB_VBI, LOG_INFO, QString (" Couldn't open decoder context" ));
avcodec_free_context (&m_decCtx);
avformat_free_context (fmt_ctx);
avformat_free_context (m_fmtCtx);
return ;
}
}
/* decode until eof */
AVPacket *pkt = av_packet_alloc ();
av_new_packet (pkt, 4096 );
while (av_read_frame (fmt_ctx, pkt) >= 0 )
{
int bytes {0 };
while ((bytes = decode (pkt)) >= 0 )
// Ask FFmpeg to convert subtitles to utf-8.
AVDictionary *dict = nullptr ;
if (!isUtf8)
{
pkt->data += bytes;
pkt->size -= bytes;
encoding = gCoreContext ->GetSetting (" SubtitleCodec" , " utf-8" );
if (encoding != " utf-8" )
{
LOG (VB_VBI, LOG_INFO,
QString (" Converting from %1 to utf-8." ).arg (encoding));
av_dict_set (&dict, " sub_charenc" , qPrintable (encoding), 0 );
}
}
if (avcodec_open2 (m_decCtx, codec, &dict) < 0 ) {
LOG (VB_VBI, LOG_INFO,
QString (" Couldn't open decoder context for encoding %1" ).arg (encoding));
avcodec_free_context (&m_decCtx);
avformat_free_context (m_fmtCtx);
return ;
}
// reset buffer for next packet
pkt->data = pkt->buf ->data ;
pkt->size = pkt->buf ->size ;
}
/* flush the decoder */
pkt->data = nullptr ;
pkt->size = 0 ;
while (decode (pkt) >= 0 )
{
}
LOG (VB_GENERAL, LOG_INFO, QString (" Loaded %1 %2 subtitles from '%3'" )
.arg (m_count)
.arg (m_decCtx->codec ->long_name , m_fileName));
LOG (VB_GENERAL, LOG_INFO, QString (" Loaded %2 '%3' subtitles from %4" )
.arg (encoding, m_decCtx->codec ->long_name , m_fileName));
m_target->SetLastLoaded ();
av_packet_free (&pkt);
m_stream = nullptr ;
avformat_free_context (fmt_ctx);
}
QByteArray TextSubtitleParser::GetSubHeader ()
Expand All
@@ -405,3 +425,13 @@ QByteArray TextSubtitleParser::GetSubHeader()
return { reinterpret_cast <char *>(m_decCtx->subtitle_header ),
m_decCtx->subtitle_header_size };
}
void TextSubtitleParser::SeekFrame (int64_t ts, int flags)
{
if (av_seek_frame (m_fmtCtx, -1 , ts, flags) < 0 )
{
LOG (VB_PLAYBACK, LOG_INFO,
QString (" TextSubtitleParser av_seek_frame(fmtCtx, -1, %1, %2) -- error" )
.arg (ts).arg (flags));
}
}