implemented core subtitle support
This commit is contained in:
+25
-1
@@ -91,7 +91,8 @@ EncodingParams::EncodingParams() :
|
||||
video_threads_(0),
|
||||
video_is_image_sequence_(false),
|
||||
audio_enabled_(false),
|
||||
audio_bit_rate_(0)
|
||||
audio_bit_rate_(0),
|
||||
subtitles_enabled_(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -114,6 +115,13 @@ void EncodingParams::EnableAudio(const AudioParams &audio_params, const ExportCo
|
||||
audio_codec_ = acodec;
|
||||
}
|
||||
|
||||
void EncodingParams::EnableSubtitles(const SubtitleParams::Encoding &encoding, const ExportCodec::Codec &scodec)
|
||||
{
|
||||
subtitles_enabled_ = true;
|
||||
subtitles_encoding_ = encoding;
|
||||
subtitles_codec_ = scodec;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_option(const QString &key, const QString &value)
|
||||
{
|
||||
video_opts_.insert(key, value);
|
||||
@@ -219,6 +227,21 @@ const AudioParams &EncodingParams::audio_params() const
|
||||
return audio_params_;
|
||||
}
|
||||
|
||||
bool EncodingParams::subtitles_enabled() const
|
||||
{
|
||||
return subtitles_enabled_;
|
||||
}
|
||||
|
||||
SubtitleParams::Encoding EncodingParams::subtitles_encoding() const
|
||||
{
|
||||
return subtitles_encoding_;
|
||||
}
|
||||
|
||||
ExportCodec::Codec EncodingParams::subtitles_codec() const
|
||||
{
|
||||
return subtitles_codec_;
|
||||
}
|
||||
|
||||
const rational &EncodingParams::GetExportLength() const
|
||||
{
|
||||
return export_length_;
|
||||
@@ -310,6 +333,7 @@ Encoder::Type Encoder::GetTypeFromFormat(ExportFormat::Format f)
|
||||
case ExportFormat::kFormatFLAC:
|
||||
case ExportFormat::kFormatOgg:
|
||||
case ExportFormat::kFormatWebM:
|
||||
case ExportFormat::kFormatSRT:
|
||||
return kEncoderTypeFFmpeg;
|
||||
case ExportFormat::kFormatOpenEXR:
|
||||
case ExportFormat::kFormatPNG:
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
#include "codec/frame.h"
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "common/timerange.h"
|
||||
#include "node/block/subtitle/subtitle.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/subtitleparams.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -47,6 +49,7 @@ public:
|
||||
|
||||
void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec);
|
||||
void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec);
|
||||
void EnableSubtitles(const SubtitleParams::Encoding &encoding, const ExportCodec::Codec &scodec);
|
||||
|
||||
void set_video_option(const QString& key, const QString& value);
|
||||
void set_video_bit_rate(const int64_t& rate);
|
||||
@@ -91,6 +94,10 @@ public:
|
||||
audio_bit_rate_ = b;
|
||||
}
|
||||
|
||||
bool subtitles_enabled() const;
|
||||
SubtitleParams::Encoding subtitles_encoding() const;
|
||||
ExportCodec::Codec subtitles_codec() const;
|
||||
|
||||
const rational& GetExportLength() const;
|
||||
void SetExportLength(const rational& GetExportLength);
|
||||
|
||||
@@ -116,6 +123,10 @@ private:
|
||||
AudioParams audio_params_;
|
||||
int64_t audio_bit_rate_;
|
||||
|
||||
bool subtitles_enabled_;
|
||||
ExportCodec::Codec subtitles_codec_;
|
||||
SubtitleParams::Encoding subtitles_encoding_;
|
||||
|
||||
rational export_length_;
|
||||
|
||||
};
|
||||
@@ -174,6 +185,7 @@ public slots:
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) = 0;
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) = 0;
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) = 0;
|
||||
|
||||
virtual void Close() = 0;
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ QString ExportCodec::GetCodecName(ExportCodec::Codec c)
|
||||
return tr("Vorbis");
|
||||
case kCodecVP9:
|
||||
return tr("VP9");
|
||||
case kCodecSRT:
|
||||
return tr("SubRip SRT");
|
||||
case kCodecCount:
|
||||
break;
|
||||
}
|
||||
@@ -82,6 +84,7 @@ bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
|
||||
case kCodecOpus:
|
||||
case kCodecFLAC:
|
||||
case kCodecVP9:
|
||||
case kCodecSRT:
|
||||
return false;
|
||||
case kCodecOpenEXR:
|
||||
case kCodecPNG:
|
||||
@@ -94,4 +97,31 @@ bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
|
||||
return false;
|
||||
}
|
||||
|
||||
SubtitleParams::Encoding ExportCodec::GetDefaultSubtitleEncoding(Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case kCodecSRT:
|
||||
return SubtitleParams::kWindows1252;
|
||||
case kCodecDNxHD:
|
||||
case kCodecH264:
|
||||
case kCodecH265:
|
||||
case kCodecProRes:
|
||||
case kCodecMP2:
|
||||
case kCodecMP3:
|
||||
case kCodecAAC:
|
||||
case kCodecPCM:
|
||||
case kCodecVorbis:
|
||||
case kCodecOpus:
|
||||
case kCodecFLAC:
|
||||
case kCodecVP9:
|
||||
case kCodecOpenEXR:
|
||||
case kCodecPNG:
|
||||
case kCodecTIFF:
|
||||
case kCodecCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return SubtitleParams::kEncodingInvalid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "render/subtitleparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -52,6 +53,9 @@ public:
|
||||
kCodecVorbis,
|
||||
kCodecFLAC,
|
||||
|
||||
// Subtitle codecs
|
||||
kCodecSRT,
|
||||
|
||||
kCodecCount
|
||||
};
|
||||
|
||||
@@ -59,6 +63,8 @@ public:
|
||||
|
||||
static bool IsCodecAStillImage(Codec c);
|
||||
|
||||
static SubtitleParams::Encoding GetDefaultSubtitleEncoding(Codec c);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ QString ExportFormat::GetName(olive::ExportFormat::Format f)
|
||||
return tr("Ogg");
|
||||
case kFormatWebM:
|
||||
return tr("WebM");
|
||||
case kFormatSRT:
|
||||
return tr("SubRip SRT");
|
||||
|
||||
case kFormatCount:
|
||||
break;
|
||||
@@ -90,6 +92,8 @@ QString ExportFormat::GetExtension(ExportFormat::Format f)
|
||||
return QStringLiteral("ogg");
|
||||
case kFormatWebM:
|
||||
return QStringLiteral("webm");
|
||||
case kFormatSRT:
|
||||
return QStringLiteral("srt");
|
||||
case kFormatCount:
|
||||
break;
|
||||
}
|
||||
@@ -121,7 +125,7 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
|
||||
case kFormatAIFF:
|
||||
case kFormatMP3:
|
||||
case kFormatFLAC:
|
||||
return {};
|
||||
case kFormatSRT:
|
||||
case kFormatCount:
|
||||
break;
|
||||
}
|
||||
@@ -132,22 +136,19 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
|
||||
QList<ExportCodec::Codec> ExportFormat::GetAudioCodecs(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
// Video/audio formats
|
||||
case kFormatDNxHD:
|
||||
return {ExportCodec::kCodecPCM};
|
||||
case kFormatMatroska:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus};
|
||||
case kFormatMPEG4:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM};
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3};
|
||||
case kFormatQuickTime:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM};
|
||||
case kFormatWebM:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus};
|
||||
case kFormatOpenEXR:
|
||||
case kFormatPNG:
|
||||
case kFormatTIFF:
|
||||
return {};
|
||||
|
||||
|
||||
// Audio only formats
|
||||
case kFormatWAV:
|
||||
return {ExportCodec::kCodecPCM};
|
||||
case kFormatAIFF:
|
||||
@@ -159,9 +160,39 @@ QList<ExportCodec::Codec> ExportFormat::GetAudioCodecs(ExportFormat::Format f)
|
||||
case kFormatOgg:
|
||||
return {ExportCodec::kCodecOpus, ExportCodec::kCodecVorbis, ExportCodec::kCodecPCM};
|
||||
|
||||
|
||||
// Video only formats
|
||||
case kFormatOpenEXR:
|
||||
case kFormatPNG:
|
||||
case kFormatTIFF:
|
||||
case kFormatSRT:
|
||||
case kFormatCount:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<ExportCodec::Codec> ExportFormat::GetSubtitleCodecs(Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case kFormatDNxHD:
|
||||
case kFormatMPEG4:
|
||||
case kFormatOpenEXR:
|
||||
case kFormatQuickTime:
|
||||
case kFormatPNG:
|
||||
case kFormatTIFF:
|
||||
case kFormatWAV:
|
||||
case kFormatAIFF:
|
||||
case kFormatMP3:
|
||||
case kFormatFLAC:
|
||||
case kFormatOgg:
|
||||
case kFormatWebM:
|
||||
case kFormatCount:
|
||||
break;
|
||||
case kFormatMatroska:
|
||||
case kFormatSRT:
|
||||
return {ExportCodec::kCodecSRT};
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
kFormatFLAC,
|
||||
kFormatOgg,
|
||||
kFormatWebM,
|
||||
kFormatSRT,
|
||||
|
||||
kFormatCount
|
||||
};
|
||||
@@ -55,6 +56,7 @@ public:
|
||||
static QString GetExtension(Format f);
|
||||
static QList<ExportCodec::Codec> GetVideoCodecs(ExportFormat::Format f);
|
||||
static QList<ExportCodec::Codec> GetAudioCodecs(ExportFormat::Format f);
|
||||
static QList<ExportCodec::Codec> GetSubtitleCodecs(ExportFormat::Format f);
|
||||
|
||||
static QStringList GetPixelFormatsForCodec(Format f, ExportCodec::Codec c);
|
||||
|
||||
|
||||
@@ -272,7 +272,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
|
||||
|
||||
if (decoder
|
||||
&& (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
|
||||
|| avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) {
|
||||
|| avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO
|
||||
|| avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) {
|
||||
|
||||
if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
|
||||
@@ -367,7 +368,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
|
||||
|
||||
desc.AddVideoStream(stream);
|
||||
|
||||
} else {
|
||||
} else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
|
||||
// Create an audio stream object
|
||||
uint64_t channel_layout = avstream->codecpar->channel_layout;
|
||||
@@ -412,6 +413,10 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
|
||||
stream.set_duration(avstream->duration);
|
||||
desc.AddAudioStream(stream);
|
||||
|
||||
} else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
|
||||
qDebug() << "Subtitle probing: Stub";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ extern "C" {
|
||||
#include <QFile>
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -83,6 +84,7 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
|
||||
case ExportCodec::kCodecFLAC:
|
||||
case ExportCodec::kCodecOpus:
|
||||
case ExportCodec::kCodecVorbis:
|
||||
case ExportCodec::kCodecSRT:
|
||||
case ExportCodec::kCodecCount:
|
||||
// These are audio or invalid codecs and therefore have no pixel formats
|
||||
break;
|
||||
@@ -178,6 +180,13 @@ bool FFmpegEncoder::Open()
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize a subtitle stream if it's enabled
|
||||
if (params().subtitles_enabled()) {
|
||||
if (!InitializeStream(AVMEDIA_TYPE_SUBTITLE, &subtitle_stream_, &subtitle_codec_ctx_, params().subtitles_codec())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
av_dump_format(fmt_ctx_, 0, filename_c_str, 1);
|
||||
|
||||
// Open output file for writing
|
||||
@@ -347,6 +356,76 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString GetAssTime(const rational &time)
|
||||
{
|
||||
int64_t total_centiseconds = qRound64(time.toDouble() * 100);
|
||||
|
||||
int64_t cs = total_centiseconds % 100;
|
||||
int64_t ss = (total_centiseconds / 100) % 60;
|
||||
int64_t mm = (total_centiseconds / 6000) % 60;
|
||||
int64_t hh = total_centiseconds / 360000;
|
||||
|
||||
return QStringLiteral("%1:%2:%3.%4").arg(
|
||||
QString::number(hh),
|
||||
QStringLiteral("%1").arg(mm, 2, 10, QLatin1Char('0')),
|
||||
QStringLiteral("%1").arg(ss, 2, 10, QLatin1Char('0')),
|
||||
QStringLiteral("%1").arg(cs, 2, 10, QLatin1Char('0'))
|
||||
);
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block)
|
||||
{
|
||||
AVSubtitle subtitle;
|
||||
memset(&subtitle, 0, sizeof(subtitle));
|
||||
|
||||
AVSubtitleRect rect;
|
||||
memset(&rect, 0, sizeof(rect));
|
||||
|
||||
QString ass_line = QStringLiteral("Dialogue: 0,%1,%2,Default,,0,0,0,,%3").arg(
|
||||
GetAssTime(sub_block->in()),
|
||||
GetAssTime(sub_block->out()),
|
||||
sub_block->GetText()
|
||||
);
|
||||
|
||||
QByteArray utf8_sub = sub_block->GetText().toUtf8();
|
||||
QByteArray utf8_ass = ass_line.toUtf8();
|
||||
|
||||
rect.type = SUBTITLE_ASS;
|
||||
rect.text = utf8_sub.data();
|
||||
rect.ass = utf8_ass.data();
|
||||
|
||||
AVSubtitleRect *rect_array = ▭
|
||||
subtitle.num_rects = 1;
|
||||
subtitle.rects = &rect_array;
|
||||
|
||||
subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), av_get_time_base_q(), true);
|
||||
subtitle.end_display_time = qRound64(sub_block->length().toDouble() * 1000);
|
||||
|
||||
QVector<uint8_t> out_buf(1024 * 1024);
|
||||
|
||||
int sub_sz = avcodec_encode_subtitle(subtitle_codec_ctx_, out_buf.data(), out_buf.size(), &subtitle);
|
||||
if (sub_sz < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AVPacket *pkt = av_packet_alloc();
|
||||
|
||||
pkt->stream_index = subtitle_stream_->index;
|
||||
pkt->data = out_buf.data();
|
||||
pkt->size = sub_sz;
|
||||
pkt->pts = subtitle.pts;
|
||||
pkt->duration = subtitle.end_display_time;
|
||||
pkt->dts = pkt->pts;
|
||||
av_packet_rescale_ts(pkt, av_get_time_base_q(), subtitle_stream_->time_base);
|
||||
|
||||
av_interleaved_write_frame(fmt_ctx_, pkt);
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file)
|
||||
{
|
||||
@@ -464,7 +543,7 @@ void FFmpegEncoder::FFmpegError(const QString& context, int error_code)
|
||||
char err[1024];
|
||||
av_strerror(error_code, err, 1024);
|
||||
|
||||
QString formatted_err = tr("%1: %2 %3").arg(context, formatted_err, QString::number(error_code));
|
||||
QString formatted_err = tr("%1: %2 %3").arg(context, err, QString::number(error_code));
|
||||
qDebug() << formatted_err;
|
||||
SetError(formatted_err);
|
||||
}
|
||||
@@ -516,8 +595,8 @@ fail:
|
||||
|
||||
bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AVCodecContext** codec_ctx_ptr, const ExportCodec::Codec& codec)
|
||||
{
|
||||
if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) {
|
||||
SetError(tr("Cannot initialize a stream that is not a video or audio type"));
|
||||
if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO && type != AVMEDIA_TYPE_SUBTITLE) {
|
||||
SetError(tr("Cannot initialize a stream that is not a video, audio, or subtitle type"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -570,6 +649,9 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
case ExportCodec::kCodecFLAC:
|
||||
codec_id = AV_CODEC_ID_FLAC;
|
||||
break;
|
||||
case ExportCodec::kCodecSRT:
|
||||
codec_id = AV_CODEC_ID_SUBRIP;
|
||||
break;
|
||||
case ExportCodec::kCodecCount:
|
||||
break;
|
||||
}
|
||||
@@ -648,7 +730,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
} else if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
|
||||
// Assume audio stream
|
||||
codec_ctx->sample_rate = params().audio_params().sample_rate();
|
||||
@@ -661,6 +743,15 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
codec_ctx->bit_rate = params().audio_bit_rate();
|
||||
}
|
||||
|
||||
} else if (type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
|
||||
codec_ctx->time_base = av_get_time_base_q();
|
||||
|
||||
QByteArray ass_header = SubtitleParams::GenerateASSHeader().toUtf8();
|
||||
codec_ctx->subtitle_header = new uint8_t[ass_header.size()];
|
||||
memcpy(codec_ctx->subtitle_header, ass_header.constData(), ass_header.size());
|
||||
codec_ctx->subtitle_header_size = ass_header.size();
|
||||
|
||||
}
|
||||
|
||||
if (!SetupCodecContext(stream, codec_ctx, encoder)) {
|
||||
|
||||
@@ -46,6 +46,8 @@ public:
|
||||
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
|
||||
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
virtual VideoParams::Format GetDesiredPixelFormat() const override
|
||||
@@ -90,6 +92,9 @@ private:
|
||||
int audio_frame_offset_;
|
||||
int audio_write_count_;
|
||||
|
||||
AVStream* subtitle_stream_;
|
||||
AVCodecContext* subtitle_codec_ctx_;
|
||||
|
||||
bool open_;
|
||||
|
||||
};
|
||||
|
||||
@@ -68,6 +68,11 @@ bool OIIOEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::WriteSubtitle(const SubtitleBlock *sub_block)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void OIIOEncoder::Close()
|
||||
{
|
||||
// Do nothing
|
||||
|
||||
@@ -36,6 +36,7 @@ public slots:
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
|
||||
virtual bool WriteAudio(SampleBufferPtr audio) override;
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user