From ba9ed9984935ed85ea62aeaffdc5b3fccb8c27b5 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 16 May 2021 10:29:05 +1000 Subject: [PATCH 01/12] footage: check files for changes --- app/codec/decoder.h | 2 ++ app/codec/ffmpeg/ffmpegdecoder.cpp | 5 ----- app/codec/ffmpeg/ffmpegdecoder.h | 2 +- app/codec/oiio/oiiodecoder.cpp | 5 ----- app/codec/oiio/oiiodecoder.h | 2 +- app/node/project/footage/footage.cpp | 26 +++++++++++++++++--------- app/render/rendercache.h | 13 ++++++++++++- app/render/rendermanager.cpp | 6 +++--- app/render/renderprocessor.cpp | 13 ++++++++----- 9 files changed, 44 insertions(+), 30 deletions(-) diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 7b61c2b35..5a7382444 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -44,6 +44,8 @@ namespace olive { class Decoder; using DecoderPtr = std::shared_ptr; +#define DECODER_DEFAULT_DESTRUCTOR(x) virtual ~x() override {CloseInternal();} + /** * @brief A decoder's is the main class for bringing external media into Olive * diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index b78c643f6..cb1152992 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -60,11 +60,6 @@ FFmpegDecoder::FFmpegDecoder() : { } -FFmpegDecoder::~FFmpegDecoder() -{ - CloseInternal(); -} - bool FFmpegDecoder::OpenInternal() { if (instance_.Open(stream().filename().toUtf8(), stream().stream())) { diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index fc87043fb..ca7fd6215 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -53,7 +53,7 @@ public: FFmpegDecoder(); // Destructor - virtual ~FFmpegDecoder() override; + DECODER_DEFAULT_DESTRUCTOR(FFmpegDecoder) virtual QString id() const override; diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index e47ca9cde..b82a4fae4 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -41,11 +41,6 @@ OIIODecoder::OIIODecoder() : { } -OIIODecoder::~OIIODecoder() -{ - CloseInternal(); -} - QString OIIODecoder::id() const { return QStringLiteral("oiio"); diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 30abd7b9e..f1b798adf 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -34,7 +34,7 @@ class OIIODecoder : public Decoder public: OIIODecoder(); - virtual ~OIIODecoder() override; + DECODER_DEFAULT_DESTRUCTOR(OIIODecoder) virtual QString id() const override; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 46ee75b83..d826476e3 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -20,7 +20,7 @@ #include "footage.h" -#include +#include #include #include @@ -56,6 +56,11 @@ Footage::Footage(const QString &filename) : Clear(); set_filename(filename); + + QTimer *check_timer = new QTimer(this); + check_timer->setInterval(5000); + connect(check_timer, &QTimer::timeout, this, &Footage::CheckFootage); + check_timer->start(); } void Footage::Retranslate() @@ -511,17 +516,20 @@ void Footage::UpdateTooltip() void Footage::CheckFootage() { - QString fn = filename(); + // Don't check files if not the active window + if (qApp->activeWindow()) { + QString fn = filename(); - if (!fn.isEmpty()) { - QFileInfo info(fn); + if (!fn.isEmpty()) { + QFileInfo info(fn); - qint64 current_file_timestamp = info.lastModified().toMSecsSinceEpoch(); + qint64 current_file_timestamp = info.lastModified().toMSecsSinceEpoch(); - if (current_file_timestamp != timestamp()) { - // File has changed! - set_timestamp(current_file_timestamp); - InvalidateAll(kFilenameInput); + if (current_file_timestamp != timestamp()) { + // File has changed! + set_timestamp(current_file_timestamp); + InvalidateAll(kFilenameInput); + } } } } diff --git a/app/render/rendercache.h b/app/render/rendercache.h index 7efc0e039..09436a9b6 100644 --- a/app/render/rendercache.h +++ b/app/render/rendercache.h @@ -39,7 +39,18 @@ private: }; -using DecoderCache = RenderCache; +struct DecoderPair { + DecoderPair() + { + decoder = nullptr; + last_modified = 0; + } + + DecoderPtr decoder; + qint64 last_modified; +}; + +using DecoderCache = RenderCache; using ShaderCache = RenderCache; } diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 8cd3ae7c4..c72212a27 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -91,10 +91,10 @@ void RenderManager::ClearOldDecoders() qint64 min_age = QDateTime::currentMSecsSinceEpoch() - kDecoderMaximumInactivity; for (auto it=decoder_cache_->begin(); it!=decoder_cache_->end(); ) { - DecoderPtr decoder = it.value(); + DecoderPair decoder = it.value(); - if (decoder->GetLastAccessedTime() < min_age) { - decoder->Close(); + if (decoder.decoder->GetLastAccessedTime() < min_age) { + decoder.decoder->Close(); it = decoder_cache_->erase(it); } else { it++; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index db49bff6a..e66833713 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -187,13 +187,16 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c QMutexLocker locker(decoder_cache_->mutex()); - DecoderPtr decoder = decoder_cache_->value(stream); + DecoderPair decoder = decoder_cache_->value(stream); - if (!decoder) { + qint64 file_last_modified = QFileInfo(stream.filename()).lastModified().toMSecsSinceEpoch(); + + if (!decoder.decoder || decoder.last_modified != file_last_modified) { // No decoder - decoder = Decoder::CreateFromID(decoder_id); + decoder.decoder = Decoder::CreateFromID(decoder_id); + decoder.last_modified = file_last_modified; - if (decoder->Open(stream)) { + if (decoder.decoder->Open(stream)) { decoder_cache_->insert(stream, decoder); } else { qWarning() << "Failed to open decoder for" << stream.filename() @@ -202,7 +205,7 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c } } - return decoder; + return decoder.decoder; } void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache *still_image_cache, DecoderCache *decoder_cache, ShaderCache *shader_cache, QVariant default_shader) From f34254625153ea92b0c82869a17d7d42a583ad86 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 16 May 2021 10:36:33 +1000 Subject: [PATCH 02/12] code: improved node custom save hierarchy --- app/node/output/track/track.cpp | 2 ++ app/node/output/viewer/viewer.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 1d7e7a224..6aebb508c 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -155,6 +155,8 @@ bool Track::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uin void Track::SaveCustom(QXmlStreamWriter *writer) const { + super::SaveCustom(writer); + writer->writeTextElement(QStringLiteral("height"), QString::number(GetTrackHeight())); } diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index c1c8715f4..072086693 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -497,12 +497,14 @@ bool ViewerOutput::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_da timeline_points_.Load(reader); return true; } else { - return LoadCustom(reader, xml_node_data, version, cancelled); + return super::LoadCustom(reader, xml_node_data, version, cancelled); } } void ViewerOutput::SaveCustom(QXmlStreamWriter *writer) const { + super::SaveCustom(writer); + // Write TimelinePoints writer->writeStartElement(QStringLiteral("points")); timeline_points_.Save(writer); From 597f35bd00cc4c45746b05efcf74211edb7c94b0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 16 May 2021 10:36:52 +1000 Subject: [PATCH 03/12] marker: actually add marker after loading it Fixes #1632 --- app/timeline/timelinemarker.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 5c5f2cfa5..4086e6983 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -104,17 +104,19 @@ void TimelineMarkerList::Load(QXmlStreamReader *reader) while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("marker")) { QString name; - TimeRange range; + rational in, out; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("name")) { name = attr.value().toString(); } else if (attr.name() == QStringLiteral("in")) { - range.set_in(rational::fromString(attr.value().toString())); + in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - range.set_out(rational::fromString(attr.value().toString())); + out = rational::fromString(attr.value().toString()); } } + + AddMarker(TimeRange(in, out), name); } reader->skipCurrentElement(); From 4adaedb3049340ac6a71d000db0340d873621eb7 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 16 May 2021 18:31:03 +1000 Subject: [PATCH 04/12] ffmpegdecoder: fixed issue with interlaced footage in some situations --- app/codec/ffmpeg/ffmpegdecoder.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index cb1152992..5c49a6ae1 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -649,15 +649,16 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c { int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); - if (params.dst_interlacing == VideoParams::kInterlaceNone && params.src_interlacing != VideoParams::kInterlaceNone) { + const int64_t min_seek = -instance_.avstream()->start_time; + int64_t seek_ts = target_ts; + bool still_seeking = false; + + if (params.src_interlacing != VideoParams::kInterlaceNone) { // If we are de-interlacing, the timebase is doubled because we get one frame per field, so we // double the target timestamp too target_ts *= 2; } - int64_t seek_ts = target_ts; - bool still_seeking = false; - if (time != kAnyTimecode) { // If the frame wasn't in the frame cache, see if this frame cache is too old to use if (cached_frames_.isEmpty() @@ -665,7 +666,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c ClearFrameCache(); instance_.Seek(seek_ts); - if (seek_ts == 0) { + if (seek_ts == min_seek) { cache_at_zero_ = true; } @@ -703,9 +704,9 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c // We'll only be here if the frame cache was emptied earlier if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame->pts > target_ts)) { - seek_ts = qMax(static_cast(0), seek_ts - second_ts_); + seek_ts = qMax(min_seek, seek_ts - second_ts_); instance_.Seek(seek_ts); - if (seek_ts == 0) { + if (seek_ts == min_seek) { cache_at_zero_ = true; } continue; From cc21ed37a7cf1749f6ea04b99e24a1c4dc169915 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 18 May 2021 12:42:47 +1000 Subject: [PATCH 05/12] implemented core subtitle support --- app/codec/encoder.cpp | 26 ++- app/codec/encoder.h | 12 ++ app/codec/exportcodec.cpp | 30 +++ app/codec/exportcodec.h | 6 + app/codec/exportformat.cpp | 47 ++++- app/codec/exportformat.h | 2 + app/codec/ffmpeg/ffmpegdecoder.cpp | 9 +- app/codec/ffmpeg/ffmpegencoder.cpp | 99 +++++++++- app/codec/ffmpeg/ffmpegencoder.h | 5 + app/codec/oiio/oiioencoder.cpp | 5 + app/codec/oiio/oiioencoder.h | 1 + app/dialog/export/CMakeLists.txt | 10 +- app/dialog/export/export.cpp | 41 ++-- app/dialog/export/export.h | 5 + app/dialog/export/exportsubtitlestab.cpp | 47 +++++ app/dialog/export/exportsubtitlestab.h | 57 ++++++ app/dialog/export/exportvideotab.cpp | 2 +- app/node/block/CMakeLists.txt | 1 + app/node/block/clip/clip.cpp | 10 +- app/node/block/clip/clip.h | 2 +- app/node/block/subtitle/CMakeLists.txt | 22 +++ app/node/block/subtitle/subtitle.cpp | 62 ++++++ app/node/block/subtitle/subtitle.h | 60 ++++++ app/node/factory.cpp | 3 + app/node/factory.h | 1 + app/render/CMakeLists.txt | 2 + app/render/subtitleparams.cpp | 188 +++++++++++++++++++ app/render/subtitleparams.h | 55 ++++++ app/task/export/export.cpp | 14 +- app/task/export/export.h | 2 + app/task/precache/precachetask.cpp | 1 + app/task/render/render.cpp | 52 ++++- app/task/render/render.h | 6 +- app/tool/tool.h | 5 + app/widget/timelinewidget/timelineundo.h | 13 +- app/widget/timelinewidget/timelinewidget.cpp | 28 ++- app/widget/timelinewidget/tool/add.cpp | 14 +- 37 files changed, 898 insertions(+), 47 deletions(-) create mode 100644 app/dialog/export/exportsubtitlestab.cpp create mode 100644 app/dialog/export/exportsubtitlestab.h create mode 100644 app/node/block/subtitle/CMakeLists.txt create mode 100644 app/node/block/subtitle/subtitle.cpp create mode 100644 app/node/block/subtitle/subtitle.h create mode 100644 app/render/subtitleparams.cpp create mode 100644 app/render/subtitleparams.h diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 5392fff9d..9bedc6b8f 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -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: diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 5bcb8738b..49466c6a5 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -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; diff --git a/app/codec/exportcodec.cpp b/app/codec/exportcodec.cpp index f4955743c..039537651 100644 --- a/app/codec/exportcodec.cpp +++ b/app/codec/exportcodec.cpp @@ -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; +} + } diff --git a/app/codec/exportcodec.h b/app/codec/exportcodec.h index 70e5cb74b..ee1a3a1c0 100644 --- a/app/codec/exportcodec.h +++ b/app/codec/exportcodec.h @@ -25,6 +25,7 @@ #include #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); + }; } diff --git a/app/codec/exportformat.cpp b/app/codec/exportformat.cpp index 6b5ec1784..3b53371e0 100644 --- a/app/codec/exportformat.cpp +++ b/app/codec/exportformat.cpp @@ -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 ExportFormat::GetVideoCodecs(ExportFormat::Format f) case kFormatAIFF: case kFormatMP3: case kFormatFLAC: - return {}; + case kFormatSRT: case kFormatCount: break; } @@ -132,22 +136,19 @@ QList ExportFormat::GetVideoCodecs(ExportFormat::Format f) QList 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 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 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 {}; diff --git a/app/codec/exportformat.h b/app/codec/exportformat.h index 50cf73685..36be9db58 100644 --- a/app/codec/exportformat.h +++ b/app/codec/exportformat.h @@ -47,6 +47,7 @@ public: kFormatFLAC, kFormatOgg, kFormatWebM, + kFormatSRT, kFormatCount }; @@ -55,6 +56,7 @@ public: static QString GetExtension(Format f); static QList GetVideoCodecs(ExportFormat::Format f); static QList GetAudioCodecs(ExportFormat::Format f); + static QList GetSubtitleCodecs(ExportFormat::Format f); static QStringList GetPixelFormatsForCodec(Format f, ExportCodec::Codec c); diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 5c49a6ae1..70250122a 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -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"; + } } diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index a5590105a..75afb11f0 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -27,6 +27,7 @@ extern "C" { #include #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 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)) { diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 24d50e7fc..cae844fc1 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -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_; }; diff --git a/app/codec/oiio/oiioencoder.cpp b/app/codec/oiio/oiioencoder.cpp index ff9d9191f..f872a758b 100644 --- a/app/codec/oiio/oiioencoder.cpp +++ b/app/codec/oiio/oiioencoder.cpp @@ -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 diff --git a/app/codec/oiio/oiioencoder.h b/app/codec/oiio/oiioencoder.h index 555efe067..7325cdf49 100644 --- a/app/codec/oiio/oiioencoder.h +++ b/app/codec/oiio/oiioencoder.h @@ -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; diff --git a/app/dialog/export/CMakeLists.txt b/app/dialog/export/CMakeLists.txt index 0b2c76203..845d4ab91 100644 --- a/app/dialog/export/CMakeLists.txt +++ b/app/dialog/export/CMakeLists.txt @@ -18,13 +18,15 @@ add_subdirectory(codec) set(OLIVE_SOURCES ${OLIVE_SOURCES} - dialog/export/export.h dialog/export/export.cpp - dialog/export/exportadvancedvideodialog.h + dialog/export/export.h dialog/export/exportadvancedvideodialog.cpp - dialog/export/exportaudiotab.h + dialog/export/exportadvancedvideodialog.h dialog/export/exportaudiotab.cpp - dialog/export/exportvideotab.h + dialog/export/exportaudiotab.h + dialog/export/exportsubtitlestab.cpp + dialog/export/exportsubtitlestab.h dialog/export/exportvideotab.cpp + dialog/export/exportvideotab.h PARENT_SCOPE ) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 0c3fceed8..c3d31fda9 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -124,29 +124,30 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : QHBoxLayout* av_enabled_layout = new QHBoxLayout(); video_enabled_ = new QCheckBox(tr("Export Video")); - video_enabled_->setChecked(true); av_enabled_layout->addWidget(video_enabled_); audio_enabled_ = new QCheckBox(tr("Export Audio")); - audio_enabled_->setChecked(true); av_enabled_layout->addWidget(audio_enabled_); + subtitles_enabled_ = new QCheckBox(tr("Export Subtitle")); + av_enabled_layout->addWidget(subtitles_enabled_); + preferences_layout->addLayout(av_enabled_layout, row, 0, 1, 4); row++; preferences_tabs_ = new QTabWidget(); - QScrollArea* video_area = new QScrollArea(); + color_manager_ = viewer_node_->project()->color_manager(); video_tab_ = new ExportVideoTab(color_manager_); - video_area->setWidgetResizable(true); - video_area->setWidget(video_tab_); - preferences_tabs_->addTab(video_area, tr("Video")); - QScrollArea* audio_area = new QScrollArea(); + AddPreferencesTab(video_tab_, tr("Video")); + audio_tab_ = new ExportAudioTab(); - audio_area->setWidgetResizable(true); - audio_area->setWidget(audio_tab_); - preferences_tabs_->addTab(audio_area, tr("Audio")); + AddPreferencesTab(audio_tab_, tr("Audio")); + + subtitle_tab_ = new ExportSubtitlesTab(); + AddPreferencesTab(subtitle_tab_, tr("Subtitles")); + preferences_layout->addWidget(preferences_tabs_, row, 0, 1, 4); row++; @@ -268,9 +269,9 @@ rational ExportDialog::GetSelectedTimebase() const void ExportDialog::StartExport() { - if (!video_enabled_->isChecked() && !audio_enabled_->isChecked()) { + if (!video_enabled_->isChecked() && !audio_enabled_->isChecked() && !subtitles_enabled_->isChecked()) { QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid parameters"), - tr("Both video and audio are disabled. There's nothing to export.")); + tr("Video, audio, and subtitles are disabled. There's nothing to export.")); return; } @@ -392,6 +393,14 @@ void ExportDialog::closeEvent(QCloseEvent *e) QDialog::closeEvent(e); } +void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title) +{ + QScrollArea* scroll_area = new QScrollArea(); + scroll_area->setWidgetResizable(true); + scroll_area->setWidget(inner_widget); + preferences_tabs_->addTab(scroll_area, title); +} + void ExportDialog::BrowseFilename() { ExportFormat::Format f = GetSelectedFormat(); @@ -437,6 +446,10 @@ void ExportDialog::FormatChanged(int index) bool has_audio_codecs = audio_tab_->SetFormat(current_format); audio_enabled_->setChecked(has_audio_codecs); audio_enabled_->setEnabled(has_audio_codecs); + + bool has_subtitle_codecs = subtitle_tab_->SetFormat(current_format); + subtitles_enabled_->setChecked(has_subtitle_codecs); + subtitles_enabled_->setEnabled(has_subtitle_codecs); } void ExportDialog::ResolutionChanged() @@ -549,6 +562,10 @@ ExportParams ExportDialog::GenerateParams() const params.set_audio_bit_rate(audio_tab_->bit_rate_slider()->GetValue() * 1000); } + if (subtitles_enabled_->isChecked()) { + params.EnableSubtitles(subtitle_tab_->GetSubtitleEncoding(), subtitle_tab_->GetSubtitleCodec()); + } + return params; } diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 99f6f88f6..58d6e16c0 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -30,6 +30,7 @@ #include "codec/exportcodec.h" #include "codec/exportformat.h" #include "exportaudiotab.h" +#include "exportsubtitlestab.h" #include "exportvideotab.h" #include "task/export/export.h" #include "widget/viewer/viewer.h" @@ -50,6 +51,8 @@ protected: virtual void closeEvent(QCloseEvent *e) override; private: + void AddPreferencesTab(QWidget *inner_widget, const QString &title); + void LoadPresets(); void SetDefaultFilename(); @@ -75,6 +78,7 @@ private: QCheckBox* video_enabled_; QCheckBox* audio_enabled_; + QCheckBox* subtitles_enabled_; ViewerWidget* preview_viewer_; QLineEdit* filename_edit_; @@ -82,6 +86,7 @@ private: ExportVideoTab* video_tab_; ExportAudioTab* audio_tab_; + ExportSubtitlesTab* subtitle_tab_; double video_aspect_ratio_; diff --git a/app/dialog/export/exportsubtitlestab.cpp b/app/dialog/export/exportsubtitlestab.cpp new file mode 100644 index 000000000..5c535b60b --- /dev/null +++ b/app/dialog/export/exportsubtitlestab.cpp @@ -0,0 +1,47 @@ +#include "exportsubtitlestab.h" + +#include +#include + +namespace olive { + +ExportSubtitlesTab::ExportSubtitlesTab(QWidget *parent) : + QWidget(parent) +{ + QVBoxLayout* outer_layout = new QVBoxLayout(this); + + QGridLayout* layout = new QGridLayout(); + outer_layout->addLayout(layout); + + int row = 0; + + layout->addWidget(new QLabel(tr("Codec:")), row, 0); + + codec_combobox_ = new QComboBox(); + layout->addWidget(codec_combobox_, row, 1); + + row++; + + layout->addWidget(new QLabel(tr("Encoding:")), row, 0); + + encoding_combobox_ = new QComboBox(); + for (int i=0; iaddItem(SubtitleParams::GetEncodingName(static_cast(i)), i); + } + layout->addWidget(encoding_combobox_, row, 1); + + outer_layout->addStretch(); +} + +int ExportSubtitlesTab::SetFormat(ExportFormat::Format format) +{ + auto scodecs = ExportFormat::GetSubtitleCodecs(format); + setEnabled(!scodecs.isEmpty()); + codec_combobox_->clear(); + foreach (ExportCodec::Codec scodec, scodecs) { + codec_combobox_->addItem(ExportCodec::GetCodecName(scodec), scodec); + } + return scodecs.size(); +} + +} diff --git a/app/dialog/export/exportsubtitlestab.h b/app/dialog/export/exportsubtitlestab.h new file mode 100644 index 000000000..f3beaa10b --- /dev/null +++ b/app/dialog/export/exportsubtitlestab.h @@ -0,0 +1,57 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef EXPORTSUBTITLESTAB_H +#define EXPORTSUBTITLESTAB_H + +#include + +#include "codec/exportformat.h" +#include "render/subtitleparams.h" + +namespace olive { + +class ExportSubtitlesTab : public QWidget +{ +public: + ExportSubtitlesTab(QWidget *parent = nullptr); + + int SetFormat(ExportFormat::Format format); + + ExportCodec::Codec GetSubtitleCodec() + { + return static_cast(codec_combobox_->currentData().toInt()); + } + + SubtitleParams::Encoding GetSubtitleEncoding() + { + return static_cast(encoding_combobox_->currentData().toInt()); + } + +private: + QComboBox *codec_combobox_; + + QComboBox *encoding_combobox_; + +}; + +} + +#endif // EXPORTSUBTITLESTAB_H diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 6c9dea587..9c5b98b57 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -74,7 +74,7 @@ QWidget* ExportVideoTab::SetupResolutionSection() int row = 0; QGroupBox* resolution_group = new QGroupBox(); - resolution_group->setTitle(tr("Basic")); + resolution_group->setTitle(tr("General")); QGridLayout* layout = new QGridLayout(resolution_group); diff --git a/app/node/block/CMakeLists.txt b/app/node/block/CMakeLists.txt index 38eb10739..eb19f639d 100644 --- a/app/node/block/CMakeLists.txt +++ b/app/node/block/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(clip) add_subdirectory(gap) +add_subdirectory(subtitle) add_subdirectory(transition) set(OLIVE_SOURCES diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 954736a9c..fe8cdc6e2 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -26,9 +26,11 @@ namespace olive { const QString ClipBlock::kBufferIn = QStringLiteral("buffer_in"); -ClipBlock::ClipBlock() +ClipBlock::ClipBlock(bool create_buffer_in) { - AddInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); + if (create_buffer_in) { + AddInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); + } } Node *ClipBlock::copy() const @@ -108,7 +110,9 @@ void ClipBlock::Retranslate() { super::Retranslate(); - SetInputName(kBufferIn, tr("Buffer")); + if (HasInputWithID(kBufferIn)) { + SetInputName(kBufferIn, tr("Buffer")); + } } void ClipBlock::Hash(const QString &out, QCryptographicHash &hash, const rational &time, const VideoParams &video_params) const diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 770be01ea..ef542526f 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -32,7 +32,7 @@ class ClipBlock : public Block { Q_OBJECT public: - ClipBlock(); + ClipBlock(bool create_buffer_in = true); NODE_DEFAULT_DESTRUCTOR(ClipBlock) diff --git a/app/node/block/subtitle/CMakeLists.txt b/app/node/block/subtitle/CMakeLists.txt new file mode 100644 index 000000000..81a745ab4 --- /dev/null +++ b/app/node/block/subtitle/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/block/subtitle/subtitle.cpp + node/block/subtitle/subtitle.h + PARENT_SCOPE +) diff --git a/app/node/block/subtitle/subtitle.cpp b/app/node/block/subtitle/subtitle.cpp new file mode 100644 index 000000000..3f5a9a212 --- /dev/null +++ b/app/node/block/subtitle/subtitle.cpp @@ -0,0 +1,62 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "subtitle.h" + +namespace olive { + +#define super ClipBlock + +const QString SubtitleBlock::kTextIn = QStringLiteral("text_in"); + +SubtitleBlock::SubtitleBlock() : + super(false) +{ + AddInput(kTextIn, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); +} + +Node *SubtitleBlock::copy() const +{ + return new SubtitleBlock(); +} + +QString SubtitleBlock::Name() const +{ + return tr("Subtitle"); +} + +QString SubtitleBlock::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.subtitle"); +} + +QString SubtitleBlock::Description() const +{ + return tr("A time-based node representing a single subtitle element for a certain period of time."); +} + +void SubtitleBlock::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextIn, tr("Text")); +} + +} diff --git a/app/node/block/subtitle/subtitle.h b/app/node/block/subtitle/subtitle.h new file mode 100644 index 000000000..c88a2bfc1 --- /dev/null +++ b/app/node/block/subtitle/subtitle.h @@ -0,0 +1,60 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef SUBTITLEBLOCK_H +#define SUBTITLEBLOCK_H + +#include "node/block/clip/clip.h" + +namespace olive { + +class SubtitleBlock : public ClipBlock +{ + Q_OBJECT +public: + SubtitleBlock(); + + NODE_DEFAULT_DESTRUCTOR(SubtitleBlock) + + virtual Node* copy() const override; + + virtual QString Name() const override; + virtual QString id() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + static const QString kTextIn; + + QString GetText() const + { + return GetStandardValue(kTextIn).toString(); + } + + void SetText(const QString &text) + { + SetStandardValue(kTextIn, text); + } + +}; + +} + +#endif // SUBTITLEBLOCK_H diff --git a/app/node/factory.cpp b/app/node/factory.cpp index c203f5a2d..9e18f0a99 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -26,6 +26,7 @@ #include "audio/volume/volume.h" #include "block/clip/clip.h" #include "block/gap/gap.h" +#include "block/subtitle/subtitle.h" #include "block/transition/crossdissolve/crossdissolvetransition.h" #include "block/transition/diptocolor/diptocolortransition.h" #include "distort/crop/cropdistortnode.h" @@ -236,6 +237,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new ValueNode(); case kTimeRemapNode: return new TimeRemapNode(); + case kSubtitleBlock: + return new SubtitleBlock(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index 5aa738237..8ba811cd3 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -58,6 +58,7 @@ public: kProjectSequence, kValueNode, kTimeRemapNode, + kSubtitleBlock, // Count value kInternalNodeCount diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 4d71d23cf..6f6898154 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -53,6 +53,8 @@ set(OLIVE_SOURCES render/renderprocessor.h render/shadercode.h render/stillimagecache.h + render/subtitleparams.cpp + render/subtitleparams.h render/texture.cpp render/texture.h render/videoparams.cpp diff --git a/app/render/subtitleparams.cpp b/app/render/subtitleparams.cpp new file mode 100644 index 000000000..d864e58e8 --- /dev/null +++ b/app/render/subtitleparams.cpp @@ -0,0 +1,188 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "subtitleparams.h" + +#include + +namespace olive { + +QString SubtitleParams::GetEncodingName(Encoding encoding) +{ + switch (encoding) { + case kISO8859_1: + return QCoreApplication::translate("SubtitleParams", "ASCII/ISO 8859-1"); + case kWindows1252: + return QCoreApplication::translate("SubtitleParams", "Windows-1252"); + case kUTF8: + return QCoreApplication::translate("SubtitleParams", "UTF-8"); + case kUTF8WithBOM: + return QCoreApplication::translate("SubtitleParams", "UTF-8 with BOM"); + case kUTF16LE: + return QCoreApplication::translate("SubtitleParams", "UTF-16LE"); + case kUTF16BE: + return QCoreApplication::translate("SubtitleParams", "UTF-16BE"); + case kEncodingInvalid: + case kEncodingCount: + break; + } + + return QCoreApplication::translate("SubtitleParams", "Unknown"); +} + +bool SubtitleParams::EncodingHasUnicodeBOM(Encoding encoding) +{ + switch (encoding) { + case kUTF8WithBOM: + case kUTF16LE: + case kUTF16BE: + return true; + + case kISO8859_1: + case kWindows1252: + case kUTF8: + case kEncodingInvalid: + case kEncodingCount: + break; + } + + return false; +} + +QByteArray SubtitleParams::GetUnicodeBOM(Encoding encoding) +{ + QByteArray arr; + + if (encoding == kUTF8WithBOM) { + arr.resize(3); + arr[0] = 0xEF; + arr[1] = 0xBB; + arr[2] = 0xBF; + } else if (encoding == kUTF16LE) { + arr.resize(2); + arr[0] = 0xFF; + arr[1] = 0xFE; + } else if (encoding == kUTF16BE) { + arr.resize(2); + arr[0] = 0xFE; + arr[1] = 0xFF; + } + + return arr; +} + +const char *SubtitleParams::GetQTextStreamCodec(Encoding encoding) +{ + switch (encoding) { + case SubtitleParams::kISO8859_1: + return "ISO 8859-1"; + case SubtitleParams::kWindows1252: + return "Windows-1252"; + case SubtitleParams::kUTF8: + case SubtitleParams::kUTF8WithBOM: + return "UTF-8"; + case SubtitleParams::kUTF16LE: + return "UTF-16LE"; + case SubtitleParams::kUTF16BE: + return "UTF-16BE"; + case SubtitleParams::kEncodingInvalid: + case SubtitleParams::kEncodingCount: + break; + } + + return nullptr; +} + +QString SubtitleParams::GenerateASSHeader() +{ + // NOTE: We'll probably implement more customization as we support ASS better. Right now, we only + // natively support SRT and only make this header because FFmpeg requires it. + static const int kAssDefaultPlayResX = 384; + static const int kAssDefaultPlayResY = 288; + static const QString kAssDefaultFont = QStringLiteral("Arial"); + static const int kAssDefaultFontSize = 16; + static const int kAssDefaultPrimaryColor = 0xFFFFFF; // White + static const int kAssDefaultSecondaryColor = 0xFFFFFF; // White + static const int kAssDefaultOutlineColor = 0x000000; // Black + static const int kAssDefaultBackColor = 0x000000; // Black + static const int kAssBold = 0; + static const int kAssItalic = 0; + static const int kAssUnderline = 0; + static const int kAssStrike = 0; + static const int kAssBorderStyle = 1; + static const int kAssAlignment = 2; + + static const QString kFormatHeader = QStringLiteral( + "[Script Info]\r\n" + "; Script generated by %1 %2\r\n" + "ScriptType: v4.00+\r\n" + "PlayResX: %3\r\n" + "PlayResY: %4\r\n" + "ScaledBorderAndShadow: yes\r\n" + "\r\n" + + /* ASSv4 header */ + "[V4+ Styles]\r\n" + "Format: Name, " + "Fontname, Fontsize, " + "PrimaryColour, SecondaryColour, OutlineColour, BackColour, " + "Bold, Italic, Underline, StrikeOut, " + "ScaleX, ScaleY, " + "Spacing, Angle, " + "BorderStyle, Outline, Shadow, " + "Alignment, MarginL, MarginR, MarginV, " + "Encoding\r\n" + + "Style: " + "Default," /* Name */ + "%5,%6," /* Font{name,size} */ + "&H%7,&H%8,&H%9,&H%10," /* {Primary,Secondary,Outline,Back}Colour */ + "%11,%12,%13,%14," /* Bold, Italic, Underline, StrikeOut */ + "100,100," /* Scale{X,Y} */ + "0,0," /* Spacing, Angle */ + "%15,1,0," /* BorderStyle, Outline, Shadow */ + "%16,10,10,10," /* Alignment, Margin[LRV] */ + "0\r\n" /* Encoding */ + + "\r\n" + "[Events]\r\n" + "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n" + ); + + return kFormatHeader.arg(QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QString::number(kAssDefaultPlayResX), + QString::number(kAssDefaultPlayResY), + kAssDefaultFont, + QString::number(kAssDefaultFontSize), + QString::number(kAssDefaultPrimaryColor, 16), + QString::number(kAssDefaultSecondaryColor, 16), + QString::number(kAssDefaultOutlineColor, 16), + QString::number(kAssDefaultBackColor, 16), + QString::number(kAssBold), + QString::number(kAssItalic), + QString::number(kAssUnderline), + QString::number(kAssStrike), + QString::number(kAssBorderStyle), + QString::number(kAssAlignment) + ); +} + +} diff --git a/app/render/subtitleparams.h b/app/render/subtitleparams.h new file mode 100644 index 000000000..241cccc33 --- /dev/null +++ b/app/render/subtitleparams.h @@ -0,0 +1,55 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef SUBTITLEPARAMS_H +#define SUBTITLEPARAMS_H + +#include + +namespace olive { + +class SubtitleParams { +public: + enum Encoding { + kEncodingInvalid = -1, + kISO8859_1, + kWindows1252, + kUTF8, + kUTF8WithBOM, + kUTF16LE, + kUTF16BE, + kEncodingCount + }; + + static QString GetEncodingName(Encoding encoding); + + static bool EncodingHasUnicodeBOM(Encoding encoding); + + static QByteArray GetUnicodeBOM(Encoding encoding); + + static const char *GetQTextStreamCodec(Encoding encoding); + + static QString GenerateASSHeader(); + +}; + +} + +#endif // SUBTITLEPARAMS_H diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index a2a0675c1..814c31125 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -56,7 +56,7 @@ bool ExportTask::Run() } if (!encoder_->Open()) { - SetError(tr("Failed to open file")); + SetError(tr("Failed to open file: %1").arg(encoder_->GetError())); encoder_->deleteLater(); return false; } @@ -102,6 +102,7 @@ bool ExportTask::Run() // Start render process TimeRangeList video_range, audio_range; + TimeRange subtitle_range; if (params_.video_enabled()) { video_range = {range}; @@ -111,7 +112,11 @@ bool ExportTask::Run() audio_range = {range}; } - Render(color_manager_, video_range, audio_range, RenderMode::kOnline, nullptr, + if (params_.subtitles_enabled()) { + subtitle_range = range; + } + + Render(color_manager_, video_range, audio_range, subtitle_range, RenderMode::kOnline, nullptr, video_force_size, video_force_matrix, encoder_->GetDesiredPixelFormat(), color_processor_); @@ -191,6 +196,11 @@ void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples } } +void ExportTask::EncodeSubtitle(const SubtitleBlock *sub) +{ + encoder_->WriteSubtitle(sub); +} + void ExportTask::WriteAudioLoop(const TimeRange& time, SampleBufferPtr samples) { encoder_->WriteAudio(samples); diff --git a/app/task/export/export.h b/app/task/export/export.h index b02691b54..7f1c0859a 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -42,6 +42,8 @@ protected: virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override; + virtual void EncodeSubtitle(const SubtitleBlock *sub) override; + virtual bool TwoStepFrameRendering() const override { return false; diff --git a/app/task/precache/precachetask.cpp b/app/task/precache/precachetask.cpp index 679082a2a..1f6adc80e 100644 --- a/app/task/precache/precachetask.cpp +++ b/app/task/precache/precachetask.cpp @@ -67,6 +67,7 @@ bool PreCacheTask::Run() Render(project_->color_manager(), video_range, TimeRangeList(), + TimeRange(), RenderMode::kOnline, viewer()->video_frame_cache()); diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index a4bd28aad..ee559fe2c 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -21,6 +21,7 @@ #include "render.h" #include "common/timecodefunctions.h" +#include "node/project/sequence/sequence.h" #include "render/rendermanager.h" namespace olive { @@ -40,7 +41,7 @@ RenderTask::~RenderTask() bool RenderTask::Render(ColorManager* manager, const TimeRangeList& video_range, - const TimeRangeList &audio_range, + const TimeRangeList &audio_range, const TimeRange &subtitle_range, RenderMode::Mode mode, FrameHashCache* cache, const QSize &force_size, const QMatrix4x4 &force_matrix, VideoParams::Format force_format, @@ -129,6 +130,50 @@ bool RenderTask::Render(ColorManager* manager, mode, cache, force_size, force_matrix, force_format, force_color_output); } + // Subtitle loop, loops over all blocks in sequence on all tracks + if (!subtitle_range.length().isNull()) { + Sequence *sequence = dynamic_cast(viewer_); + if (sequence) { + TrackList *list = sequence->track_list(Track::kSubtitle); + QVector block_indexes(list->GetTrackCount(), 0); + + QVector tracks_to_push; + do { + tracks_to_push.clear(); + + for (int i=0; iGetTrackAt(i); + int &this_block_index = block_indexes[i]; + if (this_block_index >= this_track->Blocks().size()) { + continue; + } + Block *this_block = this_track->Blocks().at(this_block_index); + + Track *compare_track = tracks_to_push.isEmpty() ? nullptr : list->GetTrackAt(tracks_to_push.first()); + const int &compare_block_index = tracks_to_push.isEmpty() ? -1 : block_indexes.at(tracks_to_push.first()); + Block *compare_block = compare_track ? compare_track->Blocks().at(compare_block_index) : nullptr; + if (!compare_track || compare_block->in() >= this_block->in()) { + if (compare_track && compare_block->in() != this_block->in()) { + tracks_to_push.clear(); + } + tracks_to_push.append(i); + } + } + + for (int i=0; iGetTrackAt(tracks_to_push.at(i)); + Block *this_block = this_track->Blocks().at(block_indexes.at(tracks_to_push.at(i))); + + if (const SubtitleBlock *sub = dynamic_cast(this_block)) { + EncodeSubtitle(sub); + } + + block_indexes[tracks_to_push.at(i)]++; + } + } while (!tracks_to_push.isEmpty()); + } + } + finished_watcher_mutex_.lock(); while (!IsCancelled()) { @@ -238,6 +283,11 @@ void RenderTask::DownloadFrame(QThread *thread, FramePtr frame, const QByteArray hash)); } +void RenderTask::EncodeSubtitle(const SubtitleBlock *subtitle) +{ + Q_UNUSED(subtitle) +} + void RenderTask::PrepareWatcher(RenderTicketWatcher *watcher, QThread *thread) { watcher->moveToThread(thread); diff --git a/app/task/render/render.h b/app/task/render/render.h index 328b78a19..af11071a7 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -23,6 +23,7 @@ #include +#include "node/block/subtitle/subtitle.h" #include "node/color/colormanager/colormanager.h" #include "node/output/viewer/viewer.h" #include "task/task.h" @@ -41,7 +42,8 @@ public: protected: bool Render(ColorManager *manager, const TimeRangeList &video_range, - const TimeRangeList &audio_range, RenderMode::Mode mode, + const TimeRangeList &audio_range, const TimeRange &subtitle_range, + RenderMode::Mode mode, FrameHashCache *cache, const QSize& force_size = QSize(0, 0), const QMatrix4x4& force_matrix = QMatrix4x4(), VideoParams::Format force_format = VideoParams::kFormatInvalid, @@ -53,6 +55,8 @@ protected: virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) = 0; + virtual void EncodeSubtitle(const SubtitleBlock *subtitle); + ViewerOutput* viewer() const { return viewer_; diff --git a/app/tool/tool.h b/app/tool/tool.h index d19abab57..c277f4b0b 100644 --- a/app/tool/tool.h +++ b/app/tool/tool.h @@ -96,6 +96,9 @@ public: /// An audio clip with a sine connected to it kAddableTone, + /// A subtitle clip + kAddableSubtitle, + kAddableCount }; @@ -112,6 +115,8 @@ public: return QCoreApplication::translate("Tool", "Title"); case kAddableTone: return QCoreApplication::translate("Tool", "Tone"); + case kAddableSubtitle: + return QCoreApplication::translate("Tool", "Subtitle"); case kAddableCount: break; } diff --git a/app/widget/timelinewidget/timelineundo.h b/app/widget/timelinewidget/timelineundo.h index de868f2d6..8c472a658 100644 --- a/app/widget/timelinewidget/timelineundo.h +++ b/app/widget/timelinewidget/timelineundo.h @@ -1230,11 +1230,11 @@ public: if (timeline_->type() == Track::kVideo) { relevant_input = ViewerOutput::kTextureInput; - } else { + } else if (timeline_->type() == Track::kAudio) { relevant_input = ViewerOutput::kSamplesInput; } - if (!timeline_->parent()->IsInputConnected(relevant_input)) { + if (!relevant_input.isEmpty() && !timeline_->parent()->IsInputConnected(relevant_input)) { direct_ = NodeInput(timeline_->parent(), relevant_input); Node::ConnectEdge(track_, direct_); @@ -1289,15 +1289,20 @@ private: merge_ = new MergeNode(); base_ = NodeInput(merge_, MergeNode::kBaseIn); blend_ = NodeInput(merge_, MergeNode::kBlendIn); - } else { + } else if (timeline_->type() == Track::kAudio) { merge_ = new MathNode(); base_ = NodeInput(merge_, MathNode::kParamAIn); blend_ = NodeInput(merge_, MathNode::kParamBIn); + } else { + merge_ = nullptr; } - merge_->setParent(&memory_manager_); } else { merge_ = nullptr; } + + if (merge_) { + merge_->setParent(&memory_manager_); + } } TrackList* timeline_; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index ddbed335b..d367489e4 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -77,7 +77,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) : // Create list of TimelineViews - these MUST correspond to the ViewType enum view_splitter_ = new QSplitter(Qt::Vertical); - view_splitter_->setChildrenCollapsible(false); vert_layout->addWidget(view_splitter_); // Video view @@ -86,6 +85,9 @@ TimelineWidget::TimelineWidget(QWidget *parent) : // Audio view views_.append(new TimelineAndTrackView(Qt::AlignTop)); + // Subtitle view + views_.append(new TimelineAndTrackView(Qt::AlignTop)); + // Create tools tools_.resize(olive::Tool::kCount); tools_.fill(nullptr); @@ -155,7 +157,17 @@ TimelineWidget::TimelineWidget(QWidget *parent) : } // Split viewer 50/50 - view_splitter_->setSizes({INT_MAX, INT_MAX}); + QList view_sizes; + view_sizes.reserve(views_.size()); + view_sizes.append(height()/2); // Video + view_sizes.append(height()/2); // Audio + view_sizes.append(0); // Subtitle (hidden by default) + view_splitter_->setSizes(view_sizes); + + // Video and audio are not collapsible, subtitle is + view_splitter_->setCollapsible(Track::kVideo, false); + view_splitter_->setCollapsible(Track::kAudio, false); + view_splitter_->setCollapsible(Track::kSubtitle, true); // FIXME: Magic number SetScale(90.0); @@ -786,11 +798,23 @@ void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event) if (hover_tool) { hover_tool->HoverMove(event); + + // Special cast for subtitle adding - ensure section is visible + if (dynamic_cast(hover_tool) + && Core::instance()->GetSelectedAddableObject() == Tool::kAddableSubtitle) { + QList sz = view_splitter_->sizes(); + int &subtitle_section_height = sz[Track::kSubtitle]; + if (subtitle_section_height == 0) { + subtitle_section_height = height() / Track::kCount; + view_splitter_->setSizes(sz); + } + } } } } } + void TimelineWidget::ViewMouseReleased(TimelineViewMouseEvent *event) { if (active_tool_) { diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 06412b6c8..bd766735a 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -20,6 +20,7 @@ #include "add.h" #include "core.h" +#include "node/block/subtitle/subtitle.h" #include "node/factory.h" #include "node/generator/solid/solid.h" #include "node/generator/text/text.h" @@ -54,6 +55,9 @@ void AddTool::MousePress(TimelineViewMouseEvent *event) case olive::Tool::kAddableTone: add_type = Track::kAudio; break; + case olive::Tool::kAddableSubtitle: + add_type = Track::kSubtitle; + break; case olive::Tool::kAddableEmpty: // Leave as "none", which means this block can be placed on any track break; @@ -93,7 +97,12 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) if (!ghost_->GetAdjustedLength().isNull()) { MultiUndoCommand* command = new MultiUndoCommand(); - ClipBlock* clip = new ClipBlock(); + ClipBlock* clip; + if (Core::instance()->GetSelectedAddableObject() == olive::Tool::kAddableSubtitle) { + clip = new SubtitleBlock(); + } else { + clip = new ClipBlock(); + } clip->set_length_and_media_out(ghost_->GetAdjustedLength()); clip->SetLabel(olive::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject())); @@ -140,6 +149,9 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) // Not implemented yet qWarning() << "Unimplemented add object:" << Core::instance()->GetSelectedAddableObject(); break; + case olive::Tool::kAddableSubtitle: + // The block itself is the node we want + break; case olive::Tool::kAddableCount: // Invalid value, do nothing break; From a561daf0719a25db119abc50c39bd3f0cd6e9b46 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 18 May 2021 13:11:18 +1000 Subject: [PATCH 06/12] ffmpegencoder: minor code improvement --- app/codec/ffmpeg/ffmpegencoder.cpp | 27 ++++++++++++--------------- app/codec/ffmpeg/ffmpegencoder.h | 1 + 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 75afb11f0..74e575f1a 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -308,29 +308,26 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) if (converted > 0) { // Split sample buffer into frames for (int i=0; inb_samples - copy_offset; + int frame_remaining_samples = audio_max_samples_ - audio_frame_offset_; int converted_remaining_samples = converted - i; int copy_length = qMin(frame_remaining_samples, converted_remaining_samples); - av_samples_copy(audio_frame_->data, output_data, copy_offset, i, + av_samples_copy(audio_frame_->data, output_data, audio_frame_offset_, i, copy_length, audio_frame_->channels, static_cast(audio_frame_->format)); - if (copy_length != frame_remaining_samples && input_data) { - // Frame didn't get all the samples it needed, save them for later - audio_frame_offset_ += copy_length; - } else { + audio_frame_offset_ += copy_length; + i += copy_length; + + if (audio_frame_offset_ == audio_max_samples_ || (i == converted && !input_data)) { // Got all the samples we needed, write the frame audio_frame_->pts = audio_write_count_; WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); - audio_write_count_ += audio_frame_->nb_samples; + audio_write_count_ += audio_frame_offset_; audio_frame_offset_ = 0; } - - i += copy_length; } } else if (converted < 0) { FFmpegError(tr("Failed to resample audio"), converted); @@ -875,15 +872,15 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio) return false; } - int max_frame_samples = audio_codec_ctx_->frame_size; - if (!max_frame_samples) { + audio_max_samples_ = audio_codec_ctx_->frame_size; + if (!audio_max_samples_) { // If not, use another frame size if (params().video_enabled()) { // If we're encoding video, use enough samples to cover roughly one frame of video - max_frame_samples = params().audio_params().time_to_samples(params().video_params().frame_rate_as_time_base()); + audio_max_samples_ = params().audio_params().time_to_samples(params().video_params().frame_rate_as_time_base()); } else { // If no video, just use an arbitrary number - max_frame_samples = 256; + audio_max_samples_ = 256; } } @@ -894,7 +891,7 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio) audio_frame_->channel_layout = audio_codec_ctx_->channel_layout; audio_frame_->format = audio_codec_ctx_->sample_fmt; - audio_frame_->nb_samples = max_frame_samples; + audio_frame_->nb_samples = audio_max_samples_; err = av_frame_get_buffer(audio_frame_, 0); if (err < 0) { diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index cae844fc1..403513e3c 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -89,6 +89,7 @@ private: AVCodecContext* audio_codec_ctx_; SwrContext* audio_resample_ctx_; AVFrame* audio_frame_; + int audio_max_samples_; int audio_frame_offset_; int audio_write_count_; From 0b1ad0a267813dca66fd756017c3f57c3fb0d950 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 18 May 2021 15:17:27 +1000 Subject: [PATCH 07/12] ffmpegencoder: added more error checking --- app/codec/ffmpeg/ffmpegencoder.cpp | 31 ++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 74e575f1a..c1b54c5aa 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -416,11 +416,17 @@ bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block) 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); + int err = av_interleaved_write_frame(fmt_ctx_, pkt); + bool ret = true; + + if (err < 0) { + FFmpegError(tr("Failed to write interleaved packet"), err); + ret = false; + } av_packet_free(&pkt); - return true; + return ret; } /* @@ -576,7 +582,11 @@ bool FFmpegEncoder::WriteAVFrame(AVFrame *frame, AVCodecContext* codec_ctx, AVSt av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); // Write packet to file - av_interleaved_write_frame(fmt_ctx_, pkt); + error_code = av_interleaved_write_frame(fmt_ctx_, pkt); + if (error_code < 0) { + FFmpegError(tr("Failed to write interleaved packet"), error_code); + goto fail; + } // Unref packet in case we're getting another av_packet_unref(pkt); @@ -822,6 +832,15 @@ void FFmpegEncoder::FlushEncoders() FlushCodecCtx(audio_codec_ctx_, audio_stream_); } + + if (fmt_ctx_) { + if (fmt_ctx_->oformat->flags & AVFMT_ALLOW_FLUSH) { + int r = av_interleaved_write_frame(fmt_ctx_, nullptr); + if (r < 0) { + FFmpegError(tr("Failed to write interleaved packet"), r); + } + } + } } void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream) @@ -839,7 +858,11 @@ void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream) pkt->stream_index = stream->index; av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); - av_interleaved_write_frame(fmt_ctx_, pkt); + int r = av_interleaved_write_frame(fmt_ctx_, pkt); + if (r < 0) { + FFmpegError(tr("Failed to write interleaved packet"), r); + break; + } av_packet_unref(pkt); } while (error_code >= 0); From a7ac1750ae87aab585b2d209291f34ff9ffe7bd0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 May 2021 10:07:54 +1000 Subject: [PATCH 08/12] nodeparamview: disable default QMainWindow popup menu Fixes #1638 --- app/widget/nodeparamview/CMakeLists.txt | 2 + app/widget/nodeparamview/nodeparamview.cpp | 2 +- app/widget/nodeparamview/nodeparamview.h | 4 +- .../nodeparamview/nodeparamviewdockarea.cpp | 35 ++++++++++++++++ .../nodeparamview/nodeparamviewdockarea.h | 40 +++++++++++++++++++ 5 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 app/widget/nodeparamview/nodeparamviewdockarea.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewdockarea.h diff --git a/app/widget/nodeparamview/CMakeLists.txt b/app/widget/nodeparamview/CMakeLists.txt index bad542897..0a7a164a4 100644 --- a/app/widget/nodeparamview/CMakeLists.txt +++ b/app/widget/nodeparamview/CMakeLists.txt @@ -22,6 +22,8 @@ set(OLIVE_SOURCES widget/nodeparamview/nodeparamviewarraywidget.cpp widget/nodeparamview/nodeparamviewconnectedlabel.h widget/nodeparamview/nodeparamviewconnectedlabel.cpp + widget/nodeparamview/nodeparamviewdockarea.h + widget/nodeparamview/nodeparamviewdockarea.cpp widget/nodeparamview/nodeparamviewitem.h widget/nodeparamview/nodeparamviewitem.cpp widget/nodeparamview/nodeparamviewkeyframecontrol.h diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 5bea49c09..605964f84 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -55,7 +55,7 @@ NodeParamView::NodeParamView(QWidget *parent) : connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); scroll_area->setWidget(param_widget_container_); - param_widget_area_ = new QMainWindow(); + param_widget_area_ = new NodeParamViewDockArea(); // Disable dock widgets from tabbing and disable glitchy animations param_widget_area_->setDockOptions(static_cast(0)); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index a5a19b0da..3e8f7edc4 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -21,11 +21,11 @@ #ifndef NODEPARAMVIEW_H #define NODEPARAMVIEW_H -#include #include #include #include "node/node.h" +#include "nodeparamviewdockarea.h" #include "nodeparamviewitem.h" #include "widget/keyframeview/keyframeview.h" #include "widget/timebased/timebasedwidget.h" @@ -121,7 +121,7 @@ private: // This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows // docking windows - QMainWindow* param_widget_area_; + NodeParamViewDockArea* param_widget_area_; QVector pinned_nodes_; diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.cpp b/app/widget/nodeparamview/nodeparamviewdockarea.cpp new file mode 100644 index 000000000..a875bbf24 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewdockarea.cpp @@ -0,0 +1,35 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodeparamviewdockarea.h" + +namespace olive { + +NodeParamViewDockArea::NodeParamViewDockArea(QWidget *parent) : + QMainWindow(parent) +{ +} + +QMenu *NodeParamViewDockArea::createPopupMenu() +{ + return nullptr; +} + +} diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.h b/app/widget/nodeparamview/nodeparamviewdockarea.h new file mode 100644 index 000000000..90a00c1a0 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewdockarea.h @@ -0,0 +1,40 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEPARAMVIEWDOCKAREA_H +#define NODEPARAMVIEWDOCKAREA_H + +#include + +namespace olive { + +class NodeParamViewDockArea : public QMainWindow +{ + Q_OBJECT +public: + explicit NodeParamViewDockArea(QWidget *parent = nullptr); + + virtual QMenu *createPopupMenu() override; + +}; + +} + +#endif // NODEPARAMVIEWDOCKAREA_H From e21d4ab0d7395bcfc19f98862339f56e215c141f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 May 2021 10:46:35 +1000 Subject: [PATCH 09/12] nodes: revised text node Clarifies text node's ability to parse HTML with Qt. Removes janky incomplete "rich text editing" functionality. I don't think this is the right approach. --- app/dialog/CMakeLists.txt | 2 +- app/dialog/richtext/richtext.cpp | 339 ------------------ app/dialog/richtext/richtext.h | 87 ----- app/dialog/{richtext => text}/CMakeLists.txt | 4 +- app/dialog/text/text.cpp | 50 +++ app/dialog/text/text.h | 51 +++ app/node/generator/text/text.cpp | 13 +- app/node/generator/text/text.h | 1 + app/widget/nodeparamview/CMakeLists.txt | 4 +- ...richtext.cpp => nodeparamviewtextedit.cpp} | 20 +- ...viewrichtext.h => nodeparamviewtextedit.h} | 22 +- .../nodeparamviewwidgetbridge.cpp | 10 +- 12 files changed, 145 insertions(+), 458 deletions(-) delete mode 100644 app/dialog/richtext/richtext.cpp delete mode 100644 app/dialog/richtext/richtext.h rename app/dialog/{richtext => text}/CMakeLists.txt (92%) create mode 100644 app/dialog/text/text.cpp create mode 100644 app/dialog/text/text.h rename app/widget/nodeparamview/{nodeparamviewrichtext.cpp => nodeparamviewtextedit.cpp} (70%) rename app/widget/nodeparamview/{nodeparamviewrichtext.h => nodeparamviewtextedit.h} (74%) diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index 98e5cb6bd..48c9884d1 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -26,9 +26,9 @@ add_subdirectory(keyframeproperties) add_subdirectory(preferences) add_subdirectory(progress) add_subdirectory(rendercancel) -add_subdirectory(richtext) add_subdirectory(sequence) add_subdirectory(task) +add_subdirectory(text) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/dialog/richtext/richtext.cpp b/app/dialog/richtext/richtext.cpp deleted file mode 100644 index f6f6c0ee2..000000000 --- a/app/dialog/richtext/richtext.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "richtext.h" - -#include -#include -#include -#include -#include - -#include "ui/icons/icons.h" - -namespace olive { - -RichTextDialog::RichTextDialog(QString start, QWidget* parent) : - QDialog(parent) -{ - QVBoxLayout* layout = new QVBoxLayout(this); - - // Create toolbar - QHBoxLayout* toolbar_layout = new QHBoxLayout(); - - bold_btn_ = CreateToolbarButton(tr("B"), tr("Bold"), {QStringLiteral("b"), QStringLiteral("strong")}); - toolbar_layout->addWidget(bold_btn_); - italic_btn_ = CreateToolbarButton(tr("I"), tr("Italic"), {QStringLiteral("i"), QStringLiteral("em")}); - toolbar_layout->addWidget(italic_btn_); - underline_btn_ = CreateToolbarButton(tr("U"), tr("Underline"), {QStringLiteral("u")}); - toolbar_layout->addWidget(underline_btn_); - strikeout_btn_ = CreateToolbarButton(tr("S"), tr("Strikethrough"), {QStringLiteral("strike")}); - toolbar_layout->addWidget(strikeout_btn_); - font_combo_ = new QFontComboBox(); - font_combo_->setToolTip(tr("Font Family")); - toolbar_layout->addWidget(font_combo_); - size_slider_ = new FloatSlider(); - size_slider_->SetMinimum(0.1); - size_slider_->SetLadderElementCount(1); - size_slider_->setToolTip(tr("Font Size")); - toolbar_layout->addWidget(size_slider_); - - toolbar_layout->addStretch(); - - left_align_btn_ = CreateToolbarButton(tr("L"), tr("Left Align"), {}); - toolbar_layout->addWidget(left_align_btn_); - center_align_btn_ = CreateToolbarButton(tr("C"), tr("Center Align"), {}); - toolbar_layout->addWidget(center_align_btn_); - right_align_btn_ = CreateToolbarButton(tr("R"), tr("Right Align"), {}); - toolbar_layout->addWidget(right_align_btn_); - justify_align_btn_ = CreateToolbarButton(tr("J"), tr("Justify Align"), {}); - toolbar_layout->addWidget(justify_align_btn_); - - layout->addLayout(toolbar_layout); - - // Create text edit widget - text_edit_ = new QTextEdit(); - text_edit_->setWordWrapMode(QTextOption::NoWrap); - connect(text_edit_, &QTextEdit::cursorPositionChanged, this, &RichTextDialog::UpdateButtons); - start.replace(QStringLiteral("
"), QStringLiteral("\n")); - text_edit_->document()->setPlainText(start); - layout->addWidget(text_edit_); - - // Create buttons - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - layout->addWidget(buttons); - connect(buttons, &QDialogButtonBox::accepted, this, &RichTextDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &RichTextDialog::reject); - - // Connect font buttons - /* - connect(size_slider_, &FloatSlider::ValueChanged, text_edit_, &QTextEdit::setFontPointSize); - connect(left_align_btn_, &QPushButton::clicked, this, [this](){ - text_edit_->setAlignment(Qt::AlignLeft); - UpdateButtons(); - }); - connect(center_align_btn_, &QPushButton::clicked, this, [this](){ - text_edit_->setAlignment(Qt::AlignCenter); - UpdateButtons(); - }); - connect(right_align_btn_, &QPushButton::clicked, this, [this](){ - text_edit_->setAlignment(Qt::AlignRight); - UpdateButtons(); - }); - connect(justify_align_btn_, &QPushButton::clicked, this, [this](){ - text_edit_->setAlignment(Qt::AlignJustify); - UpdateButtons(); - }); - - - connect(font_combo_, &QFontComboBox::currentTextChanged, this, [this](const QString& s){ - text_edit_->setFontFamily(s); - }); - */ -} - -QPushButton *RichTextDialog::CreateToolbarButton(const QString& label, const QString& tooltip, const QStringList &tags) -{ - QPushButton* btn = new QPushButton(label); - btn->setCheckable(true); - btn->setToolTip(tooltip); - btn->setFixedWidth(btn->sizeHint().height()); - - if (!tags.isEmpty()) { - btn->setProperty("tag", tags); - connect(btn, &QPushButton::clicked, this, &RichTextDialog::TagButtonToggled); - } - - return btn; -} - -int SnapPositionOutsideTags(const QString& text, int pos) -{ - // Look for closest opening bracket before position - int opening_bracket_pos = text.lastIndexOf('<', pos - text.size() -1); - - // Look for closest closing bracket before position - int closing_bracket_pos = text.indexOf('>', opening_bracket_pos); - - if (opening_bracket_pos > -1 && closing_bracket_pos >= pos) { - // Must be inside an angle bracket, snap to closest position outside of bracket - closing_bracket_pos++; - - if (pos - opening_bracket_pos < closing_bracket_pos - pos) { - // Closer to opening bracket pos - return opening_bracket_pos; - } else { - return closing_bracket_pos; - } - } - - return pos; -} - -void RichTextDialog::SetTags(const QStringList &t, bool enabled) -{ - QString s = text_edit_->toPlainText(); - - int selection_start, selection_end; - - { - QTextCursor c = text_edit_->textCursor(); - - if (c.hasSelection()) { - selection_start = SnapPositionOutsideTags(s, c.selectionStart()); - selection_end = SnapPositionOutsideTags(s, c.selectionEnd()); - - c.clearSelection(); - c.setPosition(selection_start, QTextCursor::MoveAnchor); - c.setPosition(selection_end, QTextCursor::KeepAnchor); - } else { - selection_start = SnapPositionOutsideTags(s, c.position()); - selection_end = selection_start; - c.setPosition(selection_start, QTextCursor::MoveAnchor); - } - - text_edit_->setTextCursor(c); - } - - QString open_tag = CreateOpeningTag(t.first()); - QString close_tag = CreateClosingTag(t.first()); - - // Insert tags - QString new_text; - - if (!enabled) { - std::swap(open_tag, close_tag); - } - - bool open_tag_cancels_out = !QString::compare(s.mid(selection_start - close_tag.size(), close_tag.size()), close_tag, Qt::CaseInsensitive); - bool close_tag_cancels_out = !QString::compare(s.mid(selection_end, open_tag.size()), open_tag, Qt::CaseInsensitive); - - QString selected_text = text_edit_->textCursor().selectedText(); - - if (open_tag_cancels_out && close_tag_cancels_out) { - - // Both tags cancel each other out, simply remove - selection_start -= close_tag.size(); - - QTextCursor c = text_edit_->textCursor(); - c.clearSelection(); - c.setPosition(selection_start, QTextCursor::MoveAnchor); - c.setPosition(selection_end + open_tag.size(), QTextCursor::KeepAnchor); - text_edit_->setTextCursor(c); - - selection_end -= close_tag.size(); - - new_text = selected_text; - - } else if (open_tag_cancels_out) { - - // Open tag cancels out, shift close tag rather than inserting new tags - selection_start -= close_tag.size(); - - QTextCursor c = text_edit_->textCursor(); - c.clearSelection(); - c.setPosition(selection_start, QTextCursor::MoveAnchor); - c.setPosition(selection_end, QTextCursor::KeepAnchor); - text_edit_->setTextCursor(c); - - selection_end -= close_tag.size(); - - new_text = selected_text; - new_text.append(close_tag); - - } else if (close_tag_cancels_out) { - - // Close tag cancels out, shift open tag rather than inserting new tags - selection_end += open_tag.size(); - - QTextCursor c = text_edit_->textCursor(); - c.clearSelection(); - c.setPosition(selection_start, QTextCursor::MoveAnchor); - c.setPosition(selection_end, QTextCursor::KeepAnchor); - text_edit_->setTextCursor(c); - - selection_start += open_tag.size(); - - new_text = open_tag; - new_text.append(selected_text); - - } else { - // Nothing is cancelled out, simply insert tags - new_text = QStringLiteral("%1%2%3").arg(open_tag, - selected_text, - close_tag); - - selection_start += open_tag.size(); - selection_end += open_tag.size(); - } - - text_edit_->insertPlainText(new_text); - - text_edit_->setFocus(); - - { - // Re-select text - QTextCursor c = text_edit_->textCursor(); - - c.clearSelection(); - c.setPosition(selection_start, QTextCursor::MoveAnchor); - c.setPosition(selection_end, QTextCursor::KeepAnchor); - text_edit_->setTextCursor(c); - } -} - -QString RichTextDialog::CreateOpeningTag(const QString &s) -{ - return QStringLiteral("<%1>").arg(s); -} - -QString RichTextDialog::CreateClosingTag(const QString &s) -{ - return QStringLiteral("").arg(s); -} - -void RichTextDialog::UpdateTagButton(QPushButton *btn, - const QString &text, - int cursor_pos) -{ - QStringList tags = btn->property("tag").toStringList(); - foreach (const QString& t, tags) { - QString opening = CreateOpeningTag(t); - QString closing = CreateClosingTag(t); - - int opening_index = text.lastIndexOf(opening, - cursor_pos - text.size() - 1, - Qt::CaseInsensitive); - int closing_index = text.indexOf(closing, - opening_index, - Qt::CaseInsensitive); - - if (opening_index > -1 && closing_index + closing.size() > cursor_pos) { - btn->setChecked(true); - btn->setProperty("foundtag", t); - return; - } - } - - btn->setChecked(false); - btn->setProperty("foundtag", QVariant()); -} - -void RichTextDialog::TagButtonToggled(bool checked) -{ - QPushButton* src = static_cast(sender()); - QStringList tags; - - if (src->property("foundtag").isNull()) { - tags = src->property("tag").toStringList(); - } else { - tags = QStringList({src->property("foundtag").toString()}); - } - - SetTags(tags, checked); -} - -void RichTextDialog::UpdateButtons() -{ - QString text = text_edit_->toPlainText(); - int cursor_pos = text_edit_->textCursor().position(); - - UpdateTagButton(bold_btn_, text, cursor_pos); - UpdateTagButton(italic_btn_, text, cursor_pos); - UpdateTagButton(underline_btn_, text, cursor_pos); - UpdateTagButton(strikeout_btn_, text, cursor_pos); - - /* - - // Update font family - font_combo_->blockSignals(true); - font_combo_->setCurrentFont(text_edit_->currentFont().family()); - font_combo_->blockSignals(false); - - size_slider_->SetValue(text_edit_->fontPointSize()); - - left_align_btn_->setChecked(text_edit_->alignment() == Qt::AlignLeft); - center_align_btn_->setChecked(text_edit_->alignment() == Qt::AlignCenter); - right_align_btn_->setChecked(text_edit_->alignment() == Qt::AlignRight); - justify_align_btn_->setChecked(text_edit_->alignment() == Qt::AlignJustify); - */ -} - -} diff --git a/app/dialog/richtext/richtext.h b/app/dialog/richtext/richtext.h deleted file mode 100644 index cd2a57b92..000000000 --- a/app/dialog/richtext/richtext.h +++ /dev/null @@ -1,87 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef RICHTEXTDIALOG_H -#define RICHTEXTDIALOG_H - -#include -#include -#include - -#include "common/define.h" -#include "widget/slider/floatslider.h" - -namespace olive { - -class RichTextDialog : public QDialog -{ - Q_OBJECT -public: - RichTextDialog(QString start, QWidget* parent = nullptr); - - QString text() const - { - QString s = text_edit_->document()->toPlainText(); - - // Convert linebreaks - s.replace('\n', QStringLiteral("
")); - - return s; - } - -private: - QPushButton* CreateToolbarButton(const QString &label, - const QString &tooltip, - const QStringList& tags); - - void SetTags(const QStringList& t, bool enabled); - - static QString CreateOpeningTag(const QString& s); - static QString CreateClosingTag(const QString& s); - - static void UpdateTagButton(QPushButton* btn, - const QString &text, - int cursor_pos); - - QFontDatabase font_db_; - - QTextEdit* text_edit_; - - QPushButton* bold_btn_; - QPushButton* italic_btn_; - QPushButton* underline_btn_; - QPushButton* strikeout_btn_; - QFontComboBox* font_combo_; - FloatSlider* size_slider_; - QPushButton* left_align_btn_; - QPushButton* center_align_btn_; - QPushButton* right_align_btn_; - QPushButton* justify_align_btn_; - -private slots: - void TagButtonToggled(bool checked); - - void UpdateButtons(); - -}; - -} - -#endif // RICHTEXTDIALOG_H diff --git a/app/dialog/richtext/CMakeLists.txt b/app/dialog/text/CMakeLists.txt similarity index 92% rename from app/dialog/richtext/CMakeLists.txt rename to app/dialog/text/CMakeLists.txt index e2df1928f..6a96dce13 100644 --- a/app/dialog/richtext/CMakeLists.txt +++ b/app/dialog/text/CMakeLists.txt @@ -16,7 +16,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - dialog/richtext/richtext.h - dialog/richtext/richtext.cpp + dialog/text/text.h + dialog/text/text.cpp PARENT_SCOPE ) diff --git a/app/dialog/text/text.cpp b/app/dialog/text/text.cpp new file mode 100644 index 000000000..264d6624b --- /dev/null +++ b/app/dialog/text/text.cpp @@ -0,0 +1,50 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "text.h" + +#include +#include +#include +#include +#include + +#include "ui/icons/icons.h" + +namespace olive { + +TextDialog::TextDialog(const QString &start, QWidget* parent) : + QDialog(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + + // Create text edit widget + text_edit_ = new QPlainTextEdit(); + text_edit_->document()->setPlainText(start); + layout->addWidget(text_edit_); + + // Create buttons + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addWidget(buttons); + connect(buttons, &QDialogButtonBox::accepted, this, &TextDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &TextDialog::reject); +} + +} diff --git a/app/dialog/text/text.h b/app/dialog/text/text.h new file mode 100644 index 000000000..5916b0101 --- /dev/null +++ b/app/dialog/text/text.h @@ -0,0 +1,51 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef RICHTEXTDIALOG_H +#define RICHTEXTDIALOG_H + +#include +#include +#include + +#include "common/define.h" +#include "widget/slider/floatslider.h" + +namespace olive { + +class TextDialog : public QDialog +{ + Q_OBJECT +public: + TextDialog(const QString &start, QWidget* parent = nullptr); + + QString text() const + { + return text_edit_->toPlainText(); + } + +private: + QPlainTextEdit* text_edit_; + +}; + +} + +#endif // RICHTEXTDIALOG_H diff --git a/app/node/generator/text/text.cpp b/app/node/generator/text/text.cpp index aa6587d90..6a9ff27bb 100644 --- a/app/node/generator/text/text.cpp +++ b/app/node/generator/text/text.cpp @@ -32,6 +32,7 @@ enum TextVerticalAlign { }; const QString TextGenerator::kTextInput = QStringLiteral("text_in"); +const QString TextGenerator::kHtmlInput = QStringLiteral("html_in"); const QString TextGenerator::kColorInput = QStringLiteral("color_in"); const QString TextGenerator::kVAlignInput = QStringLiteral("valign_in"); const QString TextGenerator::kFontInput = QStringLiteral("font_in"); @@ -41,6 +42,8 @@ TextGenerator::TextGenerator() { AddInput(kTextInput, NodeValue::kText, tr("Sample Text")); + AddInput(kHtmlInput, NodeValue::kBoolean, false); + AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0f, 1.0f, 1.0))); AddInput(kVAlignInput, NodeValue::kCombo, 1); @@ -78,6 +81,7 @@ QString TextGenerator::Description() const void TextGenerator::Retranslate() { SetInputName(kTextInput, tr("Text")); + SetInputName(kHtmlInput, tr("Enable HTML")); SetInputName(kFontInput, tr("Font")); SetInputName(kFontSizeInput, tr("Font Size")); SetInputName(kColorInput, tr("Color")); @@ -91,6 +95,7 @@ NodeValueTable TextGenerator::Value(const QString &output, NodeValueDatabase &va GenerateJob job; job.InsertValue(this, kTextInput, value); + job.InsertValue(this, kHtmlInput, value); job.InsertValue(this, kColorInput, value); job.InsertValue(this, kVAlignInput, value); job.InsertValue(this, kFontInput, value); @@ -126,7 +131,13 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const // Center by default text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter)); - text_doc.setHtml(job.GetValue(kTextInput).data().toString()); + QString html = job.GetValue(kTextInput).data().toString(); + if (job.GetValue(kHtmlInput).data().toBool()) { + html.replace('\n', QStringLiteral("
")); + text_doc.setHtml(html); + } else { + text_doc.setPlainText(html); + } // Align to 80% width because that's considered the "title safe" area int tenth_of_width = frame->video_params().width() / 10; diff --git a/app/node/generator/text/text.h b/app/node/generator/text/text.h index 97ea70a8b..e58525d0c 100644 --- a/app/node/generator/text/text.h +++ b/app/node/generator/text/text.h @@ -47,6 +47,7 @@ public: virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override; static const QString kTextInput; + static const QString kHtmlInput; static const QString kColorInput; static const QString kVAlignInput; static const QString kFontInput; diff --git a/app/widget/nodeparamview/CMakeLists.txt b/app/widget/nodeparamview/CMakeLists.txt index 0a7a164a4..5d0f37f40 100644 --- a/app/widget/nodeparamview/CMakeLists.txt +++ b/app/widget/nodeparamview/CMakeLists.txt @@ -28,8 +28,8 @@ set(OLIVE_SOURCES widget/nodeparamview/nodeparamviewitem.cpp widget/nodeparamview/nodeparamviewkeyframecontrol.h widget/nodeparamview/nodeparamviewkeyframecontrol.cpp - widget/nodeparamview/nodeparamviewrichtext.h - widget/nodeparamview/nodeparamviewrichtext.cpp + widget/nodeparamview/nodeparamviewtextedit.h + widget/nodeparamview/nodeparamviewtextedit.cpp widget/nodeparamview/nodeparamviewundo.h widget/nodeparamview/nodeparamviewundo.cpp widget/nodeparamview/nodeparamviewwidgetbridge.h diff --git a/app/widget/nodeparamview/nodeparamviewrichtext.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp similarity index 70% rename from app/widget/nodeparamview/nodeparamviewrichtext.cpp rename to app/widget/nodeparamview/nodeparamviewtextedit.cpp index b60265e31..ab4a77295 100644 --- a/app/widget/nodeparamview/nodeparamviewrichtext.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -18,46 +18,46 @@ ***/ -#include "nodeparamviewrichtext.h" +#include "nodeparamviewtextedit.h" #include #include -#include "dialog/richtext/richtext.h" +#include "dialog/text/text.h" #include "ui/icons/icons.h" namespace olive { -NodeParamViewRichText::NodeParamViewRichText(QWidget *parent) : +NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); - line_edit_ = new QTextEdit(); + line_edit_ = new QPlainTextEdit(); line_edit_->setUndoRedoEnabled(true); - connect(line_edit_, &QTextEdit::textChanged, this, &NodeParamViewRichText::InnerWidgetTextChanged); + connect(line_edit_, &QPlainTextEdit::textChanged, this, &NodeParamViewTextEdit::InnerWidgetTextChanged); layout->addWidget(line_edit_); QPushButton* edit_btn = new QPushButton(); edit_btn->setIcon(icon::ToolEdit); edit_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); layout->addWidget(edit_btn); - connect(edit_btn, &QPushButton::clicked, this, &NodeParamViewRichText::ShowRichTextDialog); + connect(edit_btn, &QPushButton::clicked, this, &NodeParamViewTextEdit::ShowTextDialog); } -void NodeParamViewRichText::ShowRichTextDialog() +void NodeParamViewTextEdit::ShowTextDialog() { - RichTextDialog d(this->text(), this); + TextDialog d(this->text(), this); if (d.exec() == QDialog::Accepted) { QString s = d.text(); - line_edit_->setText(s); + line_edit_->setPlainText(s); emit textEdited(s); } } -void NodeParamViewRichText::InnerWidgetTextChanged() +void NodeParamViewTextEdit::InnerWidgetTextChanged() { emit textEdited(this->text()); } diff --git a/app/widget/nodeparamview/nodeparamviewrichtext.h b/app/widget/nodeparamview/nodeparamviewtextedit.h similarity index 74% rename from app/widget/nodeparamview/nodeparamviewrichtext.h rename to app/widget/nodeparamview/nodeparamviewtextedit.h index d9c444abb..ece9f03a0 100644 --- a/app/widget/nodeparamview/nodeparamviewrichtext.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -18,32 +18,32 @@ ***/ -#ifndef NODEPARAMVIEWRICHTEXT_H -#define NODEPARAMVIEWRICHTEXT_H +#ifndef NODEPARAMVIEWTEXTEDIT_H +#define NODEPARAMVIEWTEXTEDIT_H -#include +#include #include #include "common/define.h" namespace olive { -class NodeParamViewRichText : public QWidget +class NodeParamViewTextEdit : public QWidget { Q_OBJECT public: - NodeParamViewRichText(QWidget* parent = nullptr); + NodeParamViewTextEdit(QWidget* parent = nullptr); QString text() const { - return line_edit_->toPlainText().replace('\n', QStringLiteral("
")); + return line_edit_->toPlainText(); } public slots: - void setText(QString s) + void setText(const QString &s) { line_edit_->blockSignals(true); - line_edit_->setPlainText(s.replace(QStringLiteral("
"), QStringLiteral("\n"))); + line_edit_->setPlainText(s); line_edit_->blockSignals(false); } @@ -65,10 +65,10 @@ signals: void textEdited(const QString &); private: - QTextEdit* line_edit_; + QPlainTextEdit* line_edit_; private slots: - void ShowRichTextDialog(); + void ShowTextDialog(); void InnerWidgetTextChanged(); @@ -76,4 +76,4 @@ private slots: } -#endif // NODEPARAMVIEWRICHTEXT_H +#endif // NODEPARAMVIEWTEXTEDIT_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index e5b479d83..dd92f159e 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -30,7 +30,7 @@ #include "node/node.h" #include "node/project/sequence/sequence.h" #include "nodeparamviewarraywidget.h" -#include "nodeparamviewrichtext.h" +#include "nodeparamviewtextedit.h" #include "nodeparamviewundo.h" #include "undo/undostack.h" #include "widget/colorbutton/colorbutton.h" @@ -143,9 +143,9 @@ void NodeParamViewWidgetBridge::CreateWidgets() } case NodeValue::kText: { - NodeParamViewRichText* line_edit = new NodeParamViewRichText(); + NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(); widgets_.append(line_edit); - connect(line_edit, &NodeParamViewRichText::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); + connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } case NodeValue::kBoolean: @@ -358,7 +358,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kText: { // Sender is a NodeParamViewRichText - SetInputValue(static_cast(sender())->text(), 0); + SetInputValue(static_cast(sender())->text(), 0); break; } case NodeValue::kBoolean: @@ -498,7 +498,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() } case NodeValue::kText: { - NodeParamViewRichText* e = static_cast(widgets_.first()); + NodeParamViewTextEdit* e = static_cast(widgets_.first()); e->setTextPreservingCursor(input_.GetValueAtTime(node_time).toString()); break; } From a0f26fcc7b987950ac3e8ce7ce04c09ae3d6ca46 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 May 2021 10:47:05 +1000 Subject: [PATCH 10/12] timelineview: don't playheadpress if tool is kAdd --- app/widget/timelinewidget/view/timelineview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 75cb091ad..89bc3b8f1 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -60,7 +60,7 @@ void TimelineView::mousePressEvent(QMouseEvent *event) TimelineViewMouseEvent timeline_event = CreateMouseEvent(event); if (HandPress(event) - || (!GetItemAtScenePos(timeline_event.GetFrame(), timeline_event.GetTrack().index()) && PlayheadPress(event))) { + || (!GetItemAtScenePos(timeline_event.GetFrame(), timeline_event.GetTrack().index()) && Core::instance()->tool() != Tool::kAdd && PlayheadPress(event))) { // Let the parent handle this return; } From b7916b8ae076d04d26b380a571680d5a84653699 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 May 2021 10:51:51 +1000 Subject: [PATCH 11/12] removed subtitle encoding functions --- app/codec/encoder.cpp | 8 +-- app/codec/encoder.h | 4 +- app/codec/exportcodec.cpp | 27 -------- app/codec/exportcodec.h | 2 - app/dialog/export/export.cpp | 2 +- app/dialog/export/exportsubtitlestab.cpp | 10 --- app/dialog/export/exportsubtitlestab.h | 7 -- app/render/subtitleparams.cpp | 86 ------------------------ app/render/subtitleparams.h | 19 ------ 9 files changed, 3 insertions(+), 162 deletions(-) diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 9bedc6b8f..eec326526 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -115,10 +115,9 @@ void EncodingParams::EnableAudio(const AudioParams &audio_params, const ExportCo audio_codec_ = acodec; } -void EncodingParams::EnableSubtitles(const SubtitleParams::Encoding &encoding, const ExportCodec::Codec &scodec) +void EncodingParams::EnableSubtitles(const ExportCodec::Codec &scodec) { subtitles_enabled_ = true; - subtitles_encoding_ = encoding; subtitles_codec_ = scodec; } @@ -232,11 +231,6 @@ 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_; diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 49466c6a5..ace7bf7a0 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -49,7 +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 EnableSubtitles(const ExportCodec::Codec &scodec); void set_video_option(const QString& key, const QString& value); void set_video_bit_rate(const int64_t& rate); @@ -95,7 +95,6 @@ public: } bool subtitles_enabled() const; - SubtitleParams::Encoding subtitles_encoding() const; ExportCodec::Codec subtitles_codec() const; const rational& GetExportLength() const; @@ -125,7 +124,6 @@ private: bool subtitles_enabled_; ExportCodec::Codec subtitles_codec_; - SubtitleParams::Encoding subtitles_encoding_; rational export_length_; diff --git a/app/codec/exportcodec.cpp b/app/codec/exportcodec.cpp index 039537651..bb2834ecb 100644 --- a/app/codec/exportcodec.cpp +++ b/app/codec/exportcodec.cpp @@ -97,31 +97,4 @@ 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; -} - } diff --git a/app/codec/exportcodec.h b/app/codec/exportcodec.h index ee1a3a1c0..1a2cb3c05 100644 --- a/app/codec/exportcodec.h +++ b/app/codec/exportcodec.h @@ -63,8 +63,6 @@ public: static bool IsCodecAStillImage(Codec c); - static SubtitleParams::Encoding GetDefaultSubtitleEncoding(Codec c); - }; } diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index c3d31fda9..16e0bb8ae 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -563,7 +563,7 @@ ExportParams ExportDialog::GenerateParams() const } if (subtitles_enabled_->isChecked()) { - params.EnableSubtitles(subtitle_tab_->GetSubtitleEncoding(), subtitle_tab_->GetSubtitleCodec()); + params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec()); } return params; diff --git a/app/dialog/export/exportsubtitlestab.cpp b/app/dialog/export/exportsubtitlestab.cpp index 5c535b60b..27c40fc16 100644 --- a/app/dialog/export/exportsubtitlestab.cpp +++ b/app/dialog/export/exportsubtitlestab.cpp @@ -20,16 +20,6 @@ ExportSubtitlesTab::ExportSubtitlesTab(QWidget *parent) : codec_combobox_ = new QComboBox(); layout->addWidget(codec_combobox_, row, 1); - row++; - - layout->addWidget(new QLabel(tr("Encoding:")), row, 0); - - encoding_combobox_ = new QComboBox(); - for (int i=0; iaddItem(SubtitleParams::GetEncodingName(static_cast(i)), i); - } - layout->addWidget(encoding_combobox_, row, 1); - outer_layout->addStretch(); } diff --git a/app/dialog/export/exportsubtitlestab.h b/app/dialog/export/exportsubtitlestab.h index f3beaa10b..fd160c91b 100644 --- a/app/dialog/export/exportsubtitlestab.h +++ b/app/dialog/export/exportsubtitlestab.h @@ -40,16 +40,9 @@ public: return static_cast(codec_combobox_->currentData().toInt()); } - SubtitleParams::Encoding GetSubtitleEncoding() - { - return static_cast(encoding_combobox_->currentData().toInt()); - } - private: QComboBox *codec_combobox_; - QComboBox *encoding_combobox_; - }; } diff --git a/app/render/subtitleparams.cpp b/app/render/subtitleparams.cpp index d864e58e8..ceaa06f82 100644 --- a/app/render/subtitleparams.cpp +++ b/app/render/subtitleparams.cpp @@ -24,92 +24,6 @@ namespace olive { -QString SubtitleParams::GetEncodingName(Encoding encoding) -{ - switch (encoding) { - case kISO8859_1: - return QCoreApplication::translate("SubtitleParams", "ASCII/ISO 8859-1"); - case kWindows1252: - return QCoreApplication::translate("SubtitleParams", "Windows-1252"); - case kUTF8: - return QCoreApplication::translate("SubtitleParams", "UTF-8"); - case kUTF8WithBOM: - return QCoreApplication::translate("SubtitleParams", "UTF-8 with BOM"); - case kUTF16LE: - return QCoreApplication::translate("SubtitleParams", "UTF-16LE"); - case kUTF16BE: - return QCoreApplication::translate("SubtitleParams", "UTF-16BE"); - case kEncodingInvalid: - case kEncodingCount: - break; - } - - return QCoreApplication::translate("SubtitleParams", "Unknown"); -} - -bool SubtitleParams::EncodingHasUnicodeBOM(Encoding encoding) -{ - switch (encoding) { - case kUTF8WithBOM: - case kUTF16LE: - case kUTF16BE: - return true; - - case kISO8859_1: - case kWindows1252: - case kUTF8: - case kEncodingInvalid: - case kEncodingCount: - break; - } - - return false; -} - -QByteArray SubtitleParams::GetUnicodeBOM(Encoding encoding) -{ - QByteArray arr; - - if (encoding == kUTF8WithBOM) { - arr.resize(3); - arr[0] = 0xEF; - arr[1] = 0xBB; - arr[2] = 0xBF; - } else if (encoding == kUTF16LE) { - arr.resize(2); - arr[0] = 0xFF; - arr[1] = 0xFE; - } else if (encoding == kUTF16BE) { - arr.resize(2); - arr[0] = 0xFE; - arr[1] = 0xFF; - } - - return arr; -} - -const char *SubtitleParams::GetQTextStreamCodec(Encoding encoding) -{ - switch (encoding) { - case SubtitleParams::kISO8859_1: - return "ISO 8859-1"; - case SubtitleParams::kWindows1252: - return "Windows-1252"; - case SubtitleParams::kUTF8: - case SubtitleParams::kUTF8WithBOM: - return "UTF-8"; - case SubtitleParams::kUTF16LE: - return "UTF-16LE"; - case SubtitleParams::kUTF16BE: - return "UTF-16BE"; - case SubtitleParams::kEncodingInvalid: - case SubtitleParams::kEncodingCount: - break; - } - - return nullptr; -} - QString SubtitleParams::GenerateASSHeader() { // NOTE: We'll probably implement more customization as we support ASS better. Right now, we only diff --git a/app/render/subtitleparams.h b/app/render/subtitleparams.h index 241cccc33..c72f9c25c 100644 --- a/app/render/subtitleparams.h +++ b/app/render/subtitleparams.h @@ -27,25 +27,6 @@ namespace olive { class SubtitleParams { public: - enum Encoding { - kEncodingInvalid = -1, - kISO8859_1, - kWindows1252, - kUTF8, - kUTF8WithBOM, - kUTF16LE, - kUTF16BE, - kEncodingCount - }; - - static QString GetEncodingName(Encoding encoding); - - static bool EncodingHasUnicodeBOM(Encoding encoding); - - static QByteArray GetUnicodeBOM(Encoding encoding); - - static const char *GetQTextStreamCodec(Encoding encoding); - static QString GenerateASSHeader(); }; From 45db6f45963857e532d89301b96efbbd87fdf3d6 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 May 2021 11:10:41 +1000 Subject: [PATCH 12/12] node: fixed issue with keyframes Fixes #1634 This is technically the same bug fixed in #1326, just didn't include all the cases I needed to. --- app/node/node.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/node/node.cpp b/app/node/node.cpp index a33ae58bd..044d9c263 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -610,8 +610,7 @@ QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational & NodeKeyframe* after = key_track.at(i+1); if (before->time() == time - || !NodeValue::type_can_be_interpolated(type) - || (before->type() == NodeKeyframe::kHold && after->time() > time)) { + || ((!NodeValue::type_can_be_interpolated(type) || before->type() == NodeKeyframe::kHold) && after->time() > time)) { // Time == keyframe time, so value is precise return before->value();