From 0cab96d46300cfa191456b0c3f8e593531c4969e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 2 Jul 2022 19:08:24 -0500 Subject: [PATCH 001/107] clip: correct bug in media_range() function --- app/node/block/clip/clip.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index f8bdea614..9d5e90b0c 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -286,7 +286,7 @@ void ClipBlock::Retranslate() TimeRange ClipBlock::media_range() const { - return InputTimeAdjustment(kBufferIn, -1, range()); + return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); } } From 37fad2748477189c416df9eba78e339c604d1480 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 4 Jul 2022 09:53:46 -0500 Subject: [PATCH 002/107] viewer: repush buffers Fixes nested sequences --- app/node/output/viewer/viewer.cpp | 14 ++++++++++++++ app/node/output/viewer/viewer.h | 2 ++ app/node/value.h | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index b505a8354..11f6142e2 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -376,6 +376,20 @@ Node::ValueHint ViewerOutput::GetConnectedSampleValueHint() return GetValueHintForInput(kSamplesInput); } +void ViewerOutput::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + if (HasInputWithID(kTextureInput)) { + NodeValue repush = value[kTextureInput]; + repush.set_tag(Track::Reference(Track::kVideo, 0).ToString()); + table->Push(repush); + } + if (HasInputWithID(kSamplesInput)) { + NodeValue repush = value[kSamplesInput]; + repush.set_tag(Track::Reference(Track::kAudio, 0).ToString()); + table->Push(value[kSamplesInput]); + } +} + void ViewerOutput::InputValueChangedEvent(const QString &input, int element) { if (element == 0) { diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index bc30cbc8a..09b0ac3d5 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -162,6 +162,8 @@ public: virtual ValueHint GetConnectedSampleValueHint(); + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + static const QString kVideoParamsInput; static const QString kAudioParamsInput; static const QString kSubtitleParamsInput; diff --git a/app/node/value.h b/app/node/value.h index 6cf1e537d..88db6ebf0 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -236,6 +236,11 @@ public: return tag_; } + void set_tag(const QString& tag) + { + tag_ = tag; + } + const Node* source() const { return from_; From 13a0da537f4e203dfd8eac91575c6f12e0044d83 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 8 Jul 2022 03:20:12 -0700 Subject: [PATCH 003/107] numericsliderbase: make connections before showing Fixes #1962 --- app/widget/slider/base/numericsliderbase.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/widget/slider/base/numericsliderbase.cpp b/app/widget/slider/base/numericsliderbase.cpp index 4f0397967..d0a3e78f2 100644 --- a/app/widget/slider/base/numericsliderbase.cpp +++ b/app/widget/slider/base/numericsliderbase.cpp @@ -54,15 +54,15 @@ void NumericSliderBase::LabelPressed() { // Generate width hint drag_ladder_ = new SliderLadder(drag_multiplier_, ladder_element_count_, GetFormattedValueToString(99999999)); + connect(drag_ladder_, &SliderLadder::DraggedByValue, this, &NumericSliderBase::LadderDragged); + connect(drag_ladder_, &SliderLadder::Released, this, &NumericSliderBase::LadderReleased); + drag_ladder_->SetValue(GetFormattedValueToString()); drag_ladder_->show(); drag_start_value_ = GetValueInternal(); QMetaObject::invokeMethod(this, "RepositionLadder", Qt::QueuedConnection); - - connect(drag_ladder_, &SliderLadder::DraggedByValue, this, &NumericSliderBase::LadderDragged); - connect(drag_ladder_, &SliderLadder::Released, this, &NumericSliderBase::LadderReleased); } void NumericSliderBase::LadderDragged(int value, double multiplier) From 3963797dee8e58a7a423e375caca4a8ae57d73e4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 9 Jul 2022 21:46:02 -0700 Subject: [PATCH 004/107] render: add extra flush when returning cpu frame --- app/render/renderprocessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index c8296088c..5b60436f8 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -121,6 +121,8 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time texture = blit_tex; } + render_ctx_->Flush(); + render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels()); } From 963ffe8de3ee075d44688d30aae0ca1331bf5e85 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 10 Jul 2022 11:12:43 -0700 Subject: [PATCH 005/107] viewer: reimplement add band in custom code --- app/widget/viewer/viewerdisplay.cpp | 35 ++++++++++++++++------------- app/widget/viewer/viewerdisplay.h | 3 ++- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 1efaff97f..08874ed83 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -68,7 +68,7 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : show_widget_background_(false), playback_speed_(0), push_mode_(kPushNull), - add_band_(nullptr), + add_band_(false), queue_starved_(false) { connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::ToolChanged); @@ -104,11 +104,11 @@ void ViewerDisplayWidget::SetMatrixCrop(const QMatrix4x4 &mat) void ViewerDisplayWidget::UpdateCursor() { if (Core::instance()->tool() == Tool::kHand) { - setCursor(Qt::OpenHandCursor); + this->inner_widget()->setCursor(Qt::OpenHandCursor); } else if (Core::instance()->tool() == Tool::kAdd) { - setCursor(Qt::CrossCursor); + this->inner_widget()->setCursor(Qt::CrossCursor); } else { - unsetCursor(); + this->inner_widget()->unsetCursor(); } } @@ -504,6 +504,15 @@ void ViewerDisplayWidget::OnPaint() p.drawPath(path); } } + + if (add_band_) { + QPainter p(paint_device()); + QColor highlight = palette().highlight().color(); + p.setPen(highlight); + highlight.setAlpha(128); + p.setBrush(highlight); + p.drawRect(QRect(add_band_start_, add_band_end_).normalized()); + } } void ViewerDisplayWidget::OnDestroy() @@ -770,10 +779,8 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) && (Core::instance()->GetSelectedAddableObject() == Tool::kAddableShape || Core::instance()->GetSelectedAddableObject() == Tool::kAddableTitle)) { add_band_start_ = event->pos(); - - add_band_ = new QRubberBand(QRubberBand::Rectangle, this); - add_band_->setGeometry(QRect(add_band_start_, add_band_start_)); - add_band_->show(); + add_band_end_ = add_band_start_; + add_band_ = true; } else if (gizmos_ && (gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(), @@ -813,8 +820,8 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event) } else if (add_band_) { - add_band_->setGeometry(QRect(event->pos(), add_band_start_).normalized()); - + add_band_end_ = event->pos(); + update(); return true; } else if (current_gizmo_) { @@ -870,15 +877,13 @@ bool ViewerDisplayWidget::OnMouseRelease(QMouseEvent *e) } else if (add_band_) { - const QRect &band_rect = add_band_->geometry(); + QRect band_rect = QRect(add_band_start_, add_band_end_).normalized(); if (band_rect.width() > 1 && band_rect.height() > 1) { - QRectF r = GenerateDisplayTransform().inverted().mapRect(add_band_->geometry()); + QRectF r = GenerateDisplayTransform().inverted().mapRect(band_rect); emit CreateAddableAt(r); } - add_band_->deleteLater(); - add_band_ = nullptr; - + add_band_ = false; return true; } else if (current_gizmo_) { diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 2b1491f5e..4a9e0943d 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -369,8 +369,9 @@ private: rational playback_timebase_; - QRubberBand *add_band_; + bool add_band_; QPoint add_band_start_; + QPoint add_band_end_; bool queue_starved_; From a8d46c4c5f9596322c195b8feffccc8eab617a67 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 10 Jul 2022 12:54:55 -0700 Subject: [PATCH 006/107] render: fix issue responding to cancelled tasks --- app/codec/decoder.cpp | 10 +++--- app/codec/decoder.h | 10 +++--- app/codec/ffmpeg/ffmpegdecoder.cpp | 16 ++++----- app/codec/ffmpeg/ffmpegdecoder.h | 9 +++-- app/codec/oiio/oiiodecoder.cpp | 4 +-- app/codec/oiio/oiiodecoder.h | 4 +-- app/common/CMakeLists.txt | 2 -- app/common/cancelableobject.h | 19 +++++----- app/common/threadedobject.cpp | 57 ------------------------------ app/common/threadedobject.h | 50 -------------------------- app/node/project/footage/footage.h | 5 +-- app/node/traverser.cpp | 1 - app/node/traverser.h | 22 ++++-------- app/render/CMakeLists.txt | 1 + app/render/cancelatom.h | 48 +++++++++++++++++++++++++ app/render/renderprocessor.cpp | 2 +- app/task/conform/conform.cpp | 2 +- 17 files changed, 98 insertions(+), 164 deletions(-) delete mode 100644 app/common/threadedobject.cpp delete mode 100644 app/common/threadedobject.h create mode 100644 app/render/cancelatom.h diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 0eebf8055..d208f80da 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -86,7 +86,7 @@ bool Decoder::Open(const CodecStream &stream) } } -TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled) +TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, CancelAtom *cancelled) { QMutexLocker locker(&mutex_); @@ -102,7 +102,7 @@ TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, return nullptr; } - if (cancelled && *cancelled) { + if (cancelled && cancelled->IsCancelled()) { return nullptr; } @@ -160,7 +160,7 @@ void Decoder::Close() } } -bool Decoder::ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled) +bool Decoder::ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, CancelAtom *cancelled) { return ConformAudioInternal(output_filenames, params, cancelled); } @@ -264,7 +264,7 @@ int64_t Decoder::GetImageSequenceIndex(const QString &filename) return number_only.toLongLong(); } -TexturePtr Decoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled) +TexturePtr Decoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, CancelAtom *cancelled) { Q_UNUSED(timecode) Q_UNUSED(divider) @@ -272,7 +272,7 @@ TexturePtr Decoder::RetrieveVideoInternal(Renderer *renderer, const rational &ti return nullptr; } -bool Decoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, const QAtomicInt* cancelled) +bool Decoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, CancelAtom *cancelled) { Q_UNUSED(filenames) Q_UNUSED(cancelled) diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 99fa74428..dff4cdb40 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -192,7 +192,7 @@ public: * * This function is thread safe and can only run while the decoder is open. \see Open() */ - TexturePtr RetrieveVideo(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled = nullptr); + TexturePtr RetrieveVideo(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled = nullptr); enum RetrieveAudioStatus { kInvalid = -1, @@ -227,7 +227,7 @@ public: * * This function is re-entrant. */ - virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const = 0; + virtual FootageDescription Probe(const QString& filename, CancelAtom *cancelled) const = 0; /** * @brief Closes media/deallocates memory @@ -239,7 +239,7 @@ public: /** * @brief Conform audio stream */ - bool ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled = nullptr); + bool ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, CancelAtom *cancelled = nullptr); /** * @brief Create a Decoder instance using a Decoder ID @@ -287,9 +287,9 @@ protected: * Sub-classes must override this function IF they support video. Function is already mutexed * so sub-classes don't need to worry about thread safety. */ - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled); + virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled); - virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled); + virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled); void SignalProcessingProgress(int64_t ts, int64_t duration); diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index bfd53c4f0..7469408c7 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -141,10 +141,10 @@ bool FFmpegDecoder::OpenInternal() return output_frame; }*/ -TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, const QAtomicInt *cancelled) +TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, CancelAtom *cancelled) { if (AVFramePtr f = RetrieveFrame(timecode, cancelled)) { - if (cancelled && *cancelled) { + if (cancelled && cancelled->IsCancelled()) { return nullptr; } @@ -289,7 +289,7 @@ QString FFmpegDecoder::id() const return QStringLiteral("ffmpeg"); } -FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicInt *cancelled) const +FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *cancelled) const { // Return value FootageDescription desc(id()); @@ -514,7 +514,7 @@ QString FFmpegDecoder::FFmpegError(int error_code) return QStringLiteral("%1 %2").arg(QString::number(error_code), err); } -bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, const QAtomicInt *cancelled) +bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, CancelAtom *cancelled) { // Iterate through each audio frame and extract the PCM data @@ -564,7 +564,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, cons while (true) { // Check if we have a `cancelled` ptr and its value - if (cancelled && *cancelled) { + if (cancelled && cancelled->IsCancelled()) { break; } @@ -744,7 +744,7 @@ void FFmpegDecoder::ClearFrameCache() } } -AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *cancelled) +AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancelled) { int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); @@ -782,7 +782,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt * while (true) { // Break out of loop if we've cancelled - if (cancelled && *cancelled) { + if (cancelled && cancelled->IsCancelled()) { break; } @@ -793,7 +793,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt * // Pull from the decoder ret = instance_.GetFrame(working_packet_, filtered.get()); - if (cancelled && *cancelled) { + if (cancelled && cancelled->IsCancelled()) { break; } diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 4e51f496f..1c0d25bb8 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -31,7 +31,6 @@ extern "C" { #include } -#include #include #include #include @@ -64,12 +63,12 @@ public: virtual bool SupportsVideo() override{return true;} virtual bool SupportsAudio() override{return true;} - virtual FootageDescription Probe(const QString &filename, const QAtomicInt *cancelled) const override; + virtual FootageDescription Probe(const QString &filename, CancelAtom *cancelled) const override; protected: virtual bool OpenInternal() override; - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled) override; - virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled) override; + virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled) override; + virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled) override; virtual void CloseInternal() override; private: @@ -151,7 +150,7 @@ private: void ClearFrameCache(); - AVFramePtr RetrieveFrame(const rational &time, const QAtomicInt *cancelled); + AVFramePtr RetrieveFrame(const rational &time, CancelAtom *cancelled); void RemoveFirstFrame(); diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 9eea81d18..e9a84ca28 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -45,7 +45,7 @@ QString OIIODecoder::id() const return QStringLiteral("oiio"); } -FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt* cancelled) const +FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cancelled) const { Q_UNUSED(cancelled) @@ -116,7 +116,7 @@ bool OIIODecoder::OpenInternal() return OpenImageHandler(stream().filename(), stream().stream()); } -TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, const QAtomicInt *cancelled) +TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, CancelAtom *cancelled) { Q_UNUSED(timecode) Q_UNUSED(cancelled) diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 5233678af..3cc894eba 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -40,11 +40,11 @@ public: virtual bool SupportsVideo() override{return true;} - virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const override; + virtual FootageDescription Probe(const QString& filename, CancelAtom *cancelled) const override; protected: virtual bool OpenInternal() override; - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled) override; + virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled) override; virtual void CloseInternal() override; private: diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index b4bdea348..0b7ff983a 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -55,8 +55,6 @@ set(OLIVE_SOURCES common/ratiodialog.h common/rational.cpp common/rational.h - common/threadedobject.cpp - common/threadedobject.h common/threadsafemap.h common/timecodefunctions.cpp common/timecodefunctions.h diff --git a/app/common/cancelableobject.h b/app/common/cancelableobject.h index d7a1fe5d8..eef63d375 100644 --- a/app/common/cancelableobject.h +++ b/app/common/cancelableobject.h @@ -21,35 +21,38 @@ #ifndef CANCELABLEOBJECT_H #define CANCELABLEOBJECT_H -#include - #include "common/define.h" +#include "render/cancelatom.h" namespace olive { class CancelableObject { public: - CancelableObject() : - cancelled_(false) + CancelableObject() { } void Cancel() { - cancelled_ = true; + cancel_.Cancel(); CancelEvent(); } - const QAtomicInt& IsCancelled() const + CancelAtom *GetCancelAtom() { - return cancelled_; + return &cancel_; + } + + bool IsCancelled() + { + return cancel_.IsCancelled(); } protected: virtual void CancelEvent(){} private: - QAtomicInt cancelled_; + CancelAtom cancel_; }; diff --git a/app/common/threadedobject.cpp b/app/common/threadedobject.cpp deleted file mode 100644 index fccb2cd4d..000000000 --- a/app/common/threadedobject.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 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 "threadedobject.h" - -namespace olive { - -void ThreadedObject::LockDeletes() -{ - threadobj_delete_lock_++; -} - -void ThreadedObject::UnlockDeletes() -{ - Q_ASSERT(AreDeletesLocked()); - - threadobj_delete_lock_--; -} - -bool ThreadedObject::AreDeletesLocked() -{ - return (threadobj_delete_lock_ > 0); -} - -void ThreadedObject::LockMutex() -{ - threadobj_main_lock_.lock(); -} - -void ThreadedObject::UnlockMutex() -{ - threadobj_main_lock_.unlock(); -} - -bool ThreadedObject::TryLockMutex(int timeout) -{ - return threadobj_main_lock_.tryLock(timeout); -} - -} diff --git a/app/common/threadedobject.h b/app/common/threadedobject.h deleted file mode 100644 index c42b4faa5..000000000 --- a/app/common/threadedobject.h +++ /dev/null @@ -1,50 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 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 THREADEDOBJECT_H -#define THREADEDOBJECT_H - -#include - -#include "common/define.h" - -namespace olive { - -class ThreadedObject -{ -public: - - void LockMutex(); - void UnlockMutex(); - bool TryLockMutex(int timeout = 0); - - void LockDeletes(); - void UnlockDeletes(); - bool AreDeletesLocked(); - -private: - QMutex threadobj_main_lock_; - - QAtomicInt threadobj_delete_lock_; -}; - -} - -#endif // THREADEDOBJECT_H diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 43e5f80c7..6b801972f 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -28,6 +28,7 @@ #include "footagedescription.h" #include "node/output/viewer/viewer.h" #include "render/audioparams.h" +#include "render/cancelatom.h" #include "render/videoparams.h" namespace olive { @@ -141,7 +142,7 @@ public: */ void set_timestamp(const qint64 &t); - void SetCancelPointer(const QAtomicInt* c) + void SetCancelPointer(CancelAtom *c) { cancelled_ = c; } @@ -232,7 +233,7 @@ private: bool valid_; - const QAtomicInt* cancelled_; + CancelAtom *cancelled_; private slots: void CheckFootage(); diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index ed7aae25d..a2a38a3be 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -260,7 +260,6 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu NodeTraverser::NodeTraverser() : cancel_(nullptr), - heard_cancel_(false), transform_(nullptr) { } diff --git a/app/node/traverser.h b/app/node/traverser.h index 524fc8337..1c0fbe9da 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -26,6 +26,7 @@ #include "codec/decoder.h" #include "common/cancelableobject.h" #include "node/output/track/track.h" +#include "render/cancelatom.h" #include "render/job/footagejob.h" #include "render/job/colortransformjob.h" #include "value.h" @@ -130,24 +131,16 @@ protected: bool IsCancelled() { - bool c = cancel_ && *cancel_; - if (c) { - heard_cancel_ = true; - } - return c; + return cancel_ && cancel_->IsCancelled(); } - bool HeardCancel() const { return heard_cancel_; } - - const QAtomicInt *GetCancelPointer() const + bool HeardCancel() const { - return cancel_; + return cancel_ && cancel_->HeardCancel(); } - void SetCancelPointer(const QAtomicInt *cancel) - { - cancel_ = cancel; - } + CancelAtom *GetCancelPointer() const { return cancel_; } + void SetCancelPointer(CancelAtom *cancel) { cancel_ = cancel; } void ResolveJobs(NodeValue &value, const TimeRange &range); @@ -165,8 +158,7 @@ private: AudioParams audio_params_; - const QAtomicInt *cancel_; - bool heard_cancel_; + CancelAtom *cancel_; const Node *transform_start_; const Node *transform_now_; diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index d8a9a9a22..dcde3f3ed 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -24,6 +24,7 @@ set(OLIVE_SOURCES render/audioparams.h render/audioplaybackcache.cpp render/audioplaybackcache.h + render/cancelatom.h render/color.cpp render/color.h render/colorprocessor.cpp diff --git a/app/render/cancelatom.h b/app/render/cancelatom.h new file mode 100644 index 000000000..d4db43c1c --- /dev/null +++ b/app/render/cancelatom.h @@ -0,0 +1,48 @@ +#ifndef CANCELATOM_H +#define CANCELATOM_H + +#include + +namespace olive { + +class CancelAtom +{ +public: + CancelAtom() : + cancelled_(false), + heard_(false) + {} + + bool IsCancelled() + { + QMutexLocker locker(&mutex_); + if (cancelled_) { + heard_ = true; + } + return cancelled_; + } + + void Cancel() + { + QMutexLocker locker(&mutex_); + cancelled_ = true; + } + + bool HeardCancel() + { + QMutexLocker locker(&mutex_); + return heard_; + } + +private: + QMutex mutex_; + + bool cancelled_; + + bool heard_; + +}; + +} + +#endif // CANCELATOM_H diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 5b60436f8..400f0984c 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -134,7 +134,7 @@ void RenderProcessor::Run() // Depending on the render ticket type, start a job RenderManager::TicketType type = ticket_->property("type").value(); - SetCancelPointer(&ticket_->IsCancelled()); + SetCancelPointer(ticket_->GetCancelAtom()); SetCacheVideoParams(ticket_->property("vparam").value()); SetCacheAudioParams(ticket_->property("aparam").value()); diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index a0cd5fe72..4c73ef770 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -42,7 +42,7 @@ bool ConformTask::Run() connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged); - bool ret = decoder->ConformAudio(output_filenames_, params_, &IsCancelled()); + bool ret = decoder->ConformAudio(output_filenames_, params_, GetCancelAtom()); decoder->Close(); From a6d1ec99a896c7f6b77cb0b12416a7ff95bc58f3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 11 Jul 2022 19:03:37 -0700 Subject: [PATCH 007/107] opengl: remove old geriatric texture cache --- app/render/opengl/openglrenderer.cpp | 139 ++++++--------------------- app/render/opengl/openglrenderer.h | 15 --- 2 files changed, 31 insertions(+), 123 deletions(-) diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index ade819167..5078b963c 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -84,12 +84,9 @@ QMutex global_opengl_mutex; OpenGLRenderer::OpenGLRenderer(QObject* parent) : Renderer(parent), - cache_timer_(this), context_(nullptr), framebuffer_(0) { - cache_timer_.setInterval(kTextureCacheMaxSize); - connect(&cache_timer_, &QTimer::timeout, this, &OpenGLRenderer::GarbageCollectTextureCache); } OpenGLRenderer::~OpenGLRenderer() @@ -156,8 +153,6 @@ void OpenGLRenderer::PostInit() // Set up framebuffer used for various things functions_->glGenFramebuffers(1, &framebuffer_); - - cache_timer_.start(); } void OpenGLRenderer::DestroyInternal() @@ -169,19 +164,12 @@ void OpenGLRenderer::DestroyInternal() functions_->glDeleteFramebuffers(1, &framebuffer_); framebuffer_ = 0; - for (auto it=texture_cache_.cbegin(); it!=texture_cache_.cend(); it++) { - functions_->glDeleteTextures(1, &it->texture); - } - texture_cache_.clear(); - // Delete context if it belongs to us if (context_->parent() == this) { delete context_; } context_ = nullptr; } - - cache_timer_.stop(); } void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, double b, double a) @@ -210,38 +198,25 @@ QVariant OpenGLRenderer::CreateNativeTexture3D(int width, int height, int depth, { GL_PREAMBLE; - GLuint texture = GetCachedTexture(width, height, depth, format, channel_count); + // Generate new texture + GLuint texture; + functions_->glGenTextures(1, &texture); + texture_params_.insert(texture, {width, height, depth, format, channel_count}); - // If no texture in cache, generate new texture - bool new_tex = (texture == 0); - if (new_tex) { - functions_->glGenTextures(1, &texture); - texture_params_.insert(texture, {width, height, depth, format, channel_count}); - } + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); - if (new_tex || data) { - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + GLint current_tex; + functions_->glGetIntegerv(GL_TEXTURE_BINDING_3D, ¤t_tex); - GLint current_tex; - functions_->glGetIntegerv(GL_TEXTURE_BINDING_3D, ¤t_tex); + functions_->glBindTexture(GL_TEXTURE_3D, texture); - functions_->glBindTexture(GL_TEXTURE_3D, texture); + context_->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GetInternalFormat(format, channel_count), + width, height, depth, 0, GetPixelFormat(channel_count), + GetPixelType(format), data); - if (new_tex) { - context_->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GetInternalFormat(format, channel_count), - width, height, depth, 0, GetPixelFormat(channel_count), - GetPixelType(format), data); - } else { - context_->extraFunctions()->glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, - width, height, depth, - GetPixelFormat(channel_count), GetPixelType(format), - data); - } + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - functions_->glBindTexture(GL_TEXTURE_3D, current_tex); - } + functions_->glBindTexture(GL_TEXTURE_3D, current_tex); return texture; } @@ -268,10 +243,7 @@ void OpenGLRenderer::DestroyNativeTexture(QVariant texture) GLuint t = texture.value(); if (t > 0) { - TextureCacheKey key = texture_params_.value(t); - TextureCacheEntry entry = {key, t, QDateTime::currentMSecsSinceEpoch()}; - - texture_cache_.append(entry); + functions_->glDeleteTextures(1, &t); } } @@ -818,41 +790,27 @@ void OpenGLRenderer::ClearDestinationInternal(double r, double g, double b, doub QVariant OpenGLRenderer::CreateNativeTexture2DInternal(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) { - GLuint texture = GetCachedTexture(width, height, 1, format, channel_count); + GLuint texture; + functions_->glGenTextures(1, &texture); + texture_params_.insert(texture, {width, height, 1, format, channel_count}); - // If no texture in cache, generate new texture - bool new_tex = (texture == 0); - if (new_tex) { - functions_->glGenTextures(1, &texture); - texture_params_.insert(texture, {width, height, 1, format, channel_count}); + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + + GLint current_tex; + functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); + + functions_->glBindTexture(GL_TEXTURE_2D, texture); + + { + PRINT_GL_ERRORS; + functions_->glTexImage2D(GL_TEXTURE_2D, 0, GetInternalFormat(format, channel_count), + width, height, 0, GetPixelFormat(channel_count), + GetPixelType(format), data); } - if (new_tex || data) { - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - GLint current_tex; - functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); - - functions_->glBindTexture(GL_TEXTURE_2D, texture); - - { - PRINT_GL_ERRORS; - if (new_tex) { - functions_->glTexImage2D(GL_TEXTURE_2D, 0, GetInternalFormat(format, channel_count), - width, height, 0, GetPixelFormat(channel_count), - GetPixelType(format), data); - } else { - functions_->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, - width, height, - GetPixelFormat(channel_count), GetPixelType(format), - data); - } - } - - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - functions_->glBindTexture(GL_TEXTURE_2D, current_tex); - } + functions_->glBindTexture(GL_TEXTURE_2D, current_tex); return texture; } @@ -862,25 +820,6 @@ QVariant OpenGLRenderer::CreateNativeTexture2DInternal(const VideoParams ¶ms return CreateNativeTexture2DInternal(params.effective_width(), params.effective_height(), params.format(), params.channel_count(), data, linesize); } -GLuint OpenGLRenderer::GetCachedTexture(int width, int height, int depth, VideoParams::Format format, int channel_count) -{ - TextureCacheKey input_key = {width, height, depth, format, channel_count}; - - for (int i=0; iage < max_age) { - GL_PREAMBLE; - GLuint t = it->texture; - texture_params_.remove(t); - functions_->glDeleteTextures(1, &t); - it = texture_cache_.erase(it); - } else { - it++; - } - } -} - } diff --git a/app/render/opengl/openglrenderer.h b/app/render/opengl/openglrenderer.h index b1ec02474..2e51480b1 100644 --- a/app/render/opengl/openglrenderer.h +++ b/app/render/opengl/openglrenderer.h @@ -96,12 +96,8 @@ private: QVariant CreateNativeTexture2DInternal(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0); QVariant CreateNativeTexture2DInternal(const VideoParams ¶ms, const void* data = nullptr, int linesize = 0); - GLuint GetCachedTexture(int width, int height, int depth, VideoParams::Format format, int channel_count); - GLuint CompileShader(GLenum type, const QString &code); - QTimer cache_timer_; - QOpenGLContext* context_; QOpenGLFunctions* functions_; @@ -124,21 +120,10 @@ private: } }; - struct TextureCacheEntry { - TextureCacheKey key; - GLuint texture; - qint64 age; - }; - - QVector texture_cache_; - QMap texture_params_; static const int kTextureCacheMaxSize; -private slots: - void GarbageCollectTextureCache(); - }; } From 4b8febd10d3e1cf3d38af75d7494ef7fc4bee73f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 15 Jul 2022 09:47:58 -0700 Subject: [PATCH 008/107] timebasedview: cache scroll value before scale change on ctrl zoom Fixes bug where scroll would be calculated incorrectly due to range limitation from scale change --- app/widget/timebased/timebasedview.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index fe54459e2..310df8866 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -109,20 +109,24 @@ void TimeBasedView::ZoomIntoCursorPosition(QWheelEvent *event, double scale_mult } if (!only_vertical) { + double old_scroll = horizontalScrollBar()->value(); + double old_scale = GetScale(); emit ScaleChanged(old_scale * scale_multiplier); // Use GetScale so that if this value was clamped, we don't erroneously use an unclamped value - int new_x_scroll = qRound(double(cursor_pos.x() + horizontalScrollBar()->value()) / old_scale * GetScale() - cursor_pos.x()); + int new_x_scroll = qRound((cursor_pos.x() + old_scroll) / old_scale * GetScale() - cursor_pos.x()); horizontalScrollBar()->setValue(new_x_scroll); } if (!only_horizontal) { + double old_y_scroll = verticalScrollBar()->value(); + double old_y_scale = GetYScale(); SetYScale(old_y_scale * scale_multiplier); // Use GetYScale so that if this value was clamped, we don't erroneously use an unclamped value - int new_y_scroll = qRound(double(cursor_pos.y() + verticalScrollBar()->value()) / old_y_scale * GetYScale() - cursor_pos.y()); + int new_y_scroll = qRound((cursor_pos.y() + old_y_scroll) / old_y_scale * GetYScale() - cursor_pos.y()); verticalScrollBar()->setValue(new_y_scroll); } } From e4fd2d5d2ccc5e9850a6ea1bc3ad5a95e67551db Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 15 Jul 2022 10:59:51 -0700 Subject: [PATCH 009/107] timebasedwidget: rewrote scroll catchup code Code is much better. Less hacky queuing, implements cooldowns to improve navigation, and introduces a framework so that subclasses can make use of catchup/cooldown code too with any scrollbar they want --- app/widget/curvewidget/curvewidget.cpp | 12 +++--- app/widget/curvewidget/curvewidget.h | 3 +- app/widget/keyframeview/keyframeview.cpp | 4 +- app/widget/keyframeview/keyframeview.h | 2 + app/widget/nodeparamview/nodeparamview.cpp | 8 +++- app/widget/nodeparamview/nodeparamview.h | 1 + app/widget/timebased/timebasedwidget.cpp | 43 +++++++++++++++++++ app/widget/timebased/timebasedwidget.h | 18 ++++++++ app/widget/timelinewidget/timelinewidget.cpp | 11 ++++- .../timelinewidget/view/timelineview.cpp | 3 +- .../view/timelineviewmouseevent.h | 18 +++++--- 11 files changed, 103 insertions(+), 20 deletions(-) diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index d58bde7cd..d85e6d168 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -103,6 +103,7 @@ CurveWidget::CurveWidget(QWidget *parent) : connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged); connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale); connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged); + connect(view_, &CurveView::Released, this, &CurveWidget::KeyframeViewReleased); // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of view_->setHorizontalScrollBar(scrollbar()); @@ -379,15 +380,14 @@ void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref) void CurveWidget::KeyframeViewDragged(int x, int y) { - QMetaObject::invokeMethod(this, "CatchUpScrollToPoint", Qt::QueuedConnection, - Q_ARG(int, x)); - QMetaObject::invokeMethod(this, "CatchUpYScrollToPoint", Qt::QueuedConnection, - Q_ARG(int, y)); + SetCatchUpScrollValue(x); + SetCatchUpScrollValue(view_->verticalScrollBar(), y, view_->height()); } -void CurveWidget::CatchUpYScrollToPoint(int point) +void CurveWidget::KeyframeViewReleased() { - PageScrollInternal(view_->verticalScrollBar(), view_->height(), point, false); + StopCatchUpScrollTimer(); + StopCatchUpScrollTimer(view_->verticalScrollBar()); } } diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 5fae1b4df..2d85c86df 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -122,8 +122,7 @@ private slots: void InputSelectionChanged(const NodeKeyframeTrackReference& ref); void KeyframeViewDragged(int x, int y); - - void CatchUpYScrollToPoint(int point); + void KeyframeViewReleased(); }; diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index df0349475..cf64fd668 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -290,8 +290,7 @@ void KeyframeView::mouseMoveEvent(QMouseEvent *event) if (event->buttons()) { // Signal cursor pos in case we should scroll to catch up to it - QPointF scene_pos = mapToScene(event->pos()); - emit Dragged(scene_pos.x(), scene_pos.y()); + emit Dragged(event->pos().x(), event->pos().y()); } } @@ -309,6 +308,7 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event) selection_manager_.DragStop(command); KeyframeDragRelease(event, command); Core::instance()->undo_stack()->push(command); + emit Released(); } else if (selection_manager_.IsRubberBanding()) { selection_manager_.RubberBandStop(); Redraw(); diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 2558f8cc7..0ab17f254 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -88,6 +88,8 @@ signals: void SelectionChanged(); + void Released(); + protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index c3ab8be03..8262c2949 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -132,6 +132,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime); connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged); + connect(keyframe_view_, &KeyframeView::Released, this, &NodeParamView::KeyframeViewReleased); // Connect keyframe view scaling to this connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale); @@ -929,7 +930,12 @@ void NodeParamView::KeyframeViewDragged(int x, int y) { Q_UNUSED(y) - QMetaObject::invokeMethod(this, "CatchUpScrollToPoint", Qt::QueuedConnection, Q_ARG(int, x)); + SetCatchUpScrollValue(x); +} + +void NodeParamView::KeyframeViewReleased() +{ + StopCatchUpScrollTimer(); } void NodeParamView::UpdateElementY() diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index ba918d373..f1c3793af 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -165,6 +165,7 @@ private slots: //void FocusChanged(QWidget *old, QWidget *now); void KeyframeViewDragged(int x, int y); + void KeyframeViewReleased(); void NodeAddedToContext(Node *n); diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index ebd95cf9e..add053974 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -52,6 +52,10 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu connect(scrollbar_, &ResizableScrollBar::ResizeMoved, this, &TimeBasedWidget::ScrollBarResizeMoved); PassWheelEventsToScrollBar(ruler_); + + catchup_scroll_timer_ = new QTimer(this); + catchup_scroll_timer_->setInterval(250); // Hardcoded 1/4 scroll limit value + connect(catchup_scroll_timer_, &QTimer::timeout, this, &TimeBasedWidget::CatchUpTimerTimeout); } void TimeBasedWidget::SetScaleAndCenterOnPlayhead(const double &scale) @@ -217,6 +221,15 @@ void TimeBasedWidget::CatchUpScrollToPoint(int point) PageScrollInternal(point, false); } +void TimeBasedWidget::CatchUpTimerTimeout() +{ + for (auto it=catchup_scroll_values_.cbegin(); it!=catchup_scroll_values_.cend(); it++) { + QScrollBar *sb = it.key(); + const CatchUpScrollData &d = it.value(); + PageScrollInternal(sb, d.maximum, sb->value() + d.value, false); + } +} + void TimeBasedWidget::AutoUpdateTimebase() { rational video_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base(); @@ -301,6 +314,36 @@ void TimeBasedWidget::PassWheelEventsToScrollBar(QObject *object) object->installEventFilter(this); } +void TimeBasedWidget::SetCatchUpScrollValue(QScrollBar *b, int v, int maximum) +{ + CatchUpScrollData &cudata = catchup_scroll_values_[b]; + cudata.value = v; + cudata.maximum = maximum; + + static const qint64 min_cooldown = 100; // Hardcoded 1/10 sec cooldown + if (QDateTime::currentMSecsSinceEpoch() - cudata.last_forced >= min_cooldown) { + QMetaObject::invokeMethod(this, &TimeBasedWidget::CatchUpTimerTimeout, Qt::QueuedConnection); + cudata.last_forced = QDateTime::currentMSecsSinceEpoch(); + } + + if (!catchup_scroll_timer_->isActive()) { + catchup_scroll_timer_->start(); + } +} + +void TimeBasedWidget::SetCatchUpScrollValue(int v) +{ + SetCatchUpScrollValue(scrollbar_, v, ruler()->width()); +} + +void TimeBasedWidget::StopCatchUpScrollTimer(QScrollBar *b) +{ + catchup_scroll_values_.remove(b); + if (catchup_scroll_values_.empty()) { + catchup_scroll_timer_->stop(); + } +} + void TimeBasedWidget::SetTime(const rational &time) { if (UserIsDraggingPlayhead()) { diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 509e8f8b9..905a22fb4 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -150,6 +150,14 @@ protected: void PassWheelEventsToScrollBar(QObject* object); + void SetCatchUpScrollValue(QScrollBar *b, int v, int maximum); + void SetCatchUpScrollValue(int v); + void StopCatchUpScrollTimer(QScrollBar *b); + void StopCatchUpScrollTimer() + { + StopCatchUpScrollTimer(scrollbar_); + } + virtual const QVector *GetSnapBlocks() const { return nullptr; } virtual const QVector *GetSnapKeyframes() const { return nullptr; } virtual const std::vector *GetSnapIgnoreKeyframes() const { return nullptr; } @@ -228,6 +236,14 @@ private: TimelineWorkArea *workarea_; TimelineMarkerList *markers_; + QTimer *catchup_scroll_timer_; + struct CatchUpScrollData { + qint64 last_forced = 0; + int maximum; + int value; + }; + QMap catchup_scroll_values_; + private slots: void UpdateMaximumScroll(); @@ -247,6 +263,8 @@ private slots: void CatchUpScrollToPoint(int point); + void CatchUpTimerTimeout(); + void AutoUpdateTimebase(); void ConnectedNodeRemovedFromGraph(); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 3dc750a86..76dcc6e90 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -906,8 +906,7 @@ void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event) UpdateViewports(); - QMetaObject::invokeMethod(this, "CatchUpScrollToPoint", Qt::QueuedConnection, - Q_ARG(int, qRound(event->GetSceneX()))); + SetCatchUpScrollValue(event->GetScreenPos().x()); } else { // Mouse is not down, attempt a hover event TimelineTool* hover_tool = GetActiveTool(); @@ -922,6 +921,8 @@ void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event) void TimelineWidget::ViewMouseReleased(TimelineViewMouseEvent *event) { + StopCatchUpScrollTimer(); + if (active_tool_) { if (GetConnectedNode()) { active_tool_->MouseRelease(event); @@ -956,16 +957,22 @@ void TimelineWidget::ViewDragMoved(TimelineViewMouseEvent *event) { import_tool_->DragMove(event); UpdateViewports(); + + SetCatchUpScrollValue(event->GetScreenPos().x()); } void TimelineWidget::ViewDragLeft(QDragLeaveEvent *event) { + StopCatchUpScrollTimer(); + import_tool_->DragLeave(event); UpdateViewports(); } void TimelineWidget::ViewDragDropped(TimelineViewMouseEvent *event) { + StopCatchUpScrollTimer(); + import_tool_->DragDrop(event); UpdateViewports(); } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 35b40259e..71e953c5a 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -430,7 +430,8 @@ TimelineViewMouseEvent TimelineView::CreateMouseEvent(const QPoint& pos, Qt::Mou { QPointF scene_pt = mapToScene(pos); - return TimelineViewMouseEvent(scene_pt.x(), + return TimelineViewMouseEvent(scene_pt, + pos, GetScale(), timebase(), Track::Reference(ConnectedTrackType(), SceneToTrack(scene_pt.y())), diff --git a/app/widget/timelinewidget/view/timelineviewmouseevent.h b/app/widget/timelinewidget/view/timelineviewmouseevent.h index 79bd99d85..a37a5a9d8 100644 --- a/app/widget/timelinewidget/view/timelineviewmouseevent.h +++ b/app/widget/timelinewidget/view/timelineviewmouseevent.h @@ -34,13 +34,15 @@ namespace olive { class TimelineViewMouseEvent { public: - TimelineViewMouseEvent(const qreal& scene_x, + TimelineViewMouseEvent(const QPointF& scene_pos, + const QPoint &screen_pos, const double& scale_x, const rational& timebase, const Track::Reference &track, const Qt::MouseButton &button, const Qt::KeyboardModifiers& modifiers = Qt::NoModifier) : - scene_x_(scene_x), + scene_pos_(scene_pos), + screen_pos_(screen_pos), scale_x_(scale_x), timebase_(timebase), track_(track), @@ -73,7 +75,7 @@ public: */ rational GetFrame(bool round = false) const { - return TimeScaledObject::SceneToTime(scene_x_, scale_x_, timebase_, round); + return TimeScaledObject::SceneToTime(GetSceneX(), scale_x_, timebase_, round); } const Track::Reference& GetTrack() const @@ -96,11 +98,14 @@ public: source_event_ = event; } - const qreal& GetSceneX() const + qreal GetSceneX() const { - return scene_x_; + return scene_pos_.x(); } + const QPointF &GetScenePos() const { return scene_pos_; } + const QPoint &GetScreenPos() const { return screen_pos_; } + const Qt::MouseButton& GetButton() const { return button_; @@ -122,7 +127,8 @@ public: void SetBypassImportBuffer(bool e) { bypass_import_buffer_ = e; } private: - qreal scene_x_; + QPointF scene_pos_; + QPoint screen_pos_; double scale_x_; rational timebase_; From 98a71a47c8dffc0290ea3aee547a69c7430b251e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 16 Jul 2022 16:54:32 -0700 Subject: [PATCH 010/107] projectexplorer: fixed UI issues around renaming items --- app/node/project/projectviewmodel.cpp | 5 ++ .../projectexplorer/projectexplorer.cpp | 63 ++++--------------- app/widget/projectexplorer/projectexplorer.h | 8 +-- 3 files changed, 17 insertions(+), 59 deletions(-) diff --git a/app/node/project/projectviewmodel.cpp b/app/node/project/projectviewmodel.cpp index 3aefbb13d..c7e558249 100644 --- a/app/node/project/projectviewmodel.cpp +++ b/app/node/project/projectviewmodel.cpp @@ -170,6 +170,11 @@ QVariant ProjectViewModel::data(const QModelIndex &index, int role) const } } break; + case Qt::EditRole: + if (column_type == kName) { + return internal_item->GetLabel(); + } + break; case Qt::DecorationRole: // If this is the first column, return the Item's icon if (column_type == kName) { diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 8236dfe55..05c2b5e38 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -92,10 +92,6 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) : // Set default icon size SizeChangedSlot(kProjectIconSizeDefault); - // Set rename timer timeout - rename_timer_.setInterval(500); - connect(&rename_timer_, &QTimer::timeout, this, &ProjectExplorer::RenameTimerSlot); - connect(tree_view_, &ProjectExplorerTreeView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu); connect(list_view_, &ProjectExplorerListView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu); connect(icon_view_, &ProjectExplorerIconView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu); @@ -137,8 +133,7 @@ void ProjectExplorer::Edit(Node *item) void ProjectExplorer::AddView(QAbstractItemView *view) { view->setModel(&sort_model_); - view->setEditTriggers(QAbstractItemView::NoEditTriggers); - connect(view, &QAbstractItemView::clicked, this, &ProjectExplorer::ItemClickedSlot); + view->setEditTriggers(QAbstractItemView::SelectedClicked); connect(view, &QAbstractItemView::doubleClicked, this, &ProjectExplorer::ItemDoubleClickedSlot); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectExplorer::ViewSelectionChanged); connect(view, SIGNAL(DoubleClickedEmptyArea()), this, SLOT(ViewEmptyAreaDoubleClickedSlot())); @@ -147,9 +142,6 @@ void ProjectExplorer::AddView(QAbstractItemView *view) void ProjectExplorer::BrowseToFolder(const QModelIndex &index) { - // Make sure any rename timers are stopped - rename_timer_.stop(); - // Set appropriate views to this index icon_view_->setRootIndex(index); list_view_->setRootIndex(index); @@ -264,53 +256,19 @@ QAbstractItemView *ProjectExplorer::CurrentView() const return static_cast(stacked_widget_->currentWidget()); } -void ProjectExplorer::ItemClickedSlot(const QModelIndex &index) -{ - if (index.isValid()) { - if (CurrentView()->selectionModel()->selectedRows().size() == 1) { - if (clicked_index_ == index) { - // The item has been clicked more than once, start a timer for renaming - rename_timer_.start(); - } else { - // Cache this index for the next click - clicked_index_ = index; - - // If the rename timer had started, stop it now - rename_timer_.stop(); - } - } else { - clicked_index_ = QModelIndex(); - rename_timer_.stop(); - } - } else { - // Stop the rename timer - rename_timer_.stop(); - } -} - void ProjectExplorer::ViewEmptyAreaDoubleClickedSlot() { - // Ensure no attempts to rename are made - clicked_index_ = QModelIndex(); - rename_timer_.stop(); - emit DoubleClickedItem(nullptr); } void ProjectExplorer::ItemDoubleClickedSlot(const QModelIndex &index) { - // Ensure no attempts to rename are made - clicked_index_ = QModelIndex(); - rename_timer_.stop(); - // Retrieve source item from index Node* i = static_cast(sort_model_.mapToSource(index).internalPointer()); // If the item is a folder, browse to it if (dynamic_cast(i) && (view_type() == ProjectToolbar::ListView || view_type() == ProjectToolbar::IconView)) { - BrowseToFolder(index); - } // Emit a signal @@ -335,16 +293,12 @@ void ProjectExplorer::DirUpSlot() } } -void ProjectExplorer::RenameTimerSlot() +void ProjectExplorer::RenameSelectedItem() { - // Start editing this index - CurrentView()->edit(clicked_index_); - - // Reset clicked index state - clicked_index_ = QModelIndex(); - - // Stop rename timer - rename_timer_.stop(); + auto indexes = CurrentView()->selectionModel()->selectedRows(); + if (!indexes.empty()) { + CurrentView()->edit(indexes.first()); + } } void ProjectExplorer::ShowContextMenu() @@ -447,6 +401,11 @@ void ProjectExplorer::ShowContextMenu() if (context_menu_items_.size() == 1) { menu.addSeparator(); + auto rename_action = menu.addAction(tr("Rename")); + connect(rename_action, &QAction::triggered, this, &ProjectExplorer::RenameSelectedItem); + + menu.addSeparator(); + QAction* properties_action = menu.addAction(tr("P&roperties")); connect(properties_action, &QAction::triggered, this, &ProjectExplorer::ShowItemPropertiesDialog); } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index eb31a2acc..1074e1a4f 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -160,15 +160,9 @@ private: QSortFilterProxyModel sort_model_; ProjectViewModel model_; - QModelIndex clicked_index_; - - QTimer rename_timer_; - QVector context_menu_items_; private slots: - void ItemClickedSlot(const QModelIndex& index); - void ViewEmptyAreaDoubleClickedSlot(); void ItemDoubleClickedSlot(const QModelIndex& index); @@ -177,7 +171,7 @@ private slots: void DirUpSlot(); - void RenameTimerSlot(); + void RenameSelectedItem(); void ShowContextMenu(); From 24ae3a25d3a3c6c68d82d16bdb67a8f4afdc3b76 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 16 Jul 2022 17:12:35 -0700 Subject: [PATCH 011/107] timeline: use scroll cooldown when dragging playhead --- app/widget/timebased/timebasedwidget.cpp | 3 ++- app/widget/timebased/timebasedwidget.h | 14 +++++++------- app/widget/timeruler/seekablewidget.cpp | 1 + app/widget/timeruler/seekablewidget.h | 3 +++ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index add053974..b91f44e9f 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -46,6 +46,7 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this); ConnectTimelineView(ruler_, true); ruler()->SetSnapService(this); + connect(ruler(), &TimeRuler::DragReleased, this, static_cast(&TimeBasedWidget::StopCatchUpScrollTimer)); scrollbar_ = new ResizableTimelineScrollBar(Qt::Horizontal, this); connect(scrollbar_, &ResizableScrollBar::ResizeBegan, this, &TimeBasedWidget::ScrollBarResizeBegan); @@ -348,7 +349,7 @@ void TimeBasedWidget::SetTime(const rational &time) { if (UserIsDraggingPlayhead()) { // If the user is dragging the playhead, we will simply nudge over and not use autoscroll rules. - QMetaObject::invokeMethod(this, "CatchUpScrollToPlayhead", Qt::QueuedConnection); + SetCatchUpScrollValue(qRound(TimeToScene(time)) - scrollbar_->value()); } else { // Otherwise, assume we jumped to this out of nowhere and must now autoscroll switch (static_cast(OLIVE_CONFIG("Autoscroll").toInt())) { diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 905a22fb4..ce00a3e1e 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -121,9 +121,6 @@ public slots: void DeleteSelected(); -protected slots: - void SetTimeAndSignal(const rational& t); - protected: ResizableTimelineScrollBar* scrollbar() const; @@ -153,10 +150,6 @@ protected: void SetCatchUpScrollValue(QScrollBar *b, int v, int maximum); void SetCatchUpScrollValue(int v); void StopCatchUpScrollTimer(QScrollBar *b); - void StopCatchUpScrollTimer() - { - StopCatchUpScrollTimer(scrollbar_); - } virtual const QVector *GetSnapBlocks() const { return nullptr; } virtual const QVector *GetSnapKeyframes() const { return nullptr; } @@ -177,6 +170,13 @@ protected slots: static void PageScrollInternal(QScrollBar* bar, int maximum, int screen_position, bool whole_page_scroll); + void SetTimeAndSignal(const olive::rational& t); + + void StopCatchUpScrollTimer() + { + StopCatchUpScrollTimer(scrollbar_); + } + signals: void TimeChanged(const rational&); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 5f130485f..3ef5a135c 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -231,6 +231,7 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) } dragging_ = false; + emit DragReleased(); } void SeekableWidget::mouseDoubleClickEvent(QMouseEvent *event) diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index d29686f8f..6e16592f5 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -78,6 +78,9 @@ public slots: virtual void TimebaseChangedEvent(const rational &) override; +signals: + void DragReleased(); + protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; From f14112a775d37cc1848348d540c15b17bf56c2f4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 16 Jul 2022 18:37:27 -0700 Subject: [PATCH 012/107] track: check for negative values too --- app/node/output/track/tracklist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index 3149d973b..6759d54b5 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -38,7 +38,7 @@ TrackList::TrackList(Sequence *parent, const Track::Type &type, const QString &t Track *TrackList::GetTrackAt(int index) const { - if (index < track_cache_.size()) { + if (index >= 0 && index < track_cache_.size()) { return track_cache_.at(index); } else { return nullptr; From d262b5c802ec364f843a1760dff26d3cbbfc0c12 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 16 Jul 2022 20:17:49 -0700 Subject: [PATCH 013/107] qtutils: fixed issues with word wrap Fixes issue that triggers infinite loop in some circumstances. Fixes #1947 probably --- app/common/qtutils.cpp | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index 8105de09b..1edf6448c 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -20,6 +20,8 @@ #include "qtutils.h" +#include + namespace olive { int QtUtils::QFontMetricsWidth(QFontMetrics fm, const QString& s) { @@ -94,19 +96,43 @@ QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, in QString this_line = lines.at(i); while (this_line.size() > 1 && QFontMetricsWidth(fm, this_line) >= bounding_width) { + int old_size = this_line.size(); + int hard_break = -1; + for (int j=this_line.size()-1; j>=0; j--) { - if (this_line.at(j).isSpace()) { - QString chopped = this_line.left(j); - if (QFontMetricsWidth(fm, chopped) < bounding_width) { + const QChar &char_test = this_line.at(j); + + if (char_test.isSpace() + || char_test == '-') { + if (QFontMetricsWidth(fm, this_line.left(j)) < bounding_width) { + if (!char_test.isSpace()) { + j++; + } + + QString chopped = this_line.left(j); + list.append(chopped); - int k = j+1; - while (k < this_line.size() && this_line.at(k).isSpace()) { - k++; + while (j < this_line.size() && this_line.at(j).isSpace()) { + j++; } - this_line.remove(0, k); + this_line.remove(0, j); break; } + } else if (hard_break == -1 && QFontMetricsWidth(fm, this_line.left(j)) < bounding_width) { + // In case we can't find a better place to split, split at the earliest time the line + // goes under the width limit + hard_break = j; + } + } + + if (old_size == this_line.size()) { + if (hard_break != -1) { + list.append(this_line.left(hard_break)); + this_line.remove(0, hard_break); + } else { + qWarning() << "Failed to find anywhere to wrap. Returning full line."; + break; } } } From 9662822a2e45e2663439c56b1f69ad4f33992ef1 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Jul 2022 14:13:10 -0700 Subject: [PATCH 014/107] nodevalue: implement extra constructor overrides --- app/node/project/footage/footage.cpp | 2 +- app/node/value.h | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 4a5e5f0a5..0832b2f0a 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -294,7 +294,7 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV type = NodeValue::kSamples; } - table->Push(type, QVariant::fromValue(job), this, false, ref.ToString()); + table->Push(type, QVariant::fromValue(job), this, ref.ToString()); } } } diff --git a/app/node/value.h b/app/node/value.h index 88db6ebf0..2a0b14620 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -208,6 +208,12 @@ public: set_value(data); } + template + NodeValue(Type type, const T& data, const Node* from, const QString& tag) : + NodeValue(type, data, from, false, tag) + { + } + Type type() const { return type_; @@ -331,6 +337,7 @@ public: QVector3D toVec3() const { return value(); } QVector4D toVec4() const { return value(); } Bezier toBezier() const { return value(); } + QVector toArray() const { return value >(); } private: Type type_; @@ -378,6 +385,12 @@ public: Push(NodeValue(type, data, from, array, tag)); } + template + void Push(NodeValue::Type type, const T& data, const Node *from, const QString& tag) + { + Push(NodeValue(type, data, from, false, tag)); + } + void Prepend(const NodeValue& value) { values_.prepend(value); @@ -389,6 +402,12 @@ public: Prepend(NodeValue(type, data, from, array, tag)); } + template + void Prepend(NodeValue::Type type, const T& data, const Node *from, const QString& tag) + { + Prepend(NodeValue(type, data, from, false, tag)); + } + const NodeValue& at(int index) const { return values_.at(index); From f157315690c9177fece6a554b289af195d3e47ec Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Jul 2022 14:39:58 -0700 Subject: [PATCH 015/107] timeformatnode: implement --- app/node/factory.cpp | 3 + app/node/factory.h | 1 + app/node/time/CMakeLists.txt | 1 + app/node/time/timeformat/CMakeLists.txt | 22 +++++++ app/node/time/timeformat/timeformat.cpp | 79 +++++++++++++++++++++++++ app/node/time/timeformat/timeformat.h | 53 +++++++++++++++++ 6 files changed, 159 insertions(+) create mode 100644 app/node/time/timeformat/CMakeLists.txt create mode 100644 app/node/time/timeformat/timeformat.cpp create mode 100644 app/node/time/timeformat/timeformat.h diff --git a/app/node/factory.cpp b/app/node/factory.cpp index dd82a0805..41d6f55a5 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -62,6 +62,7 @@ #include "project/folder/folder.h" #include "project/footage/footage.h" #include "project/sequence/sequence.h" +#include "time/timeformat/timeformat.h" #include "time/timeoffset/timeoffsetnode.h" #include "time/timeremap/timeremap.h" @@ -291,6 +292,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new MaskDistortNode(); case kDropShadowFilter: return new DropShadowFilter(); + case kTimeFormat: + return new TimeFormatNode(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index d7ca955ff..b6f6037f0 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -75,6 +75,7 @@ public: kChromaKey, kMaskDistort, kDropShadowFilter, + kTimeFormat, // Count value kInternalNodeCount diff --git a/app/node/time/CMakeLists.txt b/app/node/time/CMakeLists.txt index e23a85367..9f3aa1dc5 100644 --- a/app/node/time/CMakeLists.txt +++ b/app/node/time/CMakeLists.txt @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(timeformat) add_subdirectory(timeoffset) add_subdirectory(timeremap) diff --git a/app/node/time/timeformat/CMakeLists.txt b/app/node/time/timeformat/CMakeLists.txt new file mode 100644 index 000000000..552649a6d --- /dev/null +++ b/app/node/time/timeformat/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 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/time/timeformat/timeformat.cpp + node/time/timeformat/timeformat.h + PARENT_SCOPE +) diff --git a/app/node/time/timeformat/timeformat.cpp b/app/node/time/timeformat/timeformat.cpp new file mode 100644 index 000000000..50a870a86 --- /dev/null +++ b/app/node/time/timeformat/timeformat.cpp @@ -0,0 +1,79 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "timeformat.h" + +#include + +namespace olive { + +#define super Node + +const QString TimeFormatNode::kTimeInput = QStringLiteral("time_in"); +const QString TimeFormatNode::kFormatInput = QStringLiteral("format_in"); +const QString TimeFormatNode::kLocalTimeInput = QStringLiteral("localtime_in"); + +TimeFormatNode::TimeFormatNode() +{ + AddInput(kTimeInput, NodeValue::kFloat); + AddInput(kFormatInput, NodeValue::kText, QStringLiteral("hh:mm:ss")); + AddInput(kLocalTimeInput, NodeValue::kBoolean); +} + +QString TimeFormatNode::Name() const +{ + return tr("Time Format"); +} + +QString TimeFormatNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.timeformat"); +} + +QVector TimeFormatNode::Category() const +{ + return {kCategoryGenerator}; +} + +QString TimeFormatNode::Description() const +{ + return tr("Format time (in Unix epoch seconds) into a string."); +} + +void TimeFormatNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTimeInput, tr("Time")); + SetInputName(kFormatInput, tr("Format")); + SetInputName(kLocalTimeInput, tr("Interpret time as local time")); +} + +void TimeFormatNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + qint64 ms_since_epoch = value[kTimeInput].toDouble()*1000; + bool time_is_local = value[kLocalTimeInput].toBool(); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(ms_since_epoch, time_is_local ? Qt::LocalTime : Qt::UTC); + QString format = value[kFormatInput].toString(); + QString output = dt.toString(format); + table->Push(NodeValue(NodeValue::kText, output, this)); +} + +} diff --git a/app/node/time/timeformat/timeformat.h b/app/node/time/timeformat/timeformat.h new file mode 100644 index 000000000..ddc7ac8c1 --- /dev/null +++ b/app/node/time/timeformat/timeformat.h @@ -0,0 +1,53 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 TIMEFORMAT_H +#define TIMEFORMAT_H + +#include "node/node.h" + +namespace olive { + +class TimeFormatNode : public Node +{ + Q_OBJECT +public: + TimeFormatNode(); + + NODE_DEFAULT_FUNCTIONS(TimeFormatNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kTimeInput; + static const QString kFormatInput; + static const QString kLocalTimeInput; + +}; + +} + +#endif // TIMEFORMAT_H From 04c169329784e3837b7bae7eaed6a53176d064a0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Jul 2022 14:40:35 -0700 Subject: [PATCH 016/107] text: implement formatting arguments --- app/node/generator/text/textv3.cpp | 89 +++++++++++++++++++++++++++++ app/node/generator/text/textv3.h | 16 ++++++ app/node/gizmo/text.h | 4 ++ app/node/param.h | 1 + app/widget/viewer/viewerdisplay.cpp | 9 +++ app/widget/viewer/viewerdisplay.h | 1 + 6 files changed, 120 insertions(+) diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index da6f9bfe8..ce70704ab 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -39,6 +39,9 @@ enum TextVerticalAlign { }; const QString TextGeneratorV3::kTextInput = QStringLiteral("text_in"); +const QString TextGeneratorV3::kVerticalAlignmentInput = QStringLiteral("valign_in"); +const QString TextGeneratorV3::kUseArgsInput = QStringLiteral("use_args_in"); +const QString TextGeneratorV3::kArgsInput = QStringLiteral("args_in"); TextGeneratorV3::TextGeneratorV3() : ShapeNodeBase(false) @@ -48,8 +51,16 @@ TextGeneratorV3::TextGeneratorV3() : SetStandardValue(kSizeInput, QVector2D(400, 300)); + AddInput(kVerticalAlignmentInput, NodeValue::kCombo); + + AddInput(kUseArgsInput, NodeValue::kBoolean, true, InputFlags(kInputFlagHidden | kInputFlagStatic)); + + AddInput(kArgsInput, NodeValue::kText, InputFlags(kInputFlagArray)); + text_gizmo_ = new TextGizmo(this); text_gizmo_->SetInput(NodeInput(this, kTextInput)); + connect(text_gizmo_, &TextGizmo::Activated, this, &TextGeneratorV3::GizmoActivated); + connect(text_gizmo_, &TextGizmo::Deactivated, this, &TextGeneratorV3::GizmoDeactivated); } QString TextGeneratorV3::Name() const @@ -77,6 +88,9 @@ void TextGeneratorV3::Retranslate() super::Retranslate(); SetInputName(kTextInput, tr("Text")); + SetInputName(kVerticalAlignmentInput, tr("Vertical Alignment")); + SetComboBoxStrings(kVerticalAlignmentInput, {tr("Top"), tr("Middle"), tr("Bottom")}); + SetInputName(kArgsInput, tr("Arguments")); } void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const @@ -86,6 +100,21 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); job.SetRequestedFormat(VideoParams::kFormatUnsigned8); + if (value[kUseArgsInput].toBool()) { + auto args = value[kArgsInput].toArray(); + if (!args.empty()) { + QStringList list; + list.reserve(args.size()); + for (int i=0; icolor_manager()->GetDefaultInputColorSpace()); @@ -124,6 +153,18 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons p.translate(frame->video_params().width()/2, frame->video_params().height()/2); p.setClipRect(0, 0, size.x(), size.y()); + switch (static_cast(job.Get(kVerticalAlignmentInput).toInt())) { + case kVAlignTop: + // Do nothing + break; + case kVAlignMiddle: + p.translate(0, size.y()/2-text_doc.size().height()/2); + break; + case kVAlignBottom: + p.translate(0, size.y()-text_doc.size().height()); + break; + } + // Ensure default text color is white QAbstractTextDocumentLayout::PaintContext ctx; ctx.palette.setColor(QPalette::Text, Qt::white); @@ -140,4 +181,52 @@ void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGl text_gizmo_->SetHtml(row[kTextInput].toString()); } +QString TextGeneratorV3::FormatString(const QString &input, const QStringList &args) +{ + QString output; + output.reserve(input.size()); + + for (int i=0; i= 0 && index < args.size()) { + output.append(args.at(index)); + } + } else { + output.append(this_char); + } + } else { + output.append(this_char); + } + } + + return output; +} + +void TextGeneratorV3::GizmoActivated() +{ + SetStandardValue(kUseArgsInput, false); +} + +void TextGeneratorV3::GizmoDeactivated() +{ + SetStandardValue(kUseArgsInput, true); +} + } diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h index 69f08f617..a815fbb43 100644 --- a/app/node/generator/text/textv3.h +++ b/app/node/generator/text/textv3.h @@ -47,11 +47,27 @@ public: virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + enum VerticalAlignment + { + kVAlignTop, + kVAlignMiddle, + kVAlignBottom + }; + static const QString kTextInput; + static const QString kVerticalAlignmentInput; + static const QString kUseArgsInput; + static const QString kArgsInput; + + static QString FormatString(const QString &input, const QStringList &args); private: TextGizmo *text_gizmo_; +private slots: + void GizmoActivated(); + void GizmoDeactivated(); + }; } diff --git a/app/node/gizmo/text.h b/app/node/gizmo/text.h index 0c74d7cdf..30ea62f7f 100644 --- a/app/node/gizmo/text.h +++ b/app/node/gizmo/text.h @@ -42,6 +42,10 @@ public: void UpdateInputHtml(const QString &s, const rational &time); +signals: + void Activated(); + void Deactivated(); + private: QRectF rect_; diff --git a/app/node/param.h b/app/node/param.h index 4c57e9489..41bfc6736 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -37,6 +37,7 @@ enum InputFlag { kInputFlagArray = 0x1, kInputFlagNotKeyframable = 0x2, kInputFlagNotConnectable = 0x4, + kInputFlagStatic = kInputFlagNotKeyframable | kInputFlagNotConnectable, kInputFlagHidden = 0x8 }; diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 08874ed83..5ccb9bbff 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -681,6 +681,9 @@ void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) Html::HtmlToDoc(text_edit->document(), text->GetHtml()); text_edit->setProperty("gizmo", reinterpret_cast(text)); connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged); + connect(text_edit, &ViewerTextEditor::destroyed, this, &ViewerDisplayWidget::TextEditDestroyed); + + emit text->Activated(); // Get on screen text rect (this will be the text editor's global geometry) QRect global_text_area = gizmo_transform.map(text->GetRect()).boundingRect().toRect(); @@ -1048,6 +1051,12 @@ void ViewerDisplayWidget::TextEditChanged() gizmo->UpdateInputHtml(html, GetGizmoTime()); } +void ViewerDisplayWidget::TextEditDestroyed() +{ + TextGizmo *gizmo = reinterpret_cast(sender()->property("gizmo").value()); + emit gizmo->Deactivated(); +} + void ViewerDisplayWidget::SubtitlesChanged(const TimeRange &r) { if (time_ >= r.in() && time_ < r.out()) { diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 4a9e0943d..e88b845a3 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -379,6 +379,7 @@ private slots: void UpdateFromQueue(); void TextEditChanged(); + void TextEditDestroyed(); void SubtitlesChanged(const TimeRange &r); From 4c71a33b7b4a6604eb411cc95867dc643cc265e6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Jul 2022 14:48:00 -0700 Subject: [PATCH 017/107] node: implement array start offset --- app/node/generator/text/textv3.cpp | 1 + app/widget/nodeparamview/nodeparamviewitem.cpp | 2 +- app/widget/nodeview/nodeviewitem.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index ce70704ab..baadfb101 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -56,6 +56,7 @@ TextGeneratorV3::TextGeneratorV3() : AddInput(kUseArgsInput, NodeValue::kBoolean, true, InputFlags(kInputFlagHidden | kInputFlagStatic)); AddInput(kArgsInput, NodeValue::kText, InputFlags(kInputFlagArray)); + SetInputProperty(kArgsInput, QStringLiteral("arraystart"), 1); text_gizmo_ = new TextGizmo(this); text_gizmo_->SetInput(NodeInput(this, kTextInput)); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 603072e1a..7b0e82fd3 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -304,7 +304,7 @@ void NodeParamViewItemBody::Retranslate() if (ic.IsArray() && ic.element() >= 0) { // Make the label the array index - i.value().main_label->setText(tr("%1:").arg(ic.element())); + i.value().main_label->setText(tr("%1:").arg(ic.element() + ic.GetProperty(QStringLiteral("arraystart")).toInt())); } else { // Set to the input's name i.value().main_label->setText(tr("%1:").arg(ic.name())); diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9a04ee961..3879df6bd 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -373,7 +373,7 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti if (element_ == -1) { node_name = node_->GetInputName(input_); } else { - node_name = QString::number(element_); + node_name = QString::number(element_ + node_->GetInputProperty(input_, QStringLiteral("arraystart")).toInt()); } } From 0d9727b6d2cb91ca396b4b0de2cc75faa0a3b291 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Jul 2022 15:22:36 -0700 Subject: [PATCH 018/107] architecture: move loop modes to clips --- app/codec/decoder.cpp | 6 +- app/codec/decoder.h | 11 +++- .../speedduration/speeddurationdialog.cpp | 56 ++++++++++++++++--- .../speedduration/speeddurationdialog.h | 5 ++ app/node/block/clip/clip.cpp | 33 +++++++++-- app/node/block/clip/clip.h | 25 ++++++++- app/node/project/footage/footage.cpp | 26 +++------ app/node/project/footage/footage.h | 15 +---- app/node/traverser.cpp | 14 ++++- app/node/traverser.h | 4 ++ app/render/job/footagejob.h | 20 +------ app/render/renderprocessor.cpp | 2 +- .../timelinewidget/view/timelineview.cpp | 41 +++++++++++--- 13 files changed, 179 insertions(+), 79 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index d208f80da..0593da154 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -109,7 +109,7 @@ TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, return RetrieveVideoInternal(renderer, timecode, divider, cancelled); } -Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode) +Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, LoopMode loop_mode, RenderMode::Mode mode) { QMutexLocker locker(&mutex_); @@ -280,7 +280,7 @@ bool Decoder::ConformAudioInternal(const QVector &filenames, const Audi return false; } -bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) +bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange& range, LoopMode loop_mode, const AudioParams &input_params) { PlanarFileDevice input; if (input.open(conform_filenames, QFile::ReadOnly)) { @@ -290,7 +290,7 @@ bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVecto const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel(); while (write_index < buffer_length_in_bytes) { - if (loop_mode == Footage::kLoopModeLoop) { + if (loop_mode == kLoopModeLoop) { while (read_index >= input.size()) { read_index -= input.size(); } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index dff4cdb40..7fa55c5b0 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -35,7 +35,6 @@ extern "C" { #include "codec/samplebuffer.h" #include "common/rational.h" #include "node/block/block.h" -#include "node/project/footage/footage.h" #include "node/project/footage/footagedescription.h" #include "task/task.h" @@ -72,6 +71,12 @@ public: kIndexUnavailable }; + enum LoopMode { + kLoopModeOff, + kLoopModeLoop, + kLoopModeClamp + }; + Decoder(); /** @@ -209,7 +214,7 @@ public: * * This function is thread safe and can only run while the decoder is open. \see Open() */ - RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode); + RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, LoopMode loop_mode, RenderMode::Mode mode); /** * @brief Determine the last time this decoder instance was used in any way @@ -316,7 +321,7 @@ signals: private: void UpdateLastAccessed(); - bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); + bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange &range, LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; diff --git a/app/dialog/speedduration/speeddurationdialog.cpp b/app/dialog/speedduration/speeddurationdialog.cpp index 580da35d8..7214ca27c 100644 --- a/app/dialog/speedduration/speeddurationdialog.cpp +++ b/app/dialog/speedduration/speeddurationdialog.cpp @@ -38,12 +38,12 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons clips_(clips), timebase_(timebase) { - setWindowTitle(tr("Speed/Duration")); + setWindowTitle(tr("Clip Properties")); QVBoxLayout *layout = new QVBoxLayout(this); { - QGroupBox *speed_group = new QGroupBox(); + QGroupBox *speed_group = new QGroupBox(tr("Speed/Duration")); layout->addWidget(speed_group); QGridLayout *speed_layout = new QGridLayout(speed_group); @@ -72,16 +72,39 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons link_box_ = new QCheckBox(tr("Link Speed and Duration")); link_box_->setChecked(true); speed_layout->addWidget(link_box_, row, 0, 1, 2); + + row++; + + reverse_box_ = new QCheckBox(tr("Reverse")); + speed_layout->addWidget(reverse_box_, row, 0, 1, 2); + + row++; + + maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch")); + speed_layout->addWidget(maintain_audio_pitch_box_, row, 0, 1, 2); + + row++; + + ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips")); + speed_layout->addWidget(ripple_box_, row, 0, 1, 2); } - reverse_box_ = new QCheckBox(tr("Reverse")); - layout->addWidget(reverse_box_); + { + auto loop_box = new QGroupBox(tr("Loop")); + layout->addWidget(loop_box); - maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch")); - layout->addWidget(maintain_audio_pitch_box_); + auto loop_layout = new QGridLayout(loop_box); - ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips")); - layout->addWidget(ripple_box_); + int row = 0; + + loop_layout->addWidget(new QLabel(tr("Loop:")), row, 0); + + loop_combo_ = new QComboBox(); + loop_combo_->addItem(tr("None"), Decoder::kLoopModeOff); + loop_combo_->addItem(tr("Loop"), Decoder::kLoopModeLoop); + loop_combo_->addItem(tr("Clamp"), Decoder::kLoopModeClamp); + loop_layout->addWidget(loop_combo_, row, 1); + } QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); btns->setCenterButtons(true); @@ -94,6 +117,7 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons start_duration_ = clips.first()->length(); start_reverse_ = clips.first()->reverse(); start_maintain_audio_pitch_ = clips.first()->maintain_audio_pitch(); + start_loop_ = clips.first()->loop_mode(); for (int i=1; i &clips, cons if (start_maintain_audio_pitch_ != -1 && clip_maintain_pitch != start_maintain_audio_pitch_) { start_maintain_audio_pitch_ = -1; } + + if (start_loop_ != -1 && c->loop_mode() != start_loop_) { + start_loop_ = -1; + } } if (qIsNaN(start_speed_)) { @@ -141,6 +169,12 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons } else { maintain_audio_pitch_box_->setChecked(start_maintain_audio_pitch_); } + + if (start_loop_ == -1) { + loop_combo_->setCurrentIndex(-1); + } else { + loop_combo_->setCurrentIndex(start_loop_); + } } void SpeedDurationDialog::accept() @@ -211,6 +245,12 @@ void SpeedDurationDialog::accept() } } + if (loop_combo_->currentIndex() != -1) { + foreach (ClipBlock *c, clips_) { + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kLoopModeInput)), loop_combo_->currentData())); + } + } + Core::instance()->undo_stack()->push(command); super::accept(); diff --git a/app/dialog/speedduration/speeddurationdialog.h b/app/dialog/speedduration/speeddurationdialog.h index 82d95d12e..3e97d718d 100644 --- a/app/dialog/speedduration/speeddurationdialog.h +++ b/app/dialog/speedduration/speeddurationdialog.h @@ -22,6 +22,7 @@ #define SPEEDDURATIONDIALOG_H #include +#include #include #include "node/block/clip/clip.h" @@ -62,6 +63,8 @@ private: QCheckBox *ripple_box_; + QComboBox *loop_combo_; + int start_reverse_; int start_maintain_audio_pitch_; @@ -70,6 +73,8 @@ private: rational start_duration_; + int start_loop_; + rational timebase_; private slots: diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 9d5e90b0c..1b8fed631 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -34,6 +34,7 @@ const QString ClipBlock::kMediaInInput = QStringLiteral("media_in_in"); const QString ClipBlock::kSpeedInput = QStringLiteral("speed_in"); const QString ClipBlock::kReverseInput = QStringLiteral("reverse_in"); const QString ClipBlock::kMaintainAudioPitchInput = QStringLiteral("maintain_audio_pitch_in"); +const QString ClipBlock::kLoopModeInput = QStringLiteral("loop_in"); ClipBlock::ClipBlock() : in_transition_(nullptr), @@ -56,6 +57,8 @@ ClipBlock::ClipBlock() : //SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer)); SetEffectInput(kBufferIn); + + AddInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); } QString ClipBlock::Name() const @@ -89,7 +92,8 @@ void ClipBlock::set_length_and_media_out(const rational &length) if (reverse()) { // Calculate media_in adjustment - rational proposed_media_in = SequenceToMediaTime(this->length() - length, true); + + rational proposed_media_in = SequenceToMediaTime(this->length() - length, kSTMIgnoreReverse | kSTMIgnoreLoop); set_media_in(proposed_media_in); } @@ -104,7 +108,7 @@ void ClipBlock::set_length_and_media_in(const rational &length) if (!reverse()) { // Calculate media_in adjustment - rational proposed_media_in = SequenceToMediaTime(this->length() - length, false, true); + rational proposed_media_in = SequenceToMediaTime(this->length() - length, kSTMIgnoreSpeed | kSTMIgnoreLoop); waveform_.TrimIn(proposed_media_in - media_in()); @@ -127,7 +131,7 @@ void ClipBlock::set_media_in(const rational &media_in) SetStandardValue(kMediaInInput, QVariant::fromValue(media_in)); } -rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse, bool ignore_speed) const +rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, uint64_t flags) const { // These constants are not considered "values" per se, so we don't modify them if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) { @@ -136,11 +140,11 @@ rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool igno rational media_time = sequence_time; - if (reverse() && !ignore_reverse) { + if (reverse() && !(flags & kSTMIgnoreReverse)) { media_time = length() - media_time; } - if (!ignore_speed) { + if (!(flags & kSTMIgnoreSpeed)) { double speed_value = speed(); if (qIsNull(speed_value)) { // Effectively holds the frame at the in point @@ -153,6 +157,23 @@ rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool igno media_time += media_in(); + /*if (!(flags & kSTMIgnoreLoop) + && this->loop_mode() != kLoopModeOff + && connected_viewer_ + && !connected_viewer_->GetLength().isNull() + && (media_time < 0 || media_time >= connected_viewer_->GetLength())) { + if (loop_mode() == kLoopModeLoop) { + while (media_time < 0) { + media_time += connected_viewer_->GetLength(); + } + while (media_time >= connected_viewer_->GetLength()) { + media_time -= connected_viewer_->GetLength(); + } + } else if (loop_mode() == kLoopModeClamp) { + media_time = std::clamp(media_time, rational(0), connected_viewer_->GetLength()-connected_viewer_->GetVideoParams().frame_rate_as_time_base()); + } + }*/ + return media_time; } @@ -282,6 +303,8 @@ void ClipBlock::Retranslate() SetInputName(kSpeedInput, tr("Speed")); SetInputName(kReverseInput, tr("Reverse")); SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch")); + SetInputName(kLoopModeInput, tr("Loop")); + SetComboBoxStrings(kLoopModeInput, {tr("None"), tr("Loop"), tr("Clamp")}); } TimeRange ClipBlock::media_range() const diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 6541a8710..6b625eb53 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -22,6 +22,7 @@ #define CLIPBLOCK_H #include "audio/audiovisualwaveform.h" +#include "codec/decoder.h" #include "node/block/block.h" namespace olive { @@ -121,17 +122,39 @@ public: TimeRange media_range() const; + /** + * @brief Get currently set loop mode + */ + Decoder::LoopMode loop_mode() const + { + return static_cast(GetStandardValue(kLoopModeInput).toInt()); + } + + void set_loop_mode(Decoder::LoopMode l) + { + SetStandardValue(kLoopModeInput, int(l)); + } + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; static const QString kReverseInput; static const QString kMaintainAudioPitchInput; + static const QString kLoopModeInput; protected: virtual void LinkChangeEvent() override; private: - rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false, bool ignore_speed = false) const; + enum SequenceToMediaTimeFlag + { + kSTMNone, + kSTMIgnoreReverse, + kSTMIgnoreSpeed, + kSTMIgnoreLoop + }; + + rational SequenceToMediaTime(const rational& sequence_time, uint64_t flags = kSTMNone) const; rational MediaToSequenceTime(const rational& media_time) const; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 0832b2f0a..8c4ca6505 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -37,7 +37,6 @@ namespace olive { const QString Footage::kFilenameInput = QStringLiteral("file_in"); -const QString Footage::kLoopModeInput = QStringLiteral("loop_in"); #define super ViewerOutput @@ -49,8 +48,6 @@ Footage::Footage(const QString &filename) : { SetCacheTextures(true); - PrependInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); - PrependInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); Clear(); @@ -70,8 +67,6 @@ void Footage::Retranslate() super::Retranslate(); SetInputName(kFilenameInput, tr("Filename")); - SetInputName(kLoopModeInput, tr("Loop Mode")); - SetComboBoxStrings(kLoopModeInput, {tr("None"), tr("Loop"), tr("Clamp")}); } void Footage::InputValueChangedEvent(const QString &input, int element) @@ -139,11 +134,6 @@ void Footage::SetValid() valid_ = true; } -Footage::LoopMode Footage::loop_mode() const -{ - return static_cast(GetStandardValue(kLoopModeInput).toInt()); -} - QString Footage::filename() const { return GetStandardValue(kFilenameInput).toString(); @@ -263,17 +253,15 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV // Pop filename from table QString file = value[kFilenameInput].toString(); - LoopMode loop_mode = static_cast(value[kLoopModeInput].toInt()); - // If the file exists and the reference is valid, push a footage job to the renderer - if (QFileInfo(file).exists()) { + if (QFileInfo::exists(file)) { // Push length - table->Push(NodeValue::kRational, QVariant::fromValue(GetLength()), this, false, QStringLiteral("length")); + table->Push(NodeValue::kRational, QVariant::fromValue(GetLength()), this, QStringLiteral("length")); // Push each stream as a footage job for (int i=0; i= length; } -rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase) +rational Footage::AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase) { if (type == VideoParams::kVideoTypeStill) { // No looping for still images @@ -348,15 +336,15 @@ rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mod if (TimeIsOutOfBounds(time, length)) { switch (loop_mode) { - case kLoopModeOff: + case Decoder::kLoopModeOff: // Return no time to indicate no frame should be shown here time = rational::NaN; break; - case kLoopModeClamp: + case Decoder::kLoopModeClamp: // Clamp footage time to length time = clamp(time, rational(0), length - timebase); break; - case kLoopModeLoop: + case Decoder::kLoopModeLoop: // Loop footage time around job length do { if (time >= length) { diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 6b801972f..8397726a2 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -24,6 +24,7 @@ #include #include +#include "codec/decoder.h" #include "common/rational.h" #include "footagedescription.h" #include "node/output/viewer/viewer.h" @@ -44,12 +45,6 @@ class Footage : public ViewerOutput { Q_OBJECT public: - enum LoopMode { - kLoopModeOff, - kLoopModeLoop, - kLoopModeClamp - }; - /** * @brief Footage Constructor */ @@ -101,11 +96,6 @@ public: */ void SetValid(); - /** - * @brief Get currently set loop mode - */ - LoopMode loop_mode() const; - /** * @brief Return the current filename of this Footage object */ @@ -183,7 +173,7 @@ public: virtual Node *GetConnectedSampleOutput() override; - static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); + static rational AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); virtual void LoadFinishedEvent() override; @@ -191,7 +181,6 @@ public: virtual qint64 mod_time() const override; static const QString kFilenameInput; - static const QString kLoopModeInput; protected: virtual void InputValueChangedEvent(const QString &input, int element) override; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index a2a38a3be..5fcca82f5 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -21,6 +21,7 @@ #include "traverser.h" #include "node.h" +#include "node/block/clip/clip.h" #include "render/job/footagejob.h" #include "render/rendermanager.h" @@ -30,6 +31,12 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa { NodeValueDatabase database; + // HACK: Pick up loop mode from clips + Decoder::LoopMode old_loop_mode = loop_mode_; + if (const ClipBlock *clip = dynamic_cast(node)) { + loop_mode_ = clip->loop_mode(); + } + // We need to insert tables into the database for each input foreach (const QString& input, node->inputs()) { if (IsCancelled()) { @@ -39,6 +46,8 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa database.Insert(input, ProcessInput(node, input, range)); } + loop_mode_ = old_loop_mode; + return database; } @@ -260,7 +269,8 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu NodeTraverser::NodeTraverser() : cancel_(nullptr), - transform_(nullptr) + transform_(nullptr), + loop_mode_(Decoder::kLoopModeOff) { } @@ -436,7 +446,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) if (job.type() == Track::kVideo) { - rational footage_time = Footage::AdjustTimeByLoopMode(range.in(), job.loop_mode(), job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); + rational footage_time = Footage::AdjustTimeByLoopMode(range.in(), loop_mode_, job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); TexturePtr tex; diff --git a/app/node/traverser.h b/app/node/traverser.h index 1c0fbe9da..84f1b1606 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -149,6 +149,8 @@ protected: return block_stack_.empty() ? nullptr : block_stack_.back(); } + Decoder::LoopMode loop_mode() const { return loop_mode_; } + private: void PreProcessRow(const TimeRange &range, NodeValueRow &row); @@ -166,6 +168,8 @@ private: std::list block_stack_; + Decoder::LoopMode loop_mode_; + }; } diff --git a/app/render/job/footagejob.h b/app/render/job/footagejob.h index 6952d76b4..319be8a38 100644 --- a/app/render/job/footagejob.h +++ b/app/render/job/footagejob.h @@ -29,17 +29,15 @@ class FootageJob { public: FootageJob() : - type_(Track::kNone), - loop_mode_(Footage::kLoopModeOff) + type_(Track::kNone) { } - FootageJob(const QString& decoder, const QString& filename, Track::Type type, const rational& length, Footage::LoopMode loop_mode) : + FootageJob(const QString& decoder, const QString& filename, Track::Type type, const rational& length) : decoder_(decoder), filename_(filename), type_(type), - length_(length), - loop_mode_(loop_mode) + length_(length) { } @@ -98,16 +96,6 @@ public: length_ = length; } - Footage::LoopMode loop_mode() const - { - return loop_mode_; - } - - void set_loop_mode(Footage::LoopMode loop_mode) - { - loop_mode_ = loop_mode; - } - private: QString decoder_; @@ -123,8 +111,6 @@ private: rational length_; - Footage::LoopMode loop_mode_; - }; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 400f0984c..c8617e095 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -503,7 +503,7 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const Foota Decoder::RetrieveAudioStatus status = decoder->RetrieveAudio(destination, input_time, audio_params, stream.cache_path(), - stream.loop_mode(), + loop_mode(), static_cast(ticket_->property("mode").toInt())); if (status == Decoder::kWaitingForConform) { diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 71e953c5a..b6f23d911 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -531,19 +531,46 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q // Draw zebra stripes and markers if (clip->connected_viewer()) { if (!clip->connected_viewer()->GetLength().isNull()) { + painter->setPen(shadow_color); + if (clip->media_in() < 0) { - // Draw stripes for sections of clip < 0 - qreal zebra_right = TimeToScene(-clip->media_in()); - if (zebra_right > GetTimelineLeftBound()) { - DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right, block_height)); + qreal zebra_right = TimeToScene(clip->in() - clip->media_in()); + + switch (clip->loop_mode()) { + case Decoder::kLoopModeOff: + // Draw stripes for sections of clip < 0 + if (zebra_right > GetTimelineLeftBound()) { + DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right - block_left, block_height)); + } + break; + case Decoder::kLoopModeLoop: + for (qreal i=zebra_right; i>block_left; i-=TimeToScene(clip->connected_viewer()->GetLength())) { + painter->drawLine(i, block_top, i, block_top + block_height); + } + break; + case Decoder::kLoopModeClamp: + painter->drawLine(zebra_right, block_top, zebra_right, block_top + block_height); + break; } } if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { - // Draw stripes for sections for clip > clip length qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); - if (zebra_left < GetTimelineRightBound()) { - DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); + switch (clip->loop_mode()) { + case Decoder::kLoopModeOff: + // Draw stripes for sections for clip > clip length + if (zebra_left < GetTimelineRightBound()) { + DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); + } + break; + case Decoder::kLoopModeLoop: + for (qreal i=zebra_left; iconnected_viewer()->GetLength())) { + painter->drawLine(i, block_top, i, block_top + block_height); + } + break; + case Decoder::kLoopModeClamp: + painter->drawLine(zebra_left, block_top, zebra_left, block_top + block_height); + break; } } } From 2a1e87c9a9be6e76be3d71843dc433f19d1b6fee Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Jul 2022 15:42:11 -0700 Subject: [PATCH 019/107] decoder: cache still textures --- app/codec/decoder.cpp | 14 ++++++++++++-- app/codec/decoder.h | 3 +++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 0593da154..fd845737e 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -37,7 +37,8 @@ namespace olive { const rational Decoder::kAnyTimecode = RATIONAL_MIN; -Decoder::Decoder() +Decoder::Decoder() : + cached_texture_(nullptr) { UpdateLastAccessed(); } @@ -106,7 +107,14 @@ TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, return nullptr; } - return RetrieveVideoInternal(renderer, timecode, divider, cancelled); + if (cached_texture_ && cached_time_ == timecode) { + return cached_texture_; + } + + cached_texture_ = RetrieveVideoInternal(renderer, timecode, divider, cancelled); + cached_time_ = timecode; + + return cached_texture_; } Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, LoopMode loop_mode, RenderMode::Mode mode) @@ -152,6 +160,8 @@ void Decoder::Close() UpdateLastAccessed(); + cached_texture_ = nullptr; + if (stream_.IsValid()) { CloseInternal(); stream_.Reset(); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 7fa55c5b0..08ccf6e32 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -329,6 +329,9 @@ private: qint64 last_accessed_; + TexturePtr cached_texture_; + rational cached_time_; + }; uint qHash(Decoder::CodecStream stream, uint seed = 0); From 55d3915881b36161db6240d7e3ad7decc4730337 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Jul 2022 16:01:59 -0700 Subject: [PATCH 020/107] panelmanager: improve hover behavior Fixes issue where QWidget::underMouse would return false if cursor was over a child QOpenGLWindow --- app/panel/panelmanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/panel/panelmanager.cpp b/app/panel/panelmanager.cpp index a85285a4f..04943235f 100644 --- a/app/panel/panelmanager.cpp +++ b/app/panel/panelmanager.cpp @@ -66,8 +66,10 @@ PanelWidget *PanelManager::CurrentlyFocused(bool enable_hover) const PanelWidget *PanelManager::CurrentlyHovered() const { + QPoint global_mouse = QCursor::pos(); + foreach (PanelWidget* panel, focus_history_) { - if (panel->underMouse()) { + if (panel->rect().contains(panel->mapFromGlobal(global_mouse))) { return panel; } } From 9b8c23c99424460061da913ef3056c7b83938df8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 19 Jul 2022 10:23:54 -0700 Subject: [PATCH 021/107] timeline: fix issue where subtitle text wasn't shown --- app/widget/timelinewidget/tool/add.cpp | 2 +- app/widget/timelinewidget/view/timelineview.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 866a384d2..a765b0d73 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -130,9 +130,9 @@ Node *AddTool::CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, clip = new SubtitleBlock(); } else { clip = new ClipBlock(); + clip->SetLabel(olive::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject())); } clip->set_length_and_media_out(length); - clip->SetLabel(olive::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject())); NodeGraph* graph = sequence->parent(); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index b6f23d911..dcdb9c66b 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -490,7 +490,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (foreground) { painter->setBrush(Qt::NoBrush); - QString using_label = block->GetLabel().isEmpty() ? block->Name() : block->GetLabel(); + QString using_label = block->GetLabelOrName(); QRectF text_rect = r.adjusted(text_padding, text_padding, -text_padding, -text_padding); painter->setPen(block->is_enabled() ? ColorCoding::GetUISelectorColor(block->color()) : Qt::lightGray); From 35bc52ee47a7c0f4194cbcd21f4ce4be9c28f8c6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 19 Jul 2022 11:20:45 -0700 Subject: [PATCH 022/107] serializer: show context for XML load error --- app/node/project/serializer/serializer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index 362d48dc5..7a62699ce 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -261,6 +261,10 @@ ProjectSerializer::Result ProjectSerializer::LoadWithSerializerVersion(uint vers LoadData ld = serializer->Load(project, reader, nullptr); Result r(kSuccess); if (reader->hasError()) { + qWarning() << "XML error:" << reader->errorString() << "at:"; + for (int i=0; i<50; i++) { + qWarning() << reader->device()->readLine(); + } r = Result(kXmlError); r.SetDetails(reader->errorString()); } From 46be693b3ea9517a6b73d1dd5e88b489a6851954 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 19 Jul 2022 11:21:06 -0700 Subject: [PATCH 023/107] timeline: show marker text even on markers without range --- app/timeline/timelinemarker.cpp | 19 +++++++++++++++++-- app/timeline/timelinemarker.h | 2 +- .../timelinewidget/view/timelineview.cpp | 2 +- app/widget/timeruler/seekablewidget.cpp | 12 +++++++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index d8b87b7fd..25de1ab9a 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -20,6 +20,8 @@ #include "timelinemarker.h" +#include + #include "common/qtutils.h" #include "common/xmlutils.h" #include "config/config.h" @@ -76,7 +78,7 @@ int TimelineMarker::GetMarkerHeight(const QFontMetrics &fm) return fm.height(); } -QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, double scale, bool selected) +QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, int max_right, double scale, bool selected) { QFontMetrics fm = p->fontMetrics(); @@ -96,6 +98,9 @@ QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, double scale, bool sel int top = pt.y() - marker_height; + QTextOption op(Qt::AlignLeft | Qt::AlignVCenter); + op.setWrapMode(QTextOption::NoWrap); + if (time_.out() != time_.in()) { QRect marker_rect(pt.x(), top, time_.length().toDouble() * scale, marker_height); @@ -103,7 +108,7 @@ QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, double scale, bool sel if (!name_.isEmpty()) { p->setPen(ColorCoding::GetUISelectorColor(ColorCoding::GetColor(color_))); - p->drawText(marker_rect.adjusted(marker_width/4, 0, 0, 0), name_, Qt::AlignLeft | Qt::AlignVCenter); + p->drawText(marker_rect.adjusted(marker_width/4, 0, 0, 0), name_, op); } return marker_rect; @@ -125,6 +130,16 @@ QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, double scale, bool sel p->setRenderHint(QPainter::Antialiasing); p->drawPolygon(points, 6); + if (!name_.isEmpty() && max_right != -1) { + QRect text_rect(right, top, max_right - right, marker_height); + + int padding = QtUtils::QFontMetricsWidth(p->fontMetrics(), QStringLiteral(" ")); + text_rect.adjust(padding, 0, - padding - half_width, 0); + + p->setPen(qApp->palette().text().color()); + p->drawText(text_rect, name_, op); + } + return QRect(left, top, marker_width, marker_height); } } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 7712111d2..47773d237 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -51,7 +51,7 @@ public: void set_color(int c); static int GetMarkerHeight(const QFontMetrics &fm); - QRect Draw(QPainter *p, const QPoint &pt, double scale, bool selected); + QRect Draw(QPainter *p, const QPoint &pt, int max_right, double scale, bool selected); signals: void TimeChanged(const TimeRange& time); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index dcdb9c66b..34cca2fe6 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -586,7 +586,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time().in()), block_top + block_height); painter->setClipRect(r); - QRect marker_rect = marker->Draw(painter, marker_pt, GetScale(), false); + QRect marker_rect = marker->Draw(painter, marker_pt, -1, GetScale(), false); clip_marker_rects_.insert(marker, marker_rect); painter->setClipping(false); } diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 3ef5a135c..27d9f4ddc 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -277,7 +277,17 @@ void SeekableWidget::DrawMarkers(QPainter *p, int marker_bottom) break; } - QRect marker_rect = marker->Draw(p, QPoint(marker_left, marker_bottom), GetScale(), selection_manager_.IsSelected(marker)); + int max_marker_right = lim_right; + { + // Check if there's a marker next + auto next = it; + next++; + if (next != markers_->cend()) { + max_marker_right = std::min(max_marker_right, int(TimeToScene((*next)->time().in()))); + } + } + + QRect marker_rect = marker->Draw(p, QPoint(marker_left, marker_bottom), max_marker_right, GetScale(), selection_manager_.IsSelected(marker)); marker_top_ = marker_rect.top(); selection_manager_.DeclareDrawnObject(marker, marker_rect); } From bb4bbb4fd7e8ae6723f24baf99e3899424054c15 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 20 Jul 2022 08:25:52 -0700 Subject: [PATCH 024/107] gizmo: clear parent while still a gizmo Fixes #1975 --- app/node/gizmo/gizmo.cpp | 5 +++++ app/node/gizmo/gizmo.h | 1 + 2 files changed, 6 insertions(+) diff --git a/app/node/gizmo/gizmo.cpp b/app/node/gizmo/gizmo.cpp index f1e677f2a..7591115e9 100644 --- a/app/node/gizmo/gizmo.cpp +++ b/app/node/gizmo/gizmo.cpp @@ -28,4 +28,9 @@ NodeGizmo::NodeGizmo(QObject *parent) : setParent(parent); } +NodeGizmo::~NodeGizmo() +{ + setParent(nullptr); +} + } diff --git a/app/node/gizmo/gizmo.h b/app/node/gizmo/gizmo.h index 741a9dfdb..bcc878e4f 100644 --- a/app/node/gizmo/gizmo.h +++ b/app/node/gizmo/gizmo.h @@ -33,6 +33,7 @@ class NodeGizmo : public QObject Q_OBJECT public: explicit NodeGizmo(QObject *parent = nullptr); + virtual ~NodeGizmo() override; virtual void Draw(QPainter *p) const {} From 16bd15ad42add136b6cff5cc3542a5d1d0af4acf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 20 Jul 2022 21:26:49 -0700 Subject: [PATCH 025/107] serializer: improved robustness of project saving... maybe? This seems to address a bug where sometimes QXmlStreamWriter would insert nonsense null characters into a project. This is probably some real edge case like compiler optimization or some bullshit like that. I don't even know if it affects all platforms, but it definitely affected me. --- app/node/project/serializer/serializer.cpp | 11 +- .../project/serializer/serializer220403.cpp | 170 ++++++++++-------- 2 files changed, 98 insertions(+), 83 deletions(-) diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index 7a62699ce..60ab3521b 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -159,6 +159,11 @@ ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data, const QS Result inner_result = Save(&writer, data, type); + if (writer.hasError()) { + Result r(kXmlError); + return r; + } + project_file.close(); if (inner_result != kSuccess) { @@ -261,12 +266,8 @@ ProjectSerializer::Result ProjectSerializer::LoadWithSerializerVersion(uint vers LoadData ld = serializer->Load(project, reader, nullptr); Result r(kSuccess); if (reader->hasError()) { - qWarning() << "XML error:" << reader->errorString() << "at:"; - for (int i=0; i<50; i++) { - qWarning() << reader->device()->readLine(); - } r = Result(kXmlError); - r.SetDetails(reader->errorString()); + r.SetDetails(QCoreApplication::translate("Serializer", "%1 on line %2").arg(reader->errorString(), QString::number(reader->lineNumber()))); } r.SetLoadData(ld); return r; diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index a8fd3c88c..fc90aab6f 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -25,6 +25,20 @@ namespace olive { +// These wrappers may appear to do nothing, but they seem to address a bug where sometimes +// QXmlStreamWriter would insert nonsense null characters into a project. This is probably some +// real edge case like compiler optimization or some bullshit like that. I don't even know if it +// affects all platforms, but it definitely affected me on Linux. +void WriteStartElement(QXmlStreamWriter *writer, const QString &s) +{ + writer->writeStartElement(s); +} + +void WriteEndElement(QXmlStreamWriter *writer) +{ + writer->writeEndElement(); +} + ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project, QXmlStreamReader *reader, void *reserved) const { QMap > properties; @@ -319,23 +333,23 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat { if (!data.GetOnlySerializeMarkers().empty()) { - writer->writeStartElement(QStringLiteral("markers")); + WriteStartElement(writer, QStringLiteral("markers")); for (auto it=data.GetOnlySerializeMarkers().cbegin(); it!=data.GetOnlySerializeMarkers().cend(); it++) { TimelineMarker *marker = *it; - writer->writeStartElement(QStringLiteral("marker")); + WriteStartElement(writer, QStringLiteral("marker")); SaveMarker(writer, marker); - writer->writeEndElement(); // marker + WriteEndElement(writer); // marker } - writer->writeEndElement(); // markers + WriteEndElement(writer); // markers } else if (!data.GetOnlySerializeKeyframes().empty()) { - writer->writeStartElement(QStringLiteral("keyframes")); + WriteStartElement(writer, QStringLiteral("keyframes")); // Organize keyframes into node+input QHash > > > > organized; @@ -346,57 +360,57 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat } for (auto it=organized.cbegin(); it!=organized.cend(); it++) { - writer->writeStartElement(QStringLiteral("node")); + WriteStartElement(writer, QStringLiteral("node")); writer->writeAttribute(QStringLiteral("id"), it.key()); for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { - writer->writeStartElement(QStringLiteral("input")); + WriteStartElement(writer, QStringLiteral("input")); writer->writeAttribute(QStringLiteral("id"), jt.key()); for (auto kt=jt.value().cbegin(); kt!=jt.value().cend(); kt++) { - writer->writeStartElement(QStringLiteral("element")); + WriteStartElement(writer, QStringLiteral("element")); writer->writeAttribute(QStringLiteral("id"), QString::number(kt.key())); for (auto lt=kt.value().cbegin(); lt!=kt.value().cend(); lt++) { const QVector &keys = lt.value(); - writer->writeStartElement(QStringLiteral("track")); + WriteStartElement(writer, QStringLiteral("track")); writer->writeAttribute(QStringLiteral("id"), QString::number(lt.key())); for (NodeKeyframe *key : keys) { - writer->writeStartElement(QStringLiteral("key")); + WriteStartElement(writer, QStringLiteral("key")); SaveKeyframe(writer, key, key->parent()->GetInputDataType(key->input())); - writer->writeEndElement(); // key + WriteEndElement(writer); // key } - writer->writeEndElement(); // track + WriteEndElement(writer); // track } - writer->writeEndElement(); // element + WriteEndElement(writer); // element } - writer->writeEndElement(); // input + WriteEndElement(writer); // input } - writer->writeEndElement(); // node; + WriteEndElement(writer); // node; } - writer->writeEndElement(); // keyframes + WriteEndElement(writer); // keyframes } else if (Project *project = data.GetProject()) { writer->writeTextElement(QStringLiteral("uuid"), project->GetUuid().toString()); - writer->writeStartElement(QStringLiteral("nodes")); + WriteStartElement(writer, QStringLiteral("nodes")); const QVector &using_node_list = (data.GetOnlySerializeNodes().isEmpty()) ? project->nodes() : data.GetOnlySerializeNodes(); foreach (Node* node, using_node_list) { - writer->writeStartElement(QStringLiteral("node")); + WriteStartElement(writer, QStringLiteral("node")); if (node == project->root()) { writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); @@ -410,39 +424,39 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat SaveNode(node, writer); - writer->writeEndElement(); // node + WriteEndElement(writer); // node } - writer->writeEndElement(); // nodes + WriteEndElement(writer); // nodes - writer->writeStartElement(QStringLiteral("positions")); + WriteStartElement(writer, QStringLiteral("positions")); foreach (Node* context, using_node_list) { const Node::PositionMap &map = context->GetContextPositions(); if (!map.isEmpty()) { - writer->writeStartElement(QStringLiteral("context")); + WriteStartElement(writer, QStringLiteral("context")); writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { if (data.GetOnlySerializeNodes().isEmpty() || data.GetOnlySerializeNodes().contains(jt.key())) { - writer->writeStartElement(QStringLiteral("node")); + WriteStartElement(writer, QStringLiteral("node")); SavePosition(writer, jt.key(), jt.value()); - writer->writeEndElement(); // node + WriteEndElement(writer); // node } } - writer->writeEndElement(); // context + WriteEndElement(writer); // context } } - writer->writeEndElement(); // positions + WriteEndElement(writer); // positions - writer->writeStartElement(QStringLiteral("properties")); + WriteStartElement(writer, QStringLiteral("properties")); for (auto it=data.GetProperties().cbegin(); it!=data.GetProperties().cend(); it++) { - writer->writeStartElement(QStringLiteral("node")); + WriteStartElement(writer, QStringLiteral("node")); writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(it.key()))); @@ -450,10 +464,10 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat writer->writeTextElement(jt.key(), jt.value()); } - writer->writeEndElement(); // node + WriteEndElement(writer); // node } - writer->writeEndElement(); // properties + WriteEndElement(writer); // properties // Save main window project layout project->GetLayoutInfo().toXml(writer); @@ -560,50 +574,50 @@ void ProjectSerializer220403::SaveNode(Node *node, QXmlStreamWriter *writer) con writer->writeTextElement(QStringLiteral("color"), QString::number(node->GetOverrideColor())); foreach (const QString& input, node->inputs()) { - writer->writeStartElement(QStringLiteral("input")); + WriteStartElement(writer, QStringLiteral("input")); SaveInput(node, writer, input); - writer->writeEndElement(); // input + WriteEndElement(writer); // input } - writer->writeStartElement(QStringLiteral("links")); + WriteStartElement(writer, QStringLiteral("links")); foreach (Node* link, node->links()) { writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); } - writer->writeEndElement(); // links + WriteEndElement(writer); // links - writer->writeStartElement(QStringLiteral("connections")); + WriteStartElement(writer, QStringLiteral("connections")); for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - writer->writeStartElement(QStringLiteral("connection")); + WriteStartElement(writer, QStringLiteral("connection")); writer->writeAttribute(QStringLiteral("input"), it->first.input()); writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element())); writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast(it->second))); - writer->writeEndElement(); // connection + WriteEndElement(writer); // connection } - writer->writeEndElement(); // connections + WriteEndElement(writer); // connections - writer->writeStartElement(QStringLiteral("hints")); + WriteStartElement(writer, QStringLiteral("hints")); for (auto it=node->GetValueHints().cbegin(); it!=node->GetValueHints().cend(); it++) { - writer->writeStartElement(QStringLiteral("hint")); + WriteStartElement(writer, QStringLiteral("hint")); writer->writeAttribute(QStringLiteral("input"), it.key().input); writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element)); SaveValueHint(&it.value(), writer); - writer->writeEndElement(); // hint + WriteEndElement(writer); // hint } - writer->writeEndElement(); + WriteEndElement(writer); // hints - writer->writeStartElement(QStringLiteral("custom")); + WriteStartElement(writer, QStringLiteral("custom")); SaveNodeCustom(writer, node); - writer->writeEndElement(); // custom + WriteEndElement(writer); // custom } void ProjectSerializer220403::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const @@ -673,27 +687,27 @@ void ProjectSerializer220403::SaveInput(Node *node, QXmlStreamWriter *writer, co { writer->writeAttribute(QStringLiteral("id"), id); - writer->writeStartElement(QStringLiteral("primary")); + WriteStartElement(writer, QStringLiteral("primary")); SaveImmediate(writer, node, id, -1); - writer->writeEndElement(); // primary + WriteEndElement(writer); // primary - writer->writeStartElement(QStringLiteral("subelements")); + WriteStartElement(writer, QStringLiteral("subelements")); int arr_sz = node->InputArraySize(id); writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz)); for (int i=0; iwriteStartElement(QStringLiteral("element")); + WriteStartElement(writer, QStringLiteral("element")); SaveImmediate(writer, node, id, i); - writer->writeEndElement(); // element + WriteEndElement(writer); // element } - writer->writeEndElement(); // subelements + WriteEndElement(writer); // subelements } void ProjectSerializer220403::LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData &xml_node_data) const @@ -803,10 +817,10 @@ void ProjectSerializer220403::SaveImmediate(QXmlStreamWriter *writer, Node *node NodeValue::Type data_type = node->GetInputDataType(input); // Write standard value - writer->writeStartElement(QStringLiteral("standard")); + WriteStartElement(writer, QStringLiteral("standard")); foreach (const QVariant& v, node->GetSplitStandardValue(input, element)) { - writer->writeStartElement(QStringLiteral("track")); + WriteStartElement(writer, QStringLiteral("track")); if (data_type == NodeValue::kVideoParams) { v.value().Save(writer); @@ -816,29 +830,29 @@ void ProjectSerializer220403::SaveImmediate(QXmlStreamWriter *writer, Node *node writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); } - writer->writeEndElement(); // track + WriteEndElement(writer); // track } - writer->writeEndElement(); // standard + WriteEndElement(writer); // standard // Write keyframes - writer->writeStartElement(QStringLiteral("keyframes")); + WriteStartElement(writer, QStringLiteral("keyframes")); for (const NodeKeyframeTrack& track : node->GetKeyframeTracks(input, element)) { - writer->writeStartElement(QStringLiteral("track")); + WriteStartElement(writer, QStringLiteral("track")); for (NodeKeyframe* key : track) { - writer->writeStartElement(QStringLiteral("key")); + WriteStartElement(writer, QStringLiteral("key")); SaveKeyframe(writer, key, data_type); - writer->writeEndElement(); // key + WriteEndElement(writer); // key } - writer->writeEndElement(); // track + WriteEndElement(writer); // track } - writer->writeEndElement(); // keyframes + WriteEndElement(writer); // keyframes if (data_type == NodeValue::kColor) { // Save color management information @@ -1083,9 +1097,9 @@ void ProjectSerializer220403::SaveNodeCustom(QXmlStreamWriter *writer, Node *nod { if (ViewerOutput *viewer = dynamic_cast(node)) { // Write TimelinePoints - writer->writeStartElement(QStringLiteral("points")); + WriteStartElement(writer, QStringLiteral("points")); SaveTimelinePoints(writer, viewer); - writer->writeEndElement(); // points + WriteEndElement(writer); // points if (Footage *footage = dynamic_cast(node)) { writer->writeTextElement(QStringLiteral("timestamp"), QString::number(footage->timestamp())); @@ -1093,10 +1107,10 @@ void ProjectSerializer220403::SaveNodeCustom(QXmlStreamWriter *writer, Node *nod } else if (Track *track = dynamic_cast(node)) { writer->writeTextElement(QStringLiteral("height"), QString::number(track->GetTrackHeight())); } else if (NodeGroup *group = dynamic_cast(node)) { - writer->writeStartElement(QStringLiteral("inputpassthroughs")); + WriteStartElement(writer, QStringLiteral("inputpassthroughs")); foreach (const NodeGroup::InputPassthrough &ip, group->GetInputPassthroughs()) { - writer->writeStartElement(QStringLiteral("inputpassthrough")); + WriteStartElement(writer, QStringLiteral("inputpassthrough")); // Reference to inner input writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.second.node()))); @@ -1117,20 +1131,20 @@ void ProjectSerializer220403::SaveNodeCustom(QXmlStreamWriter *writer, Node *nod writer->writeTextElement(QStringLiteral("default"), NodeValue::ValueToString(data_type, group_input.GetDefaultValue(), false)); - writer->writeStartElement(QStringLiteral("properties")); + WriteStartElement(writer, QStringLiteral("properties")); auto p = group_input.GetProperties(); for (auto it=p.cbegin(); it!=p.cend(); it++) { - writer->writeStartElement(QStringLiteral("property")); + WriteStartElement(writer, QStringLiteral("property")); writer->writeTextElement(QStringLiteral("key"), it.key()); writer->writeTextElement(QStringLiteral("value"), it.value().toString()); - writer->writeEndElement(); // property + WriteEndElement(writer); // property } - writer->writeEndElement(); // properties + WriteEndElement(writer); // properties - writer->writeEndElement(); // input + WriteEndElement(writer); // input } - writer->writeEndElement(); // inputpassthroughs + WriteEndElement(writer); // inputpassthroughs writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(group->GetOutputPassthrough()))); } @@ -1151,13 +1165,13 @@ void ProjectSerializer220403::LoadTimelinePoints(QXmlStreamReader *reader, Viewe void ProjectSerializer220403::SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const { - writer->writeStartElement(QStringLiteral("workarea")); + WriteStartElement(writer, QStringLiteral("workarea")); SaveWorkArea(writer, viewer->GetWorkArea()); - writer->writeEndElement(); // workarea + WriteEndElement(writer); // workarea - writer->writeStartElement(QStringLiteral("markers")); + WriteStartElement(writer, QStringLiteral("markers")); SaveMarkerList(writer, viewer->GetMarkers()); - writer->writeEndElement(); // markers + WriteEndElement(writer); // markers } void ProjectSerializer220403::LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const @@ -1238,11 +1252,11 @@ void ProjectSerializer220403::SaveMarkerList(QXmlStreamWriter *writer, TimelineM for (auto it=markers->cbegin(); it!=markers->cend(); it++) { TimelineMarker* marker = *it; - writer->writeStartElement(QStringLiteral("marker")); + WriteStartElement(writer, QStringLiteral("marker")); SaveMarker(writer, marker); - writer->writeEndElement(); // marker + WriteEndElement(writer); // marker } } @@ -1273,13 +1287,13 @@ void ProjectSerializer220403::LoadValueHint(Node::ValueHint *hint, QXmlStreamRea void ProjectSerializer220403::SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const { - writer->writeStartElement(QStringLiteral("types")); + WriteStartElement(writer, QStringLiteral("types")); for (auto it=hint->types().cbegin(); it!=hint->types().cend(); it++) { writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); } - writer->writeEndElement(); // types + WriteEndElement(writer); // types writer->writeTextElement(QStringLiteral("index"), QString::number(hint->index())); From 752f073136f1387df9d188d709a4a72bd10b22cb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 20 Jul 2022 21:27:07 -0700 Subject: [PATCH 026/107] timeline: add separator after rename in menu --- app/widget/timelinewidget/timelinewidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 76dcc6e90..08adbbf23 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1110,6 +1110,8 @@ void TimelineWidget::ShowContextMenu() QAction* rename_action = menu.addAction(tr("Rename")); connect(rename_action, &QAction::triggered, this, &TimelineWidget::RenameSelectedBlocks); + menu.addSeparator(); + QAction* properties_action = menu.addAction(tr("Properties")); connect(properties_action, &QAction::triggered, this, &TimelineWidget::ShowSpeedDurationDialogForSelectedClips); } From 5ea97f7a64ae2f445318ab96740523ae2b331605 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:14:16 -0700 Subject: [PATCH 027/107] render: remove alpha request option --- app/codec/ffmpeg/ffmpegencoder.cpp | 62 +++++++++---------- app/codec/ffmpeg/ffmpegencoder.h | 3 +- .../crossdissolve/crossdissolvetransition.cpp | 7 --- .../crossdissolve/crossdissolvetransition.h | 2 - .../cornerpin/cornerpindistortnode.cpp | 1 - app/node/distort/crop/cropdistortnode.cpp | 1 - app/node/distort/mask/mask.cpp | 1 - .../transform/transformdistortnode.cpp | 4 -- app/node/effect/opacity/opacityeffect.cpp | 1 - app/node/filter/blur/blur.cpp | 5 -- app/node/generator/polygon/polygon.cpp | 1 - app/node/generator/shape/shapenode.cpp | 1 - app/node/generator/text/textv1.cpp | 1 - app/node/generator/text/textv2.cpp | 1 - app/node/generator/text/textv3.cpp | 1 - app/node/keying/chromakey/chromakey.cpp | 1 - .../colordifferencekey/colordifferencekey.cpp | 1 - app/node/math/math/mathbase.cpp | 3 - app/node/math/merge/merge.cpp | 6 -- app/node/traverser.cpp | 28 --------- app/render/job/generatejob.h | 13 ---- app/render/renderer.cpp | 1 - 22 files changed, 32 insertions(+), 113 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 7980e10a7..f5fc96357 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -36,8 +36,7 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : fmt_ctx_(nullptr), video_stream_(nullptr), video_codec_ctx_(nullptr), - video_alpha_scale_ctx_(nullptr), - video_noalpha_scale_ctx_(nullptr), + video_scale_ctx_(nullptr), audio_stream_(nullptr), audio_codec_ctx_(nullptr), audio_resample_ctx_(nullptr), @@ -144,27 +143,32 @@ bool FFmpegEncoder::Open() // Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it // before encoding. Even if we don't, this may be useful for converting between linesizes, etc. - video_alpha_scale_ctx_ = sws_getContext(params().video_params().width(), - params().video_params().height(), - src_alpha_pix_fmt, - params().video_params().width(), - params().video_params().height(), - encoder_pix_fmt, - 0, - nullptr, - nullptr, - nullptr); + video_scale_ctx_ = sws_getContext(params().video_params().width(), + params().video_params().height(), + src_alpha_pix_fmt, + params().video_params().width(), + params().video_params().height(), + encoder_pix_fmt, + 0, + nullptr, + nullptr, + nullptr); - video_noalpha_scale_ctx_ = sws_getContext(params().video_params().width(), - params().video_params().height(), - src_noalpha_pix_fmt, - params().video_params().width(), - params().video_params().height(), - encoder_pix_fmt, - 0, - nullptr, - nullptr, - nullptr); + int *inv_table; + int src_range; + int *table; + int dst_range; + int brightness; + int contrast; + int saturation; + + sws_getColorspaceDetails(video_scale_ctx_, &inv_table, &src_range, &table, &dst_range, &brightness, &contrast, &saturation); + + // Set swscale's dst range based on AVCodecContext's color_range. Here, 1 == JPEG range (0-255) + // and 0 == MPEG range (16-235). + dst_range = (video_codec_ctx_->color_range == AVCOL_RANGE_JPEG); + + sws_setColorspaceDetails(video_scale_ctx_, inv_table, src_range, table, dst_range, brightness, contrast, saturation); } // Initialize an audio stream if it's enabled @@ -242,7 +246,7 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) input_data = frame->const_data(); input_linesize = frame->linesize_bytes(); - error_code = sws_scale((frame->channel_count() == VideoParams::kRGBAChannelCount) ? video_alpha_scale_ctx_ : video_noalpha_scale_ctx_, + error_code = sws_scale(video_scale_ctx_, reinterpret_cast(&input_data), &input_linesize, 0, @@ -484,14 +488,9 @@ void FFmpegEncoder::Close() audio_frame_ = nullptr; } - if (video_alpha_scale_ctx_) { - sws_freeContext(video_alpha_scale_ctx_); - video_alpha_scale_ctx_ = nullptr; - } - - if (video_noalpha_scale_ctx_) { - sws_freeContext(video_noalpha_scale_ctx_); - video_noalpha_scale_ctx_ = nullptr; + if (video_scale_ctx_) { + sws_freeContext(video_scale_ctx_); + video_scale_ctx_ = nullptr; } if (video_codec_ctx_) { @@ -606,6 +605,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational(); codec_ctx->framerate = params().video_params().frame_rate().toAVRational(); codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8()); + codec_ctx->color_range = params().video_color_range() == EncodingParams::kYUVJPEG0_255 ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; if (params().video_params().interlacing() != VideoParams::kInterlaceNone) { // FIXME: I actually don't know what these flags do, the documentation helpfully doesn't diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 28786b9ce..4b7dc0edc 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -88,8 +88,7 @@ private: AVStream* video_stream_; AVCodecContext* video_codec_ctx_; - SwsContext* video_alpha_scale_ctx_; - SwsContext* video_noalpha_scale_ctx_; + SwsContext* video_scale_ctx_; VideoParams::Format video_conversion_fmt_; AVStream* audio_stream_; diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp index 3fd55b653..9a0622f75 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp @@ -53,13 +53,6 @@ ShaderCode CrossDissolveTransition::GetShaderCode(const ShaderRequest &request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag"), QString()); } -void CrossDissolveTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob &job) const -{ - Q_UNUSED(value) - - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); -} - void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const { for (int i=0; i(value[kInterpolationInput].toInt())); - // FIXME: This should be optimized, we can use matrix math to determine if this operation will - // end up with gaps in the screen that will require an alpha channel. - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); pushed_job = true; diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index 9ebabbc20..6c60b36f1 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -52,7 +52,6 @@ void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, // If there's no texture, no need to run an operation if (job.Get(kTextureInput).toTexture()) { if (!qFuzzyCompare(job.Get(kValueInput).toDouble(), 1.0)) { - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { // 1.0 float is a no-op, so just push the texture diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 25a1984c2..0ac4eb6d5 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -159,11 +159,6 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals } if (can_push_job) { - // If we're not repeating pixels, expect an alpha channel to appear - if (!job.Get(kRepeatEdgePixelsInput).toBool()) { - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); - } - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { // If we're not performing the blur job, just push the texture diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index de458c6e8..6b535764a 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -95,7 +95,6 @@ GenerateJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const job.Insert(value); job.SetRequestedFormat(VideoParams::kFormatFloat32); - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); return job; } diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp index 59b6e071c..7344d6e3d 100644 --- a/app/node/generator/shape/shapenode.cpp +++ b/app/node/generator/shape/shapenode.cpp @@ -76,7 +76,6 @@ void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod job.Insert(value); job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); job.SetShaderID(QStringLiteral("shape")); PushMergableJob(value, QVariant::fromValue(job), table); diff --git a/app/node/generator/text/textv1.cpp b/app/node/generator/text/textv1.cpp index 22f4d30c7..93d36737f 100644 --- a/app/node/generator/text/textv1.cpp +++ b/app/node/generator/text/textv1.cpp @@ -94,7 +94,6 @@ void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &global { GenerateJob job; job.Insert(value); - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); if (!job.Get(kTextInput).toString().isEmpty()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); diff --git a/app/node/generator/text/textv2.cpp b/app/node/generator/text/textv2.cpp index 0df750fc6..754c0baaf 100644 --- a/app/node/generator/text/textv2.cpp +++ b/app/node/generator/text/textv2.cpp @@ -97,7 +97,6 @@ void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &global { GenerateJob job; job.Insert(value); - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); job.SetRequestedFormat(VideoParams::kFormatFloat32); if (!job.Get(kTextInput).toString().isEmpty()) { diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index baadfb101..958bf94db 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -98,7 +98,6 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global { GenerateJob job; job.Insert(value); - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); job.SetRequestedFormat(VideoParams::kFormatUnsigned8); if (value[kUseArgsInput].toBool()) { diff --git a/app/node/keying/chromakey/chromakey.cpp b/app/node/keying/chromakey/chromakey.cpp index 4d7701476..70babc998 100644 --- a/app/node/keying/chromakey/chromakey.cpp +++ b/app/node/keying/chromakey/chromakey.cpp @@ -132,7 +132,6 @@ void ChromaKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, ColorTransformJob job; job.Insert(value); - job.SetAlphaChannelRequired(ColorTransformJob::kAlphaForceOn); job.SetColorProcessor(processor()); job.SetInputTexture(value[kTextureInput].toTexture()); job.SetNeedsCustomShader(this); diff --git a/app/node/keying/colordifferencekey/colordifferencekey.cpp b/app/node/keying/colordifferencekey/colordifferencekey.cpp index e4629816b..7ee988d66 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.cpp +++ b/app/node/keying/colordifferencekey/colordifferencekey.cpp @@ -95,7 +95,6 @@ void ColorDifferenceKeyNode::Value(const NodeValueRow &value, const NodeGlobals { ShaderJob job; job.Insert(value); - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); // If there's no texture, no need to run an operation if (job.Get(kTextureInput).toTexture()) { diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 70ba8433a..a248bb41c 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -372,9 +372,6 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt // Replace with adjusted matrix job.Insert(val_a.type() == NodeValue::kTexture ? param_b_in : param_a_in, NodeValue(NodeValue::kMatrix, adjusted_matrix, this)); - - // It's likely an alpha channel will result from this operation - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); } } diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index 227ea6577..acf271869 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -90,12 +90,6 @@ void MergeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod // We only have a base texture, no need to alpha over table->Push(job.Get(kBaseIn)); } else { - // We have both textures, push the job - if (base_tex->channel_count() < VideoParams::kRGBAChannelCount) { - // Base has no alpha, therefore this merge operation will not add an alpha channel - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOff); - } - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 5fcca82f5..ea21ffa83 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -168,34 +168,6 @@ NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams ¶ms, const Time int NodeTraverser::GetChannelCountFromJob(const GenerateJob &job) { - int max_channel_count = 0; - - // Find maximum channel count - for (auto it=job.GetValues().cbegin(); it!=job.GetValues().cend(); it++) { - if (it.value().type() == NodeValue::kTexture) { - if (TexturePtr tex = it.value().toTexture()) { - max_channel_count = qMax(max_channel_count, tex->channel_count()); - } - } - } - if (max_channel_count == 0) { - max_channel_count = VideoParams::kRGBChannelCount; - } - - switch (job.GetAlphaChannelRequired()) { - case GenerateJob::kAlphaForceOn: - return VideoParams::kRGBAChannelCount; - case GenerateJob::kAlphaForceOff: - if (max_channel_count >= 1 && max_channel_count < VideoParams::kRGBChannelCount) { - return max_channel_count; - } else { - return VideoParams::kRGBChannelCount; - } - case GenerateJob::kAlphaAuto: - return max_channel_count; - } - - // Default fallback, should never get here return VideoParams::kRGBAChannelCount; } diff --git a/app/render/job/generatejob.h b/app/render/job/generatejob.h index 38354aa55..0109e1195 100644 --- a/app/render/job/generatejob.h +++ b/app/render/job/generatejob.h @@ -28,22 +28,11 @@ namespace olive { class GenerateJob : public AcceleratedJob { public: - enum AlphaChannelSetting { - kAlphaAuto, - kAlphaForceOn, - kAlphaForceOff - }; - GenerateJob() { - alpha_channel_required_ = kAlphaAuto; requested_format_ = VideoParams::kFormatInvalid; } - AlphaChannelSetting GetAlphaChannelRequired() const { return alpha_channel_required_; } - - void SetAlphaChannelRequired(AlphaChannelSetting e) { alpha_channel_required_ = e; } - VideoParams::Format GetRequestedFormat() const { return requested_format_; } void SetRequestedFormat(VideoParams::Format f) { requested_format_ = f; } @@ -52,8 +41,6 @@ public: void SetColorspace(const QString &s) { colorspace_ = s; } private: - AlphaChannelSetting alpha_channel_required_; - VideoParams::Format requested_format_; QString colorspace_; diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 8ebdedb01..e174a4b35 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -232,7 +232,6 @@ void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *des job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, color_job.GetCropMatrix().inverted())); job.Insert(QStringLiteral("ove_maintex_alpha"), NodeValue(NodeValue::kInt, int(color_job.GetInputAlphaAssociation()))); job.Insert(color_job.GetValues()); - job.SetAlphaChannelRequired(color_job.GetAlphaChannelRequired()); foreach (const ColorContext::LUT& l, color_ctx.lut3d_textures) { job.Insert(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); From 187568324aa782987cc6fd43dcf0584ab3ecb782 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:16:08 -0700 Subject: [PATCH 028/107] render: ensure correct yuv range handling --- app/codec/encoder.cpp | 131 +----------------- app/codec/encoder.h | 84 ++++++----- app/codec/ffmpeg/ffmpegdecoder.cpp | 16 ++- app/codec/ffmpeg/ffmpegencoder.cpp | 1 + app/dialog/export/export.cpp | 1 + .../export/exportadvancedvideodialog.cpp | 6 + app/dialog/export/exportadvancedvideodialog.h | 13 ++ app/dialog/export/exportvideotab.cpp | 5 +- app/dialog/export/exportvideotab.h | 9 +- app/shaders/yuv2rgb.frag | 6 + 10 files changed, 96 insertions(+), 176 deletions(-) diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 458c71d8e..7abf5d39f 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -90,17 +90,13 @@ EncodingParams::EncodingParams() : video_buffer_size_(0), video_threads_(0), video_is_image_sequence_(false), + video_color_range_(kYUVDefault), audio_enabled_(false), audio_bit_rate_(0), subtitles_enabled_(false) { } -void EncodingParams::SetFilename(const QString &filename) -{ - filename_ = filename; -} - void EncodingParams::EnableVideo(const VideoParams &video_params, const ExportCodec::Codec &vcodec) { video_enabled_ = true; @@ -121,131 +117,6 @@ void EncodingParams::EnableSubtitles(const ExportCodec::Codec &scodec) subtitles_codec_ = scodec; } -void EncodingParams::set_video_option(const QString &key, const QString &value) -{ - video_opts_.insert(key, value); -} - -void EncodingParams::set_video_bit_rate(const int64_t &rate) -{ - video_bit_rate_ = rate; -} - -void EncodingParams::set_video_min_bit_rate(const int64_t &rate) -{ - video_min_bit_rate_ = rate; -} - -void EncodingParams::set_video_max_bit_rate(const int64_t &rate) -{ - video_max_bit_rate_ = rate; -} - -void EncodingParams::set_video_buffer_size(const int64_t &sz) -{ - video_buffer_size_ = sz; -} - -void EncodingParams::set_video_threads(const int &threads) -{ - video_threads_ = threads; -} - -void EncodingParams::set_video_pix_fmt(const QString &s) -{ - video_pix_fmt_ = s; -} - -const QString &EncodingParams::filename() const -{ - return filename_; -} - -bool EncodingParams::video_enabled() const -{ - return video_enabled_; -} - -const ExportCodec::Codec &EncodingParams::video_codec() const -{ - return video_codec_; -} - -const VideoParams &EncodingParams::video_params() const -{ - return video_params_; -} - -const QHash &EncodingParams::video_opts() const -{ - return video_opts_; -} - -const int64_t &EncodingParams::video_bit_rate() const -{ - return video_bit_rate_; -} - -const int64_t &EncodingParams::video_min_bit_rate() const -{ - return video_min_bit_rate_; -} - -const int64_t &EncodingParams::video_max_bit_rate() const -{ - return video_max_bit_rate_; -} - -const int64_t &EncodingParams::video_buffer_size() const -{ - return video_buffer_size_; -} - -const int &EncodingParams::video_threads() const -{ - return video_threads_; -} - -const QString &EncodingParams::video_pix_fmt() const -{ - return video_pix_fmt_; -} - -bool EncodingParams::audio_enabled() const -{ - return audio_enabled_; -} - -const ExportCodec::Codec &EncodingParams::audio_codec() const -{ - return audio_codec_; -} - -const AudioParams &EncodingParams::audio_params() const -{ - return audio_params_; -} - -bool EncodingParams::subtitles_enabled() const -{ - return subtitles_enabled_; -} - -ExportCodec::Codec EncodingParams::subtitles_codec() const -{ - return subtitles_codec_; -} - -const rational &EncodingParams::GetExportLength() const -{ - return export_length_; -} - -void EncodingParams::SetExportLength(const rational &export_length) -{ - export_length_ = export_length; -} - void EncodingParams::Save(QXmlStreamWriter *writer) const { writer->writeTextElement(QStringLiteral("filename"), filename_); diff --git a/app/codec/encoder.h b/app/codec/encoder.h index e1535290b..7b95fa7fb 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -43,62 +43,59 @@ using EncoderPtr = std::shared_ptr; class EncodingParams { public: + enum YUVRange + { + kYUVMPEG16_235, + kYUVJPEG0_255, + + kYUVDefault = kYUVMPEG16_235 + }; + EncodingParams(); - void SetFilename(const QString& filename); + void SetFilename(const QString& filename) { filename_ = filename; } void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec); void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec); 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); - void set_video_min_bit_rate(const int64_t& rate); - void set_video_max_bit_rate(const int64_t& rate); - void set_video_buffer_size(const int64_t& sz); - void set_video_threads(const int& threads); - void set_video_pix_fmt(const QString& s); - void set_video_is_image_sequence(bool s) - { - video_is_image_sequence_ = s; - } + void set_video_option(const QString& key, const QString& value) { video_opts_.insert(key, value); } + void set_video_bit_rate(const int64_t& rate) { video_bit_rate_ = rate; } + void set_video_min_bit_rate(const int64_t& rate) { video_min_bit_rate_ = rate; } + void set_video_max_bit_rate(const int64_t& rate) { video_max_bit_rate_ = rate; } + void set_video_buffer_size(const int64_t& sz) { video_buffer_size_ = sz; } + void set_video_threads(const int& threads) { video_threads_ = threads; } + void set_video_pix_fmt(const QString& s) { video_pix_fmt_ = s; } + void set_video_is_image_sequence(bool s) { video_is_image_sequence_ = s; } + void set_video_color_range(YUVRange r) { video_color_range_ = r; } - const QString& filename() const; + const QString& filename() const { return filename_; } - bool video_enabled() const; - const ExportCodec::Codec& video_codec() const; - const VideoParams& video_params() const; - const QHash& video_opts() const; - const int64_t& video_bit_rate() const; - const int64_t& video_min_bit_rate() const; - const int64_t& video_max_bit_rate() const; - const int64_t& video_buffer_size() const; - const int& video_threads() const; - const QString& video_pix_fmt() const; - bool video_is_image_sequence() const - { - return video_is_image_sequence_; - } + bool video_enabled() const { return video_enabled_; } + const ExportCodec::Codec& video_codec() const { return video_codec_; } + const VideoParams& video_params() const { return video_params_; } + const QHash& video_opts() const { return video_opts_; } + const int64_t& video_bit_rate() const { return video_bit_rate_; } + const int64_t& video_min_bit_rate() const { return video_min_bit_rate_; } + const int64_t& video_max_bit_rate() const { return video_max_bit_rate_; } + const int64_t& video_buffer_size() const { return video_buffer_size_; } + const int& video_threads() const { return video_threads_; } + const QString& video_pix_fmt() const { return video_pix_fmt_; } + bool video_is_image_sequence() const { return video_is_image_sequence_; } + YUVRange video_color_range() const { return video_color_range_; } - bool audio_enabled() const; - const ExportCodec::Codec &audio_codec() const; - const AudioParams& audio_params() const; + bool audio_enabled() const { return audio_enabled_; } + const ExportCodec::Codec &audio_codec() const { return audio_codec_; } + const AudioParams& audio_params() const { return audio_params_; } + const int64_t& audio_bit_rate() const { return audio_bit_rate_; } - const int64_t& audio_bit_rate() const - { - return audio_bit_rate_; - } + void set_audio_bit_rate(const int64_t& b) { audio_bit_rate_ = b; } - void set_audio_bit_rate(const int64_t& b) - { - audio_bit_rate_ = b; - } + bool subtitles_enabled() const { return subtitles_enabled_; } + ExportCodec::Codec subtitles_codec() const { return subtitles_codec_; } - bool subtitles_enabled() const; - ExportCodec::Codec subtitles_codec() const; - - const rational& GetExportLength() const; - void SetExportLength(const rational& GetExportLength); + const rational& GetExportLength() const { return export_length_; } + void SetExportLength(const rational& export_length) { export_length_ = export_length; } virtual void Save(QXmlStreamWriter* writer) const; @@ -116,6 +113,7 @@ private: int video_threads_; QString video_pix_fmt_; bool video_is_image_sequence_; + YUVRange video_color_range_; bool audio_enabled_; ExportCodec::Codec audio_codec_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 7469408c7..2ab14eadb 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -171,7 +171,10 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration || src_fmt == AV_PIX_FMT_YUV444P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE || src_fmt == AV_PIX_FMT_YUV422P12LE - || src_fmt == AV_PIX_FMT_YUV444P12LE) { + || src_fmt == AV_PIX_FMT_YUV444P12LE + || src_fmt == AV_PIX_FMT_YUVJ420P + || src_fmt == AV_PIX_FMT_YUVJ422P + || src_fmt == AV_PIX_FMT_YUVJ444P) { if (Yuv2RgbShader.isNull()) { // Compile shader Yuv2RgbShader = renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); @@ -184,6 +187,9 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUVJ420P: + case AV_PIX_FMT_YUVJ422P: + case AV_PIX_FMT_YUVJ444P: default: px_size = 1; bits_per_pixel = 8; @@ -202,6 +208,10 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration break; } + bool jpeg_range = src_fmt == AV_PIX_FMT_YUVJ420P + || src_fmt == AV_PIX_FMT_YUVJ422P + || src_fmt == AV_PIX_FMT_YUVJ444P; + VideoParams plane_params = vp; plane_params.set_channel_count(1); plane_params.set_divider(1); @@ -210,6 +220,8 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration if (src_fmt == AV_PIX_FMT_YUV420P || src_fmt == AV_PIX_FMT_YUV422P + || src_fmt == AV_PIX_FMT_YUVJ420P + || src_fmt == AV_PIX_FMT_YUVJ422P || src_fmt == AV_PIX_FMT_YUV420P10LE || src_fmt == AV_PIX_FMT_YUV422P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE @@ -218,6 +230,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration } if (src_fmt == AV_PIX_FMT_YUV420P + || src_fmt == AV_PIX_FMT_YUVJ420P || src_fmt == AV_PIX_FMT_YUV420P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE) { plane_params.set_height(plane_params.height()/2); @@ -231,6 +244,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); + job.Insert(QStringLiteral("jpeg_range"), NodeValue(NodeValue::kBoolean, jpeg_range)); tex = renderer->CreateTexture(vp); renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index f5fc96357..03d640fc1 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -219,6 +219,7 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) encoded_frame->width = frame->width(); encoded_frame->height = frame->height(); encoded_frame->format = video_codec_ctx_->pix_fmt; + encoded_frame->color_range = video_codec_ctx_->color_range; // Set interlacing if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) { diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index f93bb6177..8797b3caa 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -548,6 +548,7 @@ ExportParams ExportDialog::GenerateParams() const params.set_color_transform(video_tab_->CurrentOCIOColorSpace()); params.set_video_pix_fmt(video_tab_->pix_fmt()); + params.set_video_color_range(video_tab_->yuv_range()); params.set_video_is_image_sequence(video_tab_->IsImageSequenceSet()); } diff --git a/app/dialog/export/exportadvancedvideodialog.cpp b/app/dialog/export/exportadvancedvideodialog.cpp index 71143b50b..57851f424 100644 --- a/app/dialog/export/exportadvancedvideodialog.cpp +++ b/app/dialog/export/exportadvancedvideodialog.cpp @@ -31,6 +31,12 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList &pix_f pixel_layout->addWidget(pixel_format_combobox_, row, 1); row++; + + pixel_layout->addWidget(new QLabel(tr("YUV Color Range:")), row, 0); + + yuv_color_range_combobox_ = new QComboBox(); + yuv_color_range_combobox_->addItems({tr("MPEG (16-235)"), tr("JPEG (0-255)")}); + pixel_layout->addWidget(yuv_color_range_combobox_, row, 1); } { diff --git a/app/dialog/export/exportadvancedvideodialog.h b/app/dialog/export/exportadvancedvideodialog.h index 275ae44f9..b6d17214c 100644 --- a/app/dialog/export/exportadvancedvideodialog.h +++ b/app/dialog/export/exportadvancedvideodialog.h @@ -4,6 +4,7 @@ #include #include +#include "codec/encoder.h" #include "widget/slider/integerslider.h" namespace olive { @@ -35,11 +36,23 @@ public: pixel_format_combobox_->setCurrentText(s); } + EncodingParams::YUVRange yuv_range() const + { + return static_cast(yuv_color_range_combobox_->currentIndex()); + } + + void set_yuv_range(EncodingParams::YUVRange i) + { + yuv_color_range_combobox_->setCurrentIndex(i); + } + private: IntegerSlider* thread_slider_; QComboBox* pixel_format_combobox_; + QComboBox* yuv_color_range_combobox_; + }; } diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 1c493eecf..4373eb75f 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -36,7 +36,8 @@ namespace olive { ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) : QWidget(parent), color_manager_(color_manager), - threads_(0) + threads_(0), + yuv_range_(EncodingParams::kYUVDefault) { QVBoxLayout* outer_layout = new QVBoxLayout(this); @@ -211,10 +212,12 @@ void ExportVideoTab::OpenAdvancedDialog() d.set_threads(threads_); d.set_pix_fmt(pix_fmt_); + d.set_yuv_range(yuv_range_); if (d.exec() == QDialog::Accepted) { threads_ = d.threads(); pix_fmt_ = d.pix_fmt(); + yuv_range_ = d.yuv_range(); } } diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index c2be1aebd..0a83753ee 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -133,10 +133,16 @@ public: return threads_; } - const QString& pix_fmt() const { + const QString& pix_fmt() const + { return pix_fmt_; } + EncodingParams::YUVRange yuv_range() const + { + return yuv_range_; + } + public slots: void VideoCodecChanged(); @@ -177,6 +183,7 @@ private: int threads_; QString pix_fmt_; + EncodingParams::YUVRange yuv_range_; ExportFormat::Format format_; diff --git a/app/shaders/yuv2rgb.frag b/app/shaders/yuv2rgb.frag index 62c73e149..537df26e2 100644 --- a/app/shaders/yuv2rgb.frag +++ b/app/shaders/yuv2rgb.frag @@ -3,6 +3,7 @@ uniform sampler2D u_channel; uniform sampler2D v_channel; uniform int bits_per_pixel; +uniform bool jpeg_range; in vec2 ove_texcoord; out vec4 frag_color; @@ -31,5 +32,10 @@ void main() { rgba.b = yuv.r + 2.017 * yuv.g; rgba.a = 1.0; + if (jpeg_range) { + rgba.rgb *= 219.0 / 255.0; + rgba.rgb += 16.0 / 255.0; + } + frag_color = rgba; } From f2e604c2cb8c1cfb015c914f8264a42597de7b7b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 22 Jul 2022 13:25:46 -0700 Subject: [PATCH 029/107] ui: add rename shortcut --- app/panel/node/node.h | 5 +++++ app/panel/project/project.cpp | 5 +++++ app/panel/project/project.h | 2 ++ app/panel/timeline/timeline.cpp | 5 +++++ app/panel/timeline/timeline.h | 2 ++ app/widget/menu/menushared.cpp | 8 ++++++++ app/widget/menu/menushared.h | 3 +++ app/widget/nodeview/nodeview.cpp | 4 ---- app/widget/nodeview/nodeview.h | 4 ++-- app/widget/panel/panel.h | 2 ++ app/widget/projectexplorer/projectexplorer.h | 4 ++-- app/widget/timelinewidget/timelinewidget.cpp | 3 --- app/widget/timelinewidget/timelinewidget.h | 4 ++-- 13 files changed, 38 insertions(+), 13 deletions(-) diff --git a/app/panel/node/node.h b/app/panel/node/node.h index a572989c4..3fc07b49d 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -104,6 +104,11 @@ public: node_widget_->view()->ZoomOut(); } + virtual void RenameSelected() override + { + node_widget_->view()->LabelSelectedNodes(); + } + public slots: void Select(const QVector &p) { diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index d6c353330..b1ecf1f59 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -146,6 +146,11 @@ void ProjectPanel::DeleteSelected() explorer_->DeleteSelected(); } +void ProjectPanel::RenameSelected() +{ + explorer_->RenameSelectedItem(); +} + void ProjectPanel::Edit(Node* item) { explorer_->Edit(item); diff --git a/app/panel/project/project.h b/app/panel/project/project.h index c4720fe22..39f6299ee 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -62,6 +62,8 @@ public: virtual void DeleteSelected() override; + virtual void RenameSelected() override; + public slots: void Edit(Node *item); diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 686aec085..24e14320f 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -154,6 +154,11 @@ void TimelinePanel::MoveOutToPlayhead() timeline_widget()->MoveOutToPlayhead(); } +void TimelinePanel::RenameSelected() +{ + timeline_widget()->RenameSelectedBlocks(); +} + void TimelinePanel::InsertFootageAtPlayhead(const QVector &footage) { timeline_widget()->InsertFootageAtPlayhead(footage); diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index da0418e46..611fd275c 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -86,6 +86,8 @@ public: virtual void MoveOutToPlayhead() override; + virtual void RenameSelected() override; + void AddDefaultTransitionsToSelected() { timeline_widget()->AddDefaultTransitionsToSelected(); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 431e72f72..6332046ba 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -43,6 +43,7 @@ MenuShared::MenuShared() edit_paste_item_ = Menu::CreateItem(this, "paste", this, &MenuShared::PasteTriggered, tr("Ctrl+V")); edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", this, &MenuShared::PasteInsertTriggered, tr("Ctrl+Shift+V")); edit_duplicate_item_ = Menu::CreateItem(this, "duplicate", this, &MenuShared::DuplicateTriggered, tr("Ctrl+D")); + edit_rename_item_ = Menu::CreateItem(this, "rename", this, &MenuShared::RenameSelectedTriggered, tr("F2")); edit_delete_item_ = Menu::CreateItem(this, "delete", this, &MenuShared::DeleteSelectedTriggered, tr("Del")); edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, &MenuShared::RippleDeleteTriggered, tr("Shift+Del")); edit_split_item_ = Menu::CreateItem(this, "split", this, &MenuShared::SplitAtPlayheadTriggered, tr("Ctrl+K")); @@ -131,6 +132,7 @@ void MenuShared::AddItemsForEditMenu(Menu *m, bool for_clips) m->addAction(edit_paste_item_); m->addAction(edit_paste_insert_item_); m->addAction(edit_duplicate_item_); + m->addAction(edit_rename_item_); m->addAction(edit_delete_item_); if (for_clips) { @@ -279,6 +281,11 @@ void MenuShared::DuplicateTriggered() PanelManager::instance()->CurrentlyFocused()->Duplicate(); } +void MenuShared::RenameSelectedTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->RenameSelected(); +} + void MenuShared::EnableDisableTriggered() { PanelManager::instance()->CurrentlyFocused()->ToggleSelectedEnabled(); @@ -333,6 +340,7 @@ void MenuShared::Retranslate() edit_paste_item_->setText(tr("&Paste")); edit_paste_insert_item_->setText(tr("Paste Insert")); edit_duplicate_item_->setText(tr("Duplicate")); + edit_rename_item_->setText(tr("Rename")); edit_delete_item_->setText(tr("Delete")); edit_ripple_delete_item_->setText(tr("Ripple Delete")); edit_split_item_->setText(tr("Split")); diff --git a/app/widget/menu/menushared.h b/app/widget/menu/menushared.h index 8935457ca..61a544e53 100644 --- a/app/widget/menu/menushared.h +++ b/app/widget/menu/menushared.h @@ -72,6 +72,7 @@ private: QAction* edit_paste_item_; QAction* edit_paste_insert_item_; QAction* edit_duplicate_item_; + QAction* edit_rename_item_; QAction* edit_delete_item_; QAction* edit_ripple_delete_item_; QAction* edit_split_item_; @@ -130,6 +131,8 @@ private slots: void DuplicateTriggered(); + void RenameSelectedTriggered(); + void EnableDisableTriggered(); void NestTriggered(); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 7e3f9e138..e757e6031 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -779,10 +779,6 @@ void NodeView::ShowContextMenu(const QPoint &pos) if (itemAt(pos) && !selected.isEmpty()) { - // Label node action - QAction* label_action = m.addAction(tr("Label")); - connect(label_action, &QAction::triggered, this, &NodeView::LabelSelectedNodes); - // Grouping if (selected.size() == 1 && dynamic_cast(selected.first()->GetNode())) { QAction *ungroup_action = m.addAction(tr("Ungroup")); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 15a5ddc28..bf2d0ee0d 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -112,6 +112,8 @@ public slots: void CenterOnNode(olive::Node *n); + void LabelSelectedNodes(); + signals: void NodesSelected(const QVector& nodes); @@ -275,8 +277,6 @@ private slots: void ShowNodeProperties(); - void LabelSelectedNodes(); - void ItemAboutToBeDeleted(NodeViewItem *item); void CloseOverlay(); diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index 608c0ba5f..54df750c6 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -122,6 +122,8 @@ public: virtual void GoToNextCut(){} + virtual void RenameSelected(){} + virtual void DeleteSelected(){} virtual void RippleDelete(){} diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 1074e1a4f..086c2eab3 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -92,6 +92,8 @@ public slots: void Edit(Node* item); + void RenameSelectedItem(); + signals: /** * @brief Emitted when an Item is double clicked @@ -171,8 +173,6 @@ private slots: void DirUpSlot(); - void RenameSelectedItem(); - void ShowContextMenu(); void ShowItemPropertiesDialog(); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 08adbbf23..a293545fb 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1107,9 +1107,6 @@ void TimelineWidget::ShowContextMenu() } } - QAction* rename_action = menu.addAction(tr("Rename")); - connect(rename_action, &QAction::triggered, this, &TimelineWidget::RenameSelectedBlocks); - menu.addSeparator(); QAction* properties_action = menu.addAction(tr("Properties")); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 58846f53a..870d7065b 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -274,6 +274,8 @@ public: public slots: void ClearTentativeSubtitleTrack(); + void RenameSelectedBlocks(); + signals: void BlockSelectionChanged(const QVector& selected_blocks); @@ -431,8 +433,6 @@ private slots: void RevealInFootageViewer(); void RevealInProject(); - void RenameSelectedBlocks(); - void TrackAboutToBeDeleted(Track *track); }; From 3fab45681634fa2cb60fd0f076b14aaf8af11723 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:33:19 -0700 Subject: [PATCH 030/107] footage: merge video type Fixes issue where image sequences would revert to stills after a project is loaded --- app/node/project/footage/footage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 8c4ca6505..ebd410b6a 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -512,6 +512,7 @@ VideoParams Footage::MergeVideoStream(const VideoParams &base, const VideoParams merged.set_interlacing(over.interlacing()); merged.set_colorspace(over.colorspace()); merged.set_premultiplied_alpha(over.premultiplied_alpha()); + merged.set_video_type(over.video_type()); if (merged.video_type() == VideoParams::kVideoTypeImageSequence && over.video_type() == VideoParams::kVideoTypeImageSequence) { merged.set_start_time(over.start_time()); merged.set_duration(over.duration()); From a3cde5bd44ff35dfdc67cecb5e628baabcd3ee51 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:33:31 -0700 Subject: [PATCH 031/107] qtutils: implement parent finder --- app/common/qtutils.h | 15 +++++++++++++++ app/node/project/project.cpp | 12 ++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/common/qtutils.h b/app/common/qtutils.h index 122a5022c..fee21e596 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -64,6 +64,21 @@ public: static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width); + template + static T *GetParentOfType(const QObject *child) + { + QObject *t = child->parent(); + + while (t) { + if (T *p = dynamic_cast(t)) { + return p; + } + t = t->parent(); + } + + return nullptr; + } + }; } diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index 4cf192643..b84c8ee22 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -23,6 +23,7 @@ #include #include +#include "common/qtutils.h" #include "common/xmlutils.h" #include "core.h" #include "dialog/progress/progress.h" @@ -173,16 +174,7 @@ void Project::RegenerateUuid() Project *Project::GetProjectFromObject(const QObject *o) { - QObject *t = o->parent(); - - while (t) { - if (Project *p = dynamic_cast(t)) { - return p; - } - t = t->parent(); - } - - return nullptr; + return QtUtils::GetParentOfType(o); } void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range) From 925d63d707b3b2ad68580024b09a2e8267fe28bb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:57:19 -0700 Subject: [PATCH 032/107] timebased: transform keyframe times to global for snapping --- app/widget/curvewidget/curvewidget.h | 5 +++ app/widget/keyframeview/keyframeview.cpp | 2 +- app/widget/nodeparamview/nodeparamview.h | 5 +++ .../timebased/timebasedviewselectionmanager.h | 41 ++++++++++++++++++- app/widget/timebased/timebasedwidget.cpp | 11 ++++- app/widget/timebased/timebasedwidget.h | 2 + 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 2d85c86df..eef0ab62f 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -78,6 +78,11 @@ protected: return &view_->GetKeyframeTracks(); } + virtual const TimeTargetObject *GetKeyframeTimeTarget() const override + { + return view_; + } + virtual const std::vector *GetSnapIgnoreKeyframes() const override { return &view_->GetSelectedKeyframes(); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index cf64fd668..688e5d7f8 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -261,7 +261,7 @@ void KeyframeView::mousePressEvent(QMouseEvent *event) if (FirstChanceMousePress(event)) { first_chance_mouse_event_ = true; } else if (NodeKeyframe *initial_key = selection_manager_.MousePress(event)) { - selection_manager_.DragStart(initial_key, event); + selection_manager_.DragStart(initial_key, event, this); KeyframeDragStart(event); } else { selection_manager_.RubberBandStart(event); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index f1c3793af..09321c368 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -107,6 +107,11 @@ protected: return keyframe_view_ ? &keyframe_view_->GetSelectedKeyframes() : nullptr; } + virtual const TimeTargetObject *GetKeyframeTimeTarget() const + { + return keyframe_view_; + } + private: void UpdateItemTime(const rational &time); diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 7647ddc06..6916b5f4d 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -26,10 +26,12 @@ #include #include +#include "common/qtutils.h" #include "common/rational.h" #include "common/timecodefunctions.h" #include "timebasedview.h" #include "timebasedwidget.h" +#include "widget/timetarget/timetarget.h" namespace olive { @@ -158,8 +160,10 @@ public: return !dragging_.empty(); } - void DragStart(T *initial_item, QMouseEvent *event) + void DragStart(T *initial_item, QMouseEvent *event, TimeTargetObject *target = nullptr) { + time_target_ = target; + initial_drag_item_ = initial_item; dragging_.resize(selected_.size()); @@ -170,6 +174,13 @@ public: snap_points_.resize(selected_.size()); } + if (target) { + time_targets_.resize(snap_points_.size()); + memset(time_targets_.data(), 0, time_targets_.size() * sizeof(Node*)); + } else { + time_targets_.clear(); + } + for (size_t i=0; itime().in(); snap_points_[i] = obj->time().in(); snap_points_[i+selected_.size()] = obj->time().out(); + + if (target) { + time_targets_[i] = time_targets_[i+selected_.size()] = QtUtils::GetParentOfType(obj); + } } else { dragging_[i] = obj->time(); snap_points_[i] = obj->time(); + + if (target) { + time_targets_[i] = QtUtils::GetParentOfType(obj); + } } } @@ -188,8 +207,18 @@ public: void SnapPoints(rational *movement) { + std::vector copy = snap_points_; + + if (time_target_) { + for (size_t i=0; iGetAdjustedTime(parent, time_target_->GetTimeTarget(), copy[i], false); + } + } + } + if (Core::instance()->snapping() && view_->GetSnapService()) { - view_->GetSnapService()->SnapPoint(snap_points_, movement, snap_mask_); + view_->GetSnapService()->SnapPoint(copy, movement, snap_mask_); } } @@ -287,6 +316,11 @@ public: QToolTip::showText(QCursor::pos(), tip); } + void DragMove(QMouseEvent *event, TimeTargetObject *target) + { + return DragMove(event, QString(), target); + } + void DragStop(MultiUndoCommand *command) { QToolTip::hideText(); @@ -399,6 +433,7 @@ private: std::vector dragging_; std::vector snap_points_; + std::vector time_targets_; T *initial_drag_item_; @@ -412,6 +447,8 @@ private: TimeBasedWidget::SnapMask snap_mask_; + TimeTargetObject *time_target_; + }; } diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index b91f44e9f..03f0111c9 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -865,9 +865,16 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration continue; } - qreal key_scene_pt = TimeToScene(key->time()); + rational time = key->time(); + if (const TimeTargetObject *target = GetKeyframeTimeTarget()) { + if (Node *parent = key->parent()) { + time = target->GetAdjustedTime(parent, target->GetTimeTarget(), time, false); + } + } - AttemptSnap(potential_snaps, screen_pt, key_scene_pt, start_times, key->time()); + qreal key_scene_pt = TimeToScene(time); + + AttemptSnap(potential_snaps, screen_pt, key_scene_pt, start_times, time); } } } diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index ce00a3e1e..347c079c7 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -29,6 +29,7 @@ #include "widget/resizablescrollbar/resizabletimelinescrollbar.h" #include "widget/timebased/timescaledobject.h" #include "widget/timelinewidget/view/timelineview.h" +#include "widget/timetarget/timetarget.h" namespace olive { @@ -153,6 +154,7 @@ protected: virtual const QVector *GetSnapBlocks() const { return nullptr; } virtual const QVector *GetSnapKeyframes() const { return nullptr; } + virtual const TimeTargetObject *GetKeyframeTimeTarget() const { return nullptr; } virtual const std::vector *GetSnapIgnoreKeyframes() const { return nullptr; } virtual const std::vector *GetSnapIgnoreMarkers() const { return nullptr; } From 21d6aef0ca6d4ebcedb8f1778b198c0e4f973009 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 09:40:03 -0700 Subject: [PATCH 033/107] qtutils: rename QMessageBox wrapper to avoid conflicts on win32 --- app/common/qtutils.cpp | 2 +- app/common/qtutils.h | 8 +------- app/dialog/export/export.cpp | 14 +++++++------- app/dialog/sequence/sequence.cpp | 4 ++-- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index 1edf6448c..bb6c23de1 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -47,7 +47,7 @@ QFrame *QtUtils::CreateVerticalLine() return l; } -int QtUtils::MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &message, QMessageBox::StandardButtons buttons) +int QtUtils::MsgBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &message, QMessageBox::StandardButtons buttons) { QMessageBox b(parent); b.setIcon(icon); diff --git a/app/common/qtutils.h b/app/common/qtutils.h index fee21e596..19e2ea68c 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -33,12 +33,6 @@ #include #include -#include "common/define.h" - -#ifdef MessageBox -#undef MessageBox -#endif - namespace olive { class QtUtils { @@ -56,7 +50,7 @@ public: static QFrame* CreateVerticalLine(); - static int MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok); + static int MsgBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok); static QDateTime GetCreationDate(const QFileInfo &info); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 8797b3caa..d47ff3eab 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -259,7 +259,7 @@ rational ExportDialog::GetSelectedTimebase() const void ExportDialog::StartExport() { if (!video_enabled_->isChecked() && !audio_enabled_->isChecked() && !subtitles_enabled_->isChecked()) { - QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid parameters"), + QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid parameters"), tr("Video, audio, and subtitles are disabled. There's nothing to export.")); return; } @@ -271,7 +271,7 @@ void ExportDialog::StartExport() // If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export. if (!proposed_filename.endsWith(necessary_ext, Qt::CaseInsensitive)) { - if (QtUtils::MessageBox(this, QMessageBox::Warning, tr("Invalid filename"), + if (QtUtils::MsgBox(this, QMessageBox::Warning, tr("Invalid filename"), tr("The filename must contain the extension \"%1\". Would you like to append it " "automatically?").arg(necessary_ext), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { @@ -288,7 +288,7 @@ void ExportDialog::StartExport() // If the directory does not exist, try to create it QDir dest_dir(file_info.path()); if (!FileFunctions::DirectoryIsValid(dest_dir)) { - QtUtils::MessageBox(this, QMessageBox::Critical, tr("Failed to create output directory"), + QtUtils::MsgBox(this, QMessageBox::Critical, tr("Failed to create output directory"), tr("The intended output directory doesn't exist and Olive couldn't create it. " "Please choose a different filename.")); return; @@ -298,7 +298,7 @@ void ExportDialog::StartExport() if (video_tab_->IsImageSequenceSet()) { // Ensure filename contains digits if (!Encoder::FilenameContainsDigitPlaceholder(proposed_filename)) { - QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid filename"), + QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid filename"), tr("Export is set to an image sequence, but the filename does not have a section for digits " "(formatted as [#####] where the amount of # is the amount of digits).")); return; @@ -308,7 +308,7 @@ void ExportDialog::StartExport() int64_t needed_digit_count = GetDigitCount(frame_count); int current_digit_count = Encoder::GetImageSequencePlaceholderDigitCount(proposed_filename); if (current_digit_count < needed_digit_count) { - QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid filename"), + QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid filename"), tr("Filename doesn't contain enough digits for the amount of frames " "this export will need (need %1 for %n frame(s)).", nullptr, frame_count) .arg(QString::number(needed_digit_count))); @@ -318,7 +318,7 @@ void ExportDialog::StartExport() // Validate if the file exists and whether the user wishes to overwrite it if (file_info.exists()) { - if (QtUtils::MessageBox(this, QMessageBox::Warning, tr("Confirm Overwrite"), + if (QtUtils::MsgBox(this, QMessageBox::Warning, tr("Confirm Overwrite"), tr("The file \"%1\" already exists. Do you want to overwrite it?") .arg(proposed_filename), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { @@ -330,7 +330,7 @@ void ExportDialog::StartExport() if (video_enabled_->isChecked() && (video_tab_->GetSelectedCodec() == ExportCodec::kCodecH264 || video_tab_->GetSelectedCodec() == ExportCodec::kCodecH265) && (video_tab_->width_slider()->GetValue()%2 != 0 || video_tab_->height_slider()->GetValue()%2 != 0)) { - QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid Parameters"), + QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid Parameters"), tr("Width and height must be multiples of 2.")); return; } diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index 28f8f98e6..35b8f311e 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -102,7 +102,7 @@ void SequenceDialog::SetNameIsEditable(bool e) void SequenceDialog::accept() { if (name_field_->isEnabled() && name_field_->text().isEmpty()) { - QtUtils::MessageBox(this, QMessageBox::Critical, tr("Error editing Sequence"), tr("Please enter a name for this Sequence.")); + QtUtils::MsgBox(this, QMessageBox::Critical, tr("Error editing Sequence"), tr("Please enter a name for this Sequence.")); return; } @@ -167,7 +167,7 @@ void SequenceDialog::accept() void SequenceDialog::SetAsDefaultClicked() { - if (QtUtils::MessageBox(this, QMessageBox::Question, tr("Confirm Set As Default"), + if (QtUtils::MsgBox(this, QMessageBox::Question, tr("Confirm Set As Default"), tr("Are you sure you want to set the current parameters as defaults?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { // Maybe replace with Preset system From f152815e8fbfbcdeb028db4de6e50657f5471227 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 09:41:03 -0700 Subject: [PATCH 034/107] nodeparamview: fix missing override keyword --- app/widget/nodeparamview/nodeparamview.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 09321c368..479685a21 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -107,7 +107,7 @@ protected: return keyframe_view_ ? &keyframe_view_->GetSelectedKeyframes() : nullptr; } - virtual const TimeTargetObject *GetKeyframeTimeTarget() const + virtual const TimeTargetObject *GetKeyframeTimeTarget() const override { return keyframe_view_; } From dd32f81da81ad1d379c8e171593d06610629091a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 09:50:21 -0700 Subject: [PATCH 035/107] timebasedviewselectionmanager: only start drags if primary button pressed Fixes #1981 --- app/widget/timebased/timebasedviewselectionmanager.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 6916b5f4d..6d9e6962f 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -162,6 +162,10 @@ public: void DragStart(T *initial_item, QMouseEvent *event, TimeTargetObject *target = nullptr) { + if (event->button() != Qt::LeftButton) { + return; + } + time_target_ = target; initial_drag_item_ = initial_item; From 0e6ebee82765b41a05a239cbdb8af5b92c53abf5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 09:59:18 -0700 Subject: [PATCH 036/107] exportdialog/timeruler: disable marker and workarea editing Fixes #1979 --- app/dialog/export/export.cpp | 1 + app/widget/timeruler/seekablewidget.cpp | 13 ++++++++++--- app/widget/timeruler/seekablewidget.h | 5 +++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index d47ff3eab..b512c3910 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -182,6 +182,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : QVBoxLayout* preview_layout = new QVBoxLayout(preview_area); preview_layout->addWidget(new QLabel(tr("Preview"))); preview_viewer_ = new ViewerWidget(); + preview_viewer_->ruler()->SetMarkerEditingEnabled(false); preview_viewer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(preview_viewer_, &ViewerWidget::TimeChanged, video_tab_, &ExportVideoTab::SetTime); preview_layout->addWidget(preview_viewer_); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 27d9f4ddc..ebea9a18e 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -48,7 +48,8 @@ SeekableWidget::SeekableWidget(QWidget* parent) : selection_manager_(this), resize_item_(nullptr), marker_top_(0), - marker_bottom_(0) + marker_bottom_(0), + marker_editing_enabled_(true) { QFontMetrics fm = fontMetrics(); @@ -172,6 +173,8 @@ bool SeekableWidget::PasteMarkers() void SeekableWidget::mousePressEvent(QMouseEvent *event) { + TimelineMarker *initial; + if (resize_item_) { // Handle selection, even though we won't be using it for dragging if (!(event->modifiers() & Qt::ShiftModifier)) { @@ -182,7 +185,7 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) } dragging_ = true; resize_start_ = mapToScene(event->pos()); - } else if (TimelineMarker *initial = selection_manager_.MousePress(event)) { + } else if (marker_editing_enabled_ && (initial = selection_manager_.MousePress(event))) { selection_manager_.DragStart(initial, event); } else if (!selection_manager_.GetObjectAtPoint(event->pos()) && event->button() == Qt::LeftButton) { SeekToScenePoint(mapToScene(event->pos()).x()); @@ -430,7 +433,7 @@ int SeekableWidget::GetRightLimit() const bool SeekableWidget::ShowContextMenu(const QPoint &p) { - if (selection_manager_.GetObjectAtPoint(p) && !selection_manager_.GetSelectedObjects().empty()) { + if (marker_editing_enabled_ && selection_manager_.GetObjectAtPoint(p) && !selection_manager_.GetSelectedObjects().empty()) { // Show marker-specific menu Menu m; @@ -457,6 +460,10 @@ bool SeekableWidget::ShowContextMenu(const QPoint &p) bool SeekableWidget::FindResizeHandle(QMouseEvent *event) { + if (!marker_editing_enabled_) { + return false; + } + resize_item_ = nullptr; resize_mode_ = kResizeNone; diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 6e16592f5..fadf13492 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -52,6 +52,9 @@ public: return dragging_; } + bool IsMarkerEditingEnabled() const { return marker_editing_enabled_; } + void SetMarkerEditingEnabled(bool e) { marker_editing_enabled_ = e; } + void DeleteSelected(); bool CopySelected(bool cut); @@ -143,6 +146,8 @@ private: int marker_top_; int marker_bottom_; + bool marker_editing_enabled_; + private slots: void SetMarkerColor(int c); From 3948b7754385e6a8a50c2cfced21b051c233b27b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 13:49:02 -0700 Subject: [PATCH 037/107] viewer: allow customizing subtitle font Fixes #1980 --- app/config/config.cpp | 5 ++ app/widget/viewer/viewer.cpp | 37 +++++++++++++-- app/widget/viewer/viewer.h | 2 + app/widget/viewer/viewerdisplay.cpp | 73 +++++++++++++++++++++-------- 4 files changed, 93 insertions(+), 24 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 4b62bbacc..ce3888875 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -108,6 +108,11 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1))); + SetEntryInternal(QStringLiteral("DefaultSubtitleSize"), NodeValue::kInt, 48); + SetEntryInternal(QStringLiteral("DefaultSubtitleFamily"), NodeValue::kText, QString()); + SetEntryInternal(QStringLiteral("DefaultSubtitleWeight"), NodeValue::kInt, QFont::Bold); + SetEntryInternal(QStringLiteral("AntialiasSubtitles"), NodeValue::kBoolean, true); + SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000); SetEntryInternal(QStringLiteral("CatColor0"), NodeValue::kInt, ColorCoding::kRed); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 82c3a2459..2a9135780 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -21,6 +21,7 @@ #include "viewer.h" #include +#include #include #include #include @@ -32,7 +33,6 @@ #include "audio/audiomanager.h" #include "common/clamp.h" -#include "common/power.h" #include "common/ratiodialog.h" #include "common/timecodefunctions.h" #include "config/config.h" @@ -41,10 +41,9 @@ #include "node/generator/shape/shapenodebase.h" #include "node/project/project.h" #include "render/rendermanager.h" -#include "task/taskmanager.h" #include "viewerpreventsleep.h" +#include "widget/audiomonitor/audiomonitor.h" #include "widget/menu/menu.h" -#include "window/mainwindow/mainwindow.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/timelinewidget/tool/add.h" #include "widget/timeruler/timeruler.h" @@ -547,6 +546,20 @@ void ViewerWidget::HandleFirstRequeueDestroy() } } +void ViewerWidget::ShowSubtitleProperties() +{ + QFont f(OLIVE_CONFIG("DefaultSubtitleFamily").toString(), OLIVE_CONFIG("DefaultSubtitleSize").toInt(), OLIVE_CONFIG("DefaultSubtitleWeight").toInt()); + QFontDialog fd(f, this); + + if (fd.exec() == QDialog::Accepted) { + f = fd.selectedFont(); + OLIVE_CONFIG("DefaultSubtitleSize") = f.pointSize(); + OLIVE_CONFIG("DefaultSubtitleFamily") = f.family(); + OLIVE_CONFIG("DefaultSubtitleWeight") = f.weight(); + display_widget_->update(); + } +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); @@ -1309,10 +1322,26 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) } if (context_menu_widget_ == display_widget_) { - QAction* show_subtitles_action = menu.addAction(tr("Show Subtitles")); + auto subtitle_menu = new Menu(tr("Subtitles"), &menu); + menu.addMenu(subtitle_menu); + + QAction* show_subtitles_action = subtitle_menu->addAction(tr("Show Subtitles")); show_subtitles_action->setCheckable(true); show_subtitles_action->setChecked(display_widget_->GetShowSubtitles()); connect(show_subtitles_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetShowSubtitles); + + subtitle_menu->addSeparator(); + + auto subtitle_font_properties = subtitle_menu->addAction(tr("Subtitle Properties")); + connect(subtitle_font_properties, &QAction::triggered, this, &ViewerWidget::ShowSubtitleProperties); + + auto subtitle_antialias = subtitle_menu->addAction(tr("Use Anti-aliasing")); + subtitle_antialias->setCheckable(true); + subtitle_antialias->setChecked(OLIVE_CONFIG("AntialiasSubtitles").toBool()); + connect(subtitle_antialias, &QAction::triggered, this, [this](bool e){ + OLIVE_CONFIG("AntialiasSubtitles") = e; + display_widget_->update(); + }); } menu.exec(static_cast(sender())->mapToGlobal(pos)); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index f1aefcb85..1c5f126e0 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -352,6 +352,8 @@ private slots: void HandleFirstRequeueDestroy(); + void ShowSubtitleProperties(); + }; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 5ccb9bbff..8c0b42334 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -463,45 +463,78 @@ void ViewerDisplayWidget::OnPaint() const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); if (!subtitle_tracklist.empty()) { - QPainter p(paint_device()); + QPainterPath path; QTransform transform = GenerateWorldTransform(); QRect bounding_box = transform.mapRect(rect()); + QFont f; + qreal font_sz = OLIVE_CONFIG("DefaultSubtitleSize").toInt(); + { + // Scale font size by transform + QTransform display_transform = GenerateDisplayTransform(); + font_sz *= display_transform.m11(); + } + f.setPointSizeF(font_sz); + + QString family = OLIVE_CONFIG("DefaultSubtitleFamily").toString(); + if (!family.isEmpty()) { + f.setFamily(family); + } + + f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt()); + bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); - QFont f = p.font(); - int font_sz = bounding_box.height() / 18; - f.setStyleHint(QFont::SansSerif); - f.setFamily(f.defaultFamily()); - f.setPointSize(font_sz); - f.setWeight(QFont::Bold); - p.setFont(f); - p.setPen(Qt::white); - - QPainterPath path; - - int text_line = 1; + QFontMetrics fm(f); for (int j=subtitle_tracklist.size()-1; j>=0; j--) { Track *sub_track = subtitle_tracklist.at(j); if (!sub_track->IsMuted()) { if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { // Split into lines - QStringList list = QtUtils::WordWrapString(sub->GetText(), p.fontMetrics(), bounding_box.width()); + QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width()); for (int i=list.size()-1; i>=0; i--) { - int w = QtUtils::QFontMetricsWidth(p.fontMetrics(), list.at(i)); - path.addText(bounding_box.x() + bounding_box.width()/2 - w/2, bounding_box.y() + bounding_box.height() - p.fontMetrics().height() * text_line + p.fontMetrics().ascent(), p.font(), list.at(i)); - text_line++; + int w = QtUtils::QFontMetricsWidth(fm, list.at(i)); + path.addText(bounding_box.width()/2 - w/2, bounding_box.height() - fm.height() * (list.size() - i) + fm.ascent(), f, list.at(i)); } } } } - p.setPen(QPen(Qt::black, font_sz / 16)); - p.setBrush(Qt::white); - p.drawPath(path); + bool antialias = OLIVE_CONFIG("AntialiasSubtitles").toBool(); + + QPixmap *aa_pixmap; + QPainter *text_painter; + if (antialias) { + // QPainter only supports anti-aliasing in software, so to achieve it, we draw to a + // software buffer first and then draw that onto the hardware + aa_pixmap = new QPixmap(bounding_box.width(), bounding_box.height()); + aa_pixmap->fill(Qt::transparent); + text_painter = new QPainter(aa_pixmap); + } else { + // Just draw straight to the hardware + text_painter = new QPainter(paint_device()); + + // Offset path by however much is necessary + path.translate(bounding_box.x(), bounding_box.y()); + } + + text_painter->setPen(QPen(Qt::black, f.pointSizeF() / 16)); + text_painter->setBrush(Qt::white); + text_painter->setRenderHint(QPainter::Antialiasing); + + text_painter->drawPath(path); + + delete text_painter; + + if (antialias) { + // We just drew to a software buffer, now draw this image onto the hardware device + QPainter p(paint_device()); + p.drawPixmap(bounding_box.x(), bounding_box.y(), *aa_pixmap); + delete aa_pixmap; + } } } From 12f9b1acfc614e9ddeb27ced0c2c05c245b780e9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 15:22:57 -0700 Subject: [PATCH 038/107] export: allow exporting subtitles to sidecar files --- app/codec/encoder.cpp | 31 ++++++++++++- app/codec/encoder.h | 15 ++++++ app/dialog/export/export.cpp | 40 +++++++++++++--- app/dialog/export/export.h | 2 + app/dialog/export/exportformatcombobox.cpp | 7 +++ app/dialog/export/exportformatcombobox.h | 3 +- app/dialog/export/exportsubtitlestab.cpp | 27 ++++++++++- app/dialog/export/exportsubtitlestab.h | 15 +++++- app/task/export/export.cpp | 54 +++++++++++++++++++--- app/task/export/export.h | 4 +- app/task/export/exportparams.cpp | 12 ----- app/task/export/exportparams.h | 5 -- app/task/render/render.cpp | 7 ++- 13 files changed, 183 insertions(+), 39 deletions(-) diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 7abf5d39f..31b187a5a 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -93,7 +93,8 @@ EncodingParams::EncodingParams() : video_color_range_(kYUVDefault), audio_enabled_(false), audio_bit_rate_(0), - subtitles_enabled_(false) + subtitles_enabled_(false), + subtitles_are_sidecar_(false) { } @@ -117,6 +118,29 @@ void EncodingParams::EnableSubtitles(const ExportCodec::Codec &scodec) subtitles_codec_ = scodec; } +void EncodingParams::EnableSidecarSubtitles(const ExportFormat::Format &sfmt, const ExportCodec::Codec &scodec) +{ + subtitles_enabled_ = true; + subtitles_are_sidecar_ = true; + subtitle_sidecar_fmt_ = sfmt; + subtitles_codec_ = scodec; +} + +void EncodingParams::DisableVideo() +{ + video_enabled_ = false; +} + +void EncodingParams::DisableAudio() +{ + audio_enabled_ = false; +} + +void EncodingParams::DisableSubtitles() +{ + subtitles_enabled_ = false; +} + void EncodingParams::Save(QXmlStreamWriter *writer) const { writer->writeTextElement(QStringLiteral("filename"), filename_); @@ -217,6 +241,11 @@ Encoder *Encoder::CreateFromFormat(ExportFormat::Format f, const EncodingParams return CreateFromID(GetTypeFromFormat(f), params); } +Encoder *Encoder::CreateFromParams(const EncodingParams ¶ms) +{ + return CreateFromFormat(params.format(), params); +} + QStringList Encoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const { return QStringList(); diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 7b95fa7fb..e4c6db587 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -58,6 +58,14 @@ public: void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec); void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec); void EnableSubtitles(const ExportCodec::Codec &scodec); + void EnableSidecarSubtitles(const ExportFormat::Format &sfmt, const ExportCodec::Codec &scodec); + + void DisableVideo(); + void DisableAudio(); + void DisableSubtitles(); + + const ExportFormat::Format &format() const { return format_; } + void set_format(const ExportFormat::Format &format) { format_ = format; } void set_video_option(const QString& key, const QString& value) { video_opts_.insert(key, value); } void set_video_bit_rate(const int64_t& rate) { video_bit_rate_ = rate; } @@ -92,6 +100,8 @@ public: void set_audio_bit_rate(const int64_t& b) { audio_bit_rate_ = b; } bool subtitles_enabled() const { return subtitles_enabled_; } + bool subtitles_are_sidecar() const { return subtitles_are_sidecar_; } + ExportFormat::Format subtitle_sidecar_fmt() const { return subtitle_sidecar_fmt_; } ExportCodec::Codec subtitles_codec() const { return subtitles_codec_; } const rational& GetExportLength() const { return export_length_; } @@ -101,6 +111,7 @@ public: private: QString filename_; + ExportFormat::Format format_; bool video_enabled_; ExportCodec::Codec video_codec_; @@ -121,6 +132,8 @@ private: int64_t audio_bit_rate_; bool subtitles_enabled_; + bool subtitles_are_sidecar_; + ExportFormat::Format subtitle_sidecar_fmt_; ExportCodec::Codec subtitles_codec_; rational export_length_; @@ -152,6 +165,8 @@ public: static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms); + static Encoder *CreateFromParams(const EncodingParams ¶ms); + virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const; virtual std::vector GetSampleFormatsForCodec(ExportCodec::Codec c) const; diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index b512c3910..986d3a169 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -32,7 +32,6 @@ #include "common/digit.h" #include "common/qtutils.h" -#include "core.h" #include "dialog/task/task.h" #include "node/project/project.h" #include "node/project/sequence/sequence.h" @@ -250,6 +249,12 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preview_viewer_->ConnectViewerNode(viewer_node_); preview_viewer_->SetColorMenuEnabled(false); preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); + + // We don't check if the codec supports subtitles because we can always export to a sidecar file + bool has_subtitle_codecs = SequenceHasSubtitles(); + connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled); + subtitles_enabled_->setChecked(has_subtitle_codecs); + subtitles_enabled_->setEnabled(has_subtitle_codecs); } rational ExportDialog::GetSelectedTimebase() const @@ -445,9 +450,9 @@ void ExportDialog::FormatChanged(ExportFormat::Format 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); + if (subtitles_enabled_->isEnabled()) { + subtitle_tab_->SetFormat(current_format); + } } void ExportDialog::ResolutionChanged() @@ -503,6 +508,20 @@ void ExportDialog::SetDefaultFilename() filename_edit_->setText(file_location); } +bool ExportDialog::SequenceHasSubtitles() const +{ + if (Sequence *s = dynamic_cast(viewer_node_)) { + TrackList *tl = s->track_list(Track::kSubtitle); + for (Track *t : tl->GetTracks()) { + if (!t->IsMuted() && !t->Blocks().empty()) { + return true; + } + } + } + + return false; +} + ExportParams ExportDialog::GenerateParams() const { VideoParams video_render_params(static_cast(video_tab_->width_slider()->GetValue()), @@ -519,7 +538,7 @@ ExportParams ExportDialog::GenerateParams() const audio_tab_->sample_format_combobox()->GetSampleFormat()); ExportParams params; - params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat())); + params.set_format(format_combobox_->GetFormat()); params.SetFilename(filename_edit_->text().trimmed()); params.SetExportLength(viewer_node_->GetLength()); @@ -561,8 +580,15 @@ ExportParams ExportDialog::GenerateParams() const params.set_audio_bit_rate(audio_tab_->bit_rate_slider()->GetValue() * 1000); } - if (subtitles_enabled_->isChecked()) { - params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec()); + if (subtitles_enabled_->isEnabled() + && subtitles_enabled_->isChecked()) { + if (!subtitle_tab_->GetSidecarEnabled()) { + // Export subtitles embedded in container + params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec()); + } else { + // Export subtitles to a sidecar file + params.EnableSidecarSubtitles(subtitle_tab_->GetSidecarFormat(), subtitle_tab_->GetSubtitleCodec()); + } } return params; diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index df16c4850..9cd7d274a 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -63,6 +63,8 @@ private: void LoadPresets(); void SetDefaultFilename(); + bool SequenceHasSubtitles() const; + ExportParams GenerateParams() const; ViewerOutput* viewer_node_; diff --git a/app/dialog/export/exportformatcombobox.cpp b/app/dialog/export/exportformatcombobox.cpp index c185b27a8..dcbcdd01d 100644 --- a/app/dialog/export/exportformatcombobox.cpp +++ b/app/dialog/export/exportformatcombobox.cpp @@ -46,6 +46,13 @@ ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) : continue; } break; + case kShowSubtitlesOnly: + if (!ExportFormat::GetVideoCodecs(f).isEmpty() + || ExportFormat::GetSubtitleCodecs(f).isEmpty() + || !ExportFormat::GetAudioCodecs(f).isEmpty()) { + continue; + } + break; } QString format_name = ExportFormat::GetName(f); diff --git a/app/dialog/export/exportformatcombobox.h b/app/dialog/export/exportformatcombobox.h index 2f60867af..c90479e72 100644 --- a/app/dialog/export/exportformatcombobox.h +++ b/app/dialog/export/exportformatcombobox.h @@ -34,7 +34,8 @@ public: enum Mode { kShowAllFormats, kShowAudioOnly, - kShowVideoOnly + kShowVideoOnly, + kShowSubtitlesOnly }; ExportFormatComboBox(Mode mode, QWidget *parent = nullptr); diff --git a/app/dialog/export/exportsubtitlestab.cpp b/app/dialog/export/exportsubtitlestab.cpp index 27c40fc16..330942810 100644 --- a/app/dialog/export/exportsubtitlestab.cpp +++ b/app/dialog/export/exportsubtitlestab.cpp @@ -1,7 +1,6 @@ #include "exportsubtitlestab.h" #include -#include namespace olive { @@ -15,22 +14,46 @@ ExportSubtitlesTab::ExportSubtitlesTab(QWidget *parent) : int row = 0; + sidecar_checkbox_ = new QCheckBox(tr("Export to sidecar file")); + layout->addWidget(sidecar_checkbox_, row, 0, 1, 2); + + row++; + + sidecar_format_label_ = new QLabel(tr("Sidecar Format:")); + sidecar_format_label_->setVisible(false); + layout->addWidget(sidecar_format_label_, row, 0); + + sidecar_format_combobox_ = new ExportFormatComboBox(ExportFormatComboBox::kShowSubtitlesOnly); + sidecar_format_combobox_->setVisible(false); + layout->addWidget(sidecar_format_combobox_, row, 1); + + row++; + layout->addWidget(new QLabel(tr("Codec:")), row, 0); codec_combobox_ = new QComboBox(); layout->addWidget(codec_combobox_, row, 1); outer_layout->addStretch(); + + connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_label_, &QWidget::setVisible); + connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_combobox_, &QWidget::setVisible); } int ExportSubtitlesTab::SetFormat(ExportFormat::Format format) { auto scodecs = ExportFormat::GetSubtitleCodecs(format); - setEnabled(!scodecs.isEmpty()); + + sidecar_checkbox_->setChecked(scodecs.empty()); + sidecar_checkbox_->setEnabled(!scodecs.empty()); + + scodecs = ExportFormat::GetSubtitleCodecs(sidecar_format_combobox_->GetFormat()); + 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 index a88f4d904..faa5d4bdf 100644 --- a/app/dialog/export/exportsubtitlestab.h +++ b/app/dialog/export/exportsubtitlestab.h @@ -21,10 +21,12 @@ #ifndef EXPORTSUBTITLESTAB_H #define EXPORTSUBTITLESTAB_H +#include #include +#include #include "codec/exportformat.h" -#include "render/subtitleparams.h" +#include "dialog/export/exportformatcombobox.h" namespace olive { @@ -34,6 +36,12 @@ class ExportSubtitlesTab : public QWidget public: ExportSubtitlesTab(QWidget *parent = nullptr); + bool GetSidecarEnabled() const { return sidecar_checkbox_->isChecked(); } + void SetSidecarEnabled(bool e) { sidecar_checkbox_->setEnabled(e); } + + ExportFormat::Format GetSidecarFormat() const { return sidecar_format_combobox_->GetFormat(); } + void SetSidecarFormat(ExportFormat::Format f) { sidecar_format_combobox_->SetFormat(f); } + int SetFormat(ExportFormat::Format format); ExportCodec::Codec GetSubtitleCodec() @@ -42,6 +50,11 @@ public: } private: + QCheckBox *sidecar_checkbox_; + + QLabel *sidecar_format_label_; + ExportFormatComboBox *sidecar_format_combobox_; + QComboBox *codec_combobox_; }; diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index e471d6862..3df5f1240 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -58,7 +58,14 @@ bool ExportTask::Run() params_.SetFilename(FileFunctions::GetSafeTemporaryFilename(real_filename)); } - encoder_ = Encoder::CreateFromID(params_.encoder(), params_); + // If we're exporting to a sidecar subtitle file, disable the subtitles in the main encoder + bool subtitles_enabled = params_.subtitles_enabled(); + ExportParams sidecar_params = params_; + if (subtitles_enabled && params_.subtitles_are_sidecar()) { + params_.DisableSubtitles(); + } + + encoder_ = std::shared_ptr(Encoder::CreateFromParams(params_)); if (!encoder_) { SetError(tr("Failed to create encoder")); @@ -67,10 +74,38 @@ bool ExportTask::Run() if (!encoder_->Open()) { SetError(tr("Failed to open file: %1").arg(encoder_->GetError())); - encoder_->deleteLater(); return false; } + if (subtitles_enabled && params_.subtitles_are_sidecar()) { + // Construct sidecar params + sidecar_params.DisableVideo(); + sidecar_params.DisableAudio(); + + QString sidecar_filename; + { + QFileInfo fi(real_filename); + sidecar_filename = fi.completeBaseName(); + sidecar_filename.append('.'); + sidecar_filename.append(ExportFormat::GetExtension(sidecar_params.subtitle_sidecar_fmt())); + sidecar_filename = fi.dir().filePath(sidecar_filename); + } + sidecar_params.SetFilename(sidecar_filename); + + subtitle_encoder_ = std::shared_ptr(Encoder::CreateFromFormat(sidecar_params.subtitle_sidecar_fmt(), sidecar_params)); + if (!subtitle_encoder_) { + SetError(tr("Failed to create subtitle encoder")); + return false; + } + + if (!subtitle_encoder_->Open()) { + SetError(tr("Failed to open subtitle sidecar file: %1").arg(sidecar_filename)); + return false; + } + } else { + subtitle_encoder_ = encoder_; + } + if (params_.has_custom_range()) { // Render custom range only range = params_.custom_range(); @@ -121,7 +156,7 @@ bool ExportTask::Run() audio_range = {range}; } - if (params_.subtitles_enabled()) { + if (subtitles_enabled) { subtitle_range = range; } @@ -132,13 +167,18 @@ bool ExportTask::Run() bool success = true; encoder_->Close(); - if (!encoder_->GetError().isEmpty()) { SetError(encoder_->GetError()); success = false; } - delete encoder_; + if (subtitle_encoder_ != encoder_) { + subtitle_encoder_->Close(); + if (!subtitle_encoder_->GetError().isEmpty()) { + SetError(subtitle_encoder_->GetError()); + success = false; + } + } // If cancelled, delete the file we made, which is always a file we created since we write to a // temp file during the actual encoding process @@ -209,8 +249,8 @@ bool ExportTask::AudioDownloaded(const TimeRange &range, const SampleBuffer &sam bool ExportTask::EncodeSubtitle(const SubtitleBlock *sub) { - if (!encoder_->WriteSubtitle(sub)) { - SetError(encoder_->GetError()); + if (!subtitle_encoder_->WriteSubtitle(sub)) { + SetError(subtitle_encoder_->GetError()); return false; } else { return true; diff --git a/app/task/export/export.h b/app/task/export/export.h index bf696978e..4bed7cd8b 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -60,7 +60,9 @@ private: ExportParams params_; - Encoder* encoder_; + std::shared_ptr encoder_; + + std::shared_ptr subtitle_encoder_; ColorProcessorPtr color_processor_; diff --git a/app/task/export/exportparams.cpp b/app/task/export/exportparams.cpp index dd7154044..dfe79686d 100644 --- a/app/task/export/exportparams.cpp +++ b/app/task/export/exportparams.cpp @@ -28,16 +28,6 @@ ExportParams::ExportParams() : { } -const Encoder::Type &ExportParams::encoder() const -{ - return encoder_id_; -} - -void ExportParams::set_encoder(const Encoder::Type &id) -{ - encoder_id_ = id; -} - bool ExportParams::has_custom_range() const { return has_custom_range_; @@ -104,8 +94,6 @@ void ExportParams::Save(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("export")); - writer->writeTextElement(QStringLiteral("encoder"), QString::number(encoder_id_)); - writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_)); writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_)); diff --git a/app/task/export/exportparams.h b/app/task/export/exportparams.h index b72597a6c..46f213f9c 100644 --- a/app/task/export/exportparams.h +++ b/app/task/export/exportparams.h @@ -39,9 +39,6 @@ public: ExportParams(); - const Encoder::Type& encoder() const; - void set_encoder(const Encoder::Type& id); - bool has_custom_range() const; const TimeRange& custom_range() const; void set_custom_range(const TimeRange& custom_range); @@ -59,8 +56,6 @@ public: virtual void Save(QXmlStreamWriter* writer) const override; private: - Encoder::Type encoder_id_; - VideoScalingMethod video_scaling_method_; bool has_custom_range_; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 23fa2e387..d427b9608 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -95,8 +95,7 @@ bool RenderTask::Render(ColorManager* manager, // Subtitle loop, loops over all blocks in sequence on all tracks if (!subtitle_range.length().isNull()) { - Sequence *sequence = dynamic_cast(viewer_); - if (sequence) { + if (Sequence *sequence = dynamic_cast(viewer_)) { TrackList *list = sequence->track_list(Track::kSubtitle); QVector block_indexes(list->GetTrackCount(), 0); @@ -106,6 +105,10 @@ bool RenderTask::Render(ColorManager* manager, for (int i=0; iGetTrackAt(i); + if (this_track->IsMuted()) { + continue; + } + int &this_block_index = block_indexes[i]; if (this_block_index >= this_track->Blocks().size()) { continue; From b65d4451457735af46aae94594506c59350c08a6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 16:57:09 -0700 Subject: [PATCH 039/107] viewertexteditor: set char format to last used when cleared --- app/widget/viewer/viewerdisplay.cpp | 3 --- app/widget/viewer/viewertexteditor.cpp | 21 +++++++++++++++++++-- app/widget/viewer/viewertexteditor.h | 3 +++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 8c0b42334..92f9bd119 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -34,8 +34,6 @@ #include #include -#include "common/define.h" -#include "common/functiontimer.h" #include "common/html.h" #include "common/qtutils.h" #include "config/config.h" @@ -46,7 +44,6 @@ #include "node/gizmo/polygon.h" #include "node/gizmo/screen.h" #include "viewertexteditor.h" -#include "window/mainwindow/mainwindow.h" namespace olive { diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 6a6c3227a..c7cc4efb6 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -24,11 +24,12 @@ #include #include #include +#include #include +#include #include "common/qtutils.h" #include "ui/icons/icons.h" -#include "widget/colorbutton/colorbutton.h" namespace olive { @@ -38,7 +39,8 @@ ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) : super(parent), transparent_clone_(nullptr), block_update_toolbar_signal_(false), - listen_to_focus_events_(false) + listen_to_focus_events_(false), + forced_default_(false) { // Ensure default text color is white QPalette p = palette(); @@ -173,6 +175,10 @@ void ViewerTextEditor::FormatChanged(const QTextCharFormat &f) UpdateToolBar(toolbar, f, textCursor().blockFormat(), this->alignment()); } } + + if (!(document()->blockCount() == 1 && document()->firstBlock().text().isEmpty())) { + default_fmt_ = f; + } } void ViewerTextEditor::SetFamily(const QString &s) @@ -235,6 +241,7 @@ void ViewerTextEditor::MergeCharFormat(const QTextCharFormat &fmt) // this can be undesirable if the user is currently typing a font block_update_toolbar_signal_ = true; mergeCurrentCharFormat(fmt); + //default_fmt_ = this->currentCharFormat(); block_update_toolbar_signal_ = false; } @@ -263,6 +270,16 @@ void ViewerTextEditor::LockScrollBarMaximumToZero() void ViewerTextEditor::DocumentChanged() { + if (document()->blockCount() == 1 && document()->firstBlock().text().isEmpty()) { + if (!forced_default_) { + QTextCursor c(document()->firstBlock()); + c.setBlockCharFormat(default_fmt_); + forced_default_ = true; + } + } else { + forced_default_ = false; + } + // HACK: We want to show the text cursor and selections without necessarily rendering the text, // because the text is already being rendered underneath the gizmo (and rendering twice will // alter the overall look of the text while editing). This is something that Qt does not diff --git a/app/widget/viewer/viewertexteditor.h b/app/widget/viewer/viewertexteditor.h index 2731cb710..33b640b76 100644 --- a/app/widget/viewer/viewertexteditor.h +++ b/app/widget/viewer/viewertexteditor.h @@ -165,6 +165,9 @@ private: bool listen_to_focus_events_; + bool forced_default_; + QTextCharFormat default_fmt_; + private slots: void FormatChanged(const QTextCharFormat &f); From d7748c4ace86684772ad44372b70ed350bd4a435 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 17:07:55 -0700 Subject: [PATCH 040/107] footage: set timebase too when merging image sequence params --- app/node/project/footage/footage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index ebd410b6a..7b8870ee2 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -513,10 +513,11 @@ VideoParams Footage::MergeVideoStream(const VideoParams &base, const VideoParams merged.set_colorspace(over.colorspace()); merged.set_premultiplied_alpha(over.premultiplied_alpha()); merged.set_video_type(over.video_type()); - if (merged.video_type() == VideoParams::kVideoTypeImageSequence && over.video_type() == VideoParams::kVideoTypeImageSequence) { + if (merged.video_type() == VideoParams::kVideoTypeImageSequence) { merged.set_start_time(over.start_time()); merged.set_duration(over.duration()); merged.set_frame_rate(over.frame_rate()); + merged.set_time_base(over.time_base()); } return merged; From 02f4be6e3c0c34b2b8139eb991bcf7e5055d693c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 24 Jul 2022 08:57:09 -0700 Subject: [PATCH 041/107] footage: account for invalid streams before valid streams Fixes #1983 --- app/codec/ffmpeg/ffmpegdecoder.cpp | 2 ++ app/node/output/viewer/viewer.h | 2 +- app/node/project/footage/footage.cpp | 8 +++++++- app/node/project/footage/footage.h | 4 ++++ .../project/footage/footagedescription.cpp | 18 +++++++++++++++--- app/node/project/footage/footagedescription.h | 10 ++++++++-- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 2ab14eadb..c2dbe2488 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -513,6 +513,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can } + desc.SetStreamCount(fmt_ctx->nb_streams); + } // Free all memory diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 09b0ac3d5..8d26f4bd3 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -126,7 +126,7 @@ public: return InputArraySize(kSubtitleParamsInput); } - int GetTotalStreamCount() const + virtual int GetTotalStreamCount() const { return GetVideoStreamCount() + GetAudioStreamCount() + GetSubtitleStreamCount(); } diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 7b8870ee2..7c89f3496 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -44,7 +44,8 @@ Footage::Footage(const QString &filename) : ViewerOutput(false, false), timestamp_(0), valid_(false), - cancelled_(nullptr) + cancelled_(nullptr), + total_stream_count_(0) { SetCacheTextures(true); @@ -125,6 +126,9 @@ void Footage::Clear() // Clear decoder link decoder_.clear(); + // Clear total stream count + total_stream_count_ = 0; + // Reset ready state valid_ = false; } @@ -497,6 +501,8 @@ void Footage::Reprobe() SetStream(Track::kSubtitle, QVariant::fromValue(footage_info.GetSubtitleStreams().at(i)), i); } + total_stream_count_ = footage_info.GetStreamCount(); + SetValid(); } diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 8397726a2..7eb38f3c5 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -180,6 +180,8 @@ public: virtual qint64 creation_time() const override; virtual qint64 mod_time() const override; + virtual int GetTotalStreamCount() const override { return total_stream_count_; } + static const QString kFilenameInput; protected: @@ -224,6 +226,8 @@ private: CancelAtom *cancelled_; + int total_stream_count_; + private slots: void CheckFootage(); diff --git a/app/node/project/footage/footagedescription.cpp b/app/node/project/footage/footagedescription.cpp index 610db5c39..742372586 100644 --- a/app/node/project/footage/footagedescription.cpp +++ b/app/node/project/footage/footagedescription.cpp @@ -43,9 +43,11 @@ bool FootageDescription::Load(const QString &filename) // Default to first version of metadata (which wasn't versioned at all) unsigned version = 1; - XMLAttributeLoop((&reader), attr) { - if (attr.name() == QStringLiteral("version")) { - version = attr.value().toUInt(); + { + XMLAttributeLoop((&reader), attr) { + if (attr.name() == QStringLiteral("version")) { + version = attr.value().toUInt(); + } } } @@ -58,6 +60,14 @@ bool FootageDescription::Load(const QString &filename) if (reader.name() == QStringLiteral("decoder")) { decoder_ = reader.readElementText(); } else if (reader.name() == QStringLiteral("streams")) { + { + XMLAttributeLoop((&reader), attr) { + if (attr.name() == QStringLiteral("count")) { + total_stream_count_ = attr.value().toInt(); + } + } + } + while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("video")) { VideoParams vp; @@ -116,6 +126,8 @@ bool FootageDescription::Save(const QString &filename) const writer.writeStartElement(QStringLiteral("streams")); + writer.writeAttribute(QStringLiteral("count"), QString::number(total_stream_count_)); + foreach (const VideoParams& vp, video_streams_) { writer.writeStartElement(QStringLiteral("video")); vp.Save(&writer); diff --git a/app/node/project/footage/footagedescription.h b/app/node/project/footage/footagedescription.h index 930a6e8be..0c55cb044 100644 --- a/app/node/project/footage/footagedescription.h +++ b/app/node/project/footage/footagedescription.h @@ -32,7 +32,8 @@ class FootageDescription { public: FootageDescription(const QString& decoder = QString()) : - decoder_(decoder) + decoder_(decoder), + total_stream_count_(0) { } @@ -118,6 +119,9 @@ public: return StreamIsVideo(index) || StreamIsAudio(index) || StreamIsSubtitle(index); } + int GetStreamCount() const { return total_stream_count_; } + void SetStreamCount(int s) { total_stream_count_ = s; } + bool Load(const QString& filename); bool Save(const QString& filename) const; @@ -138,7 +142,7 @@ public: } private: - static constexpr unsigned kFootageMetaVersion = 2; + static constexpr unsigned kFootageMetaVersion = 3; QString decoder_; @@ -148,6 +152,8 @@ private: QVector subtitle_streams_; + int total_stream_count_; + }; } From 1fa171e507653a8d4439a38b605ea15f7593ff58 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 24 Jul 2022 08:57:23 -0700 Subject: [PATCH 042/107] exportdialog: rename yuv ranges --- app/dialog/export/exportadvancedvideodialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dialog/export/exportadvancedvideodialog.cpp b/app/dialog/export/exportadvancedvideodialog.cpp index 57851f424..2d08ceacd 100644 --- a/app/dialog/export/exportadvancedvideodialog.cpp +++ b/app/dialog/export/exportadvancedvideodialog.cpp @@ -35,7 +35,7 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList &pix_f pixel_layout->addWidget(new QLabel(tr("YUV Color Range:")), row, 0); yuv_color_range_combobox_ = new QComboBox(); - yuv_color_range_combobox_->addItems({tr("MPEG (16-235)"), tr("JPEG (0-255)")}); + yuv_color_range_combobox_->addItems({tr("Limited (16-235)"), tr("Full (0-255)")}); pixel_layout->addWidget(yuv_color_range_combobox_, row, 1); } From 3fb786a868b31332632c7adc9c5e408a72f5cfbc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Jul 2022 12:53:08 -0700 Subject: [PATCH 043/107] manageddisplaywidget: add toggle for QOpenGLWindow --- app/widget/manageddisplay/manageddisplay.cpp | 4 ++++ app/widget/manageddisplay/manageddisplay.h | 24 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 923d0bdee..62afcb876 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -62,7 +62,11 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : attached_renderer_ = new OpenGLRenderer(this); // Create widget wrapper for OpenGL window +#ifdef USE_QOPENGLWINDOW wrapper_ = QWidget::createWindowContainer(static_cast(inner_widget_)); +#else + wrapper_ = inner_widget_; +#endif layout->addWidget(wrapper_); } else { inner_widget_ = nullptr; diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 89a952eb0..644ab0711 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -21,9 +21,15 @@ #ifndef MANAGEDDISPLAYOBJECT_H #define MANAGEDDISPLAYOBJECT_H +//#define USE_QOPENGLWINDOW + #include #include +#ifdef USE_QOPENGLWINDOW #include +#else +#include +#endif #include "node/color/colormanager/colormanager.h" #include "render/renderer.h" @@ -31,7 +37,12 @@ namespace olive { -class ManagedDisplayWidgetOpenGL : public QOpenGLWindow +class ManagedDisplayWidgetOpenGL +#ifdef USE_QOPENGLWINDOW + : public QOpenGLWindow +#else + : public QOpenGLWidget +#endif { Q_OBJECT public: @@ -175,7 +186,12 @@ protected: void doneCurrent(); - QWindow* inner_widget() const +#ifdef USE_QOPENGLWINDOW + QWindow* +#else + QWidget* +#endif + inner_widget() const { return inner_widget_; } @@ -225,7 +241,11 @@ private: /** * @brief Main drawing surface abstraction */ +#ifdef USE_QOPENGLWINDOW QWindow* inner_widget_; +#else + QWidget* inner_widget_; +#endif QWidget *wrapper_; /** From 33742a62cfa58b1faea7381f042b56a8ba8e9521 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Jul 2022 13:08:28 -0700 Subject: [PATCH 044/107] oiiodecoder: set correct stream count Fixes #1985 --- app/codec/footagemeta.h | 10 ---------- app/codec/oiio/oiiodecoder.cpp | 5 ++++- app/node/project/footage/footagedescription.h | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) delete mode 100644 app/codec/footagemeta.h diff --git a/app/codec/footagemeta.h b/app/codec/footagemeta.h deleted file mode 100644 index 4d23025f1..000000000 --- a/app/codec/footagemeta.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef FOOTAGEMETA_H -#define FOOTAGEMETA_H - -struct FootageData { - struct StreamData { - - }; -}; - -#endif // FOOTAGEMETA_H diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index e9a84ca28..7c7dd8878 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -73,7 +73,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cance bool stream_enabled = true; - for (int i=0; in->seek_subimage(i, 0); i++) { + int i; + for (i=0; in->seek_subimage(i, 0); i++) { OIIO::ImageSpec spec = in->spec(); VideoParams video_params = GetVideoParamsFromImageSpec(spec); @@ -104,6 +105,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cance desc.AddVideoStream(video_params); } + desc.SetStreamCount(i); + // If we're here, we have a successful image open in->close(); diff --git a/app/node/project/footage/footagedescription.h b/app/node/project/footage/footagedescription.h index 0c55cb044..c05849273 100644 --- a/app/node/project/footage/footagedescription.h +++ b/app/node/project/footage/footagedescription.h @@ -142,7 +142,7 @@ public: } private: - static constexpr unsigned kFootageMetaVersion = 3; + static constexpr unsigned kFootageMetaVersion = 4; QString decoder_; From 77391984374ca40ebd6dc782f76d0f494c3809e3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Jul 2022 17:59:54 -0700 Subject: [PATCH 045/107] viewer: skip subtitle drawing if transformed font size is NaN --- app/widget/viewer/viewerdisplay.cpp | 166 +++++++++++++++------------- app/widget/viewer/viewerdisplay.h | 2 + 2 files changed, 90 insertions(+), 78 deletions(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 92f9bd119..1357af21a 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -456,84 +456,7 @@ void ViewerDisplayWidget::OnPaint() } // Extraordinarily basic subtitle renderer. Hoping to swap this out with libass at some point. - if (show_subtitles_ && subtitle_tracks_) { - const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); - - if (!subtitle_tracklist.empty()) { - QPainterPath path; - - QTransform transform = GenerateWorldTransform(); - QRect bounding_box = transform.mapRect(rect()); - - QFont f; - qreal font_sz = OLIVE_CONFIG("DefaultSubtitleSize").toInt(); - { - // Scale font size by transform - QTransform display_transform = GenerateDisplayTransform(); - font_sz *= display_transform.m11(); - } - f.setPointSizeF(font_sz); - - QString family = OLIVE_CONFIG("DefaultSubtitleFamily").toString(); - if (!family.isEmpty()) { - f.setFamily(family); - } - - f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt()); - - bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); - - QFontMetrics fm(f); - - for (int j=subtitle_tracklist.size()-1; j>=0; j--) { - Track *sub_track = subtitle_tracklist.at(j); - if (!sub_track->IsMuted()) { - if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { - // Split into lines - QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width()); - - for (int i=list.size()-1; i>=0; i--) { - int w = QtUtils::QFontMetricsWidth(fm, list.at(i)); - path.addText(bounding_box.width()/2 - w/2, bounding_box.height() - fm.height() * (list.size() - i) + fm.ascent(), f, list.at(i)); - } - } - } - } - - bool antialias = OLIVE_CONFIG("AntialiasSubtitles").toBool(); - - QPixmap *aa_pixmap; - QPainter *text_painter; - if (antialias) { - // QPainter only supports anti-aliasing in software, so to achieve it, we draw to a - // software buffer first and then draw that onto the hardware - aa_pixmap = new QPixmap(bounding_box.width(), bounding_box.height()); - aa_pixmap->fill(Qt::transparent); - text_painter = new QPainter(aa_pixmap); - } else { - // Just draw straight to the hardware - text_painter = new QPainter(paint_device()); - - // Offset path by however much is necessary - path.translate(bounding_box.x(), bounding_box.y()); - } - - text_painter->setPen(QPen(Qt::black, f.pointSizeF() / 16)); - text_painter->setBrush(Qt::white); - text_painter->setRenderHint(QPainter::Antialiasing); - - text_painter->drawPath(path); - - delete text_painter; - - if (antialias) { - // We just drew to a software buffer, now draw this image onto the hardware device - QPainter p(paint_device()); - p.drawPixmap(bounding_box.x(), bounding_box.y(), *aa_pixmap); - delete aa_pixmap; - } - } - } + DrawSubtitleTracks(); if (add_band_) { QPainter p(paint_device()); @@ -974,6 +897,93 @@ void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e) } } +void ViewerDisplayWidget::DrawSubtitleTracks() +{ + if (!show_subtitles_ || !subtitle_tracks_) { + return; + } + + const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); + if (subtitle_tracklist.empty()) { + return; + } + + // Scale font size by transform + QTransform display_transform = GenerateDisplayTransform(); + qreal font_sz = OLIVE_CONFIG("DefaultSubtitleSize").toInt(); + font_sz *= display_transform.m11(); + if (qIsNaN(font_sz)) { + return; + } + + QPainterPath path; + + QTransform transform = GenerateWorldTransform(); + QRect bounding_box = transform.mapRect(rect()); + + QFont f; + f.setPointSizeF(font_sz); + + QString family = OLIVE_CONFIG("DefaultSubtitleFamily").toString(); + if (!family.isEmpty()) { + f.setFamily(family); + } + + f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt()); + + bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); + + QFontMetrics fm(f); + + for (int j=subtitle_tracklist.size()-1; j>=0; j--) { + Track *sub_track = subtitle_tracklist.at(j); + if (!sub_track->IsMuted()) { + if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { + // Split into lines + QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width()); + + for (int i=list.size()-1; i>=0; i--) { + int w = QtUtils::QFontMetricsWidth(fm, list.at(i)); + path.addText(bounding_box.width()/2 - w/2, bounding_box.height() - fm.height() * (list.size() - i) + fm.ascent(), f, list.at(i)); + } + } + } + } + + bool antialias = OLIVE_CONFIG("AntialiasSubtitles").toBool(); + + QPixmap *aa_pixmap; + QPainter *text_painter; + if (antialias) { + // QPainter only supports anti-aliasing in software, so to achieve it, we draw to a + // software buffer first and then draw that onto the hardware + aa_pixmap = new QPixmap(bounding_box.width(), bounding_box.height()); + aa_pixmap->fill(Qt::transparent); + text_painter = new QPainter(aa_pixmap); + } else { + // Just draw straight to the hardware + text_painter = new QPainter(paint_device()); + + // Offset path by however much is necessary + path.translate(bounding_box.x(), bounding_box.y()); + } + + text_painter->setPen(QPen(Qt::black, f.pointSizeF() / 16)); + text_painter->setBrush(Qt::white); + text_painter->setRenderHint(QPainter::Antialiasing); + + text_painter->drawPath(path); + + delete text_painter; + + if (antialias) { + // We just drew to a software buffer, now draw this image onto the hardware device + QPainter p(paint_device()); + p.drawPixmap(bounding_box.x(), bounding_box.y(), *aa_pixmap); + delete aa_pixmap; + } +} + void ViewerDisplayWidget::SetShowFPS(bool e) { show_fps_ = e; diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index e88b845a3..430f11b54 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -263,6 +263,8 @@ private: void EmitColorAtCursor(QMouseEvent* e); + void DrawSubtitleTracks(); + /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ From 9eb0aa80e1b4c56020c57ad77cf3281bdb0e0fa8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Jul 2022 18:17:51 -0700 Subject: [PATCH 046/107] timeline: when ripple removing area, don't split gaps --- app/widget/timelinewidget/undo/timelineundoripple.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/widget/timelinewidget/undo/timelineundoripple.cpp b/app/widget/timelinewidget/undo/timelineundoripple.cpp index 3b7bdcac9..d940ea00d 100644 --- a/app/widget/timelinewidget/undo/timelineundoripple.cpp +++ b/app/widget/timelinewidget/undo/timelineundoripple.cpp @@ -63,8 +63,14 @@ void TrackRippleRemoveAreaCommand::prepare() // If it's getting trimmed, determine if it's actually getting spliced if (first_block_is_out_trimmed && first_block_is_in_trimmed) { - // This block is getting spliced, so we'll handle that later - splice_split_command_ = new BlockSplitCommand(first_block, range_.in()); + if (dynamic_cast(first_block)) { + trim_out_ = {first_block, + first_block->length(), + first_block->length() - range_.length()}; + } else { + // This block is getting spliced, so we'll handle that later + splice_split_command_ = new BlockSplitCommand(first_block, range_.in()); + } } else { // It's just getting trimmed or removed, so we'll append that operation if (first_block_is_out_trimmed) { From fb8bb654a09f335861814b51816e576b0896d6bf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 08:33:05 -0700 Subject: [PATCH 047/107] timeline: add comment to recent code addition --- app/widget/timelinewidget/undo/timelineundoripple.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/widget/timelinewidget/undo/timelineundoripple.cpp b/app/widget/timelinewidget/undo/timelineundoripple.cpp index d940ea00d..71550f64c 100644 --- a/app/widget/timelinewidget/undo/timelineundoripple.cpp +++ b/app/widget/timelinewidget/undo/timelineundoripple.cpp @@ -64,6 +64,7 @@ void TrackRippleRemoveAreaCommand::prepare() // If it's getting trimmed, determine if it's actually getting spliced if (first_block_is_out_trimmed && first_block_is_in_trimmed) { if (dynamic_cast(first_block)) { + // As a rule, we don't split gaps, so we just treat it as a trim of the range requested trim_out_ = {first_block, first_block->length(), first_block->length() - range_.length()}; From e3403a5a5352acfa89bb32e3617e21428b8f1d3f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:40:26 -0700 Subject: [PATCH 048/107] render: create less textures --- app/codec/ffmpeg/ffmpegdecoder.cpp | 33 +++++++++-- app/codec/ffmpeg/ffmpegdecoder.h | 4 ++ app/render/opengl/openglrenderer.cpp | 80 +++++++++----------------- app/render/opengl/openglrenderer.h | 19 +++---- app/render/renderer.cpp | 84 +++++++++++++++++++--------- app/render/renderer.h | 39 +++++++++---- app/render/texture.cpp | 2 +- app/render/texture.h | 20 +------ app/render/videoparams.h | 2 + 9 files changed, 156 insertions(+), 127 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index c2dbe2488..9f66a9e3e 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -61,6 +61,9 @@ FFmpegDecoder::FFmpegDecoder() : input_fmt_(AV_PIX_FMT_NONE), native_internal_pix_fmt_(VideoParams::kFormatInvalid), native_output_pix_fmt_(VideoParams::kFormatInvalid), + y_tex_(nullptr), + u_tex_(nullptr), + v_tex_(nullptr), working_frame_(nullptr), working_packet_(nullptr), cache_at_zero_(false), @@ -216,7 +219,12 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration plane_params.set_channel_count(1); plane_params.set_divider(1); plane_params.set_format(native_internal_pix_fmt_); - TexturePtr y_plane = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); + + if (!y_tex_) { + y_tex_ = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); + } else { + y_tex_->Upload(f->data[0], f->linesize[0] / px_size); + } if (src_fmt == AV_PIX_FMT_YUV420P || src_fmt == AV_PIX_FMT_YUV422P @@ -236,13 +244,22 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration plane_params.set_height(plane_params.height()/2); } - TexturePtr u_plane = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); - TexturePtr v_plane = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); + if (!u_tex_) { + u_tex_ = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); + } else { + u_tex_->Upload(f->data[1], f->linesize[1] / px_size); + } + + if (!v_tex_) { + v_tex_ = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); + } else { + v_tex_->Upload(f->data[2], f->linesize[2] / px_size); + } ShaderJob job; - job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); - job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); - job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); + job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_tex_))); + job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_tex_))); + job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_tex_))); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); job.Insert(QStringLiteral("jpeg_range"), NodeValue(NodeValue::kBoolean, jpeg_range)); @@ -296,6 +313,10 @@ void FFmpegDecoder::CloseInternal() input_fmt_ = AV_PIX_FMT_NONE; native_internal_pix_fmt_ = VideoParams::kFormatInvalid; native_output_pix_fmt_ = VideoParams::kFormatInvalid; + + y_tex_ = nullptr; + u_tex_ = nullptr; + v_tex_ = nullptr; } QString FFmpegDecoder::id() const diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 1c0d25bb8..837d5e04c 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -165,6 +165,10 @@ private: VideoParams::Format native_output_pix_fmt_; int native_channel_count_; + TexturePtr y_tex_; + TexturePtr u_tex_; + TexturePtr v_tex_; + AVFrame *working_frame_; AVPacket *working_packet_; diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 5078b963c..a06c44ffd 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -107,7 +107,7 @@ void OpenGLRenderer::Init(QOpenGLContext *existing_ctx) bool OpenGLRenderer::Init() { - QMutexLocker locker(&global_opengl_mutex); + GL_PREAMBLE; if (context_) { qCritical() << "Can't initialize already initialized OpenGLRenderer"; @@ -187,16 +187,11 @@ void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, doub } } -QVariant OpenGLRenderer::CreateNativeTexture2D(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) +QVariant OpenGLRenderer::CreateNativeTexture(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize) { GL_PREAMBLE; - return CreateNativeTexture2DInternal(width, height, format, channel_count, data, linesize); -} - -QVariant OpenGLRenderer::CreateNativeTexture3D(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize) -{ - GL_PREAMBLE; + bool is_3d = depth > 1; // Generate new texture GLuint texture; @@ -205,18 +200,27 @@ QVariant OpenGLRenderer::CreateNativeTexture3D(int width, int height, int depth, functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + GLenum target_current = is_3d ? GL_TEXTURE_BINDING_3D : GL_TEXTURE_BINDING_2D; + GLenum target = is_3d ? GL_TEXTURE_3D : GL_TEXTURE_2D; + GLint current_tex; - functions_->glGetIntegerv(GL_TEXTURE_BINDING_3D, ¤t_tex); + functions_->glGetIntegerv(target_current, ¤t_tex); - functions_->glBindTexture(GL_TEXTURE_3D, texture); + functions_->glBindTexture(target, texture); - context_->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GetInternalFormat(format, channel_count), - width, height, depth, 0, GetPixelFormat(channel_count), - GetPixelType(format), data); + if (is_3d) { + context_->extraFunctions()->glTexImage3D(target, 0, GetInternalFormat(format, channel_count), + width, height, depth, 0, GetPixelFormat(channel_count), + GetPixelType(format), data); + } else { + functions_->glTexImage2D(target, 0, GetInternalFormat(format, channel_count), + width, height, 0, GetPixelFormat(channel_count), + GetPixelType(format), data); + } functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - functions_->glBindTexture(GL_TEXTURE_3D, current_tex); + functions_->glBindTexture(target, current_tex); return texture; } @@ -294,8 +298,10 @@ void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int lin GLuint t = texture->id().value(); const VideoParams& p = texture->params(); - GLenum tex_type = texture->type() == Texture::k2D ? GL_TEXTURE_2D : GL_TEXTURE_3D; - GLenum tex_binding = texture->type() == Texture::k2D ? GL_TEXTURE_BINDING_2D : GL_TEXTURE_BINDING_3D; + bool is_3d = texture->params().is_3d(); + + GLenum tex_type = !is_3d ? GL_TEXTURE_2D : GL_TEXTURE_3D; + GLenum tex_binding = !is_3d ? GL_TEXTURE_BINDING_2D : GL_TEXTURE_BINDING_3D; // Store currently bound texture so it can be restored later GLint current_tex; @@ -308,7 +314,7 @@ void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int lin { PRINT_GL_ERRORS; - if (texture->type() == Texture::k2D) { + if (!is_3d) { functions_->glTexSubImage2D(tex_type, 0, 0, 0, p.effective_width(), p.effective_height(), GetPixelFormat(p.channel_count()), GetPixelType(p.format()), @@ -502,7 +508,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video functions_->glActiveTexture(GL_TEXTURE0 + i); - GLenum target = (texture && texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D; + GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D : GL_TEXTURE_2D; functions_->glBindTexture(target, tex_id); if (tex_id) { @@ -584,11 +590,11 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video TexturePtr output_tex, input_tex; if (real_iteration_count > 1) { // Create one texture to bounce off - output_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params); + output_tex = CreateTexture(destination_params); if (real_iteration_count > 2) { // Create a second texture bounce off - input_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params); + input_tex = CreateTexture(destination_params); } } @@ -648,7 +654,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video // Release any textures we bound before for (int i=textures_to_bind.size()-1; i>=0; i--) { TexturePtr texture = textures_to_bind.at(i).texture; - GLenum target = (texture && texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D; + GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D : GL_TEXTURE_2D; functions_->glActiveTexture(GL_TEXTURE0 + i); functions_->glBindTexture(target, 0); } @@ -788,38 +794,6 @@ void OpenGLRenderer::ClearDestinationInternal(double r, double g, double b, doub functions_->glClear(GL_COLOR_BUFFER_BIT); } -QVariant OpenGLRenderer::CreateNativeTexture2DInternal(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) -{ - GLuint texture; - functions_->glGenTextures(1, &texture); - texture_params_.insert(texture, {width, height, 1, format, channel_count}); - - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); - - GLint current_tex; - functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); - - functions_->glBindTexture(GL_TEXTURE_2D, texture); - - { - PRINT_GL_ERRORS; - functions_->glTexImage2D(GL_TEXTURE_2D, 0, GetInternalFormat(format, channel_count), - width, height, 0, GetPixelFormat(channel_count), - GetPixelType(format), data); - } - - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - functions_->glBindTexture(GL_TEXTURE_2D, current_tex); - - return texture; -} - -QVariant OpenGLRenderer::CreateNativeTexture2DInternal(const VideoParams ¶ms, const void *data, int linesize) -{ - return CreateNativeTexture2DInternal(params.effective_width(), params.effective_height(), params.format(), params.channel_count(), data, linesize); -} - GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code) { static const QString shader_preamble = diff --git a/app/render/opengl/openglrenderer.h b/app/render/opengl/openglrenderer.h index 2e51480b1..1377e475c 100644 --- a/app/render/opengl/openglrenderer.h +++ b/app/render/opengl/openglrenderer.h @@ -47,18 +47,10 @@ public: virtual void PostDestroy() override; -public slots: virtual void PostInit() override; - virtual void DestroyInternal() override; - virtual void ClearDestination(olive::Texture *texture = nullptr, double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0) override; - virtual QVariant CreateNativeTexture2D(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; - virtual QVariant CreateNativeTexture3D(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; - - virtual void DestroyNativeTexture(QVariant texture) override; - virtual QVariant CreateNativeShader(olive::ShaderCode code) override; virtual void DestroyNativeShader(QVariant shader) override; @@ -71,13 +63,19 @@ public slots: virtual Color GetPixelFromTexture(olive::Texture *texture, const QPointF &pt) override; -protected slots: +protected: virtual void Blit(QVariant shader, olive::ShaderJob job, olive::Texture* destination, olive::VideoParams destination_params, bool clear_destination) override; + virtual QVariant CreateNativeTexture(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; + + virtual void DestroyNativeTexture(QVariant texture) override; + + virtual void DestroyInternal() override; + private: static GLint GetInternalFormat(VideoParams::Format format, int channel_layout); @@ -93,9 +91,6 @@ private: void ClearDestinationInternal(double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0); - QVariant CreateNativeTexture2DInternal(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0); - QVariant CreateNativeTexture2DInternal(const VideoParams ¶ms, const void* data = nullptr, int linesize = 0); - GLuint CompileShader(GLenum type, const QString &code); QOpenGLContext* context_; diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index e174a4b35..39a0aaff0 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -20,36 +20,54 @@ #include "renderer.h" +#include +#include #include -#include "common/ocioutils.h" - namespace olive { Renderer::Renderer(QObject *parent) : QObject(parent) { - -} - -TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, Texture::Type type, const void *data, int linesize) -{ - QVariant v; - - if (type == Texture::k3D) { - v = CreateNativeTexture3D(params.effective_width(), params.effective_height(), - params.effective_depth(), params.format(), params.channel_count(), data, linesize); - } else { - v = CreateNativeTexture2D(params.effective_width(), params.effective_height(), params.format(), - params.channel_count(), data, linesize); - } - - return CreateTextureFromNativeHandle(v, params, type); + QTimer *texture_garbage_collector = new QTimer(this); + texture_garbage_collector->setInterval(MAX_TEXTURE_LIFE); + connect(texture_garbage_collector, &QTimer::timeout, this, &Renderer::ClearOldTextures); + texture_garbage_collector->start(); } TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, int linesize) { - return CreateTexture(params, Texture::k2D, data, linesize); + QVariant v; + + /*for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { + if (it->width == params.effective_width() + && it->height == params.effective_height() + && it->depth == params.effective_depth() + && it->format == params.format() + && it->channel_count == params.channel_count()) { + this->Flush(); + v = it->handle; + texture_cache_.erase(it); + break; + } + }*/ + + v = CreateNativeTexture(params.effective_width(), params.effective_height(), params.effective_depth(), + params.format(), params.channel_count(), data, linesize); + + return CreateTextureFromNativeHandle(v, params); +} + +void Renderer::DestroyTexture(Texture *texture) +{ + /*texture_cache_.push_back({texture->params().effective_width(), + texture->params().effective_height(), + texture->params().effective_depth(), + texture->params().format(), + texture->params().channel_count(), + texture->id(), + QDateTime::currentMSecsSinceEpoch()});*/ + DestroyNativeTexture(texture->id()); } TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms) @@ -83,6 +101,11 @@ QVariant Renderer::GetDefaultShader() void Renderer::Destroy() { + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { + DestroyNativeTexture(it->handle); + } + texture_cache_.clear(); + if (!default_shader_.isNull()) { DestroyNativeShader(default_shader_); default_shader_.clear(); @@ -98,13 +121,13 @@ void Renderer::Destroy() DestroyInternal(); } -TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms, Texture::Type type) +TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms) { if (v.isNull()) { return nullptr; } - return std::make_shared(this, v, params, type); + return std::make_shared(this, v, params); } bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::ColorContext *ctx) @@ -175,8 +198,7 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col } // Allocate 3D LUT - color_ctx.lut3d_textures[i].texture = CreateTexture(VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount), - Texture::k3D, values); + color_ctx.lut3d_textures[i].texture = CreateTexture(VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount), values); color_ctx.lut3d_textures[i].name = sampler_name; color_ctx.lut3d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; } @@ -206,9 +228,7 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col } // Allocate 1D LUT - color_ctx.lut1d_textures[i].texture = CreateTexture(VideoParams(width, height, VideoParams::kFormatFloat32, (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount), - Texture::k2D, - values); + color_ctx.lut1d_textures[i].texture = CreateTexture(VideoParams(width, height, VideoParams::kFormatFloat32, (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount), values); color_ctx.lut1d_textures[i].name = sampler_name; color_ctx.lut1d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; } @@ -219,6 +239,18 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col } } +void Renderer::ClearOldTextures() +{ + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); ) { + if (it->accessed < QDateTime::currentMSecsSinceEpoch() - MAX_TEXTURE_LIFE) { + DestroyNativeTexture(it->handle); + it = texture_cache_.erase(it); + } else { + it++; + } + } +} + void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *destination, const VideoParams ¶ms) { ColorContext color_ctx; diff --git a/app/render/renderer.h b/app/render/renderer.h index 839be32cf..dc1fb19a0 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -45,9 +45,10 @@ public: virtual bool Init() = 0; - TexturePtr CreateTexture(const VideoParams& params, Texture::Type type, const void* data = nullptr, int linesize = 0); TexturePtr CreateTexture(const VideoParams& params, const void *data = nullptr, int linesize = 0); + void DestroyTexture(Texture *texture); + void BlitToTexture(QVariant shader, olive::ShaderJob job, olive::Texture* destination, @@ -82,18 +83,10 @@ public: virtual void PostDestroy() = 0; -public slots: virtual void PostInit() = 0; - virtual void DestroyInternal() = 0; - virtual void ClearDestination(olive::Texture *texture = nullptr, double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0) = 0; - virtual QVariant CreateNativeTexture2D(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; - virtual QVariant CreateNativeTexture3D(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; - - virtual void DestroyNativeTexture(QVariant texture) = 0; - virtual QVariant CreateNativeShader(olive::ShaderCode code) = 0; virtual void DestroyNativeShader(QVariant shader) = 0; @@ -106,15 +99,18 @@ public slots: virtual Color GetPixelFromTexture(olive::Texture *texture, const QPointF &pt) = 0; -protected slots: +protected: virtual void Blit(QVariant shader, olive::ShaderJob job, olive::Texture* destination, olive::VideoParams destination_params, bool clear_destination) = 0; -protected: - TexturePtr CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms, Texture::Type type = Texture::k2D); + virtual QVariant CreateNativeTexture(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; + + virtual void DestroyNativeTexture(QVariant texture) = 0; + + virtual void DestroyInternal() = 0; private: struct ColorContext { @@ -130,16 +126,35 @@ private: }; + TexturePtr CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms); + bool GetColorContext(const ColorTransformJob &color_job, ColorContext* ctx); QHash color_cache_; + struct CachedTexture + { + int width; + int height; + int depth; + VideoParams::Format format; + int channel_count; + QVariant handle; + qint64 accessed; + }; + + const int MAX_TEXTURE_LIFE = 10000; + std::list texture_cache_; + QMutex color_cache_mutex_; QVariant default_shader_; QVariant interlace_texture_; +private slots: + void ClearOldTextures(); + }; } diff --git a/app/render/texture.cpp b/app/render/texture.cpp index 33973ddaf..f3b5fb9d0 100644 --- a/app/render/texture.cpp +++ b/app/render/texture.cpp @@ -29,7 +29,7 @@ const Texture::Interpolation Texture::kDefaultInterpolation = Texture::kMipmappe Texture::~Texture() { if (renderer_) { - renderer_->DestroyNativeTexture(id_); + renderer_->DestroyTexture(this); } } diff --git a/app/render/texture.h b/app/render/texture.h index 612b3e2f2..6179878a8 100644 --- a/app/render/texture.h +++ b/app/render/texture.h @@ -32,11 +32,6 @@ class Renderer; class Texture { public: - enum Type { - k2D, - k3D - }; - enum Interpolation { kNearest, kLinear, @@ -50,19 +45,17 @@ public: */ Texture(const VideoParams& param) : renderer_(nullptr), - params_(param), - type_(k2D) + params_(param) { } /** * @brief Construct a real texture linked to a renderer backend */ - Texture(Renderer* renderer, const QVariant& native, const VideoParams& param, Type type) : + Texture(Renderer* renderer, const QVariant& native, const VideoParams& param) : renderer_(renderer), params_(param), - id_(native), - type_(type) + id_(native) { } @@ -117,11 +110,6 @@ public: return params_.pixel_aspect_ratio(); } - Type type() const - { - return type_; - } - Renderer* renderer() const { return renderer_; @@ -134,8 +122,6 @@ private: QVariant id_; - Type type_; - }; using TexturePtr = std::shared_ptr; diff --git a/app/render/videoparams.h b/app/render/videoparams.h index f3cedd28b..28d8c29fe 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -121,6 +121,8 @@ public: calculate_effective_size(); } + bool is_3d() const { return depth_ > 1; } + const rational& time_base() const { return time_base_; From 07f63ddc17cf2128658385932b523cbf82b6e057 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:12:19 -0700 Subject: [PATCH 049/107] renderer: reinstated new and improved texture cache --- app/codec/ffmpeg/ffmpegdecoder.cpp | 33 +++------------- app/codec/ffmpeg/ffmpegdecoder.h | 4 -- app/render/opengl/openglrenderer.cpp | 25 ++++++------ app/render/opengl/openglrenderer.h | 6 +-- app/render/renderer.cpp | 57 ++++++++++++++++------------ app/render/renderer.h | 12 +++--- app/render/texture.cpp | 4 +- 7 files changed, 60 insertions(+), 81 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 9f66a9e3e..c2dbe2488 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -61,9 +61,6 @@ FFmpegDecoder::FFmpegDecoder() : input_fmt_(AV_PIX_FMT_NONE), native_internal_pix_fmt_(VideoParams::kFormatInvalid), native_output_pix_fmt_(VideoParams::kFormatInvalid), - y_tex_(nullptr), - u_tex_(nullptr), - v_tex_(nullptr), working_frame_(nullptr), working_packet_(nullptr), cache_at_zero_(false), @@ -219,12 +216,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration plane_params.set_channel_count(1); plane_params.set_divider(1); plane_params.set_format(native_internal_pix_fmt_); - - if (!y_tex_) { - y_tex_ = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); - } else { - y_tex_->Upload(f->data[0], f->linesize[0] / px_size); - } + TexturePtr y_plane = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); if (src_fmt == AV_PIX_FMT_YUV420P || src_fmt == AV_PIX_FMT_YUV422P @@ -244,22 +236,13 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration plane_params.set_height(plane_params.height()/2); } - if (!u_tex_) { - u_tex_ = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); - } else { - u_tex_->Upload(f->data[1], f->linesize[1] / px_size); - } - - if (!v_tex_) { - v_tex_ = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); - } else { - v_tex_->Upload(f->data[2], f->linesize[2] / px_size); - } + TexturePtr u_plane = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); + TexturePtr v_plane = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); ShaderJob job; - job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_tex_))); - job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_tex_))); - job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_tex_))); + job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); + job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); + job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); job.Insert(QStringLiteral("jpeg_range"), NodeValue(NodeValue::kBoolean, jpeg_range)); @@ -313,10 +296,6 @@ void FFmpegDecoder::CloseInternal() input_fmt_ = AV_PIX_FMT_NONE; native_internal_pix_fmt_ = VideoParams::kFormatInvalid; native_output_pix_fmt_ = VideoParams::kFormatInvalid; - - y_tex_ = nullptr; - u_tex_ = nullptr; - v_tex_ = nullptr; } QString FFmpegDecoder::id() const diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 837d5e04c..1c0d25bb8 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -165,10 +165,6 @@ private: VideoParams::Format native_output_pix_fmt_; int native_channel_count_; - TexturePtr y_tex_; - TexturePtr u_tex_; - TexturePtr v_tex_; - AVFrame *working_frame_; AVPacket *working_packet_; diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index a06c44ffd..014c124bc 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -177,7 +177,7 @@ void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, doub GL_PREAMBLE; if (texture) { - AttachTextureAsDestination(texture); + AttachTextureAsDestination(texture->id()); } ClearDestinationInternal(r, g, b, a); @@ -225,7 +225,7 @@ QVariant OpenGLRenderer::CreateNativeTexture(int width, int height, int depth, V return texture; } -void OpenGLRenderer::AttachTextureAsDestination(Texture* texture) +void OpenGLRenderer::AttachTextureAsDestination(const QVariant &texture) { PRINT_GL_ERRORS; @@ -233,7 +233,7 @@ void OpenGLRenderer::AttachTextureAsDestination(Texture* texture) functions_->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - texture->id().value(), + texture.value(), 0); } @@ -291,14 +291,13 @@ void OpenGLRenderer::DestroyNativeShader(QVariant shader) functions_->glDeleteProgram(program); } -void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int linesize) +void OpenGLRenderer::UploadToTexture(const QVariant &handle, const VideoParams &p, const void *data, int linesize) { GL_PREAMBLE; - GLuint t = texture->id().value(); - const VideoParams& p = texture->params(); + GLuint t = handle.value(); - bool is_3d = texture->params().is_3d(); + bool is_3d = p.is_3d(); GLenum tex_type = !is_3d ? GL_TEXTURE_2D : GL_TEXTURE_3D; GLenum tex_binding = !is_3d ? GL_TEXTURE_BINDING_2D : GL_TEXTURE_BINDING_3D; @@ -332,16 +331,14 @@ void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int lin functions_->glBindTexture(tex_type, current_tex); } -void OpenGLRenderer::DownloadFromTexture(Texture* texture, void *data, int linesize) +void OpenGLRenderer::DownloadFromTexture(const QVariant &id, const VideoParams &p, void *data, int linesize) { GL_PREAMBLE; - const VideoParams& p = texture->params(); - GLint current_tex; functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); - AttachTextureAsDestination(texture); + AttachTextureAsDestination(id); functions_->glPixelStorei(GL_PACK_ROW_LENGTH, linesize); @@ -372,7 +369,7 @@ void OpenGLRenderer::Flush() Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt) { - AttachTextureAsDestination(texture); + AttachTextureAsDestination(texture->id()); QByteArray data(VideoParams::GetBytesPerPixel(texture->format(), texture->channel_count()), Qt::Uninitialized); @@ -610,7 +607,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video // This is the last iteration, draw to the destination if (destination) { // If we have a destination texture, draw to it - AttachTextureAsDestination(destination); + AttachTextureAsDestination(destination->id()); } else if (iteration > 0) { // Otherwise, if we were iterating before, detach texture now DetachTextureAsDestination(); @@ -622,7 +619,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video } } else { // Always draw to output_tex, which gets swapped with input_tex every iteration - AttachTextureAsDestination(output_tex.get()); + AttachTextureAsDestination(output_tex->id()); } if (iteration > 0) { diff --git a/app/render/opengl/openglrenderer.h b/app/render/opengl/openglrenderer.h index 1377e475c..fe7f0098a 100644 --- a/app/render/opengl/openglrenderer.h +++ b/app/render/opengl/openglrenderer.h @@ -55,9 +55,9 @@ public: virtual void DestroyNativeShader(QVariant shader) override; - virtual void UploadToTexture(olive::Texture* texture, const void* data, int linesize) override; + virtual void UploadToTexture(const QVariant &handle, const VideoParams ¶ms, const void* data, int linesize) override; - virtual void DownloadFromTexture(olive::Texture* texture, void* data, int linesize) override; + virtual void DownloadFromTexture(const QVariant &handle, const VideoParams ¶ms, void* data, int linesize) override; virtual void Flush() override; @@ -83,7 +83,7 @@ private: static GLenum GetPixelFormat(int channel_count); - void AttachTextureAsDestination(olive::Texture* texture); + void AttachTextureAsDestination(const QVariant &texture); void DetachTextureAsDestination(); diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 39a0aaff0..98566a244 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -29,45 +29,52 @@ namespace olive { Renderer::Renderer(QObject *parent) : QObject(parent) { - QTimer *texture_garbage_collector = new QTimer(this); - texture_garbage_collector->setInterval(MAX_TEXTURE_LIFE); - connect(texture_garbage_collector, &QTimer::timeout, this, &Renderer::ClearOldTextures); - texture_garbage_collector->start(); } TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, int linesize) { QVariant v; - /*for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { - if (it->width == params.effective_width() - && it->height == params.effective_height() - && it->depth == params.effective_depth() - && it->format == params.format() - && it->channel_count == params.channel_count()) { - this->Flush(); - v = it->handle; - texture_cache_.erase(it); - break; + if (USE_TEXTURE_CACHE) { + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { + if (it->width == params.effective_width() + && it->height == params.effective_height() + && it->depth == params.effective_depth() + && it->format == params.format() + && it->channel_count == params.channel_count()) { + this->Flush(); + v = it->handle; + texture_cache_.erase(it); + break; + } } - }*/ + } - v = CreateNativeTexture(params.effective_width(), params.effective_height(), params.effective_depth(), - params.format(), params.channel_count(), data, linesize); + if (v.isNull()) { + v = CreateNativeTexture(params.effective_width(), params.effective_height(), params.effective_depth(), + params.format(), params.channel_count(), data, linesize); + } else { + UploadToTexture(v, params, data, linesize); + } return CreateTextureFromNativeHandle(v, params); } void Renderer::DestroyTexture(Texture *texture) { - /*texture_cache_.push_back({texture->params().effective_width(), - texture->params().effective_height(), - texture->params().effective_depth(), - texture->params().format(), - texture->params().channel_count(), - texture->id(), - QDateTime::currentMSecsSinceEpoch()});*/ - DestroyNativeTexture(texture->id()); + if (USE_TEXTURE_CACHE) { + texture_cache_.push_back({texture->params().effective_width(), + texture->params().effective_height(), + texture->params().effective_depth(), + texture->params().format(), + texture->params().channel_count(), + texture->id(), + QDateTime::currentMSecsSinceEpoch()}); + + ClearOldTextures(); + } else { + DestroyNativeTexture(texture->id()); + } } TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms) diff --git a/app/render/renderer.h b/app/render/renderer.h index dc1fb19a0..96141df4e 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -91,9 +91,9 @@ public: virtual void DestroyNativeShader(QVariant shader) = 0; - virtual void UploadToTexture(olive::Texture* texture, const void* data, int linesize) = 0; + virtual void UploadToTexture(const QVariant &handle, const VideoParams ¶ms, const void* data, int linesize) = 0; - virtual void DownloadFromTexture(olive::Texture* texture, void* data, int linesize) = 0; + virtual void DownloadFromTexture(const QVariant &handle, const VideoParams ¶ms, void* data, int linesize) = 0; virtual void Flush() = 0; @@ -130,6 +130,8 @@ private: bool GetColorContext(const ColorTransformJob &color_job, ColorContext* ctx); + void ClearOldTextures(); + QHash color_cache_; struct CachedTexture @@ -143,7 +145,8 @@ private: qint64 accessed; }; - const int MAX_TEXTURE_LIFE = 10000; + static const int MAX_TEXTURE_LIFE = 5000; + static const bool USE_TEXTURE_CACHE = true; std::list texture_cache_; QMutex color_cache_mutex_; @@ -152,9 +155,6 @@ private: QVariant interlace_texture_; -private slots: - void ClearOldTextures(); - }; } diff --git a/app/render/texture.cpp b/app/render/texture.cpp index f3b5fb9d0..2c5c9fdea 100644 --- a/app/render/texture.cpp +++ b/app/render/texture.cpp @@ -36,14 +36,14 @@ Texture::~Texture() void Texture::Upload(void *data, int linesize) { if (renderer_) { - renderer_->UploadToTexture(this, data, linesize); + renderer_->UploadToTexture(this->id(), this->params(), data, linesize); } } void Texture::Download(void *data, int linesize) { if (renderer_) { - renderer_->DownloadFromTexture(this, data, linesize); + renderer_->DownloadFromTexture(this->id(), this->params(), data, linesize); } } From e067acb9993c1bcc25502fa5a98226ad137ddb85 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:14:46 -0700 Subject: [PATCH 050/107] renderprocessor: commit line that github desktop undid for no reason --- app/render/renderprocessor.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index c8617e095..e0219cbb1 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -45,6 +45,9 @@ RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, D TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational &frame_length) { + QElapsedTimer t; + t.restart(); + TimeRange range = TimeRange(time, time + frame_length); NodeValueTable table; @@ -56,6 +59,8 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational ResolveJobs(tex_val, range); + qDebug() << "Frame took:" << t.elapsed(); + return tex_val.toTexture(); } @@ -107,6 +112,8 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time job.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); job.SetTransformMatrix(matrix); + qDebug() << "Blitting with" << output_color_transform.get(); + render_ctx_->BlitColorManaged(job, blit_tex.get()); } else { // No color transform, just blit @@ -123,7 +130,7 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time render_ctx_->Flush(); - render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels()); + render_ctx_->DownloadFromTexture(texture->id(), texture->params(), frame->data(), frame->linesize_pixels()); } return frame; From b0446e237befeeb0fd088d140b9980648b5af88a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:37:44 -0700 Subject: [PATCH 051/107] render: how the fuck did it do this? --- app/render/renderprocessor.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index e0219cbb1..08a4fbdbc 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -45,9 +45,6 @@ RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, D TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational &frame_length) { - QElapsedTimer t; - t.restart(); - TimeRange range = TimeRange(time, time + frame_length); NodeValueTable table; @@ -59,8 +56,6 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational ResolveJobs(tex_val, range); - qDebug() << "Frame took:" << t.elapsed(); - return tex_val.toTexture(); } @@ -112,8 +107,6 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time job.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); job.SetTransformMatrix(matrix); - qDebug() << "Blitting with" << output_color_transform.get(); - render_ctx_->BlitColorManaged(job, blit_tex.get()); } else { // No color transform, just blit From 3caa5352f84fc02dd02cf1df04c7c6413a4e7d3b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:37:56 -0700 Subject: [PATCH 052/107] render: only upload to texture if data was passed --- app/render/renderer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 98566a244..37294759a 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -42,7 +42,6 @@ TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, && it->depth == params.effective_depth() && it->format == params.format() && it->channel_count == params.channel_count()) { - this->Flush(); v = it->handle; texture_cache_.erase(it); break; @@ -53,8 +52,10 @@ TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, if (v.isNull()) { v = CreateNativeTexture(params.effective_width(), params.effective_height(), params.effective_depth(), params.format(), params.channel_count(), data, linesize); - } else { + } else if (data) { UploadToTexture(v, params, data, linesize); + } else { + this->Flush(); } return CreateTextureFromNativeHandle(v, params); From f7b7f515814f6c4363d0fcb0b49ff099bfcd7d0c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:39:33 -0700 Subject: [PATCH 053/107] manageddisplaywidget: switch back to QOpenGLWindow --- app/widget/manageddisplay/manageddisplay.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 644ab0711..10e805d77 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -21,7 +21,7 @@ #ifndef MANAGEDDISPLAYOBJECT_H #define MANAGEDDISPLAYOBJECT_H -//#define USE_QOPENGLWINDOW +#define USE_QOPENGLWINDOW #include #include From d05146170edde593101a6ccb5d5a9f79e48e2ca3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:50:42 -0700 Subject: [PATCH 054/107] viewer: increase playback interval --- app/widget/viewer/viewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 2a9135780..1b023cefb 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -60,7 +60,7 @@ QVector ViewerWidget::instances_; // changing values. 1/4 second seems to be a good middleground. const rational ViewerWidget::kAudioPlaybackInterval = rational(1, 4); -const rational kVideoPlaybackInterval = rational(1, 2); +const rational kVideoPlaybackInterval = rational(2); ViewerWidget::ViewerWidget(QWidget *parent) : super(false, true, parent), From f6dc3c7fcc7a630b79e4d969fc636266c808ee3e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 11:04:24 -0700 Subject: [PATCH 055/107] node: implement transform parenting --- .../transform/transformdistortnode.cpp | 32 ++++++++++--------- .../distort/transform/transformdistortnode.h | 1 + app/node/generator/matrix/matrix.cpp | 12 +++---- app/node/generator/matrix/matrix.h | 5 +-- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index 24d199811..c839c93f0 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -22,12 +22,9 @@ #include -#include "common/range.h" -#include "core.h" -#include "node/traverser.h" - namespace olive { +const QString TransformDistortNode::kParentInput = QStringLiteral("parent_in"); const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in"); const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in"); const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in"); @@ -36,6 +33,8 @@ const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interp TransformDistortNode::TransformDistortNode() { + AddInput(kParentInput, NodeValue::kMatrix); + AddInput(kAutoscaleInput, NodeValue::kCombo, 0); AddInput(kInterpolationInput, NodeValue::kCombo, 2); @@ -73,6 +72,7 @@ void TransformDistortNode::Retranslate() { super::Retranslate(); + SetInputName(kParentInput, tr("Parent")); SetInputName(kAutoscaleInput, tr("Auto-Scale")); SetInputName(kTextureInput, tr("Texture")); SetInputName(kInterpolationInput, tr("Interpolation")); @@ -84,12 +84,12 @@ void TransformDistortNode::Retranslate() void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // Generate matrix - QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false); + QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false, value[kParentInput].toMatrix()); // Pop texture NodeValue texture_meta = value[kTextureInput]; - bool pushed_job = false; + QVariant job_to_push; // If we have a texture, generate a matrix and make it happen if (TexturePtr texture = texture_meta.toTexture()) { @@ -103,15 +103,17 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this)); job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast(value[kInterpolationInput].toInt())); - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); - - pushed_job = true; + job_to_push = QVariant::fromValue(job); } } - if (!pushed_job) { + table->Push(NodeValue::kMatrix, QVariant::fromValue(generated_matrix), this); + + if (job_to_push.isNull()) { // Re-push whatever value we received table->Push(texture_meta); + } else { + table->Push(NodeValue::kTexture, job_to_push, this); } } @@ -129,7 +131,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou if (gizmo == anchor_gizmo_) { - gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false, row[kParentInput].toMatrix()).toTransform().inverted(); } else if (IsAScaleGizmo(gizmo)) { @@ -171,7 +173,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } // Store current matrix - gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true, row[kParentInput].toMatrix()).toTransform().inverted(); } else if (gizmo == rotation_gizmo_) { @@ -356,7 +358,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N // Fold values into a matrix for the rectangle QMatrix4x4 rectangle_matrix; rectangle_matrix.scale(sequence_half_res); - rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false), + rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()), sequence_res, tex_sz, tex_offset, @@ -376,7 +378,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N // Draw anchor point QMatrix4x4 anchor_matrix; anchor_matrix.scale(sequence_half_res); - anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false), + anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false, row[kParentInput].toMatrix()), sequence_res, tex_sz, tex_offset, @@ -402,7 +404,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N QTransform TransformDistortNode::GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const { if (TexturePtr texture = row[kTextureInput].toTexture()) { - auto m = GenerateMatrix(row, false, false, false); + auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()); return GenerateAutoScaledMatrix(m, row, globals, texture->params()).toTransform(); } return super::GizmoTransformation(row, globals); diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index b6ae714b9..be17de561 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -84,6 +84,7 @@ public: virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const override; + static const QString kParentInput; static const QString kTextureInput; static const QString kAutoscaleInput; static const QString kInterpolationInput; diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index 81e43c4ea..81dbdb8f6 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -90,11 +90,11 @@ void MatrixGenerator::Retranslate() void MatrixGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // Push matrix output - QMatrix4x4 mat = GenerateMatrix(value, false, false, false); + QMatrix4x4 mat = GenerateMatrix(value, false, false, false, QMatrix4x4()); table->Push(NodeValue::kMatrix, mat, this); } -QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const +QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const { QVector2D anchor; QVector2D position; @@ -116,17 +116,17 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignor value[kRotationInput].toDouble(), scale, value[kUniformScaleInput].toBool(), - anchor); + anchor, + mat); } QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos, const float& rot, const QVector2D& scale, bool uniform_scale, - const QVector2D& anchor) + const QVector2D& anchor, + QMatrix4x4 mat) { - QMatrix4x4 mat; - // Position mat.translate(pos); diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index 5d93cb51d..60ea1a035 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -53,12 +53,13 @@ public: static const QString kAnchorInput; protected: - QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const; + QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const; static QMatrix4x4 GenerateMatrix(const QVector2D &pos, const float &rot, const QVector2D &scale, bool uniform_scale, - const QVector2D &anchor); + const QVector2D &anchor, + QMatrix4x4 mat); virtual void InputValueChangedEvent(const QString& input, int element) override; From 15a67bc12af044f109270ba02ac0e8fcdc2d1e67 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 11:39:35 -0700 Subject: [PATCH 056/107] project: auto-select imported files --- app/core.cpp | 2 ++ app/node/project/folder/folder.cpp | 15 ++++++++++++++ app/node/project/folder/folder.h | 2 ++ app/panel/project/project.h | 4 ++-- .../projectexplorer/projectexplorer.cpp | 6 ++++-- app/widget/projectexplorer/projectexplorer.h | 2 +- app/window/mainwindow/mainwindow.cpp | 20 +++++++++++++++++++ app/window/mainwindow/mainwindow.h | 4 ++++ 8 files changed, 50 insertions(+), 5 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index c9b701ead..d6989319a 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -586,6 +586,8 @@ void Core::ImportTaskComplete(Task* task) } undo_stack_.pushIfHasChildren(command); + + main_window_->SelectFootage(import_task->GetImportedFootage()); } bool Core::ConfirmImageSequence(const QString& filename) diff --git a/app/node/project/folder/folder.cpp b/app/node/project/folder/folder.cpp index 3f8ffe700..4bcd7cabc 100644 --- a/app/node/project/folder/folder.cpp +++ b/app/node/project/folder/folder.cpp @@ -73,6 +73,21 @@ bool Folder::ChildExistsWithName(const QString &s) const return ChildExistsWithNameInternal(this, s); } +bool Folder::HasChildRecursive(Node *child) const +{ + for (Node *i : item_children_) { + if (i == child) { + return true; + } else if (Folder *f = dynamic_cast(i)) { + if (f->HasChildRecursive(child)) { + return true; + } + } + } + + return false; +} + int Folder::index_of_child_in_array(Node *item) const { int index_of_item = item_children_.indexOf(item); diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index 7274960f1..22d25f110 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -65,6 +65,8 @@ public: bool ChildExistsWithName(const QString& s) const; + bool HasChildRecursive(Node *child) const; + int item_child_count() const { return item_children_.size(); diff --git a/app/panel/project/project.h b/app/panel/project/project.h index 39f6299ee..d4918b7b3 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -52,9 +52,9 @@ public: ProjectViewModel* model() const; - bool SelectItem(Node *n) + bool SelectItem(Node *n, bool deselect_all_first = true) { - return explorer_->SelectItem(n); + return explorer_->SelectItem(n, deselect_all_first); } virtual void SelectAll() override; diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 05c2b5e38..1a3cc36a4 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -653,9 +653,11 @@ void ProjectExplorer::DeleteSelected() } } -bool ProjectExplorer::SelectItem(Node *n) +bool ProjectExplorer::SelectItem(Node *n, bool deselect_all_first) { - DeselectAll(); + if (deselect_all_first) { + DeselectAll(); + } QModelIndex index = model_.CreateIndexFromItem(n); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 086c2eab3..d69f1b8e3 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -85,7 +85,7 @@ public: void DeleteSelected(); - bool SelectItem(Node *n); + bool SelectItem(Node *n, bool deselect_all_first = true); public slots: void set_view_type(ProjectToolbar::ViewType type); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index e7f017a69..39d943eea 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -410,6 +410,16 @@ void MainWindow::SetApplicationProgressValue(int value) #endif } +void MainWindow::SelectFootage(const QVector &e) +{ + for (ProjectPanel *p : project_panels_) { + SelectFootageForProjectPanel(e, p); + } + for (ProjectPanel *p : folder_panels_) { + SelectFootageForProjectPanel(e, p); + } +} + void MainWindow::closeEvent(QCloseEvent *e) { // Try to close all projects (this will return false if the user chooses not to close) @@ -759,6 +769,16 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel) param_panel_->SetContexts(context); } +void MainWindow::SelectFootageForProjectPanel(const QVector &e, ProjectPanel *p) +{ + p->DeselectAll(); + for (Footage *f : e) { + if (p->get_root()->HasChildRecursive(f)) { + p->SelectItem(f, false); + } + } +} + void MainWindow::FocusedPanelChanged(PanelWidget *panel) { // Update audio monitor panel diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 20498b684..1f6504c6f 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -92,6 +92,8 @@ public: */ void SetApplicationProgressValue(int value); + void SelectFootage(const QVector &e); + public slots: void ProjectOpen(Project *p); @@ -142,6 +144,8 @@ private: void UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel); + void SelectFootageForProjectPanel(const QVector &e, ProjectPanel *p); + QByteArray premaximized_state_; // Standard panels From 9fa1ce00e5a10ed34ed10f18de5d02a6260f94ae Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 26 Jul 2022 20:19:23 +0100 Subject: [PATCH 057/107] Add basic nclc tag support Sets nclc tags based on the selected output color space. Also adds a Rec.709 OETF to the config --- app/codec/encoder.h | 3 + app/codec/ffmpeg/ffmpegencoder.cpp | 18 + app/render/ocioconf/config.ocio | 14 + .../ocioconf/luts/rec709_to_linear.spi1d | 4102 +++++++++++++++++ app/task/export/exportparams.cpp | 12 +- app/task/export/exportparams.h | 5 - 6 files changed, 4138 insertions(+), 16 deletions(-) create mode 100644 app/render/ocioconf/luts/rec709_to_linear.spi1d diff --git a/app/codec/encoder.h b/app/codec/encoder.h index e4c6db587..c77c6e1f2 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -76,6 +76,7 @@ public: void set_video_pix_fmt(const QString& s) { video_pix_fmt_ = s; } void set_video_is_image_sequence(bool s) { video_is_image_sequence_ = s; } void set_video_color_range(YUVRange r) { video_color_range_ = r; } + void set_color_transform(const ColorTransform& color_transform) { color_transform_ = color_transform; } const QString& filename() const { return filename_; } @@ -91,6 +92,7 @@ public: const QString& video_pix_fmt() const { return video_pix_fmt_; } bool video_is_image_sequence() const { return video_is_image_sequence_; } YUVRange video_color_range() const { return video_color_range_; } + const ColorTransform& color_transform() const { return color_transform_; } bool audio_enabled() const { return audio_enabled_; } const ExportCodec::Codec &audio_codec() const { return audio_codec_; } @@ -125,6 +127,7 @@ private: QString video_pix_fmt_; bool video_is_image_sequence_; YUVRange video_color_range_; + ColorTransform color_transform_; bool audio_enabled_; ExportCodec::Codec audio_codec_; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 03d640fc1..6103345ba 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -220,6 +220,10 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) encoded_frame->height = frame->height(); encoded_frame->format = video_codec_ctx_->pix_fmt; encoded_frame->color_range = video_codec_ctx_->color_range; + encoded_frame->color_trc = video_codec_ctx_->color_trc; + encoded_frame->color_primaries = video_codec_ctx_->color_primaries; + encoded_frame->colorspace = video_codec_ctx_->colorspace; + // Set interlacing if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) { @@ -647,6 +651,20 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV if (params().video_buffer_size() > 0) { codec_ctx->rc_buffer_size = static_cast(params().video_buffer_size()); } + + if (params().format() == ExportFormat::Format::kFormatQuickTime) { + // nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7 + if (params().color_transform().output().contains("sRGB")) { + codec_ctx->color_primaries = AVCOL_PRI_BT709; + codec_ctx->color_trc = AVCOL_TRC_BT709; + codec_ctx->colorspace = AVCOL_SPC_BT709; + } else { // Assume Rec.709 + codec_ctx->color_primaries = AVCOL_PRI_BT709; + codec_ctx->color_trc = AVCOL_TRC_BT709; + codec_ctx->colorspace = AVCOL_SPC_BT709; + } + + } } } else if (type == AVMEDIA_TYPE_AUDIO) { diff --git a/app/render/ocioconf/config.ocio b/app/render/ocioconf/config.ocio index dcf75c465..9d251ab8b 100755 --- a/app/render/ocioconf/config.ocio +++ b/app/render/ocioconf/config.ocio @@ -281,6 +281,20 @@ colorspaces: - ! {matrix: [0.606530, 0.220408, 0.123479, 0, 0.267989, 0.832731, -0.100720, 0, -0.029442, -0.086611, 1.204861, 0, 0, 0, 0, 1]} - ! {src: CIE-XYZ D65, dst: reference} + - ! + name: Rec.709 OETF + family: Camera Footage + equalitygroup: "" + bitdepth: 32f + description: | + Rec.709 OETF + isdata: false + allocation: uniform + allocationvars: [0, 1] + to_reference: ! + children: + - ! {src: rec709_to_linear.spi1d, interpolation: linear} + - ! name: Non-Colour Data family: diff --git a/app/render/ocioconf/luts/rec709_to_linear.spi1d b/app/render/ocioconf/luts/rec709_to_linear.spi1d new file mode 100644 index 000000000..28bd19666 --- /dev/null +++ b/app/render/ocioconf/luts/rec709_to_linear.spi1d @@ -0,0 +1,4102 @@ +Version 1 +From 0.000000 1.000000 +Length 4096 +Components 1 +{ + 0.0 + 5.42667221453e-05 + 0.000108533444291 + 0.000162800162798 + 0.000217066888581 + 0.000271333614364 + 0.000325600325596 + 0.000379867036827 + 0.000434133777162 + 0.000488400517497 + 0.000542667228729 + 0.00059693393996 + 0.000651200651191 + 0.000705467362422 + 0.000759734073654 + 0.000814000843093 + 0.000868267554324 + 0.000922534265555 + 0.000976801034994 + 0.00103106768802 + 0.00108533445746 + 0.00113960111048 + 0.00119386787992 + 0.00124813453294 + 0.00130240130238 + 0.00135666807182 + 0.00141093472484 + 0.00146520149428 + 0.00151946814731 + 0.00157373491675 + 0.00162800168619 + 0.00168226833921 + 0.00173653510865 + 0.00179080176167 + 0.00184506853111 + 0.00189933518413 + 0.00195360206999 + 0.0020078686066 + 0.00206213537604 + 0.00211640214548 + 0.00217066891491 + 0.00222493545152 + 0.00227920222096 + 0.0023334689904 + 0.00238773575984 + 0.00244200252928 + 0.00249626906589 + 0.00255053583533 + 0.00260480260476 + 0.0026590693742 + 0.00271333614364 + 0.00276760268025 + 0.00282186944969 + 0.00287613621913 + 0.00293040298857 + 0.00298466975801 + 0.00303893629462 + 0.00309320306405 + 0.00314746983349 + 0.00320173660293 + 0.00325600337237 + 0.00331026990898 + 0.00336453667842 + 0.00341880344786 + 0.0034730702173 + 0.0035273367539 + 0.00358160352334 + 0.00363587029278 + 0.00369013706222 + 0.00374440383166 + 0.00379867036827 + 0.00385293713771 + 0.00390720413998 + 0.00396147044376 + 0.00401573721319 + 0.00407000398263 + 0.00412427075207 + 0.00417853752151 + 0.00423280429095 + 0.00428707106039 + 0.00434133782983 + 0.00439560459927 + 0.00444987090304 + 0.00450413767248 + 0.00455840444192 + 0.00461267121136 + 0.0046669379808 + 0.00472120475024 + 0.00477547151968 + 0.00482973828912 + 0.00488400505856 + 0.004938271828 + 0.00499253813177 + 0.00504680490121 + 0.00510107167065 + 0.00515533844009 + 0.00520960520953 + 0.00526387197897 + 0.00531813874841 + 0.00537240551785 + 0.00542667228729 + 0.00548093859106 + 0.0055352053605 + 0.00558947212994 + 0.00564373889938 + 0.00569800566882 + 0.00575227243826 + 0.0058065392077 + 0.00586080597714 + 0.00591507274657 + 0.00596933951601 + 0.00602360581979 + 0.00607787258923 + 0.00613213935867 + 0.00618640612811 + 0.00624067289755 + 0.00629493966699 + 0.00634920643643 + 0.00640347320586 + 0.0064577399753 + 0.00651200674474 + 0.00656627304852 + 0.00662053981796 + 0.0066748065874 + 0.00672907335684 + 0.00678334012628 + 0.00683760689571 + 0.00689187366515 + 0.00694614043459 + 0.00700040720403 + 0.00705467350781 + 0.00710894027725 + 0.00716320704669 + 0.00721747381613 + 0.00727174058557 + 0.007326007355 + 0.00738027412444 + 0.00743454089388 + 0.00748880766332 + 0.00754307443276 + 0.00759734073654 + 0.00765160750598 + 0.00770587427542 + 0.00776014104486 + 0.00781440827996 + 0.00786867458373 + 0.00792294088751 + 0.00797720812261 + 0.00803147442639 + 0.00808574166149 + 0.00814000796527 + 0.00819427520037 + 0.00824854150414 + 0.00830280873924 + 0.00835707504302 + 0.0084113413468 + 0.0084656085819 + 0.00851987488568 + 0.00857414212078 + 0.00862840842456 + 0.00868267565966 + 0.00873694196343 + 0.00879120919853 + 0.00884547550231 + 0.00889974180609 + 0.00895400904119 + 0.00900827534497 + 0.00906254258007 + 0.00911680888385 + 0.00917107611895 + 0.00922534242272 + 0.00927960965782 + 0.0093338759616 + 0.00938814226538 + 0.00944240950048 + 0.00949667580426 + 0.00955094303936 + 0.00960520934314 + 0.00965947657824 + 0.00971374288201 + 0.00976801011711 + 0.00982227642089 + 0.00987654365599 + 0.00993080995977 + 0.00998507626355 + 0.0100393434986 + 0.0100936098024 + 0.0101478770375 + 0.0102021433413 + 0.0102564105764 + 0.0103106768802 + 0.0103649441153 + 0.0104192104191 + 0.0104734767228 + 0.0105277439579 + 0.0105820102617 + 0.0106362774968 + 0.0106905438006 + 0.0107448110357 + 0.0107990773395 + 0.0108533445746 + 0.0109076108783 + 0.0109618771821 + 0.0110161444172 + 0.011070410721 + 0.0111246779561 + 0.0111789442599 + 0.011233211495 + 0.0112874777988 + 0.0113417450339 + 0.0113960113376 + 0.0114502785727 + 0.0115045448765 + 0.0115588111803 + 0.0116130784154 + 0.0116673447192 + 0.0117216119543 + 0.011775878258 + 0.0118301454931 + 0.0118844117969 + 0.011938679032 + 0.0119929453358 + 0.0120472116396 + 0.0121014788747 + 0.0121557451785 + 0.0122100124136 + 0.0122642787173 + 0.0123185459524 + 0.0123728122562 + 0.0124270794913 + 0.0124813457951 + 0.0125356120989 + 0.012589879334 + 0.0126441456378 + 0.0126984128729 + 0.0127526791766 + 0.0128069464117 + 0.0128612127155 + 0.0129154799506 + 0.0129697462544 + 0.0130240134895 + 0.0130782797933 + 0.013132546097 + 0.0131868133321 + 0.0132410796359 + 0.013295346871 + 0.0133496131748 + 0.0134038804099 + 0.0134581467137 + 0.0135124139488 + 0.0135666802526 + 0.0136209465563 + 0.0136752137914 + 0.0137294800952 + 0.0137837473303 + 0.0138380136341 + 0.0138922808692 + 0.013946547173 + 0.0140008144081 + 0.0140550807118 + 0.0141093470156 + 0.0141636142507 + 0.0142178805545 + 0.0142721477896 + 0.0143264140934 + 0.0143806813285 + 0.0144349476323 + 0.0144892148674 + 0.0145434811711 + 0.0145977474749 + 0.01465201471 + 0.0147062810138 + 0.0147605482489 + 0.0148148145527 + 0.0148690817878 + 0.0149233480915 + 0.0149776153266 + 0.0150318816304 + 0.0150861488655 + 0.0151404151693 + 0.0151946814731 + 0.0152489487082 + 0.015303215012 + 0.0153574822471 + 0.0154117485508 + 0.0154660157859 + 0.0155202820897 + 0.0155745493248 + 0.0156288165599 + 0.0156830828637 + 0.0157373491675 + 0.0157916154712 + 0.015845881775 + 0.0159001499414 + 0.0159544162452 + 0.016008682549 + 0.0160629488528 + 0.0161172170192 + 0.016171483323 + 0.0162257496268 + 0.0162800159305 + 0.0163342822343 + 0.0163885504007 + 0.0164428167045 + 0.0164970830083 + 0.0165513493121 + 0.0166056174785 + 0.0166598837823 + 0.016714150086 + 0.0167684163898 + 0.0168226826936 + 0.01687695086 + 0.0169312171638 + 0.0169854834676 + 0.0170397497714 + 0.0170940179378 + 0.0171482842416 + 0.0172025505453 + 0.0172568168491 + 0.0173110831529 + 0.0173653513193 + 0.0174196176231 + 0.0174738839269 + 0.0175281502306 + 0.0175824183971 + 0.0176366847008 + 0.0176909510046 + 0.0177452173084 + 0.0177994836122 + 0.0178537517786 + 0.0179080180824 + 0.0179622843862 + 0.0179615281522 + 0.0180157013237 + 0.0180699639022 + 0.0181243177503 + 0.0181787591428 + 0.0182332918048 + 0.0182879138738 + 0.0183426272124 + 0.0183974280953 + 0.0184523202479 + 0.0185073018074 + 0.0185623746365 + 0.01861753501 + 0.0186727885157 + 0.0187281295657 + 0.0187835618854 + 0.018839083612 + 0.0188946966082 + 0.0189503990114 + 0.0190061908215 + 0.0190620739013 + 0.019118046388 + 0.0191741101444 + 0.0192302651703 + 0.0192865077406 + 0.0193428434432 + 0.0193992685527 + 0.0194557830691 + 0.0195123888552 + 0.0195690840483 + 0.0196258705109 + 0.0196827482432 + 0.0197397153825 + 0.0197967737913 + 0.0198539234698 + 0.0199111625552 + 0.0199684929103 + 0.0200259126723 + 0.0200834255666 + 0.0201410278678 + 0.020198719576 + 0.0202565044165 + 0.0203143786639 + 0.0203723441809 + 0.020430399105 + 0.0204885471612 + 0.0205467846245 + 0.0206051133573 + 0.0206635333598 + 0.0207220446318 + 0.0207806453109 + 0.0208393391222 + 0.0208981223404 + 0.020956998691 + 0.0210159644485 + 0.0210750214756 + 0.0211341697723 + 0.0211934093386 + 0.0212527401745 + 0.0213121622801 + 0.0213716756552 + 0.0214312803 + 0.0214909762144 + 0.0215507633984 + 0.021610641852 + 0.0216706115752 + 0.0217306725681 + 0.0217908266932 + 0.0218510702252 + 0.0219114068896 + 0.0219718329608 + 0.0220323521644 + 0.0220929626375 + 0.0221536643803 + 0.0222144592553 + 0.0222753435373 + 0.0223363209516 + 0.0223973896354 + 0.0224585495889 + 0.0225198026747 + 0.02258114703 + 0.022642582655 + 0.0227041095495 + 0.0227657295763 + 0.0228274390101 + 0.0228892434388 + 0.0229511372745 + 0.0230131242424 + 0.0230752043426 + 0.0231373757124 + 0.0231996383518 + 0.0232619922608 + 0.0233244393021 + 0.0233869794756 + 0.0234496109188 + 0.0235123336315 + 0.0235751494765 + 0.0236380565912 + 0.023701056838 + 0.0237641483545 + 0.0238273330033 + 0.0238906107843 + 0.0239539798349 + 0.0240174401551 + 0.0240809954703 + 0.0241446401924 + 0.0242083799094 + 0.024272210896 + 0.0243361331522 + 0.0244001504034 + 0.0244642589241 + 0.0245284587145 + 0.0245927534997 + 0.0246571395546 + 0.0247216168791 + 0.0247861891985 + 0.0248508527875 + 0.0249156095088 + 0.0249804593623 + 0.0250454004854 + 0.0251104366034 + 0.0251755639911 + 0.025240784511 + 0.0253060963005 + 0.0253715030849 + 0.0254370030016 + 0.0255025941879 + 0.0255682785064 + 0.0256340559572 + 0.0256999265403 + 0.0257658902556 + 0.0258319471031 + 0.025898097083 + 0.0259643401951 + 0.0260306764394 + 0.0260971039534 + 0.0261636264622 + 0.0262302421033 + 0.0262969508767 + 0.0263637527823 + 0.0264306459576 + 0.0264976341277 + 0.0265647154301 + 0.0266318917274 + 0.0266991592944 + 0.0267665199935 + 0.0268339756876 + 0.0269015226513 + 0.0269691646099 + 0.0270368997008 + 0.0271047279239 + 0.0271726492792 + 0.0272406656295 + 0.0273087732494 + 0.0273769758642 + 0.0274452716112 + 0.0275136623532 + 0.0275821462274 + 0.0276507232338 + 0.0277193933725 + 0.0277881566435 + 0.0278570149094 + 0.0279259663075 + 0.0279950127006 + 0.0280641522259 + 0.0281333848834 + 0.0282027125359 + 0.0282721333206 + 0.0283416472375 + 0.0284112561494 + 0.0284809581935 + 0.0285507552326 + 0.0286206454039 + 0.0286906305701 + 0.0287607088685 + 0.0288308802992 + 0.0289011467248 + 0.0289715081453 + 0.0290419626981 + 0.0291125103831 + 0.0291831549257 + 0.0292538907379 + 0.0293247234076 + 0.0293956492096 + 0.0294666681439 + 0.029537782073 + 0.0296089909971 + 0.0296802930534 + 0.0297516901046 + 0.0298231821507 + 0.0298947673291 + 0.0299664475024 + 0.0300382226706 + 0.0301100928336 + 0.030182056129 + 0.0302541144192 + 0.0303262658417 + 0.0303985141218 + 0.0304708555341 + 0.0305432919413 + 0.0306158214808 + 0.0306884478778 + 0.030761167407 + 0.0308339819312 + 0.0309068914503 + 0.0309798959643 + 0.0310529954731 + 0.0311261899769 + 0.031199477613 + 0.0312728621066 + 0.0313463397324 + 0.0314199142158 + 0.0314935818315 + 0.0315673425794 + 0.0316412001848 + 0.0317151546478 + 0.0317892022431 + 0.0318633429706 + 0.0319375805557 + 0.0320119149983 + 0.0320863425732 + 0.0321608632803 + 0.032235480845 + 0.0323101952672 + 0.0323850028217 + 0.0324599072337 + 0.032534904778 + 0.0326099991798 + 0.0326851867139 + 0.0327604711056 + 0.0328358523548 + 0.0329113267362 + 0.0329868979752 + 0.0330625623465 + 0.0331383235753 + 0.0332141779363 + 0.0332901291549 + 0.0333661772311 + 0.0334423184395 + 0.0335185565054 + 0.0335948914289 + 0.0336713194847 + 0.033747844398 + 0.0338244661689 + 0.033901181072 + 0.0339779928327 + 0.0340548977256 + 0.0341318994761 + 0.0342089980841 + 0.0342861935496 + 0.0343634821475 + 0.0344408676028 + 0.0345183499157 + 0.0345959253609 + 0.0346735976636 + 0.0347513668239 + 0.0348292291164 + 0.0349071882665 + 0.0349852442741 + 0.0350633971393 + 0.035141646862 + 0.035219989717 + 0.0352984294295 + 0.0353769659996 + 0.0354555957019 + 0.0355343222618 + 0.0356131494045 + 0.0356920659542 + 0.0357710830867 + 0.0358501970768 + 0.0359294041991 + 0.036008708179 + 0.0360881090164 + 0.0361676067114 + 0.0362471975386 + 0.0363268889487 + 0.036406673491 + 0.0364865548909 + 0.0365665331483 + 0.0366466082633 + 0.0367267765105 + 0.0368070453405 + 0.0368874073029 + 0.0369678661227 + 0.0370484255254 + 0.0371290780604 + 0.0372098274529 + 0.0372906699777 + 0.0373716130853 + 0.0374526530504 + 0.0375337861478 + 0.0376150198281 + 0.0376963466406 + 0.0377777740359 + 0.0378592945635 + 0.0379409119487 + 0.0380226299167 + 0.0381044410169 + 0.0381863489747 + 0.03826835379 + 0.0383504554629 + 0.0384326539934 + 0.0385149493814 + 0.0385973416269 + 0.03867983073 + 0.0387624166906 + 0.0388450995088 + 0.0389278791845 + 0.039010759443 + 0.0390937328339 + 0.0391768030822 + 0.0392599701881 + 0.0393432341516 + 0.0394265986979 + 0.0395100563765 + 0.0395936109126 + 0.0396772660315 + 0.0397610142827 + 0.0398448631167 + 0.039928805083 + 0.0400128476322 + 0.0400969870389 + 0.0401812233031 + 0.0402655564249 + 0.0403499864042 + 0.0404345132411 + 0.0405191406608 + 0.0406038612127 + 0.0406886823475 + 0.0407736003399 + 0.0408586151898 + 0.0409437268972 + 0.0410289354622 + 0.0411142408848 + 0.0411996468902 + 0.0412851497531 + 0.0413707457483 + 0.0414564460516 + 0.0415422394872 + 0.0416281297803 + 0.0417141206563 + 0.0418002083898 + 0.0418863929808 + 0.0419726744294 + 0.0420590527356 + 0.0421455316246 + 0.0422321073711 + 0.0423187799752 + 0.0424055494368 + 0.0424924194813 + 0.0425793863833 + 0.0426664501429 + 0.04275361076 + 0.0428408719599 + 0.0429282300174 + 0.0430156849325 + 0.0431032404304 + 0.0431908890605 + 0.0432786382735 + 0.0433664880693 + 0.0434544309974 + 0.0435424745083 + 0.043630618602 + 0.043718855828 + 0.0438071936369 + 0.0438956283033 + 0.0439841635525 + 0.0440727956593 + 0.0441615246236 + 0.0442503541708 + 0.0443392805755 + 0.0444283038378 + 0.0445174276829 + 0.0446066483855 + 0.0446959659457 + 0.0447853840888 + 0.0448748990893 + 0.0449645146728 + 0.0450542271137 + 0.0451440364122 + 0.0452339462936 + 0.0453239530325 + 0.0454140603542 + 0.0455042645335 + 0.0455945655704 + 0.04568496719 + 0.0457754656672 + 0.0458660647273 + 0.0459567606449 + 0.0460475571454 + 0.0461384505033 + 0.0462294444442 + 0.0463205352426 + 0.0464117266238 + 0.0465030148625 + 0.0465943999588 + 0.046685885638 + 0.0467774719 + 0.0468691550195 + 0.0469609349966 + 0.0470528155565 + 0.0471447966993 + 0.0472368746996 + 0.0473290532827 + 0.0474213287234 + 0.047513704747 + 0.047606177628 + 0.047698751092 + 0.0477914214134 + 0.0478841923177 + 0.0479770600796 + 0.0480700284243 + 0.0481630973518 + 0.0482562631369 + 0.0483495295048 + 0.0484428927302 + 0.0485363565385 + 0.0486299209297 + 0.0487235821784 + 0.0488173440099 + 0.0489112026989 + 0.0490051619709 + 0.0490992218256 + 0.0491933785379 + 0.049287635833 + 0.0493819899857 + 0.0494764484465 + 0.0495710000396 + 0.0496656559408 + 0.0497604086995 + 0.0498552620411 + 0.0499502122402 + 0.0500452667475 + 0.050140414387 + 0.0502356663346 + 0.0503310151398 + 0.0504264645278 + 0.0505220144987 + 0.0506176613271 + 0.0507134087384 + 0.0508092567325 + 0.0509052015841 + 0.0510012507439 + 0.0510973967612 + 0.051193639636 + 0.051289986819 + 0.0513864308596 + 0.0514829754829 + 0.0515796169639 + 0.0516763627529 + 0.0517732053995 + 0.051870148629 + 0.0519671924412 + 0.052064333111 + 0.052161578089 + 0.0522589199245 + 0.0523563623428 + 0.0524539016187 + 0.0525515452027 + 0.0526492856443 + 0.0527471266687 + 0.0528450682759 + 0.052943110466 + 0.0530412532389 + 0.0531394928694 + 0.053237836808 + 0.0533362776041 + 0.0534348189831 + 0.0535334609449 + 0.0536321997643 + 0.0537310428917 + 0.0538299828768 + 0.0539290271699 + 0.0540281683207 + 0.0541274100542 + 0.0542267523706 + 0.0543261952698 + 0.0544257387519 + 0.0545253828168 + 0.0546251237392 + 0.0547249689698 + 0.0548249110579 + 0.0549249574542 + 0.055025100708 + 0.0551253445446 + 0.0552256889641 + 0.0553261376917 + 0.0554266832769 + 0.0555273294449 + 0.0556280761957 + 0.0557289235294 + 0.0558298714459 + 0.0559309162199 + 0.0560320653021 + 0.0561333149672 + 0.056234665215 + 0.0563361160457 + 0.0564376674592 + 0.0565393194556 + 0.0566410720348 + 0.0567429251969 + 0.0568448752165 + 0.0569469295442 + 0.0570490844548 + 0.0571513399482 + 0.0572536960244 + 0.0573561526835 + 0.0574587136507 + 0.0575613714755 + 0.0576641298831 + 0.0577669888735 + 0.057869952172 + 0.0579730123281 + 0.0580761767924 + 0.0581794381142 + 0.0582828037441 + 0.0583862699568 + 0.0584898330271 + 0.0585935004056 + 0.0586972720921 + 0.0588011406362 + 0.0589051097631 + 0.0590091794729 + 0.0591133534908 + 0.0592176280916 + 0.0593219995499 + 0.0594264753163 + 0.0595310553908 + 0.0596357323229 + 0.0597405098379 + 0.0598453916609 + 0.0599503703415 + 0.0600554533303 + 0.0601606369019 + 0.0602659247816 + 0.0603713095188 + 0.0604767985642 + 0.0605823844671 + 0.0606880746782 + 0.0607938691974 + 0.0608997605741 + 0.061005756259 + 0.0611118488014 + 0.0612180456519 + 0.0613243468106 + 0.0614307448268 + 0.0615372471511 + 0.0616438500583 + 0.0617505535483 + 0.0618573613465 + 0.0619642660022 + 0.062071274966 + 0.062178388238 + 0.0622855983675 + 0.0623929128051 + 0.0625003278255 + 0.0626078471541 + 0.0627154633403 + 0.0628231838346 + 0.0629310011864 + 0.0630389302969 + 0.063146956265 + 0.0632550790906 + 0.0633633062243 + 0.0634716376662 + 0.0635800734162 + 0.0636886060238 + 0.0637972429395 + 0.0639059767127 + 0.0640148222446 + 0.0641237571836 + 0.0642328038812 + 0.0643419474363 + 0.0644511952996 + 0.064560547471 + 0.0646699965 + 0.0647795498371 + 0.0648892074823 + 0.0649989619851 + 0.065108820796 + 0.065218783915 + 0.0653288438916 + 0.0654390081763 + 0.0655492767692 + 0.0656596496701 + 0.0657701194286 + 0.0658806934953 + 0.06599137187 + 0.0661021471024 + 0.0662130266428 + 0.0663240104914 + 0.0664350986481 + 0.0665462836623 + 0.0666575729847 + 0.0667689666152 + 0.0668804571033 + 0.0669920518994 + 0.0671037510037 + 0.0672155544162 + 0.0673274546862 + 0.0674394592643 + 0.0675515681505 + 0.0676637813449 + 0.0677760913968 + 0.0678885057569 + 0.068001024425 + 0.0681136474013 + 0.0682263672352 + 0.0683391988277 + 0.0684521272779 + 0.0685651525855 + 0.0686782896519 + 0.0687915235758 + 0.0689048618078 + 0.069018304348 + 0.0691318437457 + 0.0692454949021 + 0.0693592429161 + 0.0694730952382 + 0.0695870444179 + 0.0697011053562 + 0.0698152631521 + 0.0699295252562 + 0.0700438916683 + 0.0701583623886 + 0.0702729299664 + 0.0703876018524 + 0.0705023780465 + 0.0706172585487 + 0.0707322433591 + 0.070847325027 + 0.0709625184536 + 0.0710778087378 + 0.07119320333 + 0.0713087022305 + 0.0714242979884 + 0.0715399980545 + 0.0716558098793 + 0.0717717185616 + 0.0718877315521 + 0.0720038414001 + 0.0721200630069 + 0.0722363814712 + 0.0723528042436 + 0.0724693387747 + 0.0725859627128 + 0.0727026984096 + 0.0728195384145 + 0.0729364752769 + 0.0730535238981 + 0.0731706693769 + 0.0732879191637 + 0.0734052732587 + 0.0735227242112 + 0.0736402869225 + 0.0737579464912 + 0.0738757178187 + 0.0739935860038 + 0.074111558497 + 0.0742296352983 + 0.0743478164077 + 0.0744661018252 + 0.0745844841003 + 0.0747029781342 + 0.0748215690255 + 0.074940264225 + 0.0750590637326 + 0.0751779675484 + 0.0752969756722 + 0.0754160881042 + 0.0755353048444 + 0.0756546184421 + 0.0757740437984 + 0.0758935660124 + 0.076013199985 + 0.0761329308152 + 0.0762527659535 + 0.0763727054 + 0.0764927491546 + 0.0766128972173 + 0.0767331495881 + 0.0768535062671 + 0.0769739598036 + 0.0770945250988 + 0.0772151947021 + 0.077335961163 + 0.0774568393826 + 0.0775778144598 + 0.0776988938451 + 0.0778200775385 + 0.0779413729906 + 0.0780627653003 + 0.0781842619181 + 0.078305862844 + 0.078427568078 + 0.0785493776202 + 0.0786712840199 + 0.0787933021784 + 0.0789154246449 + 0.0790376514196 + 0.0791599825025 + 0.0792824104428 + 0.0794049501419 + 0.0795275941491 + 0.0796503350139 + 0.0797731876373 + 0.0798961371183 + 0.0800191983581 + 0.0801423564553 + 0.0802656263113 + 0.0803889930248 + 0.0805124714971 + 0.0806360468268 + 0.0807597339153 + 0.0808835178614 + 0.0810074135661 + 0.0811314061284 + 0.0812555029988 + 0.081379711628 + 0.0815040171146 + 0.08162843436 + 0.081752948463 + 0.0818775743246 + 0.0820022970438 + 0.0821271315217 + 0.0822520628572 + 0.0823771059513 + 0.082502245903 + 0.0826274976134 + 0.082752853632 + 0.0828783065081 + 0.0830038711429 + 0.0831295400858 + 0.0832553058863 + 0.0833811834455 + 0.0835071653128 + 0.0836332514882 + 0.0837594419718 + 0.0838857367635 + 0.0840121358633 + 0.0841386392713 + 0.0842652469873 + 0.0843919590116 + 0.0845187753439 + 0.0846457034349 + 0.0847727283835 + 0.0848998576403 + 0.0850270986557 + 0.0851544365287 + 0.0852818861604 + 0.0854094401002 + 0.0855370908976 + 0.0856648534536 + 0.0857927203178 + 0.0859206914902 + 0.0860487669706 + 0.0861769467592 + 0.0863052383065 + 0.0864336267114 + 0.0865621194243 + 0.086690723896 + 0.0868194252253 + 0.0869482383132 + 0.0870771557093 + 0.0872061774135 + 0.0873353034258 + 0.0874645337462 + 0.0875938683748 + 0.0877233073115 + 0.087852858007 + 0.0879825055599 + 0.0881122648716 + 0.0882421284914 + 0.0883720964193 + 0.0885021686554 + 0.0886323451996 + 0.0887626260519 + 0.0888930186629 + 0.0890235081315 + 0.0891541093588 + 0.0892848074436 + 0.0894156172872 + 0.0895465314388 + 0.0896775573492 + 0.0898086801171 + 0.0899399071932 + 0.0900712460279 + 0.0902026891708 + 0.0903342366219 + 0.090465888381 + 0.0905976444483 + 0.0907295048237 + 0.0908614769578 + 0.0909935534 + 0.0911257341504 + 0.0912580192089 + 0.0913904085755 + 0.0915229022503 + 0.0916555076838 + 0.0917882174253 + 0.0919210240245 + 0.0920539423823 + 0.0921869724989 + 0.092320099473 + 0.0924533382058 + 0.0925866812468 + 0.0927201285958 + 0.092853680253 + 0.0929873362184 + 0.0931211039424 + 0.093254968524 + 0.0933889448643 + 0.0935230329633 + 0.0936572179198 + 0.0937915071845 + 0.0939259082079 + 0.0940604135394 + 0.0941950231791 + 0.0943297445774 + 0.0944645628333 + 0.0945994928479 + 0.0947345271707 + 0.0948696658015 + 0.0950049161911 + 0.0951402708888 + 0.0952757298946 + 0.0954112932086 + 0.0955469608307 + 0.0956827402115 + 0.0958186239004 + 0.0959546118975 + 0.0960907042027 + 0.0962269082665 + 0.096363209188 + 0.0964996218681 + 0.096636146307 + 0.0967727676034 + 0.0969095006585 + 0.0970463380218 + 0.0971832796931 + 0.0973203331232 + 0.0974574908614 + 0.0975947529078 + 0.0977321192622 + 0.0978695973754 + 0.0980071797967 + 0.0981448665261 + 0.0982826575637 + 0.09842056036 + 0.0985585674644 + 0.0986966788769 + 0.0988349020481 + 0.0989732220769 + 0.099111661315 + 0.0992501974106 + 0.0993888452649 + 0.0995275974274 + 0.099666453898 + 0.0998054146767 + 0.0999444872141 + 0.10008366406 + 0.100222952664 + 0.100362338126 + 0.100501835346 + 0.100641444325 + 0.100781150162 + 0.100920967758 + 0.101060889661 + 0.101200923324 + 0.101341061294 + 0.101481303573 + 0.101621650159 + 0.101762108505 + 0.101902671158 + 0.102043345571 + 0.102184124291 + 0.102325007319 + 0.102465994656 + 0.102607093751 + 0.102748297155 + 0.102889612317 + 0.103031024337 + 0.103172548115 + 0.103314183652 + 0.103455923498 + 0.103597767651 + 0.103739716113 + 0.103881776333 + 0.104023940861 + 0.104166217148 + 0.104308597744 + 0.104451082647 + 0.104593679309 + 0.104736380279 + 0.104879185557 + 0.105022102594 + 0.10516512394 + 0.105308249593 + 0.105451487005 + 0.105594828725 + 0.105738282204 + 0.105881839991 + 0.106025502086 + 0.106169275939 + 0.106313154101 + 0.106457144022 + 0.106601238251 + 0.106745436788 + 0.106889739633 + 0.107034154236 + 0.107178680599 + 0.107323311269 + 0.107468046248 + 0.107612892985 + 0.107757844031 + 0.107902899384 + 0.108048066497 + 0.108193337917 + 0.108338721097 + 0.108484208584 + 0.108629800379 + 0.108775503933 + 0.108921319246 + 0.109067231417 + 0.109213255346 + 0.109359391034 + 0.10950563103 + 0.109651975334 + 0.109798431396 + 0.109944999218 + 0.110091663897 + 0.110238447785 + 0.110385328531 + 0.110532321036 + 0.110679425299 + 0.110826633871 + 0.11097394675 + 0.111121371388 + 0.111268900335 + 0.11141654104 + 0.111564286053 + 0.111712142825 + 0.111860103905 + 0.112008169293 + 0.11215634644 + 0.112304635346 + 0.11245302856 + 0.112601526082 + 0.112750135362 + 0.112898848951 + 0.113047674298 + 0.113196603954 + 0.113345645368 + 0.11349479109 + 0.113644048572 + 0.113793410361 + 0.113942883909 + 0.114092461765 + 0.114242143929 + 0.114391945302 + 0.114541843534 + 0.114691853523 + 0.114841975272 + 0.114992201328 + 0.115142539144 + 0.115292981267 + 0.115443527699 + 0.115594185889 + 0.115744955838 + 0.115895830095 + 0.116046816111 + 0.116197906435 + 0.116349108517 + 0.116500414908 + 0.116651825607 + 0.116803355515 + 0.116954982281 + 0.117106728256 + 0.117258571088 + 0.11741053313 + 0.11756259203 + 0.117714770138 + 0.117867052555 + 0.11801943928 + 0.118171937764 + 0.118324548006 + 0.118477262557 + 0.118630081415 + 0.118783012033 + 0.118936054409 + 0.119089201093 + 0.119242459536 + 0.119395822287 + 0.119549296796 + 0.119702883065 + 0.119856566191 + 0.120010368526 + 0.120164275169 + 0.120318293571 + 0.120472416282 + 0.120626650751 + 0.120780989528 + 0.120935440063 + 0.121090002358 + 0.121244668961 + 0.121399439871 + 0.121554322541 + 0.121709316969 + 0.121864423156 + 0.122019633651 + 0.122174948454 + 0.122330375016 + 0.122485913336 + 0.122641555965 + 0.122797310352 + 0.122953176498 + 0.123109146953 + 0.123265221715 + 0.123421415687 + 0.123577713966 + 0.123734116554 + 0.123890630901 + 0.124047257006 + 0.12420398742 + 0.124360829592 + 0.124517783523 + 0.124674841762 + 0.124832011759 + 0.124989286065 + 0.12514667213 + 0.125304162502 + 0.125461772084 + 0.125619485974 + 0.125777304173 + 0.12593524158 + 0.126093283296 + 0.126251429319 + 0.126409679651 + 0.126568049192 + 0.126726523042 + 0.1268851161 + 0.127043798566 + 0.127202615142 + 0.127361521125 + 0.127520546317 + 0.127679675817 + 0.127838909626 + 0.127998262644 + 0.12815771997 + 0.128317281604 + 0.128476962447 + 0.128636747599 + 0.128796651959 + 0.128956645727 + 0.129116758704 + 0.129276990891 + 0.129437312484 + 0.129597753286 + 0.129758313298 + 0.129918977618 + 0.130079746246 + 0.130240619183 + 0.130401611328 + 0.130562707782 + 0.130723908544 + 0.130885228515 + 0.131046652794 + 0.131208196282 + 0.131369829178 + 0.131531581283 + 0.131693452597 + 0.131855428219 + 0.132017508149 + 0.132179692388 + 0.132341995835 + 0.132504418492 + 0.132666930556 + 0.13282956183 + 0.132992297411 + 0.133155152202 + 0.1333181113 + 0.133481174707 + 0.133644357324 + 0.133807644248 + 0.133971050382 + 0.134134545922 + 0.134298175573 + 0.134461894631 + 0.134625732899 + 0.134789675474 + 0.134953737259 + 0.135117903352 + 0.135282173753 + 0.135446563363 + 0.135611057281 + 0.135775670409 + 0.135940372944 + 0.136105209589 + 0.136270135641 + 0.136435180902 + 0.136600330472 + 0.136765599251 + 0.136930972338 + 0.137096464634 + 0.137262046337 + 0.137427762151 + 0.137593567371 + 0.137759491801 + 0.137925520539 + 0.138091668487 + 0.138257920742 + 0.138424292207 + 0.13859076798 + 0.138757348061 + 0.138924047351 + 0.139090850949 + 0.139257758856 + 0.139424785972 + 0.139591917396 + 0.139759168029 + 0.13992652297 + 0.14009398222 + 0.140261560678 + 0.140429243445 + 0.140597045422 + 0.140764936805 + 0.140932962298 + 0.1411010921 + 0.14126932621 + 0.141437664628 + 0.141606122255 + 0.141774699092 + 0.141943365335 + 0.142112165689 + 0.14228105545 + 0.142450064421 + 0.142619177699 + 0.142788410187 + 0.142957746983 + 0.143127202988 + 0.143296763301 + 0.143466427922 + 0.143636211753 + 0.143806114793 + 0.14397610724 + 0.144146218896 + 0.144316449761 + 0.144486784935 + 0.144657224417 + 0.144827783108 + 0.144998446107 + 0.145169213414 + 0.145340099931 + 0.145511105657 + 0.145682215691 + 0.145853430033 + 0.146024763584 + 0.146196201444 + 0.146367743611 + 0.146539404988 + 0.146711185575 + 0.146883055568 + 0.147055059671 + 0.147227153182 + 0.147399365902 + 0.147571697831 + 0.147744134068 + 0.147916674614 + 0.148089334369 + 0.148262098432 + 0.148434981704 + 0.148607969284 + 0.148781076074 + 0.148954287171 + 0.149127602577 + 0.149301037192 + 0.149474576116 + 0.149648234248 + 0.149821996689 + 0.149995878339 + 0.150169864297 + 0.150343969464 + 0.15051817894 + 0.150692492723 + 0.150866925716 + 0.151041463017 + 0.151216119528 + 0.151390880346 + 0.151565760374 + 0.15174074471 + 0.151915848255 + 0.152091056108 + 0.15226636827 + 0.152441799641 + 0.152617350221 + 0.152793005109 + 0.152968764305 + 0.153144642711 + 0.153320625424 + 0.153496727347 + 0.153672933578 + 0.153849244118 + 0.154025688767 + 0.154202222824 + 0.15437887609 + 0.154555648565 + 0.154732525349 + 0.15490950644 + 0.155086606741 + 0.15526381135 + 0.155441135168 + 0.155618563294 + 0.15579611063 + 0.155973762274 + 0.156151533127 + 0.156329408288 + 0.156507402658 + 0.156685501337 + 0.156863719225 + 0.157042041421 + 0.157220467925 + 0.157399013638 + 0.157577678561 + 0.157756447792 + 0.157935321331 + 0.158114314079 + 0.158293426037 + 0.158472642303 + 0.158651962876 + 0.158831402659 + 0.159010961652 + 0.159190624952 + 0.159370392561 + 0.159550279379 + 0.159730270505 + 0.15991038084 + 0.160090595484 + 0.160270929337 + 0.160451382399 + 0.160631924868 + 0.160812601447 + 0.160993382335 + 0.16117426753 + 0.161355271935 + 0.161536380649 + 0.161717608571 + 0.161898940802 + 0.162080392241 + 0.162261947989 + 0.162443622947 + 0.162625402212 + 0.162807300687 + 0.162989318371 + 0.163171425462 + 0.163353666663 + 0.163536012173 + 0.16371846199 + 0.163901031017 + 0.164083704352 + 0.164266496897 + 0.16444940865 + 0.164632409811 + 0.164815545082 + 0.164998784661 + 0.165182128549 + 0.165365591645 + 0.165549173951 + 0.165732860565 + 0.165916651487 + 0.166100561619 + 0.16628459096 + 0.166468724608 + 0.166652977467 + 0.166837334633 + 0.167021811008 + 0.167206391692 + 0.167391076684 + 0.167575895786 + 0.167760804296 + 0.167945846915 + 0.168130993843 + 0.168316245079 + 0.168501615524 + 0.168687090278 + 0.16887268424 + 0.169058397412 + 0.169244214892 + 0.169430136681 + 0.169616177678 + 0.169802337885 + 0.1699886024 + 0.170174986124 + 0.170361474156 + 0.170548081398 + 0.170734792948 + 0.170921623707 + 0.171108573675 + 0.17129561305 + 0.171482786536 + 0.17167006433 + 0.171857461333 + 0.172044962645 + 0.172232568264 + 0.172420307994 + 0.172608137131 + 0.172796100378 + 0.172984167933 + 0.173172339797 + 0.17336063087 + 0.173549026251 + 0.173737555742 + 0.173926174641 + 0.174114912748 + 0.174303770065 + 0.17449273169 + 0.174681812525 + 0.174871012568 + 0.17506031692 + 0.17524972558 + 0.175439253449 + 0.175628900528 + 0.175818651915 + 0.176008522511 + 0.176198497415 + 0.176388591528 + 0.176578804851 + 0.176769122481 + 0.17695954442 + 0.17715010047 + 0.177340745926 + 0.177531525493 + 0.177722409368 + 0.177913397551 + 0.178104504943 + 0.178295731544 + 0.178487062454 + 0.178678512573 + 0.178870067 + 0.179061740637 + 0.179253518581 + 0.179445430636 + 0.179637432098 + 0.17982955277 + 0.18002179265 + 0.18021415174 + 0.180406615138 + 0.180599182844 + 0.18079186976 + 0.180984675884 + 0.181177586317 + 0.181370615959 + 0.181563764811 + 0.18175701797 + 0.181950375438 + 0.182143867016 + 0.182337462902 + 0.182531163096 + 0.1827249825 + 0.182918921113 + 0.183112964034 + 0.183307126164 + 0.183501392603 + 0.183695778251 + 0.183890283108 + 0.184084892273 + 0.184279620647 + 0.18447445333 + 0.184669405222 + 0.184864476323 + 0.185059651732 + 0.18525493145 + 0.185450345278 + 0.185645863414 + 0.185841485858 + 0.186037242413 + 0.186233103275 + 0.186429068446 + 0.186625152826 + 0.186821356416 + 0.187017664313 + 0.18721409142 + 0.187410622835 + 0.187607273459 + 0.187804043293 + 0.188000917435 + 0.188197910786 + 0.188395023346 + 0.188592240214 + 0.188789576292 + 0.188987016678 + 0.189184576273 + 0.189382255077 + 0.18958003819 + 0.189777940512 + 0.189975947142 + 0.190174072981 + 0.190372318029 + 0.190570667386 + 0.190769135952 + 0.190967723727 + 0.191166415811 + 0.191365227103 + 0.191564157605 + 0.191763192415 + 0.191962331533 + 0.192161604762 + 0.192360982299 + 0.192560464144 + 0.192760080099 + 0.192959800363 + 0.193159624934 + 0.193359568715 + 0.193559631705 + 0.193759813905 + 0.193960100412 + 0.194160491228 + 0.194361016154 + 0.194561645389 + 0.194762378931 + 0.194963246584 + 0.195164218545 + 0.195365294814 + 0.195566490293 + 0.19576780498 + 0.195969238877 + 0.196170777082 + 0.196372434497 + 0.196574196219 + 0.196776077151 + 0.196978077292 + 0.197180181742 + 0.1973824054 + 0.197584748268 + 0.197787195444 + 0.197989761829 + 0.198192447424 + 0.198395237327 + 0.198598146439 + 0.19880117476 + 0.199004307389 + 0.199207559228 + 0.199410930276 + 0.199614405632 + 0.199818000197 + 0.200021699071 + 0.200225532055 + 0.200429454446 + 0.200633510947 + 0.200837671757 + 0.201041951776 + 0.201246351004 + 0.20145085454 + 0.201655477285 + 0.201860204339 + 0.202065065503 + 0.202270016074 + 0.202475100756 + 0.202680289745 + 0.202885597944 + 0.203091025352 + 0.203296557069 + 0.203502207994 + 0.203707963228 + 0.203913852572 + 0.204119846225 + 0.204325944185 + 0.204532176256 + 0.204738512635 + 0.204944953322 + 0.20515152812 + 0.205358207226 + 0.20556499064 + 0.205771908164 + 0.205978929996 + 0.206186071038 + 0.206393316388 + 0.206600680947 + 0.206808164716 + 0.207015752792 + 0.207223474979 + 0.207431286573 + 0.207639232278 + 0.20784728229 + 0.208055451512 + 0.208263739944 + 0.208472132683 + 0.208680644631 + 0.208889275789 + 0.209098011255 + 0.209306865931 + 0.209515839815 + 0.209724932909 + 0.209934130311 + 0.210143446922 + 0.210352867842 + 0.210562422872 + 0.21077208221 + 0.210981845856 + 0.211191743612 + 0.211401745677 + 0.211611866951 + 0.211822092533 + 0.212032452226 + 0.212242901325 + 0.212453484535 + 0.212664172053 + 0.212874993682 + 0.213085904717 + 0.213296949863 + 0.213508099318 + 0.213719367981 + 0.213930740952 + 0.214142248034 + 0.214353859425 + 0.214565590024 + 0.214777424932 + 0.214989379048 + 0.215201452374 + 0.21541364491 + 0.215625941753 + 0.215838357806 + 0.216050893068 + 0.216263532639 + 0.216476306319 + 0.216689184308 + 0.216902166605 + 0.217115283012 + 0.217328503728 + 0.217541843653 + 0.217755287886 + 0.217968851328 + 0.218182533979 + 0.21839633584 + 0.21861025691 + 0.218824282289 + 0.219038426876 + 0.219252675772 + 0.219467058778 + 0.219681546092 + 0.219896152616 + 0.220110863447 + 0.220325708389 + 0.22054065764 + 0.220755726099 + 0.220970898867 + 0.221186205745 + 0.221401616931 + 0.221617132425 + 0.22183278203 + 0.222048535943 + 0.222264409065 + 0.222480401397 + 0.222696498036 + 0.222912728786 + 0.223129048944 + 0.223345503211 + 0.223562076688 + 0.223778754473 + 0.223995551467 + 0.22421246767 + 0.224429488182 + 0.224646627903 + 0.224863886833 + 0.225081264973 + 0.22529874742 + 0.225516363978 + 0.225734069943 + 0.225951910019 + 0.226169869304 + 0.226387932897 + 0.226606115699 + 0.22682441771 + 0.22704282403 + 0.227261349559 + 0.227479994297 + 0.227698758245 + 0.227917641401 + 0.228136628866 + 0.22835573554 + 0.228574961424 + 0.228794306517 + 0.229013755918 + 0.229233324528 + 0.229453012347 + 0.229672819376 + 0.229892730713 + 0.230112761259 + 0.230332911015 + 0.230553179979 + 0.230773568153 + 0.230994060636 + 0.231214672327 + 0.231435403228 + 0.231656238437 + 0.231877207756 + 0.232098281384 + 0.23231947422 + 0.232540786266 + 0.232762202621 + 0.232983738184 + 0.233205392957 + 0.233427166939 + 0.23364906013 + 0.23387105763 + 0.234093174338 + 0.234315410256 + 0.234537765384 + 0.23476023972 + 0.234982818365 + 0.235205516219 + 0.235428333282 + 0.235651269555 + 0.235874310136 + 0.236097469926 + 0.236320748925 + 0.236544147134 + 0.236767664552 + 0.236991286278 + 0.237215027213 + 0.237438887358 + 0.237662866712 + 0.237886965275 + 0.238111168146 + 0.238335490227 + 0.238559931517 + 0.238784492016 + 0.239009171724 + 0.239233955741 + 0.239458858967 + 0.239683881402 + 0.239909023046 + 0.2401342839 + 0.240359649062 + 0.240585133433 + 0.240810737014 + 0.241036459804 + 0.241262301803 + 0.24148824811 + 0.241714313626 + 0.241940498352 + 0.242166802287 + 0.242393225431 + 0.242619752884 + 0.242846399546 + 0.243073165417 + 0.243300050497 + 0.243527054787 + 0.243754178286 + 0.243981406093 + 0.244208753109 + 0.244436219335 + 0.24466380477 + 0.244891494513 + 0.245119318366 + 0.245347246528 + 0.245575293899 + 0.245803460479 + 0.246031746268 + 0.246260136366 + 0.246488645673 + 0.24671728909 + 0.246946036816 + 0.247174888849 + 0.247403874993 + 0.247632965446 + 0.247862190008 + 0.248091518879 + 0.248320966959 + 0.248550534248 + 0.248780205846 + 0.249010011554 + 0.24923992157 + 0.249469950795 + 0.24970009923 + 0.249930366874 + 0.250160753727 + 0.250391244888 + 0.250621855259 + 0.250852584839 + 0.251083433628 + 0.251314401627 + 0.251545488834 + 0.251776665449 + 0.252007991076 + 0.252239435911 + 0.252470970154 + 0.252702653408 + 0.252934426069 + 0.25316631794 + 0.25339832902 + 0.253630459309 + 0.253862708807 + 0.254095077515 + 0.254327565432 + 0.254560172558 + 0.254792898893 + 0.255025714636 + 0.25525867939 + 0.255491733551 + 0.255724936724 + 0.255958229303 + 0.256191670895 + 0.256425201893 + 0.2566588521 + 0.256892621517 + 0.257126510143 + 0.257360517979 + 0.257594645023 + 0.257828861475 + 0.258063226938 + 0.258297711611 + 0.25853228569 + 0.258767008781 + 0.25900182128 + 0.259236752987 + 0.259471833706 + 0.259707003832 + 0.259942293167 + 0.260177701712 + 0.260413229465 + 0.260648876429 + 0.260884642601 + 0.261120527983 + 0.261356532574 + 0.261592626572 + 0.261828869581 + 0.262065201998 + 0.262301683426 + 0.262538254261 + 0.262774974108 + 0.263011783361 + 0.263248711824 + 0.263485759497 + 0.263722926378 + 0.263960242271 + 0.264197617769 + 0.264435142279 + 0.264672785997 + 0.264910548925 + 0.265148431063 + 0.265386402607 + 0.265624523163 + 0.265862762928 + 0.2661010921 + 0.266339570284 + 0.266578137875 + 0.266816824675 + 0.267055660486 + 0.267294585705 + 0.267533630133 + 0.26777279377 + 0.268012076616 + 0.268251478672 + 0.268490999937 + 0.268730640411 + 0.268970400095 + 0.269210278988 + 0.269450247288 + 0.269690364599 + 0.26993060112 + 0.270170927048 + 0.270411401987 + 0.270651966333 + 0.270892649889 + 0.271133482456 + 0.27137440443 + 0.271615445614 + 0.271856635809 + 0.272097915411 + 0.272339314222 + 0.272580832243 + 0.272822469473 + 0.273064225912 + 0.273306101561 + 0.273548096418 + 0.273790180683 + 0.27403241396 + 0.274274766445 + 0.27451723814 + 0.274759799242 + 0.275002509356 + 0.275245308876 + 0.275488257408 + 0.275731295347 + 0.275974482298 + 0.276217758656 + 0.276461154222 + 0.276704698801 + 0.276948332787 + 0.277192085981 + 0.277435958385 + 0.277679949999 + 0.277924060822 + 0.278168290854 + 0.278412640095 + 0.278657108545 + 0.278901696205 + 0.279146403074 + 0.279391229153 + 0.27963617444 + 0.279881209135 + 0.280126392841 + 0.280371695757 + 0.280617088079 + 0.280862629414 + 0.281108289957 + 0.281354039907 + 0.281599938869 + 0.281845927238 + 0.282092034817 + 0.282338291407 + 0.282584637403 + 0.28283110261 + 0.283077716827 + 0.283324420452 + 0.283571243286 + 0.283818185329 + 0.284065276384 + 0.284312456846 + 0.284559756517 + 0.284807175398 + 0.285054713488 + 0.285302370787 + 0.285550147295 + 0.285798043013 + 0.286046028137 + 0.286294162273 + 0.286542415619 + 0.286790788174 + 0.287039279938 + 0.287287861109 + 0.287536591291 + 0.287785440683 + 0.288034409285 + 0.288283467293 + 0.288532674313 + 0.288781970739 + 0.289031416178 + 0.289280951023 + 0.28953063488 + 0.289780408144 + 0.29003033042 + 0.290280342102 + 0.290530502796 + 0.290780752897 + 0.29103115201 + 0.29128164053 + 0.291532248259 + 0.291782975197 + 0.292033851147 + 0.292284816504 + 0.29253590107 + 0.292787104845 + 0.293038457632 + 0.293289899826 + 0.293541461229 + 0.293793141842 + 0.294044941664 + 0.294296860695 + 0.294548898935 + 0.294801056385 + 0.295053333044 + 0.295305728912 + 0.29555824399 + 0.295810878277 + 0.296063631773 + 0.296316504478 + 0.296569496393 + 0.296822607517 + 0.297075837851 + 0.297329187393 + 0.297582656145 + 0.297836244106 + 0.298089951277 + 0.298343747854 + 0.298597693443 + 0.298851758242 + 0.299105942249 + 0.299360245466 + 0.29961463809 + 0.299869179726 + 0.30012384057 + 0.300378620625 + 0.300633490086 + 0.300888508558 + 0.30114364624 + 0.301398903131 + 0.30165424943 + 0.30190974474 + 0.302165359259 + 0.302421063185 + 0.302676916122 + 0.302932888269 + 0.303188949823 + 0.303445160389 + 0.303701490164 + 0.303957909346 + 0.304214477539 + 0.304471164942 + 0.304727941751 + 0.304984867573 + 0.305241882801 + 0.305499047041 + 0.30575633049 + 0.306013703346 + 0.306271225214 + 0.306528836489 + 0.306786596775 + 0.307044476271 + 0.307302445173 + 0.307560563087 + 0.307818770409 + 0.308077126741 + 0.308335572481 + 0.308594167233 + 0.308852881193 + 0.309111684561 + 0.30937063694 + 0.309629678726 + 0.309888869524 + 0.310148179531 + 0.310407578945 + 0.310667127371 + 0.310926765203 + 0.311186552048 + 0.311446458101 + 0.311706453562 + 0.311966598034 + 0.312226861715 + 0.312487214804 + 0.312747716904 + 0.313008308411 + 0.313269048929 + 0.313529908657 + 0.313790857792 + 0.314051955938 + 0.314313173294 + 0.314574480057 + 0.314835935831 + 0.315097510815 + 0.315359205008 + 0.315620988607 + 0.315882921219 + 0.31614497304 + 0.316407114267 + 0.316669404507 + 0.316931813955 + 0.317194342613 + 0.31745699048 + 0.317719727755 + 0.31798261404 + 0.318245619535 + 0.31850874424 + 0.318771988153 + 0.319035351276 + 0.319298803806 + 0.319562405348 + 0.319826126099 + 0.320089966059 + 0.320353925228 + 0.320618003607 + 0.320882201195 + 0.321146517992 + 0.321410953999 + 0.321675509214 + 0.32194018364 + 0.322204977274 + 0.322469890118 + 0.322734922171 + 0.323000103235 + 0.323265373707 + 0.323530763388 + 0.323796272278 + 0.324061900377 + 0.324327647686 + 0.324593544006 + 0.324859529734 + 0.32512563467 + 0.325391858816 + 0.325658231974 + 0.325924694538 + 0.326191276312 + 0.326458007097 + 0.32672482729 + 0.326991796494 + 0.327258855104 + 0.327526062727 + 0.327793359756 + 0.328060805798 + 0.328328341246 + 0.328596025705 + 0.328863799572 + 0.32913172245 + 0.329399764538 + 0.329667896032 + 0.329936176538 + 0.330204576254 + 0.330473095179 + 0.33074170351 + 0.331010460854 + 0.331279337406 + 0.331548333168 + 0.331817448139 + 0.33208668232 + 0.332356035709 + 0.332625508308 + 0.332895100117 + 0.333164811134 + 0.333434641361 + 0.333704590797 + 0.333974659443 + 0.334244847298 + 0.334515184164 + 0.334785610437 + 0.33505615592 + 0.335326820612 + 0.335597634315 + 0.335868537426 + 0.336139589548 + 0.336410731077 + 0.336682021618 + 0.336953401566 + 0.337224930525 + 0.337496548891 + 0.337768316269 + 0.338040202856 + 0.33831217885 + 0.338584303856 + 0.338856548071 + 0.339128911495 + 0.339401394129 + 0.339673966169 + 0.339946687222 + 0.340219527483 + 0.340492486954 + 0.340765595436 + 0.341038793325 + 0.341312110424 + 0.341585546732 + 0.341859102249 + 0.342132776976 + 0.342406600714 + 0.342680513859 + 0.342954546213 + 0.343228727579 + 0.343502998352 + 0.343777418137 + 0.34405195713 + 0.344326585531 + 0.344601362944 + 0.344876229763 + 0.345151245594 + 0.345426380634 + 0.345701634884 + 0.345977008343 + 0.346252501011 + 0.346528112888 + 0.346803843975 + 0.347079694271 + 0.347355663776 + 0.347631752491 + 0.347907960415 + 0.34818431735 + 0.348460763693 + 0.348737329245 + 0.349014043808 + 0.349290847778 + 0.34956780076 + 0.349844843149 + 0.35012203455 + 0.35039934516 + 0.350676745176 + 0.350954294205 + 0.351231962442 + 0.351509749889 + 0.351787656546 + 0.352065682411 + 0.352343827486 + 0.35262209177 + 0.352900475264 + 0.353178977966 + 0.353457599878 + 0.353736370802 + 0.354015231133 + 0.354294210672 + 0.354573339224 + 0.354852586985 + 0.355131924152 + 0.355411410332 + 0.355690985918 + 0.355970710516 + 0.356250554323 + 0.35653051734 + 0.356810599566 + 0.357090801001 + 0.357371121645 + 0.357651561499 + 0.357932120562 + 0.358212798834 + 0.358493626118 + 0.358774542809 + 0.359055608511 + 0.35933676362 + 0.359618067741 + 0.359899461269 + 0.360181003809 + 0.360462665558 + 0.360744416714 + 0.361026316881 + 0.361308336258 + 0.361590474844 + 0.361872732639 + 0.362155109644 + 0.36243763566 + 0.362720251083 + 0.363002985716 + 0.363285839558 + 0.363568842411 + 0.363851934671 + 0.364135175943 + 0.364418536425 + 0.364701986313 + 0.364985585213 + 0.365269303322 + 0.36555314064 + 0.365837097168 + 0.366121172905 + 0.366405367851 + 0.366689682007 + 0.366974145174 + 0.367258697748 + 0.367543369532 + 0.367828190327 + 0.368113100529 + 0.368398159742 + 0.368683338165 + 0.368968605995 + 0.369254022837 + 0.369539558887 + 0.369825214148 + 0.370110988617 + 0.370396882296 + 0.370682924986 + 0.370969057083 + 0.37125530839 + 0.371541708708 + 0.371828198433 + 0.37211483717 + 0.372401565313 + 0.372688442469 + 0.372975438833 + 0.373262554407 + 0.37354978919 + 0.373837143183 + 0.374124616385 + 0.374412208796 + 0.374699950218 + 0.374987781048 + 0.375275731087 + 0.375563830137 + 0.375852018595 + 0.376140356064 + 0.376428812742 + 0.37671738863 + 0.377006083727 + 0.377294898033 + 0.377583831549 + 0.377872884274 + 0.378162056208 + 0.378451377153 + 0.378740787506 + 0.379030317068 + 0.379319995642 + 0.379609793425 + 0.379899680614 + 0.380189716816 + 0.380479872227 + 0.380770146847 + 0.381060540676 + 0.381351083517 + 0.381641715765 + 0.381932467222 + 0.382223367691 + 0.382514357567 + 0.382805496454 + 0.383096724749 + 0.383388102055 + 0.38367959857 + 0.383971214294 + 0.384262949228 + 0.384554803371 + 0.384846776724 + 0.385138899088 + 0.385431110859 + 0.385723471642 + 0.386015921831 + 0.386308521032 + 0.386601239443 + 0.386894077063 + 0.387187033892 + 0.38748010993 + 0.387773305178 + 0.388066619635 + 0.388360053301 + 0.388653635979 + 0.388947308064 + 0.38924112916 + 0.389535069466 + 0.389829099178 + 0.390123277903 + 0.390417575836 + 0.390711992979 + 0.391006559134 + 0.391301214695 + 0.391595989466 + 0.391890913248 + 0.392185926437 + 0.392481088638 + 0.392776370049 + 0.393071770668 + 0.393367290497 + 0.393662929535 + 0.393958687782 + 0.394254565239 + 0.394550561905 + 0.394846707582 + 0.395142972469 + 0.395439326763 + 0.395735830069 + 0.396032452583 + 0.396329194307 + 0.396626055241 + 0.396923035383 + 0.397220134735 + 0.397517383099 + 0.397814720869 + 0.398112207651 + 0.39840978384 + 0.398707509041 + 0.399005353451 + 0.39930331707 + 0.399601399899 + 0.399899601936 + 0.400197952986 + 0.400496393442 + 0.40079498291 + 0.401093661785 + 0.401392489672 + 0.401691436768 + 0.401990503073 + 0.402289688587 + 0.402588993311 + 0.402888417244 + 0.403187990189 + 0.40348765254 + 0.403787463903 + 0.404087394476 + 0.404387414455 + 0.404687583447 + 0.404987871647 + 0.405288308859 + 0.405588835478 + 0.405889481306 + 0.406190276146 + 0.406491160393 + 0.406792193651 + 0.407093346119 + 0.407394617796 + 0.407696008682 + 0.407997518778 + 0.408299148083 + 0.408600926399 + 0.408902794123 + 0.409204810858 + 0.409506946802 + 0.409809201956 + 0.410111576319 + 0.410414069891 + 0.410716682673 + 0.411019414663 + 0.411322295666 + 0.411625266075 + 0.411928385496 + 0.412231624126 + 0.412534981966 + 0.412838459015 + 0.413142055273 + 0.413445770741 + 0.413749605417 + 0.414053589106 + 0.414357662201 + 0.414661884308 + 0.414966225624 + 0.41527068615 + 0.415575265884 + 0.415879964828 + 0.416184812784 + 0.416489750147 + 0.416794836521 + 0.417100042105 + 0.417405337095 + 0.417710781097 + 0.418016344309 + 0.418322056532 + 0.418627858162 + 0.418933779001 + 0.419239848852 + 0.419546037912 + 0.419852346182 + 0.420158773661 + 0.420465320349 + 0.420771986246 + 0.421078771353 + 0.421385705471 + 0.421692728996 + 0.421999901533 + 0.422307193279 + 0.422614604235 + 0.422922134399 + 0.423229783773 + 0.423537582159 + 0.423845469952 + 0.424153506756 + 0.424461632967 + 0.42476990819 + 0.425078302622 + 0.425386816263 + 0.425695478916 + 0.426004230976 + 0.426313132048 + 0.426622122526 + 0.426931262016 + 0.427240520716 + 0.427549898624 + 0.427859395742 + 0.428169041872 + 0.428478777409 + 0.428788661957 + 0.429098665714 + 0.429408758879 + 0.429719001055 + 0.430029392242 + 0.430339872837 + 0.430650472641 + 0.430961221457 + 0.431272059679 + 0.431583046913 + 0.431894153357 + 0.432205379009 + 0.432516753674 + 0.432828217745 + 0.433139801025 + 0.433451533318 + 0.433763384819 + 0.43407535553 + 0.43438744545 + 0.434699654579 + 0.435011982918 + 0.435324460268 + 0.435637056828 + 0.435949742794 + 0.436262577772 + 0.43657553196 + 0.436888635159 + 0.437201827765 + 0.43751513958 + 0.437828600407 + 0.438142180443 + 0.438455879688 + 0.438769698143 + 0.439083635807 + 0.43939769268 + 0.439711898565 + 0.440026193857 + 0.440340638161 + 0.440655201674 + 0.440969884396 + 0.441284686327 + 0.44159963727 + 0.44191467762 + 0.442229866982 + 0.442545175552 + 0.442860603333 + 0.443176150322 + 0.443491816521 + 0.443807601929 + 0.444123536348 + 0.444439560175 + 0.444755733013 + 0.445072025061 + 0.445388436317 + 0.445704996586 + 0.446021646261 + 0.446338444948 + 0.446655333042 + 0.446972370148 + 0.447289526463 + 0.447606831789 + 0.447924226522 + 0.448241740465 + 0.448559403419 + 0.448877185583 + 0.449195086956 + 0.449513107538 + 0.44983124733 + 0.450149536133 + 0.450467914343 + 0.450786441565 + 0.451105087996 + 0.451423853636 + 0.451742738485 + 0.452061742544 + 0.452380895615 + 0.452700167894 + 0.453019529581 + 0.453339040279 + 0.453658699989 + 0.453978449106 + 0.454298317432 + 0.45461833477 + 0.454938471317 + 0.455258727074 + 0.455579102039 + 0.455899596214 + 0.456220209599 + 0.456540971994 + 0.4568618536 + 0.457182824612 + 0.457503944635 + 0.457825213671 + 0.458146572113 + 0.458468079567 + 0.458789676428 + 0.4591114223 + 0.459433287382 + 0.459755271673 + 0.460077404976 + 0.460399627686 + 0.460721999407 + 0.461044490337 + 0.461367100477 + 0.461689829826 + 0.462012678385 + 0.462335675955 + 0.462658762932 + 0.46298199892 + 0.463305354118 + 0.463628828526 + 0.463952451944 + 0.46427616477 + 0.464600026608 + 0.464924007654 + 0.46524810791 + 0.465572327375 + 0.46589666605 + 0.466221153736 + 0.466545730829 + 0.466870456934 + 0.467195302248 + 0.467520266771 + 0.467845380306 + 0.468170583248 + 0.468495935202 + 0.468821406364 + 0.469146996737 + 0.469472706318 + 0.469798535109 + 0.470124512911 + 0.470450609922 + 0.470776826143 + 0.471103161573 + 0.471429616213 + 0.471756190062 + 0.472082912922 + 0.472409754992 + 0.47273671627 + 0.473063796759 + 0.473390996456 + 0.473718315363 + 0.474045783281 + 0.474373370409 + 0.474701076746 + 0.475028902292 + 0.475356847048 + 0.475684940815 + 0.476013153791 + 0.476341456175 + 0.476669937372 + 0.476998507977 + 0.47732719779 + 0.477656036615 + 0.47798499465 + 0.478314042091 + 0.478643268347 + 0.478972584009 + 0.479302018881 + 0.479631602764 + 0.479961305857 + 0.480291128159 + 0.48062106967 + 0.480951160192 + 0.481281340122 + 0.481611669064 + 0.481942117214 + 0.482272684574 + 0.482603371143 + 0.482934206724 + 0.483265131712 + 0.483596205711 + 0.48392739892 + 0.48425874114 + 0.484590172768 + 0.484921753407 + 0.485253423452 + 0.48558524251 + 0.485917210579 + 0.486249268055 + 0.48658144474 + 0.486913770437 + 0.487246215343 + 0.487578779459 + 0.487911462784 + 0.48824429512 + 0.488577246666 + 0.488910287619 + 0.489243477583 + 0.489576816559 + 0.489910244942 + 0.490243822336 + 0.49057751894 + 0.490911304951 + 0.491245269775 + 0.491579324007 + 0.49191352725 + 0.492247819901 + 0.492582261562 + 0.492916822433 + 0.493251532316 + 0.493586331606 + 0.493921279907 + 0.494256347418 + 0.494591534138 + 0.494926840067 + 0.495262295008 + 0.495597839355 + 0.495933532715 + 0.496269345284 + 0.496605306864 + 0.496941357851 + 0.49727755785 + 0.497613877058 + 0.497950315475 + 0.498286873102 + 0.498623549938 + 0.498960375786 + 0.499297320843 + 0.499634385109 + 0.499971568584 + 0.500308871269 + 0.500646352768 + 0.500983893871 + 0.501321613789 + 0.501659393311 + 0.501997351646 + 0.502335429192 + 0.502673625946 + 0.50301194191 + 0.503350377083 + 0.503688931465 + 0.504027605057 + 0.504366457462 + 0.504705369473 + 0.505044460297 + 0.505383610725 + 0.505722939968 + 0.50606238842 + 0.506401956081 + 0.506741642952 + 0.507081508636 + 0.507421433926 + 0.507761478424 + 0.508101701736 + 0.508442044258 + 0.508782446384 + 0.509123027325 + 0.509463727474 + 0.509804546833 + 0.510145485401 + 0.510486602783 + 0.51082777977 + 0.511169075966 + 0.511510550976 + 0.511852145195 + 0.512193799019 + 0.512535631657 + 0.512877583504 + 0.51321965456 + 0.51356190443 + 0.513904213905 + 0.51424664259 + 0.514589250088 + 0.514931917191 + 0.515274763107 + 0.515617728233 + 0.515960812569 + 0.516304016113 + 0.516647338867 + 0.51699078083 + 0.517334401608 + 0.517678081989 + 0.518021941185 + 0.518365859985 + 0.5187099576 + 0.519054174423 + 0.519398510456 + 0.519742965698 + 0.52008754015 + 0.520432293415 + 0.520777106285 + 0.521122097969 + 0.521467149258 + 0.52181237936 + 0.522157728672 + 0.522503197193 + 0.522848784924 + 0.523194491863 + 0.523540318012 + 0.523886322975 + 0.524232387543 + 0.524578630924 + 0.524924993515 + 0.52527141571 + 0.52561801672 + 0.525964736938 + 0.526311635971 + 0.526658594608 + 0.527005672455 + 0.527352929115 + 0.52770024538 + 0.528047740459 + 0.528395354748 + 0.528743088245 + 0.529090940952 + 0.529438912868 + 0.529787003994 + 0.530135273933 + 0.530483603477 + 0.530832111835 + 0.531180739403 + 0.531529426575 + 0.531878292561 + 0.532227277756 + 0.53257638216 + 0.532925665379 + 0.533275008202 + 0.533624529839 + 0.53397411108 + 0.534323871136 + 0.534673750401 + 0.535023748875 + 0.535373866558 + 0.535724103451 + 0.536074459553 + 0.536424994469 + 0.536775588989 + 0.537126362324 + 0.537477254868 + 0.537828266621 + 0.538179397583 + 0.538530647755 + 0.538882017136 + 0.539233505726 + 0.53958517313 + 0.539936900139 + 0.540288805962 + 0.540640830994 + 0.540992975235 + 0.541345238686 + 0.541697621346 + 0.542050123215 + 0.542402744293 + 0.542755544186 + 0.543108403683 + 0.543461441994 + 0.543814599514 + 0.544167876244 + 0.544521272182 + 0.544874787331 + 0.545228421688 + 0.545582234859 + 0.545936107635 + 0.546290159225 + 0.546644330025 + 0.546998620033 + 0.547353029251 + 0.547707557678 + 0.548062205315 + 0.54841697216 + 0.54877191782 + 0.549126982689 + 0.549482107162 + 0.54983741045 + 0.550192832947 + 0.550548374653 + 0.550904035568 + 0.551259875298 + 0.551615774632 + 0.551971852779 + 0.552327990532 + 0.552684307098 + 0.553040742874 + 0.553397297859 + 0.553753972054 + 0.554110825062 + 0.554467737675 + 0.554824769497 + 0.555181980133 + 0.555539309978 + 0.555896759033 + 0.556254327297 + 0.556612014771 + 0.556969821453 + 0.55732780695 + 0.557685852051 + 0.558044075966 + 0.558402359486 + 0.558760821819 + 0.559119403362 + 0.559478104115 + 0.559836983681 + 0.560195922852 + 0.560554981232 + 0.560914218426 + 0.561273574829 + 0.561633050442 + 0.561992645264 + 0.562352359295 + 0.562712192535 + 0.563072144985 + 0.563432276249 + 0.563792467117 + 0.5641528368 + 0.564513325691 + 0.564873933792 + 0.565234661102 + 0.565595507622 + 0.565956473351 + 0.566317617893 + 0.566678822041 + 0.567040205002 + 0.567401707172 + 0.567763328552 + 0.568125069141 + 0.56848692894 + 0.568848967552 + 0.569211065769 + 0.5695733428 + 0.569935679436 + 0.570298194885 + 0.570660829544 + 0.571023583412 + 0.571386516094 + 0.571749508381 + 0.572112619877 + 0.572475910187 + 0.572839319706 + 0.573202848434 + 0.573566496372 + 0.573930263519 + 0.574294149876 + 0.574658155441 + 0.575022339821 + 0.57538664341 + 0.575751006603 + 0.576115548611 + 0.576480209827 + 0.576845049858 + 0.577209949493 + 0.577574968338 + 0.577940165997 + 0.57830542326 + 0.578670859337 + 0.579036414623 + 0.579402089119 + 0.579767882824 + 0.580133855343 + 0.580499887466 + 0.580866098404 + 0.581232428551 + 0.581598818302 + 0.581965386868 + 0.582332134247 + 0.582698941231 + 0.583065867424 + 0.583432972431 + 0.583800137043 + 0.584167480469 + 0.584534943104 + 0.584902524948 + 0.585270226002 + 0.585638046265 + 0.586006045341 + 0.586374104023 + 0.586742341518 + 0.587110698223 + 0.587479174137 + 0.58784776926 + 0.588216483593 + 0.58858537674 + 0.588954329491 + 0.589323461056 + 0.58969271183 + 0.590062022209 + 0.590431571007 + 0.590801179409 + 0.591170907021 + 0.591540753841 + 0.591910779476 + 0.59228092432 + 0.592651188374 + 0.593021571636 + 0.593392074108 + 0.593762695789 + 0.59413343668 + 0.594504356384 + 0.594875395298 + 0.595246493816 + 0.595617771149 + 0.59598916769 + 0.596360743046 + 0.596732378006 + 0.597104132175 + 0.597476065159 + 0.597848117352 + 0.598220288754 + 0.598592579365 + 0.598964989185 + 0.599337518215 + 0.599710166454 + 0.600082993507 + 0.60045593977 + 0.600829005241 + 0.601202189922 + 0.601575493813 + 0.601948916912 + 0.602322459221 + 0.602696180344 + 0.603070020676 + 0.603443920612 + 0.603817999363 + 0.604192256927 + 0.604566574097 + 0.604941010475 + 0.605315625668 + 0.605690300465 + 0.606065154076 + 0.606440126896 + 0.606815218925 + 0.607190430164 + 0.607565820217 + 0.607941269875 + 0.608316898346 + 0.608692646027 + 0.609068512917 + 0.609444499016 + 0.609820604324 + 0.610196828842 + 0.610573232174 + 0.61094969511 + 0.611326336861 + 0.61170309782 + 0.612079977989 + 0.612456977367 + 0.612834095955 + 0.613211393356 + 0.613588809967 + 0.613966286182 + 0.614343941212 + 0.61472171545 + 0.615099668503 + 0.61547768116 + 0.615855813026 + 0.616234123707 + 0.616612553596 + 0.616991102695 + 0.617369771004 + 0.617748558521 + 0.618127465248 + 0.618506550789 + 0.618885695934 + 0.619265019894 + 0.619644463062 + 0.62002402544 + 0.620403707027 + 0.620783567429 + 0.621163487434 + 0.621543586254 + 0.621923804283 + 0.622304081917 + 0.622684597969 + 0.623065173626 + 0.623445868492 + 0.623826742172 + 0.624207675457 + 0.624588787556 + 0.624970018864 + 0.625351369381 + 0.625732839108 + 0.626114487648 + 0.626496195793 + 0.626878082752 + 0.627260088921 + 0.627642214298 + 0.628024458885 + 0.628406822681 + 0.628789365292 + 0.629171967506 + 0.629554748535 + 0.629937648773 + 0.630320668221 + 0.630703806877 + 0.631087064743 + 0.631470501423 + 0.631854057312 + 0.632237672806 + 0.632621467113 + 0.63300538063 + 0.633389413357 + 0.633773624897 + 0.634157896042 + 0.634542346001 + 0.634926915169 + 0.635311603546 + 0.635696411133 + 0.636081337929 + 0.636466443539 + 0.636851608753 + 0.637236952782 + 0.637622416019 + 0.638007998466 + 0.638393700123 + 0.638779520988 + 0.639165520668 + 0.639551579952 + 0.63993781805 + 0.640324175358 + 0.640710651875 + 0.641097247601 + 0.641484022141 + 0.641870856285 + 0.642257869244 + 0.642645001411 + 0.643032252789 + 0.643419623375 + 0.643807113171 + 0.644194722176 + 0.644582509995 + 0.644970417023 + 0.64535844326 + 0.645746588707 + 0.646134853363 + 0.646523237228 + 0.646911799908 + 0.647300481796 + 0.647689223289 + 0.648078143597 + 0.648467183113 + 0.648856401443 + 0.649245679379 + 0.649635136127 + 0.650024712086 + 0.650414347649 + 0.65080422163 + 0.651194155216 + 0.651584208012 + 0.651974439621 + 0.652364730835 + 0.652755200863 + 0.6531457901 + 0.653536498547 + 0.653927385807 + 0.654318332672 + 0.654709458351 + 0.655100703239 + 0.655492007732 + 0.655883550644 + 0.65627515316 + 0.656666874886 + 0.657058775425 + 0.657450735569 + 0.657842874527 + 0.658235132694 + 0.658627569675 + 0.659020066261 + 0.659412682056 + 0.659805476665 + 0.660198390484 + 0.660591423512 + 0.660984575748 + 0.661377847195 + 0.661771297455 + 0.66216480732 + 0.662558495998 + 0.662952303886 + 0.663346230984 + 0.66374027729 + 0.664134502411 + 0.664528787136 + 0.664923250675 + 0.665317833424 + 0.665712535381 + 0.666107356548 + 0.666502356529 + 0.666897416115 + 0.667292654514 + 0.667688012123 + 0.668083488941 + 0.668479084969 + 0.668874800205 + 0.669270694256 + 0.669666707516 + 0.67006278038 + 0.670459032059 + 0.670855402946 + 0.671251952648 + 0.671648561954 + 0.672045350075 + 0.672442257404 + 0.672839283943 + 0.673236429691 + 0.673633694649 + 0.674031078815 + 0.674428641796 + 0.674826323986 + 0.675224125385 + 0.675622045994 + 0.676020085812 + 0.676418304443 + 0.67681658268 + 0.67721503973 + 0.67761361599 + 0.678012311459 + 0.678411126137 + 0.678810119629 + 0.679209172726 + 0.679608404636 + 0.680007755756 + 0.680407226086 + 0.680806815624 + 0.681206524372 + 0.681606411934 + 0.682006418705 + 0.682406544685 + 0.682806789875 + 0.683207154274 + 0.683607637882 + 0.684008300304 + 0.684409081936 + 0.684809923172 + 0.685210943222 + 0.685612142086 + 0.686013400555 + 0.686414837837 + 0.686816334724 + 0.687218010426 + 0.687619805336 + 0.688021719456 + 0.688423812389 + 0.688825964928 + 0.68922829628 + 0.689630746841 + 0.690033316612 + 0.690436005592 + 0.690838873386 + 0.691241800785 + 0.691644906998 + 0.69204813242 + 0.692451477051 + 0.692854940891 + 0.693258583546 + 0.693662285805 + 0.694066166878 + 0.69447016716 + 0.694874286652 + 0.695278525352 + 0.695682942867 + 0.696087419987 + 0.69649207592 + 0.696896851063 + 0.697301745415 + 0.697706758976 + 0.698111951351 + 0.698517203331 + 0.698922634125 + 0.699328184128 + 0.69973385334 + 0.700139641762 + 0.700545608997 + 0.700951635838 + 0.701357841492 + 0.701764166355 + 0.702170610428 + 0.702577233315 + 0.702983915806 + 0.703390777111 + 0.703797757626 + 0.704204857349 + 0.704612076283 + 0.705019414425 + 0.705426931381 + 0.705834507942 + 0.706242263317 + 0.706650137901 + 0.707058191299 + 0.707466304302 + 0.707874596119 + 0.70828294754 + 0.708691477776 + 0.70910012722 + 0.709508955479 + 0.709917843342 + 0.710326910019 + 0.710736036301 + 0.711145341396 + 0.711554765701 + 0.71196436882 + 0.712374031544 + 0.712783873081 + 0.713193833828 + 0.713603913784 + 0.714014112949 + 0.714424431324 + 0.714834928513 + 0.715245485306 + 0.715656220913 + 0.716067075729 + 0.71647810936 + 0.716889202595 + 0.717300474644 + 0.717711806297 + 0.718123316765 + 0.718534946442 + 0.718946754932 + 0.719358623028 + 0.719770669937 + 0.720182836056 + 0.720595121384 + 0.721007525921 + 0.721420049667 + 0.721832752228 + 0.722245514393 + 0.722658455372 + 0.72307151556 + 0.723484754562 + 0.723898053169 + 0.72431153059 + 0.724725067616 + 0.725138783455 + 0.725552618504 + 0.725966632366 + 0.726380705833 + 0.726794958115 + 0.727209329605 + 0.727623820305 + 0.728038430214 + 0.728453159332 + 0.728868067265 + 0.729283094406 + 0.729698240757 + 0.730113506317 + 0.730528891087 + 0.73094445467 + 0.731360077858 + 0.73177587986 + 0.732191801071 + 0.732607841492 + 0.733024060726 + 0.733440339565 + 0.733856797218 + 0.734273374081 + 0.734690070152 + 0.735106885433 + 0.735523879528 + 0.735940933228 + 0.736358165741 + 0.736775517464 + 0.737192988396 + 0.737610638142 + 0.738028347492 + 0.738446235657 + 0.738864243031 + 0.739282369614 + 0.739700615406 + 0.740119040012 + 0.740537524223 + 0.740956187248 + 0.741374969482 + 0.741793870926 + 0.742212951183 + 0.742632091045 + 0.743051409721 + 0.743470847607 + 0.743890404701 + 0.744310081005 + 0.744729936123 + 0.74514991045 + 0.745569944382 + 0.745990216732 + 0.746410548687 + 0.746830999851 + 0.747251629829 + 0.747672379017 + 0.748093247414 + 0.74851423502 + 0.748935341835 + 0.749356627464 + 0.749777972698 + 0.750199496746 + 0.750621140003 + 0.75104290247 + 0.75146484375 + 0.75188690424 + 0.752309024334 + 0.752731323242 + 0.753153800964 + 0.753576338291 + 0.753999054432 + 0.754421830177 + 0.754844784737 + 0.755267858505 + 0.755691111088 + 0.756114423275 + 0.756537914276 + 0.756961524487 + 0.757385253906 + 0.757809102535 + 0.758233129978 + 0.758657217026 + 0.759081482887 + 0.759505867958 + 0.759930372238 + 0.760355055332 + 0.760779798031 + 0.761204719543 + 0.761629760265 + 0.762054920197 + 0.762480258942 + 0.762905657291 + 0.763331234455 + 0.763756930828 + 0.76418274641 + 0.764608681202 + 0.765034794807 + 0.765460968018 + 0.765887320042 + 0.766313791275 + 0.766740381718 + 0.767167150974 + 0.76759403944 + 0.768020987511 + 0.768448114395 + 0.768875420094 + 0.769302785397 + 0.769730329514 + 0.770157933235 + 0.770585715771 + 0.771013617516 + 0.771441698074 + 0.771869838238 + 0.772298157215 + 0.772726595402 + 0.773155152798 + 0.773583829403 + 0.774012684822 + 0.774441659451 + 0.774870753288 + 0.775299966335 + 0.775729298592 + 0.776158750057 + 0.776588380337 + 0.777018129826 + 0.777447998524 + 0.777877986431 + 0.778308153152 + 0.778738379478 + 0.779168784618 + 0.779599308968 + 0.780029952526 + 0.780460774899 + 0.780891656876 + 0.781322717667 + 0.781753897667 + 0.782185196877 + 0.7826166749 + 0.783048212528 + 0.78347992897 + 0.783911764622 + 0.784343719482 + 0.784775853157 + 0.785208046436 + 0.78564041853 + 0.786072909832 + 0.786505520344 + 0.786938250065 + 0.7873711586 + 0.787804186344 + 0.788237333298 + 0.788670599461 + 0.789103984833 + 0.789537549019 + 0.78997117281 + 0.790404975414 + 0.790838897228 + 0.791272997856 + 0.791707158089 + 0.792141497135 + 0.792575955391 + 0.793010532856 + 0.79344522953 + 0.793880105019 + 0.794315099716 + 0.794750154018 + 0.795185446739 + 0.795620799065 + 0.796056270599 + 0.796491920948 + 0.796927690506 + 0.797363579273 + 0.79779958725 + 0.79823577404 + 0.79867208004 + 0.799108445644 + 0.799545049667 + 0.799981713295 + 0.800418496132 + 0.800855457783 + 0.801292538643 + 0.801729738712 + 0.802167057991 + 0.802604556084 + 0.803042173386 + 0.803479850292 + 0.803917765617 + 0.804355740547 + 0.804793834686 + 0.805232107639 + 0.805670499802 + 0.806109011173 + 0.806547641754 + 0.806986451149 + 0.807425379753 + 0.807864427567 + 0.808303594589 + 0.808742880821 + 0.809182345867 + 0.809621870518 + 0.810061573982 + 0.810501396656 + 0.810941398144 + 0.811381459236 + 0.811821699142 + 0.812262058258 + 0.812702536583 + 0.813143134117 + 0.813583910465 + 0.814024806023 + 0.814465820789 + 0.814906954765 + 0.815348207951 + 0.81578963995 + 0.816231191158 + 0.816672861576 + 0.817114651203 + 0.81755656004 + 0.81799864769 + 0.818440854549 + 0.818883180618 + 0.819325625896 + 0.819768190384 + 0.820210933685 + 0.820653796196 + 0.821096777916 + 0.821539878845 + 0.821983158588 + 0.822426497936 + 0.822870016098 + 0.823313653469 + 0.823757410049 + 0.824201345444 + 0.824645400047 + 0.825089514256 + 0.825533866882 + 0.825978279114 + 0.826422810555 + 0.826867520809 + 0.827312350273 + 0.827757298946 + 0.828202426434 + 0.828647613525 + 0.829092979431 + 0.829538464546 + 0.829984068871 + 0.830429792404 + 0.830875694752 + 0.831321716309 + 0.831767857075 + 0.83221411705 + 0.83266055584 + 0.833107054234 + 0.833553731441 + 0.834000527859 + 0.834447443485 + 0.834894537926 + 0.835341751575 + 0.83578902483 + 0.836236536503 + 0.83668410778 + 0.837131798267 + 0.837579667568 + 0.838027656078 + 0.838475763798 + 0.838924050331 + 0.839372396469 + 0.839820921421 + 0.840269565582 + 0.840718328953 + 0.841167271137 + 0.841616272926 + 0.842065453529 + 0.842514753342 + 0.842964231968 + 0.843413770199 + 0.843863487244 + 0.844313323498 + 0.844763278961 + 0.845213353634 + 0.845663607121 + 0.846113920212 + 0.846564412117 + 0.847015082836 + 0.84746581316 + 0.847916722298 + 0.84836769104 + 0.848818838596 + 0.849270164967 + 0.849721550941 + 0.85017311573 + 0.850624799728 + 0.851076602936 + 0.851528525352 + 0.851980626583 + 0.852432787418 + 0.852885127068 + 0.853337645531 + 0.853790223598 + 0.85424298048 + 0.854695796967 + 0.855148792267 + 0.855601966381 + 0.8560552001 + 0.856508612633 + 0.856962144375 + 0.857415795326 + 0.857869565487 + 0.858323514462 + 0.858777523041 + 0.859231710434 + 0.859686076641 + 0.860140502453 + 0.860595107079 + 0.861049771309 + 0.861504614353 + 0.861959636211 + 0.862414717674 + 0.862869977951 + 0.863325357437 + 0.863780856133 + 0.864236474037 + 0.864692270756 + 0.865148186684 + 0.865604221821 + 0.866060376167 + 0.866516649723 + 0.866973102093 + 0.867429673672 + 0.86788636446 + 0.868343174458 + 0.868800163269 + 0.86925727129 + 0.86971449852 + 0.870171844959 + 0.870629310608 + 0.87108695507 + 0.871544718742 + 0.872002601624 + 0.872460603714 + 0.872918725014 + 0.873377025127 + 0.87383544445 + 0.874293982983 + 0.874752700329 + 0.87521147728 + 0.875670433044 + 0.876129508018 + 0.876588702202 + 0.877048075199 + 0.877507567406 + 0.877967119217 + 0.878426909447 + 0.878886759281 + 0.87934678793 + 0.879806876183 + 0.88026714325 + 0.88072758913 + 0.881188094616 + 0.881648778915 + 0.882109582424 + 0.882570505142 + 0.88303154707 + 0.883492767811 + 0.883954107761 + 0.884415566921 + 0.88487714529 + 0.885338842869 + 0.885800719261 + 0.886262714863 + 0.886724829674 + 0.887187063694 + 0.887649476528 + 0.888112008572 + 0.888574659824 + 0.889037430286 + 0.889500379562 + 0.889963388443 + 0.890426576138 + 0.890889883041 + 0.891353368759 + 0.891816914082 + 0.892280638218 + 0.892744481564 + 0.893208444118 + 0.893672585487 + 0.894136846066 + 0.894601225853 + 0.89506572485 + 0.895530343056 + 0.895995140076 + 0.896460056305 + 0.896925091743 + 0.897390246391 + 0.897855520248 + 0.898320972919 + 0.8987865448 + 0.899252235889 + 0.899718105793 + 0.900184035301 + 0.900650143623 + 0.901116371155 + 0.9015827775 + 0.90204924345 + 0.902515888214 + 0.902982652187 + 0.90344953537 + 0.903916597366 + 0.904383718967 + 0.904851019382 + 0.905318498611 + 0.905786037445 + 0.906253755093 + 0.906721532345 + 0.907189488411 + 0.907657623291 + 0.908125817776 + 0.908594191074 + 0.909062683582 + 0.9095312953 + 0.910000085831 + 0.910468935966 + 0.910937964916 + 0.911407113075 + 0.911876440048 + 0.912345826626 + 0.912815392017 + 0.913285076618 + 0.913754880428 + 0.914224863052 + 0.914694905281 + 0.915165126324 + 0.91563552618 + 0.916105985641 + 0.916576623917 + 0.917047321796 + 0.917518258095 + 0.917989253998 + 0.91846036911 + 0.918931663036 + 0.919403076172 + 0.919874608517 + 0.920346319675 + 0.920818150043 + 0.921290099621 + 0.921762168407 + 0.922234356403 + 0.922706723213 + 0.923179209232 + 0.923651814461 + 0.924124538898 + 0.92459744215 + 0.925070464611 + 0.925543606281 + 0.926016867161 + 0.92649024725 + 0.926963806152 + 0.927437484264 + 0.927911281586 + 0.928385257721 + 0.928859293461 + 0.929333508015 + 0.929807841778 + 0.930282354355 + 0.930756926537 + 0.931231677532 + 0.931706547737 + 0.932181596756 + 0.932656705379 + 0.933131992817 + 0.933607399464 + 0.93408292532 + 0.93455862999 + 0.935034394264 + 0.935510337353 + 0.935986399651 + 0.936462640762 + 0.936938941479 + 0.937415421009 + 0.937892019749 + 0.938368797302 + 0.93884563446 + 0.939322650433 + 0.939799785614 + 0.940277099609 + 0.940754473209 + 0.941232025623 + 0.941709697247 + 0.942187488079 + 0.942665457726 + 0.943143486977 + 0.943621695042 + 0.944100022316 + 0.944578528404 + 0.945057153702 + 0.945535838604 + 0.946014761925 + 0.94649374485 + 0.94697290659 + 0.947452127934 + 0.947931528091 + 0.948411107063 + 0.94889074564 + 0.94937056303 + 0.94985049963 + 0.950330555439 + 0.950810790062 + 0.951291143894 + 0.951771616936 + 0.952252209187 + 0.952732920647 + 0.953213810921 + 0.953694820404 + 0.954175949097 + 0.954657256603 + 0.955138623714 + 0.95562016964 + 0.956101834774 + 0.956583678722 + 0.957065582275 + 0.957547664642 + 0.958029866219 + 0.958512246609 + 0.958994686604 + 0.959477305412 + 0.95996004343 + 0.960442960262 + 0.960925936699 + 0.961409091949 + 0.961892366409 + 0.962375760078 + 0.962859332561 + 0.963342964649 + 0.963826775551 + 0.964310765266 + 0.964794814587 + 0.965279042721 + 0.965763390064 + 0.966247856617 + 0.966732442379 + 0.967217206955 + 0.96770209074 + 0.968187093735 + 0.968672275543 + 0.969157516956 + 0.969642937183 + 0.97012847662 + 0.97061419487 + 0.971099972725 + 0.971585929394 + 0.972072005272 + 0.972558259964 + 0.973044574261 + 0.973531067371 + 0.974017679691 + 0.974504470825 + 0.974991321564 + 0.975478351116 + 0.975965499878 + 0.976452767849 + 0.976940214634 + 0.977427780628 + 0.977915465832 + 0.978403270245 + 0.978891253471 + 0.979379296303 + 0.979867517948 + 0.980355918407 + 0.980844378471 + 0.981333017349 + 0.981821775436 + 0.982310652733 + 0.982799708843 + 0.983288884163 + 0.983778178692 + 0.98426759243 + 0.984757125378 + 0.985246837139 + 0.98573666811 + 0.98622661829 + 0.986716747284 + 0.987206935883 + 0.987697303295 + 0.988187849522 + 0.988678455353 + 0.989169239998 + 0.989660143852 + 0.990151166916 + 0.990642309189 + 0.991133630276 + 0.991625070572 + 0.992116630077 + 0.992608368397 + 0.993100166321 + 0.993592143059 + 0.994084239006 + 0.994576513767 + 0.995068848133 + 0.995561361313 + 0.996054053307 + 0.996546804905 + 0.997039735317 + 0.997532784939 + 0.99802595377 + 0.99851924181 + 0.999012708664 + 0.999506294727 + 1.0 +} diff --git a/app/task/export/exportparams.cpp b/app/task/export/exportparams.cpp index dfe79686d..f866f3cbd 100644 --- a/app/task/export/exportparams.cpp +++ b/app/task/export/exportparams.cpp @@ -54,16 +54,6 @@ void ExportParams::set_video_scaling_method(const ExportParams::VideoScalingMeth video_scaling_method_ = video_scaling_method; } -const ColorTransform &ExportParams::color_transform() const -{ - return color_transform_; -} - -void ExportParams::set_color_transform(const ColorTransform &color_transform) -{ - color_transform_ = color_transform; -} - QMatrix4x4 ExportParams::GenerateMatrix(ExportParams::VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height) @@ -103,7 +93,7 @@ void ExportParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("customrangeout"), custom_range_.out().toString()); // FIXME: Change this when color chains are implemented - writer->writeTextElement(QStringLiteral("color"), color_transform_.output()); + writer->writeTextElement(QStringLiteral("color"), color_transform().output()); EncodingParams::Save(writer); diff --git a/app/task/export/exportparams.h b/app/task/export/exportparams.h index 46f213f9c..437271eab 100644 --- a/app/task/export/exportparams.h +++ b/app/task/export/exportparams.h @@ -46,9 +46,6 @@ public: const VideoScalingMethod& video_scaling_method() const; void set_video_scaling_method(const VideoScalingMethod& video_scaling_method); - const ColorTransform& color_transform() const; - void set_color_transform(const ColorTransform& color_transform); - static QMatrix4x4 GenerateMatrix(ExportParams::VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height); @@ -61,8 +58,6 @@ private: bool has_custom_range_; TimeRange custom_range_; - ColorTransform color_transform_; - }; } From a09d2f41e920547e44c80673402938861387b90a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 12:40:00 -0700 Subject: [PATCH 058/107] opengl: disable mutex Shouldn't really need this anymore and it fixes a deadlock --- app/render/opengl/openglrenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 014c124bc..8c6f2a703 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -78,9 +78,9 @@ private: #define PRINT_GL_ERRORS ErrorPrinter __e(__FUNCTION__, functions_) #define GL_PREAMBLE \ - QMutexLocker __l(&global_opengl_mutex); + //QMutexLocker __l(&global_opengl_mutex); -QMutex global_opengl_mutex; +//QMutex global_opengl_mutex; OpenGLRenderer::OpenGLRenderer(QObject* parent) : Renderer(parent), From 66152b4aa3d5d6a8a81aae0151d949dc2365a812 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 12:50:16 -0700 Subject: [PATCH 059/107] renderer: clear texture cache after deleting interlace texture --- app/render/renderer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 37294759a..85f3aed69 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -109,11 +109,6 @@ QVariant Renderer::GetDefaultShader() void Renderer::Destroy() { - for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { - DestroyNativeTexture(it->handle); - } - texture_cache_.clear(); - if (!default_shader_.isNull()) { DestroyNativeShader(default_shader_); default_shader_.clear(); @@ -126,6 +121,11 @@ void Renderer::Destroy() interlace_texture_.clear(); } + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { + DestroyNativeTexture(it->handle); + } + texture_cache_.clear(); + DestroyInternal(); } From f0ebbda16c8db4fa0381fe7e00e143d7bd595b09 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 14:08:49 -0700 Subject: [PATCH 060/107] viewer: fix invalid remaining length when queuing --- app/widget/viewer/viewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 1b023cefb..18d086386 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1048,7 +1048,7 @@ int ViewerWidget::DeterminePlaybackQueueSize() end_ts = 0; } - int remaining_frames = (end_ts - GetTimestamp()) / playback_speed_; + int remaining_frames = (end_ts - GetTimestamp() - 1) / playback_speed_; // Generate maximum queue int max_frames = qCeil(kVideoPlaybackInterval.toDouble() / timebase().toDouble()); From d1ec2b909b8a4eeb29bbec438174b6e8cf4125b3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 14:41:01 -0700 Subject: [PATCH 061/107] cmake: default werror and tests to off, and enable on CI --- .github/workflows/ci.yml | 10 +++++++--- CMakeLists.txt | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 857057649..8841062c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,7 @@ on: env: DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60 UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60 + CMAKE_ARGS: -DUSE_WERROR=ON -DBUILD_TESTS=ON jobs: linux: @@ -73,7 +74,8 @@ jobs: cmake .. -G "${{ matrix.cmake-gen }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DCMAKE_C_COMPILER="${{ matrix.cc-compiler }}" \ - -DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" + -DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" \ + $CMAKE_ARGS - name: Build working-directory: build @@ -204,7 +206,8 @@ jobs: working-directory: ${{ runner.workspace }}/build run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" \ - -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" + -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \ + $CMAKE_ARGS - name: Build working-directory: ${{ runner.workspace }}/build @@ -356,7 +359,8 @@ jobs: PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \ - -DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" + -DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" \ + $CMAKE_ARGS - name: Build working-directory: ${{ runner.workspace }}/build diff --git a/CMakeLists.txt b/CMakeLists.txt index b46d11f6f..1206118dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,8 +19,8 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR) project(olive-editor VERSION 0.2.0 LANGUAGES CXX) option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) -option(BUILD_TESTS "Build unit tests" ON) -option(USE_WERROR "Error on compile warning" ON) +option(BUILD_TESTS "Build unit tests" OFF) +option(USE_WERROR "Error on compile warning" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) From 25e94112f8723266799b5d35ab0211e747db7ea7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Jul 2022 14:49:19 -0700 Subject: [PATCH 062/107] viewer: implement lookahead to ensure footage is ready in advance --- app/codec/decoder.cpp | 6 ++ app/codec/decoder.h | 2 + app/render/previewautocacher.cpp | 13 ++-- app/render/previewautocacher.h | 8 +-- app/render/rendermanager.cpp | 14 +++- app/render/rendermanager.h | 6 +- app/render/renderprocessor.cpp | 107 ++++++++++++++++--------------- app/widget/viewer/viewer.cpp | 36 ++++++++++- app/widget/viewer/viewer.h | 7 ++ 9 files changed, 137 insertions(+), 62 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index fd845737e..c98cb5c2a 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -43,6 +43,12 @@ Decoder::Decoder() : UpdateLastAccessed(); } +void Decoder::IncrementAccessTime(qint64 t) +{ + QMutexLocker locker(&mutex_); + last_accessed_ += t; +} + bool Decoder::Open(const CodecStream &stream) { QMutexLocker locker(&mutex_); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 08ccf6e32..665f21cc8 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -87,6 +87,8 @@ public: virtual bool SupportsVideo(){return false;} virtual bool SupportsAudio(){return false;} + void IncrementAccessTime(qint64 t); + class CodecStream { public: diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index d5707c845..e876ddafa 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -61,7 +61,7 @@ PreviewAutoCacher::~PreviewAutoCacher() SetViewerNode(nullptr); } -RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t) +RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry) { // If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now CancelQueuedSingleFrameRender(); @@ -70,6 +70,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t) auto sfr = std::make_shared(); sfr->Start(); sfr->setProperty("time", QVariant::fromValue(t)); + sfr->setProperty("dry", dry); // Queue it and try to render single_frame_render_ = sfr; @@ -566,7 +567,8 @@ void PreviewAutoCacher::TryRender() if (single_frame_render_) { // Check if already caching this RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), - nullptr); + nullptr, + single_frame_render_->property("dry").toBool()); video_immediate_passthroughs_[watcher].append(single_frame_render_); single_frame_render_ = nullptr; @@ -583,7 +585,7 @@ void PreviewAutoCacher::TryRender() // We want this hash, if we're not already rendering, start render now if (!render_task) { // Don't render any hash more than once - RenderFrame(t, viewer_node_->video_frame_cache()); + RenderFrame(t, viewer_node_->video_frame_cache(), false); } emit SignalCacheProxyTaskProgress(double(queued_frame_iterator_.frame_index()) / double(queued_frame_iterator_.size())); @@ -609,7 +611,7 @@ void PreviewAutoCacher::TryRender() } } -RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, FrameHashCache *cache) +RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, FrameHashCache *cache, bool dry) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); @@ -623,7 +625,8 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, RenderMode::kOffline, cache, - RenderManager::kTexture)); + dry ? RenderManager::kNull : RenderManager::kTexture)); + return watcher; } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index c9343021f..0ce429673 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -49,7 +49,7 @@ public: virtual ~PreviewAutoCacher() override; - RenderTicketPtr GetSingleFrame(const rational& t); + RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false); RenderTicketPtr GetRangeOfAudio(TimeRange range); @@ -98,10 +98,10 @@ signals: private: void TryRender(); - RenderTicketWatcher *RenderFrame(Node *node, const rational &time, FrameHashCache *cache); - RenderTicketWatcher *RenderFrame(const rational &time, FrameHashCache *cache) + RenderTicketWatcher *RenderFrame(Node *node, const rational &time, FrameHashCache *cache, bool dry); + RenderTicketWatcher *RenderFrame(const rational &time, FrameHashCache *cache, bool dry) { - return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, cache); + return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, cache, dry); } RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, bool generate_waveforms); diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 202820be5..1df5590b3 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -35,6 +35,7 @@ namespace olive { RenderManager* RenderManager::instance_ = nullptr; +const rational RenderManager::kDryRunInterval = rational(10); RenderManager::RenderManager(QObject *parent) : backend_(kOpenGL), @@ -52,9 +53,11 @@ RenderManager::RenderManager(QObject *parent) : if (context_) { video_thread_ = new RenderThread(context_, decoder_cache_, shader_cache_, this); + dry_run_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); audio_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); video_thread_->start(QThread::IdlePriority); + dry_run_thread_->start(QThread::IdlePriority); audio_thread_->start(QThread::IdlePriority); } @@ -73,6 +76,9 @@ RenderManager::~RenderManager() video_thread_->quit(); video_thread_->wait(); + dry_run_thread_->quit(); + dry_run_thread_->wait(); + context_->PostDestroy(); delete context_; @@ -129,7 +135,11 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag ticket->setProperty("cacheuuid", QVariant::fromValue(cache->GetUuid())); } - video_thread_->AddTicket(ticket); + if (return_type == ReturnType::kNull) { + dry_run_thread_->AddTicket(ticket); + } else { + video_thread_->AddTicket(ticket); + } return ticket; } @@ -157,6 +167,8 @@ bool RenderManager::RemoveTicket(RenderTicketPtr ticket) return true; } else if (audio_thread_->RemoveTicket(ticket)) { return true; + } else if (dry_run_thread_->RemoveTicket(ticket)) { + return true; } else { return false; } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index a75c58aaf..891e2fffc 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -97,9 +97,12 @@ public: enum ReturnType { kTexture, - kFrame + kFrame, + kNull }; + static const rational kDryRunInterval; + /** * @brief Asynchronously generate a frame at a given time * @@ -168,6 +171,7 @@ private: QTimer *decoder_clear_timer_; RenderThread *video_thread_; + RenderThread *dry_run_thread_; RenderThread *audio_thread_; private slots: diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 08a4fbdbc..0a6d78078 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -151,53 +151,57 @@ void RenderProcessor::Run() TexturePtr texture = GenerateTexture(time, frame_length); - if (GetCacheVideoParams().interlacing() != VideoParams::kInterlaceNone) { - // Get next between frame and interlace it - TexturePtr top = texture; - TexturePtr bottom = GenerateTexture(time + frame_length, frame_length); - - if (GetCacheVideoParams().interlacing() == VideoParams::kInterlacedBottomFirst) { - std::swap(top, bottom); - } - - texture = render_ctx_->InterlaceTexture(top, bottom, GetCacheVideoParams()); - } - - if (HeardCancel()) { - // Finish cancelled ticket with nothing since we can't guarantee the frame we generated - // is actually "complete + if (!render_ctx_) { ticket_->Finish(); } else { - RenderManager::ReturnType return_type = RenderManager::ReturnType(ticket_->property("return").toInt()); + if (GetCacheVideoParams().interlacing() != VideoParams::kInterlaceNone) { + // Get next between frame and interlace it + TexturePtr top = texture; + TexturePtr bottom = GenerateTexture(time + frame_length, frame_length); - FramePtr frame; - QString cache = ticket_->property("cache").toString(); - - if (return_type == RenderManager::kFrame || !cache.isEmpty()) { - // Convert to CPU frame - frame = GenerateFrame(texture, time); - - // Save to cache if requested - if (!cache.isEmpty()) { - rational timebase = ticket_->property("cachetimebase").value(); - QUuid uuid = ticket_->property("cacheuuid").value(); - bool cache_result = FrameHashCache::SaveCacheFrame(cache, uuid, time, timebase, frame); - ticket_->setProperty("cached", cache_result); + if (GetCacheVideoParams().interlacing() == VideoParams::kInterlacedBottomFirst) { + std::swap(top, bottom); } + + texture = render_ctx_->InterlaceTexture(top, bottom, GetCacheVideoParams()); } - if (return_type == RenderManager::kTexture) { - // Return GPU texture - if (!texture) { - texture = render_ctx_->CreateTexture(GetCacheVideoParams()); - render_ctx_->ClearDestination(texture.get()); + if (HeardCancel()) { + // Finish cancelled ticket with nothing since we can't guarantee the frame we generated + // is actually "complete + ticket_->Finish(); + } else { + RenderManager::ReturnType return_type = RenderManager::ReturnType(ticket_->property("return").toInt()); + + FramePtr frame; + QString cache = ticket_->property("cache").toString(); + + if (return_type == RenderManager::kFrame || !cache.isEmpty()) { + // Convert to CPU frame + frame = GenerateFrame(texture, time); + + // Save to cache if requested + if (!cache.isEmpty()) { + rational timebase = ticket_->property("cachetimebase").value(); + QUuid uuid = ticket_->property("cacheuuid").value(); + bool cache_result = FrameHashCache::SaveCacheFrame(cache, uuid, time, timebase, frame); + ticket_->setProperty("cached", cache_result); + } } - render_ctx_->Flush(); + if (return_type == RenderManager::kTexture) { + // Return GPU texture + if (!texture) { + texture = render_ctx_->CreateTexture(GetCacheVideoParams()); + render_ctx_->ClearDestination(texture.get()); + } - ticket_->Finish(QVariant::fromValue(texture)); - } else { - ticket_->Finish(QVariant::fromValue(frame)); + render_ctx_->Flush(); + + ticket_->Finish(QVariant::fromValue(texture)); + } else { + ticket_->Finish(QVariant::fromValue(frame)); + } } } break; @@ -265,6 +269,11 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c << "::" << stream.stream(); return nullptr; } + + if (!render_ctx_) { + // Assume dry run and increment access time + decoder.decoder->IncrementAccessTime(RenderManager::kDryRunInterval.toDouble() * 1000); + } } return decoder.decoder; @@ -404,10 +413,6 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) { - if (!render_ctx_) { - return; - } - if (ticket_->property("type").value() != RenderManager::kTypeVideo) { // Video cannot contribute to audio, so we do nothing here return; @@ -440,21 +445,23 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ break; case VideoParams::kVideoTypeImageSequence: { - // Since image sequences involve multiple files, we don't engage the decoder cache - decoder = Decoder::CreateFromID(decoder_id); + if (render_ctx_) { + // Since image sequences involve multiple files, we don't engage the decoder cache + decoder = Decoder::CreateFromID(decoder_id); - QString frame_filename; + QString frame_filename; - int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); - frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); + int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); + frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); - // Decoder will close automatically since it's a stream_ptr - decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock())); + // Decoder will close automatically since it's a stream_ptr + decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock())); + } break; } } - if (decoder) { + if (decoder && render_ctx_) { Decoder::RetrieveVideoParams p; p.divider = stream.video_params().divider(); p.maximum_format = destination->format(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 18d086386..c5635cc86 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -60,7 +60,7 @@ QVector ViewerWidget::instances_; // changing values. 1/4 second seems to be a good middleground. const rational ViewerWidget::kAudioPlaybackInterval = rational(1, 4); -const rational kVideoPlaybackInterval = rational(2); +const rational kVideoPlaybackInterval = rational(1, 2); ViewerWidget::ViewerWidget(QWidget *parent) : super(false, true, parent), @@ -560,6 +560,35 @@ void ViewerWidget::ShowSubtitleProperties() } } +void ViewerWidget::DryRunFinished() +{ + RenderTicketWatcher *w = static_cast(sender()); + + if (dry_run_watchers_.contains(w)) { + RequestNextDryRun(); + } + + delete w; +} + +void ViewerWidget::RequestNextDryRun() +{ + if (IsPlaying()) { + rational next_time = Timecode::timestamp_to_time(dry_run_next_frame_, timebase()); + if (FrameExistsAtTime(next_time)) { + if (next_time > GetTime() + RenderManager::kDryRunInterval) { + QTimer::singleShot(timebase().toDouble() / playback_speed_, this, &ViewerWidget::RequestNextDryRun); + } else { + RenderTicketWatcher *watcher = new RenderTicketWatcher(this); + connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::DryRunFinished); + watcher->SetTicket(auto_cacher_.GetSingleFrame(next_time, true)); + dry_run_next_frame_ += playback_speed_; + dry_run_watchers_.append(watcher); + } + } + } +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); @@ -828,6 +857,9 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) for (int i=0; i playback_devices_; bool prequeuing_video_; @@ -294,6 +295,8 @@ private: WaveformMode waveform_mode_; + QVector dry_run_watchers_; + private slots: void PlaybackTimerUpdate(); @@ -354,6 +357,10 @@ private slots: void ShowSubtitleProperties(); + void DryRunFinished(); + + void RequestNextDryRun(); + }; } From fb34a4b799b2ddb2e714be797dd33a67f3816b0d Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 27 Jul 2022 14:17:06 +0100 Subject: [PATCH 063/107] Reset transfer tag --- app/codec/ffmpeg/ffmpegencoder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 6103345ba..9793faa29 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -654,9 +654,10 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV if (params().format() == ExportFormat::Format::kFormatQuickTime) { // nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7 + // ffprobe -v error -show_format -show_streams "C:\Users\Tom\Documents\srgb correct tags.mov" if (params().color_transform().output().contains("sRGB")) { codec_ctx->color_primaries = AVCOL_PRI_BT709; - codec_ctx->color_trc = AVCOL_TRC_BT709; + codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1; codec_ctx->colorspace = AVCOL_SPC_BT709; } else { // Assume Rec.709 codec_ctx->color_primaries = AVCOL_PRI_BT709; From 3f6ee1dc1b14b765ec85a777b93ee44b244711a5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 10:03:01 -0700 Subject: [PATCH 064/107] timeline: make timecode secondary tab item Fixes usability issue where maximizing the timeline panel would focus the timecode label and make it impossible to unmaximize it using a keyboard shortcut --- app/widget/timelinewidget/timelinewidget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index a293545fb..033c50fa8 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -86,6 +86,9 @@ TimelineWidget::TimelineWidget(QWidget *parent) : ruler_and_time_layout->addWidget(ruler()); + ruler()->setFocusPolicy(Qt::TabFocus); + QWidget::setTabOrder(ruler(), timecode_label_); + // Create list of TimelineViews - these MUST correspond to the ViewType enum view_splitter_ = new QSplitter(Qt::Vertical); From 301a773d62b4bf09bcd41a8b13af8f78c21c749d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 11:38:13 -0700 Subject: [PATCH 065/107] handmovableview: wrap cursor when hand dragging --- .../handmovableview/handmovableview.cpp | 26 ++++++++++++++++++- app/widget/handmovableview/handmovableview.h | 2 ++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index 25536ec7b..5ff36be0a 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -66,6 +66,8 @@ bool HandMovableView::HandPress(QMouseEvent *event) Qt::LeftButton, event->modifiers()); + transformed_pos_ = QPoint(0, 0); + super::mousePressEvent(&transformed); return true; @@ -78,12 +80,34 @@ bool HandMovableView::HandMove(QMouseEvent *event) { if (dragging_hand_) { // Transform mouse event to act like the left button is pressed + QPoint adjustment(0, 0); + QMouseEvent transformed(event->type(), - event->localPos(), + event->localPos() - transformed_pos_, Qt::LeftButton, Qt::LeftButton, event->modifiers()); + if (event->localPos().x() < 0) { + transformed_pos_.setX(transformed_pos_.x() + width()); + adjustment.setX(width()); + } else if (event->localPos().x() >= width()) { + transformed_pos_.setX(transformed_pos_.x() - width()); + adjustment.setX(-width()); + } + + if (event->pos().y() < 0) { + transformed_pos_.setY(transformed_pos_.y() + height()); + adjustment.setY(height()); + } else if (event->pos().y() >= height()) { + transformed_pos_.setY(transformed_pos_.y() - height()); + adjustment.setY(-height()); + } + + if (!adjustment.isNull()) { + QCursor::setPos(QCursor::pos() + adjustment); + } + super::mouseMoveEvent(&transformed); } return dragging_hand_; diff --git a/app/widget/handmovableview/handmovableview.h b/app/widget/handmovableview/handmovableview.h index e9579e7dd..c1f9220e8 100644 --- a/app/widget/handmovableview/handmovableview.h +++ b/app/widget/handmovableview/handmovableview.h @@ -77,6 +77,8 @@ private: */ bool scroll_zooms_by_default_; + QPointF transformed_pos_; + private slots: void ApplicationToolChanged(Tool::Item tool); From a8305dadb38e429a65399dd866ce19cf0d538c42 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 11:47:10 -0700 Subject: [PATCH 066/107] slider: only use one screen for wrapping --- app/widget/slider/base/sliderladder.cpp | 35 +++++++++++++++---------- app/widget/slider/base/sliderladder.h | 2 ++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app/widget/slider/base/sliderladder.cpp b/app/widget/slider/base/sliderladder.cpp index 747a14cf4..5c927f61a 100644 --- a/app/widget/slider/base/sliderladder.cpp +++ b/app/widget/slider/base/sliderladder.cpp @@ -77,6 +77,14 @@ SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values, QString drag_timer_.setInterval(10); connect(&drag_timer_, &QTimer::timeout, this, &SliderLadder::TimerUpdate); + screen_ = nullptr; + foreach (QScreen *screen, qApp->screens()) { + if (screen->geometry().contains(QCursor::pos())) { + screen_ = screen; + break; + } + } + if (UsingLadders()) { drag_start_x_ = -1; wrap_count_ = 0; @@ -198,21 +206,20 @@ void SliderLadder::TimerUpdate() emit DraggedByValue(now_pos - drag_start_x_, elements_.at(active_element_)->GetMultiplier()); // Determine if cursor is at desktop edge, if so wrap around to other side - int left = 0; - int right = 0; - foreach (QScreen *screen, qApp->screens()) { - left = qMin(left, screen->geometry().left()); - right = qMax(right, screen->geometry().right()); - } - if (now_pos == left || now_pos == right) { - if (now_pos == left) { - wrap_count_--; - now_pos = right-1; - } else { - wrap_count_++; - now_pos = left+1; + if (screen_) { + int left = screen_->geometry().left(); + int right = screen_->geometry().right(); + int width = right - left; + if (now_pos <= left || now_pos >= right) { + if (now_pos <= left) { + wrap_count_--; + now_pos += width; + } else { + wrap_count_++; + now_pos -= width; + } + QCursor::setPos(now_pos, QCursor::pos().y()); } - QCursor::setPos(now_pos, QCursor::pos().y()); } drag_start_x_ = now_pos; diff --git a/app/widget/slider/base/sliderladder.h b/app/widget/slider/base/sliderladder.h index 8426c1f22..16c1307ad 100644 --- a/app/widget/slider/base/sliderladder.h +++ b/app/widget/slider/base/sliderladder.h @@ -95,6 +95,8 @@ private: QTimer drag_timer_; + QScreen *screen_; + private slots: void TimerUpdate(); From 992f3fd3af94c906e6eb85432bae96276f5acf60 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:15:11 -0700 Subject: [PATCH 067/107] nodeview: increase hitbox of node connectors --- app/widget/nodeview/nodeview.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index e757e6031..c820c902d 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -432,7 +432,23 @@ void NodeView::mousePressEvent(QMouseEvent *event) if (HandPress(event)) return; // Get the item that the user clicked on, if any - QGraphicsItem* item = itemAt(event->pos()); + QGraphicsItem* item = nullptr; + { + // Prioritize connectors. I tried overriding boundingRect() and contains() on the connector + // object, but it ended up not working or causing other issues, so this is my hackier solution + const int radius = fontMetrics().height()/2; + QRect connector_rect(event->pos().x()-radius, event->pos().y()-radius, radius*2, radius*2); + QList items = this->items(connector_rect); + for (QGraphicsItem *i : items) { + if (dynamic_cast(i)) { + item = i; + break; + } + } + } + if (!item) { + item = itemAt(event->pos()); + } if (event->button() == Qt::LeftButton) { // Sane defaults From 183dc8c48ef5392f526f9f300322d0dca20aac79 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:40:27 -0700 Subject: [PATCH 068/107] nodeview: better implementation of larger connector hitboxes --- app/widget/nodeview/nodeview.cpp | 18 +----------------- app/widget/nodeview/nodeviewitem.cpp | 4 ++-- app/widget/nodeview/nodeviewitemconnector.cpp | 15 +++++++++++++++ app/widget/nodeview/nodeviewitemconnector.h | 3 +++ 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index c820c902d..e757e6031 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -432,23 +432,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) if (HandPress(event)) return; // Get the item that the user clicked on, if any - QGraphicsItem* item = nullptr; - { - // Prioritize connectors. I tried overriding boundingRect() and contains() on the connector - // object, but it ended up not working or causing other issues, so this is my hackier solution - const int radius = fontMetrics().height()/2; - QRect connector_rect(event->pos().x()-radius, event->pos().y()-radius, radius*2, radius*2); - QList items = this->items(connector_rect); - for (QGraphicsItem *i : items) { - if (dynamic_cast(i)) { - item = i; - break; - } - } - } - if (!item) { - item = itemAt(event->pos()); - } + QGraphicsItem* item = itemAt(event->pos()); if (event->button() == Qt::LeftButton) { // Sane defaults diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 3879df6bd..7c6a78cee 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -581,7 +581,7 @@ QPointF NodeViewItem::GetInputPoint() const QPointF NodeViewItem::GetOutputPoint() const { QPointF p = output_connector_->scenePos(); - QRectF r = output_connector_->boundingRect(); + QRectF r = output_connector_->polygon().boundingRect(); switch (flow_dir_) { case NodeViewCommon::kLeftToRight: @@ -628,7 +628,7 @@ void NodeViewItem::UpdateNodePosition() void NodeViewItem::UpdateInputConnectorPosition() { - QRectF output_rect = input_connector_->boundingRect(); + QRectF output_rect = input_connector_->polygon().boundingRect(); NodeViewCommon::FlowDirection using_flow_dir = flow_dir_; diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp index de723a36c..d8ad33fbc 100644 --- a/app/widget/nodeview/nodeviewitemconnector.cpp +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -81,4 +81,19 @@ void NodeViewItemConnector::SetFlowDirection(NodeViewCommon::FlowDirection dir) setPolygon(p); } +QPainterPath NodeViewItemConnector::shape() const +{ + // Yes, we skip QGraphicsPolygonItem because it adds the polygon. QGraphicsItem adds the + // boundingRect which we modify below + return QGraphicsItem::shape(); // clazy:exclude=skipped-base-method +} + +QRectF NodeViewItemConnector::boundingRect() const +{ + QRectF b = this->polygon().boundingRect(); + const int radius = QFontMetrics(QFont()).height()/2; + b.adjust(-radius, -radius, radius, radius); + return b; +} + } diff --git a/app/widget/nodeview/nodeviewitemconnector.h b/app/widget/nodeview/nodeviewitemconnector.h index b309f05e0..b207cb536 100644 --- a/app/widget/nodeview/nodeviewitemconnector.h +++ b/app/widget/nodeview/nodeviewitemconnector.h @@ -39,6 +39,9 @@ public: return output_; } + virtual QPainterPath shape() const override; + virtual QRectF boundingRect() const override; + private: bool output_; From 32a1d31bc0a20bf757053c44fced89c0ca08ba1a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:44:50 -0700 Subject: [PATCH 069/107] seekablewidget: add hand move events --- app/widget/timeruler/seekablewidget.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index ebea9a18e..8dc21dace 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -175,7 +175,9 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) { TimelineMarker *initial; - if (resize_item_) { + if (HandPress(event)) { + return; + } else if (resize_item_) { // Handle selection, even though we won't be using it for dragging if (!(event->modifiers() & Qt::ShiftModifier)) { selection_manager_.ClearSelection(); @@ -197,7 +199,9 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) void SeekableWidget::mouseMoveEvent(QMouseEvent *event) { - if (selection_manager_.IsDragging()) { + if (HandMove(event)) { + return; + } else if (selection_manager_.IsDragging()) { selection_manager_.DragMove(event); } else if (dragging_) { QPointF scene = mapToScene(event->pos()); @@ -218,6 +222,10 @@ void SeekableWidget::mouseMoveEvent(QMouseEvent *event) void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) { + if (HandRelease(event)) { + return; + } + if (selection_manager_.IsDragging()) { MultiUndoCommand *command = new MultiUndoCommand(); selection_manager_.DragStop(command); From 0d8fa001a1a59b5b7fcb9014707714f727471652 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:51:10 -0700 Subject: [PATCH 070/107] transformdistortnode: don't include parent in gizmo transform --- app/node/distort/transform/transformdistortnode.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index c839c93f0..ab58b3378 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -404,7 +404,8 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N QTransform TransformDistortNode::GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const { if (TexturePtr texture = row[kTextureInput].toTexture()) { - auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()); + //auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()); + auto m = GenerateMatrix(row, false, false, false, QMatrix4x4()); return GenerateAutoScaledMatrix(m, row, globals, texture->params()).toTransform(); } return super::GizmoTransformation(row, globals); From 91eaa759b79e08757ef337216d7d2c3eb0713755 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 13:39:27 -0700 Subject: [PATCH 071/107] ffmpegencoder: various improvements --- app/codec/ffmpeg/ffmpegencoder.cpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 9793faa29..2c9f87775 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -224,7 +224,6 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) encoded_frame->color_primaries = video_codec_ctx_->color_primaries; encoded_frame->colorspace = video_codec_ctx_->colorspace; - // Set interlacing if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) { encoded_frame->interlaced_frame = 1; @@ -652,19 +651,16 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV codec_ctx->rc_buffer_size = static_cast(params().video_buffer_size()); } - if (params().format() == ExportFormat::Format::kFormatQuickTime) { - // nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7 - // ffprobe -v error -show_format -show_streams "C:\Users\Tom\Documents\srgb correct tags.mov" - if (params().color_transform().output().contains("sRGB")) { - codec_ctx->color_primaries = AVCOL_PRI_BT709; - codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1; - codec_ctx->colorspace = AVCOL_SPC_BT709; - } else { // Assume Rec.709 - codec_ctx->color_primaries = AVCOL_PRI_BT709; - codec_ctx->color_trc = AVCOL_TRC_BT709; - codec_ctx->colorspace = AVCOL_SPC_BT709; - } - + // nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7 + // ffprobe -v error -show_format -show_streams "C:\Users\Tom\Documents\srgb correct tags.mov" + if (params().color_transform().output().contains(QStringLiteral("sRGB"), Qt::CaseInsensitive)) { + codec_ctx->color_primaries = AVCOL_PRI_BT709; + codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1; + codec_ctx->colorspace = AVCOL_SPC_RGB; + } else { // Assume Rec.709 + codec_ctx->color_primaries = AVCOL_PRI_BT709; + codec_ctx->color_trc = AVCOL_TRC_BT709; + codec_ctx->colorspace = AVCOL_SPC_BT709; } } From 02f40ad3b4cd51b8182169b21ea3fc545c280a68 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 27 Jul 2022 14:10:13 -0700 Subject: [PATCH 072/107] nodeparamview: fix issue detecting node's effect input Fixes #1988 --- app/widget/nodeparamview/nodeparamview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 8262c2949..cec7c16db 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -435,7 +435,7 @@ void NodeParamView::DeleteSelected() Node *n = item->GetNode(); Node *node_being_deleted = n; - Node *connected_to_effect_input = n; + Node *connected_to_effect_input = nullptr; while (true) { if (node_being_deleted->GetEffectInput().IsValid()) { From 1c9d86e03b12c1f5945f64323ed02a21008b4a2c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 27 Jul 2022 20:25:29 -0700 Subject: [PATCH 073/107] viewer: update waveform/viewer on construction --- app/widget/viewer/viewer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index c5635cc86..a11acbce5 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -149,6 +149,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) : setAcceptDrops(true); + UpdateWaveformViewFromMode(); + connect(Core::instance(), &Core::ColorPickerEnabled, this, &ViewerWidget::SetSignalCursorColorEnabled); connect(this, &ViewerWidget::CursorColor, Core::instance(), &Core::ColorPickerColorEmitted); connect(AudioManager::instance(), &AudioManager::OutputParamsChanged, this, &ViewerWidget::UpdateAudioProcessor); From ed744802c404b6ba729ea95bb1b6b3fa29579536 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 27 Jul 2022 22:57:46 -0700 Subject: [PATCH 074/107] viewer: disable QOpenGLWindow While there was a theoretical performance improvement, it seems there are irreparable compatibility issues that make it not worthwhile. This is probably an optimization that will have to come from a Vulkan backend. --- app/widget/manageddisplay/manageddisplay.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 10e805d77..644ab0711 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -21,7 +21,7 @@ #ifndef MANAGEDDISPLAYOBJECT_H #define MANAGEDDISPLAYOBJECT_H -#define USE_QOPENGLWINDOW +//#define USE_QOPENGLWINDOW #include #include From 9636ec202dc7aa4334b05aa2541bf8a1d0595474 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 28 Jul 2022 08:57:18 -0700 Subject: [PATCH 075/107] render: allow forcing channel count --- app/render/rendermanager.cpp | 3 +++ app/render/rendermanager.h | 1 + app/render/renderprocessor.cpp | 7 ++++++- app/task/export/export.cpp | 2 +- app/task/render/render.cpp | 13 +++++++------ app/task/render/render.h | 4 ++-- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 1df5590b3..bf7dc449b 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -100,6 +100,7 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, const VideoParams &vparam QSize(0, 0), QMatrix4x4(), VideoParams::kFormatInvalid, + 0, nullptr, cache, return_type); @@ -110,6 +111,7 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag const VideoParams &video_params, const AudioParams &audio_params, const QSize& force_size, const QMatrix4x4& force_matrix, VideoParams::Format force_format, + int force_channel_count, ColorProcessorPtr force_color_output, FrameHashCache* cache, ReturnType return_type) { @@ -121,6 +123,7 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag ticket->setProperty("size", force_size); ticket->setProperty("matrix", force_matrix); ticket->setProperty("format", force_format); + ticket->setProperty("channelcount", force_channel_count); ticket->setProperty("mode", mode); ticket->setProperty("type", kTypeVideo); ticket->setProperty("colormanager", Node::PtrToValue(color_manager)); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 891e2fffc..6eed0a5bc 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -119,6 +119,7 @@ public: const VideoParams& video_params, const AudioParams& audio_params, const QSize& force_size, const QMatrix4x4& force_matrix, VideoParams::Format force_format, + int force_channel_count, ColorProcessorPtr force_color_output, FrameHashCache* cache = nullptr, ReturnType return_type = kFrame); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 0a6d78078..da419eef7 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -75,7 +75,12 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time frame_params.set_format(frame_format); } - frame_params.set_channel_count(texture ? texture->channel_count() : VideoParams::kRGBChannelCount); + int force_channel_count = ticket_->property("channelcount").toInt(); + if (force_channel_count != 0) { + frame_params.set_channel_count(force_channel_count); + } else { + frame_params.set_channel_count(texture ? texture->channel_count() : VideoParams::kRGBAChannelCount); + } FramePtr frame = Frame::Create(); frame->set_timestamp(time); diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 3df5f1240..7688860d7 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -162,7 +162,7 @@ bool ExportTask::Run() Render(color_manager_, video_range, audio_range, subtitle_range, RenderMode::kOnline, nullptr, video_force_size, video_force_matrix, encoder_->GetDesiredPixelFormat(), - color_processor_); + VideoParams::kRGBAChannelCount, color_processor_); bool success = true; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index d427b9608..1bb764022 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -42,7 +42,7 @@ bool RenderTask::Render(ColorManager* manager, RenderMode::Mode mode, FrameHashCache* cache, const QSize &force_size, const QMatrix4x4 &force_matrix, VideoParams::Format force_format, - ColorProcessorPtr force_color_output) + int force_channel_count, ColorProcessorPtr force_color_output) { QMetaObject::invokeMethod(RenderManager::instance(), "SetAggressiveGarbageCollection", Q_ARG(bool, true)); @@ -88,7 +88,7 @@ bool RenderTask::Render(ColorManager* manager, rational next_frame; for (int i=0; isetProperty("time", QVariant::fromValue(time)); @@ -291,8 +292,8 @@ void RenderTask::StartTicket(QThread* watcher_thread, ColorManager* manager, watcher->SetTicket(RenderManager::instance()->RenderFrame(viewer_->GetConnectedTextureOutput(), manager, time, mode, video_params_, audio_params_, force_size, force_matrix, - force_format, force_color_output, - cache)); + force_format, force_channel_count, + force_color_output, cache)); } void RenderTask::TicketDone(RenderTicketWatcher* watcher) diff --git a/app/task/render/render.h b/app/task/render/render.h index 2609bc3d9..a9313e738 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -46,7 +46,7 @@ protected: FrameHashCache *cache, const QSize& force_size = QSize(0, 0), const QMatrix4x4& force_matrix = QMatrix4x4(), VideoParams::Format force_format = VideoParams::kFormatInvalid, - ColorProcessorPtr force_color_output = nullptr); + int force_channel_count = 0, ColorProcessorPtr force_color_output = nullptr); virtual bool DownloadFrame(QThread* thread, FramePtr frame, const rational &time); @@ -116,7 +116,7 @@ private: void IncrementRunningTickets(); - void StartTicket(QThread *watcher_thread, ColorManager *manager, const rational &time, RenderMode::Mode mode, FrameHashCache *cache, const QSize &force_size, const QMatrix4x4 &force_matrix, VideoParams::Format force_format, ColorProcessorPtr force_color_output); + void StartTicket(QThread *watcher_thread, ColorManager *manager, const rational &time, RenderMode::Mode mode, FrameHashCache *cache, const QSize &force_size, const QMatrix4x4 &force_matrix, VideoParams::Format force_format, int force_channel_count, ColorProcessorPtr force_color_output); ViewerOutput* viewer_; From 8476c9dbeb7072480c05a8d8f5a4334640999c41 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 28 Jul 2022 09:29:38 -0700 Subject: [PATCH 076/107] polygon: use gpu for upconversion --- app/node/distort/mask/mask.cpp | 4 +- app/node/generator/polygon/polygon.cpp | 55 ++++++++++---------------- app/node/generator/polygon/polygon.h | 4 +- app/shaders/rgb.frag | 15 +++++++ 4 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 app/shaders/rgb.frag diff --git a/app/node/distort/mask/mask.cpp b/app/node/distort/mask/mask.cpp index b9bdae32d..13bc6840e 100644 --- a/app/node/distort/mask/mask.cpp +++ b/app/node/distort/mask/mask.cpp @@ -44,7 +44,7 @@ ShaderCode MaskDistortNode::GetShaderCode(const ShaderRequest &request) const } else if (request.id == QStringLiteral("feather")) { return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/blur.frag"))); } else { - return ShaderCode(); + return super::GetShaderCode(request); } } @@ -58,7 +58,7 @@ void MaskDistortNode::Retranslate() void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - GenerateJob job = GetGenerateJob(value); + ShaderJob job = GetGenerateJob(value); if (value[kBaseInput].toTexture()) { // Push as merge node diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index 6b535764a..ec65f8b7f 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -23,8 +23,6 @@ #include #include -#include "common/cpuoptimize.h" - namespace olive { const QString PolygonGenerator::kPointsInput = QStringLiteral("points_in"); @@ -89,19 +87,25 @@ void PolygonGenerator::Retranslate() SetInputName(kColorInput, tr("Color")); } -GenerateJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const +ShaderJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const { GenerateJob job; job.Insert(value); - job.SetRequestedFormat(VideoParams::kFormatFloat32); + job.SetRequestedFormat(VideoParams::kFormatUnsigned8); - return job; + // Conversion to RGB + ShaderJob rgb; + rgb.SetShaderID(QStringLiteral("rgb")); + rgb.Insert(QStringLiteral("texture_in"), NodeValue(NodeValue::kTexture, job, this)); + rgb.Insert(QStringLiteral("color_in"), value[kColorInput]); + + return rgb; } void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - GenerateJob job = GetGenerateJob(value); + ShaderJob job = GetGenerateJob(value); PushMergableJob(value, QVariant::fromValue(job), table); } @@ -112,7 +116,7 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con // QImages only support integer pixels and we use float pixels, so what we do here is draw onto // a single-channel QImage (alpha only) and then transplant that alpha channel to our float buffer // with correct float RGB. - QImage img(frame->width(), frame->height(), QImage::Format_Grayscale8); + QImage img((uchar *) frame->data(), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied); img.fill(Qt::transparent); QVector points = job.Get(kPointsInput).value< QVector >(); @@ -127,34 +131,6 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con p.setPen(Qt::NoPen); p.drawPath(path); - - // Transplant alpha channel to frame - Color rgba = job.Get(kColorInput).toColor(); -#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) - __m128 sse_color = _mm_loadu_ps(rgba.data()); -#endif - - float *frame_dst = reinterpret_cast(frame->data()); - for (int y=0; yheight(); y++) { - uchar *src_y = img.bits() + img.bytesPerLine() * y; - float *dst_y = frame_dst + y*frame->linesize_pixels()*VideoParams::kRGBAChannelCount; - - for (int x=0; xwidth(); x++) { - float alpha = float(src_y[x]) / 255.0f; - float *dst = dst_y + x*VideoParams::kRGBAChannelCount; - -#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) - __m128 sse_alpha = _mm_load1_ps(&alpha); - __m128 sse_res = _mm_mul_ps(sse_color, sse_alpha); - - _mm_store_ps(dst, sse_res); -#else - for (int i=0; i @@ -240,6 +216,15 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG poly_gizmo_->SetPath(GeneratePath(points).translated(half_res)); } +ShaderCode PolygonGenerator::GetShaderCode(const ShaderRequest &request) const +{ + if (request.id == QStringLiteral("rgb")) { + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/rgb.frag")); + } else { + return super::GetShaderCode(request); + } +} + void PolygonGenerator::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) { DraggableGizmo *gizmo = static_cast(sender()); diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h index 7a6bbc340..dd7e5623b 100644 --- a/app/node/generator/polygon/polygon.h +++ b/app/node/generator/polygon/polygon.h @@ -54,11 +54,13 @@ public: virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + static const QString kPointsInput; static const QString kColorInput; protected: - GenerateJob GetGenerateJob(const NodeValueRow &value) const; + ShaderJob GetGenerateJob(const NodeValueRow &value) const; protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; diff --git a/app/shaders/rgb.frag b/app/shaders/rgb.frag new file mode 100644 index 000000000..84112d2a4 --- /dev/null +++ b/app/shaders/rgb.frag @@ -0,0 +1,15 @@ +// Input texture +uniform sampler2D texture_in; + +// Input texture coordinate +in vec2 ove_texcoord; +out vec4 frag_color; + +// Input color +uniform vec4 color_in; + +void main() { + vec4 color = texture(texture_in, ove_texcoord); + color.rgb = color_in.rgb * color.a; + frag_color = color; +} From 5a067f57dadefa29dacc41333b846cfb7128af38 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 28 Jul 2022 14:49:10 -0700 Subject: [PATCH 077/107] previewautocacher: run passthroughs before TryRender --- app/render/previewautocacher.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index e876ddafa..2bf3dcaca 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -193,6 +193,17 @@ void PreviewAutoCacher::VideoRendered() { RenderTicketWatcher* watcher = static_cast(sender()); + // Process passthroughs no matter what, if the viewer was switched, the passthrough map would be + // cleared anyway + QVector tickets = video_immediate_passthroughs_.take(watcher); + foreach (RenderTicketPtr t, tickets) { + if (watcher->HasResult()) { + t->Finish(watcher->Get()); + } else { + t->Finish(); + } + } + // If the task list doesn't contain this watcher, presumably it was cleared as a result of a // viewer switch, so we'll completely ignore this watcher auto it = video_tasks_.find(watcher); @@ -214,17 +225,6 @@ void PreviewAutoCacher::VideoRendered() TryRender(); } - // Process passthroughs no matter what, if the viewer was switched, the passthrough map would be - // cleared anyway - QVector tickets = video_immediate_passthroughs_.take(watcher); - foreach (RenderTicketPtr t, tickets) { - if (watcher->HasResult()) { - t->Finish(watcher->Get()); - } else { - t->Finish(); - } - } - delete watcher; } From a96afa5a0442d2ceb18deb8c7705844d83686bc9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 29 Jul 2022 13:44:26 -0700 Subject: [PATCH 078/107] videoparams: just use float for the pixel aspect ratio calculation --- app/render/videoparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index c9db6f408..f4b341238 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -275,7 +275,7 @@ void VideoParams::set_defaults_for_footage() void VideoParams::calculate_square_pixel_width() { if (pixel_aspect_ratio_.denominator() != 0) { - par_width_ = width_ * pixel_aspect_ratio_.numerator() / pixel_aspect_ratio_.denominator(); + par_width_ = qRound(width_ * pixel_aspect_ratio_.toDouble()); } else { par_width_ = width_; } From 10bc448caa00d007f350c4ec7f61637fd6152054 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:36:23 -0700 Subject: [PATCH 079/107] render: copy and set null single frame render immediately --- app/render/previewautocacher.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 2bf3dcaca..15cce575a 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -565,13 +565,15 @@ void PreviewAutoCacher::TryRender() } if (single_frame_render_) { - // Check if already caching this - RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), - nullptr, - single_frame_render_->property("dry").toBool()); - video_immediate_passthroughs_[watcher].append(single_frame_render_); - + // Make an explicit copy of the render ticket here - it seems that on some systems it can be set + // to NULL before we're done with it... + RenderTicketPtr t = single_frame_render_; single_frame_render_ = nullptr; + + RenderTicketWatcher *watcher = RenderFrame(t->property("time").value(), + nullptr, + t->property("dry").toBool()); + video_immediate_passthroughs_[watcher].append(t); } // Completely arbitrary number. I don't know what's optimal for this yet. From a5ac4f889efd81c180b9714814ada527a7d4493c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 7 Aug 2022 09:50:15 -0700 Subject: [PATCH 080/107] timeline: null check on ripple to Fixes #1999 --- app/widget/timelinewidget/timelinewidget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 033c50fa8..00aa7e40e 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1515,6 +1515,10 @@ QVector TimelineWidget::GetEditToInfo(const rational& play void TimelineWidget::RippleTo(Timeline::MovementMode mode) { + if (!GetConnectedNode()) { + return; + } + rational playhead_time = GetTime(); QVector tracks = GetEditToInfo(playhead_time, mode); From 73fa930f020d5d0c230451b0649e87f8d1d86186 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 7 Aug 2022 14:00:12 -0700 Subject: [PATCH 081/107] ffmpegencoder: use avfilter instead of swscale for pixel format conversions --- app/codec/ffmpeg/ffmpegdecoder.h | 7 +- app/codec/ffmpeg/ffmpegencoder.cpp | 157 +++++++++++++++-------------- app/codec/ffmpeg/ffmpegencoder.h | 6 +- app/common/ffmpegutils.h | 6 ++ 4 files changed, 90 insertions(+), 86 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 1c0d25bb8..ad9bc571a 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -36,15 +36,10 @@ extern "C" { #include #include "codec/decoder.h" +#include "common/ffmpegutils.h" namespace olive { -using AVFramePtr = std::shared_ptr; -inline AVFramePtr CreateAVFramePtr(AVFrame *f) -{ - return std::shared_ptr(f, [](AVFrame *g){ av_frame_free(&g); }); -} - /** * @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder */ diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 2c9f87775..1a66cbae8 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -21,6 +21,8 @@ #include "ffmpegencoder.h" extern "C" { +#include +#include #include } @@ -37,6 +39,8 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : video_stream_(nullptr), video_codec_ctx_(nullptr), video_scale_ctx_(nullptr), + video_buffersrc_ctx_(nullptr), + video_buffersink_ctx_(nullptr), audio_stream_(nullptr), audio_codec_ctx_(nullptr), audio_resample_ctx_(nullptr), @@ -141,34 +145,59 @@ bool FFmpegEncoder::Open() // This is the pixel format the encoder wants to encode to AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt; - // Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it - // before encoding. Even if we don't, this may be useful for converting between linesizes, etc. - video_scale_ctx_ = sws_getContext(params().video_params().width(), - params().video_params().height(), - src_alpha_pix_fmt, - params().video_params().width(), - params().video_params().height(), - encoder_pix_fmt, - 0, - nullptr, - nullptr, - nullptr); + video_scale_ctx_ = avfilter_graph_alloc(); + if (!video_scale_ctx_) { + return false; + } - int *inv_table; - int src_range; - int *table; - int dst_range; - int brightness; - int contrast; - int saturation; + static const int FILTER_ARG_SZ = 1024; + char filter_args[FILTER_ARG_SZ]; - sws_getColorspaceDetails(video_scale_ctx_, &inv_table, &src_range, &table, &dst_range, &brightness, &contrast, &saturation); + snprintf(filter_args, FILTER_ARG_SZ, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + params().video_params().effective_width(), + params().video_params().effective_height(), + src_alpha_pix_fmt, + params().video_params().time_base().numerator(), + params().video_params().time_base().denominator(), + params().video_params().pixel_aspect_ratio().numerator(), + params().video_params().pixel_aspect_ratio().denominator()); - // Set swscale's dst range based on AVCodecContext's color_range. Here, 1 == JPEG range (0-255) - // and 0 == MPEG range (16-235). - dst_range = (video_codec_ctx_->color_range == AVCOL_RANGE_JPEG); + avfilter_graph_create_filter(&video_buffersrc_ctx_, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, video_scale_ctx_); + avfilter_graph_create_filter(&video_buffersink_ctx_, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, video_scale_ctx_); - sws_setColorspaceDetails(video_scale_ctx_, inv_table, src_range, table, dst_range, brightness, contrast, saturation); + AVFilterContext *last_filter = video_buffersrc_ctx_; + + { + // Set color range + AVFilterContext* range_filter; + + snprintf(filter_args, FILTER_ARG_SZ, "in_range=full:out_range=%s", + params().video_color_range() == EncodingParams::kYUVJPEG0_255 ? "full" : "limited"); + + avfilter_graph_create_filter(&range_filter, avfilter_get_by_name("scale"), "range", filter_args, nullptr, video_scale_ctx_); + + avfilter_link(last_filter, 0, range_filter, 0); + last_filter = range_filter; + } + + if (src_alpha_pix_fmt != encoder_pix_fmt) { + // Transform pixel format + AVFilterContext* format_filter; + + snprintf(filter_args, FILTER_ARG_SZ, "pix_fmts=%u", encoder_pix_fmt); + + avfilter_graph_create_filter(&format_filter, avfilter_get_by_name("format"), "format", filter_args, nullptr, video_scale_ctx_); + + avfilter_link(last_filter, 0, format_filter, 0); + last_filter = format_filter; + } + + avfilter_link(last_filter, 0, video_buffersink_ctx_, 0); + + if (avfilter_graph_config(video_scale_ctx_, nullptr) < 0) { + SetError(tr("Failed to configure filter graph")); + return false; + } } // Initialize an audio stream if it's enabled @@ -207,71 +236,41 @@ bool FFmpegEncoder::Open() bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) { - bool success = false; - - AVFrame* encoded_frame = av_frame_alloc(); - - int error_code; - const char* input_data; - int input_linesize; - - // Frame must be video - encoded_frame->width = frame->width(); - encoded_frame->height = frame->height(); - encoded_frame->format = video_codec_ctx_->pix_fmt; - encoded_frame->color_range = video_codec_ctx_->color_range; - encoded_frame->color_trc = video_codec_ctx_->color_trc; - encoded_frame->color_primaries = video_codec_ctx_->color_primaries; - encoded_frame->colorspace = video_codec_ctx_->colorspace; - - // Set interlacing - if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) { - encoded_frame->interlaced_frame = 1; - - if (frame->video_params().interlacing() == VideoParams::kInterlacedTopFirst) { - encoded_frame->top_field_first = 1; - } else { - encoded_frame->top_field_first = 0; - } - } - - error_code = av_frame_get_buffer(encoded_frame, 0); - if (error_code < 0) { - FFmpegError(tr("Failed to create AVFrame buffer"), error_code); - goto fail; - } - // We may need to convert this frame to a frame that swscale will understand if (frame->format() != video_conversion_fmt_) { frame = frame->convert(video_conversion_fmt_); } // Use swscale context to convert formats/linesizes - input_data = frame->const_data(); - input_linesize = frame->linesize_bytes(); + AVFramePtr input_frame = CreateAVFramePtr(av_frame_alloc()); + input_frame->width = frame->width(); + input_frame->height = frame->height(); + input_frame->format = FFmpegUtils::GetFFmpegPixelFormat(frame->format(), frame->channel_count()); + input_frame->data[0] = reinterpret_cast(frame->data()); + input_frame->linesize[0] = frame->linesize_bytes(); - error_code = sws_scale(video_scale_ctx_, - reinterpret_cast(&input_data), - &input_linesize, - 0, - frame->height(), - encoded_frame->data, - encoded_frame->linesize); + input_frame->color_primaries = video_codec_ctx_->color_primaries; + input_frame->color_trc = video_codec_ctx_->color_trc; + input_frame->colorspace = video_codec_ctx_->colorspace; + input_frame->color_range = video_codec_ctx_->color_range; + int r; + r = av_buffersrc_add_frame_flags(video_buffersrc_ctx_, input_frame.get(), AV_BUFFERSRC_FLAG_KEEP_REF); + if (r < 0) { + FFmpegError(tr("Failed to add frame to filter graph"), r); + return false; + } - if (error_code < 0) { - FFmpegError(tr("Failed to scale frame"), error_code); - goto fail; + AVFramePtr encoded_frame = CreateAVFramePtr(av_frame_alloc()); + r = av_buffersink_get_frame(video_buffersink_ctx_, encoded_frame.get()); + if (r < 0) { + FFmpegError(tr("Failed to retrieve frame from buffer sink"), r); + return false; } encoded_frame->pts = qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base)); - success = WriteAVFrame(encoded_frame, video_codec_ctx_, video_stream_); - -fail: - av_frame_free(&encoded_frame); - - return success; + return WriteAVFrame(encoded_frame.get(), video_codec_ctx_, video_stream_); } bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) @@ -493,8 +492,10 @@ void FFmpegEncoder::Close() } if (video_scale_ctx_) { - sws_freeContext(video_scale_ctx_); + avfilter_graph_free(&video_scale_ctx_); video_scale_ctx_ = nullptr; + video_buffersrc_ctx_ = nullptr; + video_buffersink_ctx_ = nullptr; } if (video_codec_ctx_) { @@ -656,7 +657,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV if (params().color_transform().output().contains(QStringLiteral("sRGB"), Qt::CaseInsensitive)) { codec_ctx->color_primaries = AVCOL_PRI_BT709; codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1; - codec_ctx->colorspace = AVCOL_SPC_RGB; + codec_ctx->colorspace = AVCOL_SPC_BT709; } else { // Assume Rec.709 codec_ctx->color_primaries = AVCOL_PRI_BT709; codec_ctx->color_trc = AVCOL_TRC_BT709; diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 4b7dc0edc..9a9f6cae3 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -23,8 +23,8 @@ extern "C" { #include +#include #include -#include #include #include } @@ -88,7 +88,9 @@ private: AVStream* video_stream_; AVCodecContext* video_codec_ctx_; - SwsContext* video_scale_ctx_; + AVFilterGraph *video_scale_ctx_; + AVFilterContext *video_buffersrc_ctx_; + AVFilterContext *video_buffersink_ctx_; VideoParams::Format video_conversion_fmt_; AVStream* audio_stream_; diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index cc74d8c48..f8f85aedd 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -59,6 +59,12 @@ public: static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt); }; +using AVFramePtr = std::shared_ptr; +inline AVFramePtr CreateAVFramePtr(AVFrame *f) +{ + return std::shared_ptr(f, [](AVFrame *g){ av_frame_free(&g); }); +} + } #endif // FFMPEGABSTRACTION_H From 42db352921b32a781a5a4de25831b5bef46a27b6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 7 Aug 2022 22:25:31 -0700 Subject: [PATCH 082/107] mainwindow: don't disable focus when appending timeline panel I guess this was supposed to fix something at some point but now breaks it, so I'm removing it --- app/window/mainwindow/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 39d943eea..d80e3fad5 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -186,7 +186,7 @@ TimelinePanel* MainWindow::OpenSequence(Sequence *sequence, bool enable_focus) panel = timeline_panels_.first(); } else { panel = AppendTimelinePanel(); - enable_focus = false; + //enable_focus = false; } panel->ConnectViewerNode(sequence); From b39e5028dd518045025c60992ec6525fa11cdf39 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 7 Aug 2022 23:20:10 -0700 Subject: [PATCH 083/107] ocioconf: change default colorspace to Rec.709 --- app/render/ocioconf/config.ocio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/ocioconf/config.ocio b/app/render/ocioconf/config.ocio index 9d251ab8b..5ae436b2b 100755 --- a/app/render/ocioconf/config.ocio +++ b/app/render/ocioconf/config.ocio @@ -16,7 +16,7 @@ luma: [0.2126, 0.7152, 0.0722] description: A filmlike dynamic range encoding set for Blender roles: - default: sRGB OETF + default: Rec.709 OETF reference: Linear scene_linear: Linear data: Non-Colour Data From ad1b8a5a35e53ee124b9f953caeec2fdd6ea82f2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 7 Aug 2022 23:40:21 -0700 Subject: [PATCH 084/107] ffmpegdecoder: use real colorspace coefficients in shader --- app/codec/ffmpeg/ffmpegdecoder.cpp | 10 +++++-- app/common/ffmpegutils.cpp | 22 ++++++++++++++ app/common/ffmpegutils.h | 9 ++++++ app/shaders/yuv2rgb.frag | 46 +++++++++++++++++++++--------- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index c2dbe2488..036e8e4d7 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -208,7 +208,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration break; } - bool jpeg_range = src_fmt == AV_PIX_FMT_YUVJ420P + bool full_range = src_fmt == AV_PIX_FMT_YUVJ420P || src_fmt == AV_PIX_FMT_YUVJ422P || src_fmt == AV_PIX_FMT_YUVJ444P; @@ -244,7 +244,13 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); - job.Insert(QStringLiteral("jpeg_range"), NodeValue(NodeValue::kBoolean, jpeg_range)); + job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, full_range)); + + const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->colorspace)); + job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0])); + job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kInt, yuv_coeffs[2])); + job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3])); + job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1])); tex = renderer->CreateTexture(vp); renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index 94a314f3a..c26172d26 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -111,6 +111,28 @@ AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp return AV_SAMPLE_FMT_NONE; } +int FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVColorSpace cs) +{ + switch (cs) { + case AVCOL_SPC_BT709: + return SWS_CS_ITU709; + case AVCOL_SPC_FCC: + return SWS_CS_FCC; + case AVCOL_SPC_BT470BG: + return SWS_CS_ITU624; + case AVCOL_SPC_SMPTE170M: + return SWS_CS_SMPTE170M; + case AVCOL_SPC_SMPTE240M: + return SWS_CS_SMPTE240M; + case AVCOL_SPC_BT2020_NCL: + return SWS_CS_BT2020; + default: + break; + } + + return SWS_CS_DEFAULT; +} + AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_fmt, int channel_layout) { if (channel_layout == VideoParams::kRGBChannelCount) { diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index f8f85aedd..03d1a1008 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -24,6 +24,7 @@ extern "C" { #include #include +#include } #include "render/audioparams.h" @@ -57,6 +58,14 @@ public: * @brief Returns an FFmpeg sample format type for a given native type */ static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt); + + /** + * @brief Returns an SWS_CS_* macro from an AVColorSpace enum member + * + * Why aren't these the same thing anyway? And for that matter, why doesn't FFmpeg provide a + * convenience function to do this conversion for us? Who knows, but here we are. + */ + static int GetSwsColorspaceFromAVColorSpace(AVColorSpace cs); }; using AVFramePtr = std::shared_ptr; diff --git a/app/shaders/yuv2rgb.frag b/app/shaders/yuv2rgb.frag index 537df26e2..13e1cbbe1 100644 --- a/app/shaders/yuv2rgb.frag +++ b/app/shaders/yuv2rgb.frag @@ -3,39 +3,59 @@ uniform sampler2D u_channel; uniform sampler2D v_channel; uniform int bits_per_pixel; -uniform bool jpeg_range; +uniform bool full_range; + +uniform int yuv_crv; +uniform int yuv_cgu; +uniform int yuv_cgv; +uniform int yuv_cbu; in vec2 ove_texcoord; out vec4 frag_color; -void main() { - vec4 rgba; - +void main() +{ + // Sample YUV planes vec3 yuv; - yuv.r = texture(y_channel, ove_texcoord).r; yuv.g = texture(u_channel, ove_texcoord).r; yuv.b = texture(v_channel, ove_texcoord).r; + // Pixels will have come in aligned to 16-bit regardless of their actual bit depth, so they must + // be scaled as if they were actually 16-bit if (bits_per_pixel == 10) { yuv *= 64.0; } else if (bits_per_pixel == 12) { yuv *= 16.0; } - yuv.r = 1.1643 * (yuv.r - 0.0625); + // Convert YUV limited range from 16-235 to 0-255 + yuv.r -= 0.0625; // 16/256 + yuv.r *= 1.1643; // 255/219 + + // Convert 0.0-1.0 to -0.5-0.5 yuv.g = yuv.g - 0.5; yuv.b = yuv.b - 0.5; - rgba.r = yuv.r + 1.5958 * yuv.b; - rgba.g = yuv.r - 0.39173 * yuv.g - 0.81290 * yuv.b; - rgba.b = yuv.r + 2.017 * yuv.g; - rgba.a = 1.0; + // Use coefficients to weigh YUV into RGB + float crv = float(yuv_crv) / 65536.0; + float cgu = float(yuv_cgu) / 65536.0; + float cgv = float(yuv_cgv) / 65536.0; + float cbu = float(yuv_cbu) / 65536.0; - if (jpeg_range) { - rgba.rgb *= 219.0 / 255.0; - rgba.rgb += 16.0 / 255.0; + vec4 rgba; + rgba.r = yuv.r + crv * yuv.b; + rgba.g = yuv.r - cgu * yuv.g - cgv * yuv.b; + rgba.b = yuv.r + cbu * yuv.g; + + // If the expected value is full range, transform to full range here + if (full_range) { + rgba.rgb /= 1.1643; + rgba.rgb += 0.0625; } + // Currently this shader is only used for RGB textures, so just set alpha to 1 + rgba.a = 1.0; + frag_color = rgba; } From 4f58268f0853dcd7aebb11fda6d18d087f807ad6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 8 Aug 2022 11:27:26 -0700 Subject: [PATCH 085/107] timeline: add exception to never splicing gaps --- app/widget/timelinewidget/undo/timelineundopointer.cpp | 2 +- app/widget/timelinewidget/undo/timelineundoripple.cpp | 3 ++- app/widget/timelinewidget/undo/timelineundoripple.h | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/widget/timelinewidget/undo/timelineundopointer.cpp b/app/widget/timelinewidget/undo/timelineundopointer.cpp index f8b51ca4c..f46f5a47e 100644 --- a/app/widget/timelinewidget/undo/timelineundopointer.cpp +++ b/app/widget/timelinewidget/undo/timelineundopointer.cpp @@ -340,7 +340,7 @@ void TrackPlaceBlockCommand::redo() // Place the Block at this point if (!ripple_remove_command_) { ripple_remove_command_ = new TrackRippleRemoveAreaCommand(track, TimeRange(in_, in_ + insert_->length())); - + ripple_remove_command_->SetAllowSplittingGaps(true); } ripple_remove_command_->redo_now(); diff --git a/app/widget/timelinewidget/undo/timelineundoripple.cpp b/app/widget/timelinewidget/undo/timelineundoripple.cpp index 71550f64c..42ff4449e 100644 --- a/app/widget/timelinewidget/undo/timelineundoripple.cpp +++ b/app/widget/timelinewidget/undo/timelineundoripple.cpp @@ -30,6 +30,7 @@ namespace olive { TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(Track* track, const TimeRange& range) : track_(track), range_(range), + allow_splitting_gaps_(false), splice_split_command_(nullptr) { trim_out_.block = nullptr; @@ -63,7 +64,7 @@ void TrackRippleRemoveAreaCommand::prepare() // If it's getting trimmed, determine if it's actually getting spliced if (first_block_is_out_trimmed && first_block_is_in_trimmed) { - if (dynamic_cast(first_block)) { + if (!allow_splitting_gaps_ && dynamic_cast(first_block)) { // As a rule, we don't split gaps, so we just treat it as a trim of the range requested trim_out_ = {first_block, first_block->length(), diff --git a/app/widget/timelinewidget/undo/timelineundoripple.h b/app/widget/timelinewidget/undo/timelineundoripple.h index 11e211575..bd17648cf 100644 --- a/app/widget/timelinewidget/undo/timelineundoripple.h +++ b/app/widget/timelinewidget/undo/timelineundoripple.h @@ -67,6 +67,11 @@ public: return nullptr; } + void SetAllowSplittingGaps(bool e) + { + allow_splitting_gaps_ = e; + } + protected: virtual void prepare() override; @@ -93,6 +98,7 @@ private: QVector removals_; TrimOperation trim_in_; Block* insert_previous_; + bool allow_splitting_gaps_; BlockSplitCommand* splice_split_command_; QVector remove_block_commands_; From a3d6ecc4fd2994ec84466c05deffc4204064fb50 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 8 Aug 2022 11:31:31 -0700 Subject: [PATCH 086/107] timeline: skip gaps when moving blocks --- app/widget/timelinewidget/tool/pointer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index d2bf263bb..3c8f9a7bf 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -383,6 +383,10 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, } else { // Prepare for a standard pointer move by creating ghosts for them and any related blocks foreach (Block* block, clips) { + if (dynamic_cast(block)) { + continue; + } + // Create ghost for this block auto ghost = AddGhostFromBlock(block, trim_mode, true); Q_UNUSED(ghost) From 1ac205413cf12f78c678c7b56c22393ca901611b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 8 Aug 2022 12:49:50 -0700 Subject: [PATCH 087/107] footage: allow overriding YCbCr range Fixes #1354 --- app/codec/decoder.cpp | 16 +++--- app/codec/decoder.h | 33 +++-------- app/codec/encoder.cpp | 1 - app/codec/encoder.h | 11 ---- app/codec/ffmpeg/ffmpegdecoder.cpp | 57 +++++++++---------- app/codec/ffmpeg/ffmpegdecoder.h | 2 +- app/codec/ffmpeg/ffmpegencoder.cpp | 9 ++- app/codec/oiio/oiiodecoder.cpp | 16 +++--- app/codec/oiio/oiiodecoder.h | 2 +- app/common/ffmpegutils.cpp | 15 +++++ app/common/ffmpegutils.h | 9 +++ app/dialog/export/export.cpp | 4 +- app/dialog/export/exportadvancedvideodialog.h | 6 +- app/dialog/export/exportvideotab.cpp | 6 +- app/dialog/export/exportvideotab.h | 6 +- .../footageproperties/footageproperties.h | 5 -- .../videostreamproperties.cpp | 25 ++++++-- .../streamproperties/videostreamproperties.h | 10 +++- app/node/project/footage/footage.cpp | 1 + app/node/project/footage/footagedescription.h | 2 +- app/render/renderprocessor.cpp | 9 ++- app/render/videoparams.cpp | 4 ++ app/render/videoparams.h | 12 ++++ 23 files changed, 150 insertions(+), 111 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index c98cb5c2a..666cec629 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -93,7 +93,7 @@ bool Decoder::Open(const CodecStream &stream) } } -TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, CancelAtom *cancelled) +TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p) { QMutexLocker locker(&mutex_); @@ -109,16 +109,16 @@ TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, return nullptr; } - if (cancelled && cancelled->IsCancelled()) { + if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } - if (cached_texture_ && cached_time_ == timecode) { + if (cached_texture_ && cached_time_ == p.time) { return cached_texture_; } - cached_texture_ = RetrieveVideoInternal(renderer, timecode, divider, cancelled); - cached_time_ = timecode; + cached_texture_ = RetrieveVideoInternal(p); + cached_time_ = p.time; return cached_texture_; } @@ -280,11 +280,9 @@ int64_t Decoder::GetImageSequenceIndex(const QString &filename) return number_only.toLongLong(); } -TexturePtr Decoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, CancelAtom *cancelled) +TexturePtr Decoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - Q_UNUSED(timecode) - Q_UNUSED(divider) - Q_UNUSED(cancelled) + Q_UNUSED(p) return nullptr; } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 665f21cc8..841d1901c 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -164,29 +164,12 @@ public: struct RetrieveVideoParams { - RetrieveVideoParams() - { - divider = 1; - maximum_format = VideoParams::kFormatInvalid; - } - - int divider; - VideoParams::Format maximum_format; - - void reset() - { - *this = RetrieveVideoParams(); - } - - bool operator==(const RetrieveVideoParams& rhs) const - { - return divider == rhs.divider && maximum_format == rhs.maximum_format; - } - - bool operator!=(const RetrieveVideoParams& rhs) const - { - return !(*this == rhs); - } + Renderer *renderer = nullptr; + rational time; + int divider = 1; + VideoParams::Format maximum_format = VideoParams::kFormatInvalid; + CancelAtom *cancelled = nullptr; + VideoParams::ColorRange force_range = VideoParams::kColorRangeDefault; }; /** @@ -199,7 +182,7 @@ public: * * This function is thread safe and can only run while the decoder is open. \see Open() */ - TexturePtr RetrieveVideo(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled = nullptr); + TexturePtr RetrieveVideo(const RetrieveVideoParams& p); enum RetrieveAudioStatus { kInvalid = -1, @@ -294,7 +277,7 @@ protected: * Sub-classes must override this function IF they support video. Function is already mutexed * so sub-classes don't need to worry about thread safety. */ - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled); + virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p); virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled); diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 31b187a5a..8e6642449 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -90,7 +90,6 @@ EncodingParams::EncodingParams() : video_buffer_size_(0), video_threads_(0), video_is_image_sequence_(false), - video_color_range_(kYUVDefault), audio_enabled_(false), audio_bit_rate_(0), subtitles_enabled_(false), diff --git a/app/codec/encoder.h b/app/codec/encoder.h index c77c6e1f2..2982764ed 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -43,14 +43,6 @@ using EncoderPtr = std::shared_ptr; class EncodingParams { public: - enum YUVRange - { - kYUVMPEG16_235, - kYUVJPEG0_255, - - kYUVDefault = kYUVMPEG16_235 - }; - EncodingParams(); void SetFilename(const QString& filename) { filename_ = filename; } @@ -75,7 +67,6 @@ public: void set_video_threads(const int& threads) { video_threads_ = threads; } void set_video_pix_fmt(const QString& s) { video_pix_fmt_ = s; } void set_video_is_image_sequence(bool s) { video_is_image_sequence_ = s; } - void set_video_color_range(YUVRange r) { video_color_range_ = r; } void set_color_transform(const ColorTransform& color_transform) { color_transform_ = color_transform; } const QString& filename() const { return filename_; } @@ -91,7 +82,6 @@ public: const int& video_threads() const { return video_threads_; } const QString& video_pix_fmt() const { return video_pix_fmt_; } bool video_is_image_sequence() const { return video_is_image_sequence_; } - YUVRange video_color_range() const { return video_color_range_; } const ColorTransform& color_transform() const { return color_transform_; } bool audio_enabled() const { return audio_enabled_; } @@ -126,7 +116,6 @@ private: int video_threads_; QString video_pix_fmt_; bool video_is_image_sequence_; - YUVRange video_color_range_; ColorTransform color_transform_; bool audio_enabled_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 036e8e4d7..e6c5fa4ba 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -141,28 +141,32 @@ bool FFmpegDecoder::OpenInternal() return output_frame; }*/ -TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, CancelAtom *cancelled) +TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - if (AVFramePtr f = RetrieveFrame(timecode, cancelled)) { - if (cancelled && cancelled->IsCancelled()) { + if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) { + if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } - if (InitScaler(f.get(), params)) { + int &src_fmt = f.get()->format; + src_fmt = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast(src_fmt)); + + f->color_range = p.force_range == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + + if (InitScaler(f.get(), p)) { VideoParams vp(instance_.avstream()->codecpar->width, instance_.avstream()->codecpar->height, native_output_pix_fmt_, native_channel_count_, av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr), VideoParams::kInterlaceNone, - params.divider); + p.divider); TexturePtr tex = nullptr; - const bool hwscale = true; + const bool hwscale = false; // Attempt to use GLSL shader for faster YUV to RGB conversion if (hwscale) { - AVPixelFormat src_fmt = AVPixelFormat(f.get()->format); if (src_fmt == AV_PIX_FMT_YUV420P || src_fmt == AV_PIX_FMT_YUV422P || src_fmt == AV_PIX_FMT_YUV444P @@ -171,13 +175,10 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration || src_fmt == AV_PIX_FMT_YUV444P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE || src_fmt == AV_PIX_FMT_YUV422P12LE - || src_fmt == AV_PIX_FMT_YUV444P12LE - || src_fmt == AV_PIX_FMT_YUVJ420P - || src_fmt == AV_PIX_FMT_YUVJ422P - || src_fmt == AV_PIX_FMT_YUVJ444P) { + || src_fmt == AV_PIX_FMT_YUV444P12LE) { if (Yuv2RgbShader.isNull()) { // Compile shader - Yuv2RgbShader = renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); + Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); } if (!Yuv2RgbShader.isNull()) { @@ -187,9 +188,6 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV444P: - case AV_PIX_FMT_YUVJ420P: - case AV_PIX_FMT_YUVJ422P: - case AV_PIX_FMT_YUVJ444P: default: px_size = 1; bits_per_pixel = 8; @@ -208,20 +206,14 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration break; } - bool full_range = src_fmt == AV_PIX_FMT_YUVJ420P - || src_fmt == AV_PIX_FMT_YUVJ422P - || src_fmt == AV_PIX_FMT_YUVJ444P; - VideoParams plane_params = vp; plane_params.set_channel_count(1); plane_params.set_divider(1); plane_params.set_format(native_internal_pix_fmt_); - TexturePtr y_plane = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); + TexturePtr y_plane = p.renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); if (src_fmt == AV_PIX_FMT_YUV420P || src_fmt == AV_PIX_FMT_YUV422P - || src_fmt == AV_PIX_FMT_YUVJ420P - || src_fmt == AV_PIX_FMT_YUVJ422P || src_fmt == AV_PIX_FMT_YUV420P10LE || src_fmt == AV_PIX_FMT_YUV422P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE @@ -230,21 +222,20 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration } if (src_fmt == AV_PIX_FMT_YUV420P - || src_fmt == AV_PIX_FMT_YUVJ420P || src_fmt == AV_PIX_FMT_YUV420P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE) { plane_params.set_height(plane_params.height()/2); } - TexturePtr u_plane = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); - TexturePtr v_plane = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); + TexturePtr u_plane = p.renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); + TexturePtr v_plane = p.renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); ShaderJob job; job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); - job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, full_range)); + job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, f->color_range == AVCOL_RANGE_JPEG)); const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->colorspace)); job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0])); @@ -252,8 +243,8 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3])); job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1])); - tex = renderer->CreateTexture(vp); - renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); + tex = p.renderer->CreateTexture(vp); + p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); } } } @@ -261,6 +252,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration if (!tex) { // Fallback to software pixel format conversion int r; + r = av_buffersrc_add_frame_flags(buffersrc_ctx_, f.get(), AV_BUFFERSRC_FLAG_KEEP_REF); if (r < 0) { return nullptr; @@ -270,7 +262,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration return nullptr; } - tex = renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel()); + tex = p.renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel()); av_frame_unref(working_frame_); } @@ -435,6 +427,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can stream.set_start_time(avstream->start_time); stream.set_time_base(avstream->time_base); stream.set_duration(avstream->duration); + stream.set_color_range(avstream->codecpar->color_range == AVCOL_RANGE_JPEG ? VideoParams::kColorRangeFull : VideoParams::kColorRangeLimited); // Defaults to false, requires user intervention if incorrect stream.set_premultiplied_alpha(false); @@ -900,7 +893,11 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancel bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params) { - if (params == filter_params_ && filter_graph_ && input_fmt_ == input->format) { + if (params.divider == filter_params_.divider + && params.force_range == filter_params_.force_range + && params.maximum_format == filter_params_.maximum_format + && filter_graph_ + && input_fmt_ == input->format) { // We have an appropriate filter for these parameters, just return true return true; } diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index ad9bc571a..756997098 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -62,7 +62,7 @@ public: protected: virtual bool OpenInternal() override; - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled) override; + virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override; virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled) override; virtual void CloseInternal() override; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 1a66cbae8..b532d0689 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -57,6 +57,11 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const if (codec_info) { for (int i=0; codec_info->pix_fmts[i]!=-1; i++) { + if (FFmpegUtils::ConvertJPEGSpaceToRegularSpace(codec_info->pix_fmts[i]) != codec_info->pix_fmts[i]) { + // This is a deprecated "JPEG" space, skip it + continue; + } + const char* pix_fmt_name = av_get_pix_fmt_name(codec_info->pix_fmts[i]); pix_fmts.append(pix_fmt_name); } @@ -172,7 +177,7 @@ bool FFmpegEncoder::Open() AVFilterContext* range_filter; snprintf(filter_args, FILTER_ARG_SZ, "in_range=full:out_range=%s", - params().video_color_range() == EncodingParams::kYUVJPEG0_255 ? "full" : "limited"); + params().video_params().color_range() == VideoParams::kColorRangeFull ? "full" : "limited"); avfilter_graph_create_filter(&range_filter, avfilter_get_by_name("scale"), "range", filter_args, nullptr, video_scale_ctx_); @@ -610,7 +615,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational(); codec_ctx->framerate = params().video_params().frame_rate().toAVRational(); codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8()); - codec_ctx->color_range = params().video_color_range() == EncodingParams::kYUVJPEG0_255 ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + codec_ctx->color_range = params().video_params().color_range() == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; if (params().video_params().interlacing() != VideoParams::kInterlaceNone) { // FIXME: I actually don't know what these flags do, the documentation helpfully doesn't diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 7c7dd8878..5593bbbe1 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -119,22 +119,20 @@ bool OIIODecoder::OpenInternal() return OpenImageHandler(stream().filename(), stream().stream()); } -TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, CancelAtom *cancelled) +TexturePtr OIIODecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - Q_UNUSED(timecode) - Q_UNUSED(cancelled) - VideoParams vp = GetVideoParamsFromImageSpec(image_->spec()); - vp.set_divider(params.divider); + vp.set_divider(p.divider); - if (!buffer_.is_allocated() || last_params_ != params) { - last_params_ = params; + if (!buffer_.is_allocated() + || last_params_.divider != p.divider) { + last_params_ = p; buffer_.destroy(); buffer_.set_video_params(vp); buffer_.allocate(); - if (params.divider == 1) { + if (p.divider == 1) { // Just upload straight to the buffer image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes()); } else { @@ -156,7 +154,7 @@ TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational } } - return renderer->CreateTexture(vp, buffer_.data(), buffer_.linesize_pixels()); + return p.renderer->CreateTexture(vp, buffer_.data(), buffer_.linesize_pixels()); } void OIIODecoder::CloseInternal() diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 3cc894eba..f16e33692 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -44,7 +44,7 @@ public: protected: virtual bool OpenInternal() override; - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled) override; + virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override; virtual void CloseInternal() override; private: diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index c26172d26..565f0269c 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -133,6 +133,21 @@ int FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVColorSpace cs) return SWS_CS_DEFAULT; } +AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f) +{ + switch (f) { + case AV_PIX_FMT_YUVJ420P: return AV_PIX_FMT_YUV420P; + case AV_PIX_FMT_YUVJ422P: return AV_PIX_FMT_YUV422P; + case AV_PIX_FMT_YUVJ444P: return AV_PIX_FMT_YUV444P; + case AV_PIX_FMT_YUVJ440P: return AV_PIX_FMT_YUV440P; + case AV_PIX_FMT_YUVJ411P: return AV_PIX_FMT_YUV411P; + default: + break; + } + + return f; +} + AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_fmt, int channel_layout) { if (channel_layout == VideoParams::kRGBChannelCount) { diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index 03d1a1008..7d13b8642 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -66,6 +66,15 @@ public: * convenience function to do this conversion for us? Who knows, but here we are. */ static int GetSwsColorspaceFromAVColorSpace(AVColorSpace cs); + + /** + * @brief Convert "JPEG"/full-range colorspace to its regular counterpart + * + * "JPEG "spaces are deprecated in favor of the regular space and setting `color_range`. For the + * time being, FFmpeg still uses these JPEG spaces, so for simplicity (since we *are* color_range + * aware), we use this function. + */ + static AVPixelFormat ConvertJPEGSpaceToRegularSpace(AVPixelFormat f); }; using AVFramePtr = std::shared_ptr; diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 986d3a169..0968428f8 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -557,6 +557,9 @@ ExportParams ExportDialog::GenerateParams() const if (video_enabled_->isChecked()) { ExportCodec::Codec video_codec = video_tab_->GetSelectedCodec(); + + video_render_params.set_color_range(video_tab_->color_range()); + params.EnableVideo(video_render_params, video_codec); params.set_video_threads(video_tab_->threads()); @@ -568,7 +571,6 @@ ExportParams ExportDialog::GenerateParams() const params.set_color_transform(video_tab_->CurrentOCIOColorSpace()); params.set_video_pix_fmt(video_tab_->pix_fmt()); - params.set_video_color_range(video_tab_->yuv_range()); params.set_video_is_image_sequence(video_tab_->IsImageSequenceSet()); } diff --git a/app/dialog/export/exportadvancedvideodialog.h b/app/dialog/export/exportadvancedvideodialog.h index b6d17214c..f001a476d 100644 --- a/app/dialog/export/exportadvancedvideodialog.h +++ b/app/dialog/export/exportadvancedvideodialog.h @@ -36,12 +36,12 @@ public: pixel_format_combobox_->setCurrentText(s); } - EncodingParams::YUVRange yuv_range() const + VideoParams::ColorRange yuv_range() const { - return static_cast(yuv_color_range_combobox_->currentIndex()); + return static_cast(yuv_color_range_combobox_->currentIndex()); } - void set_yuv_range(EncodingParams::YUVRange i) + void set_yuv_range(VideoParams::ColorRange i) { yuv_color_range_combobox_->setCurrentIndex(i); } diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 4373eb75f..fd175c8a0 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -37,7 +37,7 @@ ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) : QWidget(parent), color_manager_(color_manager), threads_(0), - yuv_range_(EncodingParams::kYUVDefault) + color_range_(VideoParams::kColorRangeDefault) { QVBoxLayout* outer_layout = new QVBoxLayout(this); @@ -212,12 +212,12 @@ void ExportVideoTab::OpenAdvancedDialog() d.set_threads(threads_); d.set_pix_fmt(pix_fmt_); - d.set_yuv_range(yuv_range_); + d.set_yuv_range(color_range_); if (d.exec() == QDialog::Accepted) { threads_ = d.threads(); pix_fmt_ = d.pix_fmt(); - yuv_range_ = d.yuv_range(); + color_range_ = d.yuv_range(); } } diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index 0a83753ee..f6f997ee2 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -138,9 +138,9 @@ public: return pix_fmt_; } - EncodingParams::YUVRange yuv_range() const + VideoParams::ColorRange color_range() const { - return yuv_range_; + return color_range_; } public slots: @@ -183,7 +183,7 @@ private: int threads_; QString pix_fmt_; - EncodingParams::YUVRange yuv_range_; + VideoParams::ColorRange color_range_; ExportFormat::Format format_; diff --git a/app/dialog/footageproperties/footageproperties.h b/app/dialog/footageproperties/footageproperties.h index aa71fa01e..e25898381 100644 --- a/app/dialog/footageproperties/footageproperties.h +++ b/app/dialog/footageproperties/footageproperties.h @@ -83,11 +83,6 @@ private: */ QStackedWidget* stacked_widget_; - /** - * @brief ComboBox for interlacing setting - */ - QComboBox* interlacing_box; - /** * @brief Media name text field */ diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp index 6fcbdd914..6ed1e486f 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp @@ -79,6 +79,17 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) video_layout->addWidget(video_color_space_, row, 1); + row++; + + video_layout->addWidget(new QLabel(tr("Color Range:")), row, 0); + + color_range_combo_ = new QComboBox(); + color_range_combo_->addItem(tr("Limited (16-235)"), VideoParams::kColorRangeLimited); + color_range_combo_->addItem(tr("Full (0-255)"), VideoParams::kColorRangeFull); + color_range_combo_->setCurrentIndex(vp.color_range()); + + video_layout->addWidget(color_range_combo_, row, 1); + if (vp.channel_count() == VideoParams::kRGBAChannelCount) { row++; @@ -136,14 +147,16 @@ void VideoStreamProperties::Accept(MultiUndoCommand *parent) if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha()) || set_colorspace != vp.colorspace() || static_cast(video_interlace_combo_->currentIndex()) != vp.interlacing() - || pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio()) { + || pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio() + || color_range_combo_->currentData().toInt() != vp.color_range()) { parent->add_child(new VideoStreamChangeCommand(footage_, video_index_, video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : vp.premultiplied_alpha(), set_colorspace, static_cast(video_interlace_combo_->currentIndex()), - pixel_aspect_combo_->GetPixelAspectRatio())); + pixel_aspect_combo_->GetPixelAspectRatio(), + static_cast(color_range_combo_->currentData().toInt()))); } if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { @@ -181,13 +194,14 @@ VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footag bool premultiplied, QString colorspace, VideoParams::Interlacing interlacing, - const rational &pixel_ar) : + const rational &pixel_ar, VideoParams::ColorRange range) : footage_(footage), video_index_(video_index), new_premultiplied_(premultiplied), new_colorspace_(colorspace), new_interlacing_(interlacing), - new_pixel_ar_(pixel_ar) + new_pixel_ar_(pixel_ar), + new_range_(range) { } @@ -204,11 +218,13 @@ void VideoStreamProperties::VideoStreamChangeCommand::redo() old_colorspace_ = vp.colorspace(); old_interlacing_ = vp.interlacing(); old_pixel_ar_ = vp.pixel_aspect_ratio(); + old_range_ = vp.color_range(); vp.set_premultiplied_alpha(new_premultiplied_); vp.set_colorspace(new_colorspace_); vp.set_interlacing(new_interlacing_); vp.set_pixel_aspect_ratio(new_pixel_ar_); + vp.set_color_range(new_range_); footage_->SetVideoParams(vp, video_index_); } @@ -221,6 +237,7 @@ void VideoStreamProperties::VideoStreamChangeCommand::undo() vp.set_colorspace(old_colorspace_); vp.set_interlacing(old_interlacing_); vp.set_pixel_aspect_ratio(old_pixel_ar_); + vp.set_color_range(old_range_); footage_->SetVideoParams(vp, video_index_); } diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.h b/app/dialog/footageproperties/streamproperties/videostreamproperties.h index ea689e2f0..dec410c97 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.h +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.h @@ -56,6 +56,11 @@ private: */ QComboBox* video_color_space_; + /** + * @brief Setting for this streams's color range + */ + QComboBox *color_range_combo_; + /** * @brief Setting for video interlacing */ @@ -88,7 +93,8 @@ private: bool premultiplied, QString colorspace, VideoParams::Interlacing interlacing, - const rational& pixel_ar); + const rational& pixel_ar, + VideoParams::ColorRange range); virtual Project* GetRelevantProject() const override; @@ -104,11 +110,13 @@ private: QString new_colorspace_; VideoParams::Interlacing new_interlacing_; rational new_pixel_ar_; + VideoParams::ColorRange new_range_; bool old_premultiplied_; QString old_colorspace_; VideoParams::Interlacing old_interlacing_; rational old_pixel_ar_; + VideoParams::ColorRange old_range_; }; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 7c89f3496..459e1711f 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -519,6 +519,7 @@ VideoParams Footage::MergeVideoStream(const VideoParams &base, const VideoParams merged.set_colorspace(over.colorspace()); merged.set_premultiplied_alpha(over.premultiplied_alpha()); merged.set_video_type(over.video_type()); + merged.set_color_range(over.color_range()); if (merged.video_type() == VideoParams::kVideoTypeImageSequence) { merged.set_start_time(over.start_time()); merged.set_duration(over.duration()); diff --git a/app/node/project/footage/footagedescription.h b/app/node/project/footage/footagedescription.h index c05849273..42e6a07be 100644 --- a/app/node/project/footage/footagedescription.h +++ b/app/node/project/footage/footagedescription.h @@ -142,7 +142,7 @@ public: } private: - static constexpr unsigned kFootageMetaVersion = 4; + static constexpr unsigned kFootageMetaVersion = 5; QString decoder_; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index da419eef7..9e8ee169c 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -475,7 +475,14 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ VideoParams tex_params = stream.video_params(); if (tex_params.is_valid()) { - TexturePtr unmanaged_texture = decoder->RetrieveVideo(render_ctx_, (stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode, p, GetCancelPointer()); + TexturePtr unmanaged_texture; + + p.renderer = render_ctx_; + p.time = (stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode; + p.cancelled = GetCancelPointer(); + p.force_range = stream_data.color_range(); + + unmanaged_texture = decoder->RetrieveVideo(p); if (unmanaged_texture) { // We convert to our rendering pixel format, since that will always be float-based which diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index f4b341238..239507b98 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -270,6 +270,7 @@ void VideoParams::set_defaults_for_footage() premultiplied_alpha_ = false; x_ = 0; y_ = 0; + color_range_ = kColorRangeDefault; } void VideoParams::calculate_square_pixel_width() @@ -374,6 +375,8 @@ void VideoParams::Load(QXmlStreamReader *reader) set_premultiplied_alpha(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("colorspace")) { set_colorspace(reader->readElementText()); + } else if (reader->name() == QStringLiteral("colorrange")) { + set_color_range(static_cast(reader->readElementText().toInt())); } else { reader->skipCurrentElement(); } @@ -401,6 +404,7 @@ void VideoParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("duration"), QString::number(duration_)); writer->writeTextElement(QStringLiteral("premultipliedalpha"), QString::number(premultiplied_alpha_)); writer->writeTextElement(QStringLiteral("colorspace"), colorspace_); + writer->writeTextElement(QStringLiteral("colorrange"), QString::number(color_range_)); } } diff --git a/app/render/videoparams.h b/app/render/videoparams.h index 28d8c29fe..e65ad7a93 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -66,6 +66,14 @@ public: kVideoTypeStill, kVideoTypeImageSequence }; + enum ColorRange + { + kColorRangeLimited, // 16_235 + kColorRangeFull, // 0-255 + + kColorRangeDefault = kColorRangeLimited + }; + VideoParams(); VideoParams(int width, int height, Format format, int nb_channels, @@ -352,6 +360,9 @@ public: colorspace_ = c; } + const ColorRange &color_range() const { return color_range_; } + void set_color_range(const ColorRange &color_range) { color_range_ = color_range; } + int64_t get_time_in_timebase_units(const rational& time) const; void Load(QXmlStreamReader* reader); @@ -398,6 +409,7 @@ private: QString colorspace_; float x_; float y_; + ColorRange color_range_; }; From a270b1e3e178a640a43226d65b0321b442517044 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 8 Aug 2022 12:55:57 -0700 Subject: [PATCH 088/107] ffmpegdecoder: re-enable hardware scaling --- app/codec/ffmpeg/ffmpegdecoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index e6c5fa4ba..556907ab3 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -163,7 +163,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) p.divider); TexturePtr tex = nullptr; - const bool hwscale = false; + bool hwscale = true; // Attempt to use GLSL shader for faster YUV to RGB conversion if (hwscale) { From 30b64d9b9cc43f6fb0112d6ec07b19142ab1dff2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 8 Aug 2022 14:05:11 -0700 Subject: [PATCH 089/107] render: reimplemented deinterlacing with new shader-based format conversion --- app/codec/decoder.h | 1 + app/codec/ffmpeg/ffmpegdecoder.cpp | 39 ++++++++++++++++++++++++++++++ app/codec/ffmpeg/ffmpegdecoder.h | 1 + app/render/renderprocessor.cpp | 1 + app/shaders/yuv2rgb.frag | 19 ++++++++++++--- 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 841d1901c..983efb753 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -170,6 +170,7 @@ public: VideoParams::Format maximum_format = VideoParams::kFormatInvalid; CancelAtom *cancelled = nullptr; VideoParams::ColorRange force_range = VideoParams::kColorRangeDefault; + VideoParams::Interlacing src_interlacing = VideoParams::kInterlaceNone; }; /** diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 556907ab3..3cb61d8f1 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -79,6 +79,7 @@ bool FFmpegDecoder::OpenInternal() working_frame_ = av_frame_alloc(); working_packet_ = av_packet_alloc(); + frame_rate_tb_ = rational::NaN; return true; } @@ -243,6 +244,29 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3])); job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1])); + int interlacing = 0; + if (p.src_interlacing != VideoParams::kInterlaceNone) { + if (frame_rate_tb_.isNull()) { + frame_rate_tb_ = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), f.get()); + + // Double frame rate for interlaced fields + frame_rate_tb_ *= 2; + + // Flip frame rate so it can be used as a timebase + frame_rate_tb_.flip(); + } + + int64_t req = Timecode::time_to_timestamp(p.time, frame_rate_tb_); + int64_t frm = Timecode::rescale_timestamp(f->pts - instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb_); + + bool first = (req == frm); + bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst); + + interlacing = (first == top_first) ? 1 : 2; + } + job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing)); + job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, f->height)); + tex = p.renderer->CreateTexture(vp); p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); } @@ -896,6 +920,7 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params if (params.divider == filter_params_.divider && params.force_range == filter_params_.force_range && params.maximum_format == filter_params_.maximum_format + && params.src_interlacing == filter_params_.src_interlacing && filter_graph_ && input_fmt_ == input->format) { // We have an appropriate filter for these parameters, just return true @@ -962,6 +987,20 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params // Link filters as necessary AVFilterContext *last_filter = buffersrc_ctx_; + // Add deinterlace filter if necessary + if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) { + AVFilterContext* deint_filter; + + snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s", + filter_params_.src_interlacing == VideoParams::kInterlacedTopFirst ? "0" : "1"); + + avfilter_graph_create_filter(&deint_filter, avfilter_get_by_name("yadif"), "deint", filter_args, nullptr, filter_graph_); + + avfilter_link(last_filter, 0, deint_filter, 0); + + last_filter = deint_filter; + } + // Add scale filter if necessary int dst_width, dst_height; if (filter_params_.divider > 1) { diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 756997098..c29f45c1a 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -159,6 +159,7 @@ private: VideoParams::Format native_internal_pix_fmt_; VideoParams::Format native_output_pix_fmt_; int native_channel_count_; + rational frame_rate_tb_; AVFrame *working_frame_; AVPacket *working_packet_; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 9e8ee169c..94cea35f8 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -481,6 +481,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ p.time = (stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode; p.cancelled = GetCancelPointer(); p.force_range = stream_data.color_range(); + p.src_interlacing = stream_data.interlacing(); unmanaged_texture = decoder->RetrieveVideo(p); diff --git a/app/shaders/yuv2rgb.frag b/app/shaders/yuv2rgb.frag index 13e1cbbe1..8c40b5918 100644 --- a/app/shaders/yuv2rgb.frag +++ b/app/shaders/yuv2rgb.frag @@ -10,16 +10,29 @@ uniform int yuv_cgu; uniform int yuv_cgv; uniform int yuv_cbu; +uniform int interlacing; +uniform int pixel_height; + in vec2 ove_texcoord; out vec4 frag_color; void main() { + vec2 real_coord = ove_texcoord; + if (interlacing != 0) { + float field_height = float(pixel_height / 2); + real_coord.y = floor(real_coord.y * field_height) + 0.25; + if (interlacing == 2) { + real_coord.y += 0.5; + } + real_coord.y /= field_height; + } + // Sample YUV planes vec3 yuv; - yuv.r = texture(y_channel, ove_texcoord).r; - yuv.g = texture(u_channel, ove_texcoord).r; - yuv.b = texture(v_channel, ove_texcoord).r; + yuv.r = texture(y_channel, real_coord).r; + yuv.g = texture(u_channel, real_coord).r; + yuv.b = texture(v_channel, real_coord).r; // Pixels will have come in aligned to 16-bit regardless of their actual bit depth, so they must // be scaled as if they were actually 16-bit From cc7893e7bc09fc8529a021611d4f1faeb40103a0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 8 Aug 2022 14:21:01 -0700 Subject: [PATCH 090/107] opengl: use flush instead of finish I may live to regret this, but it helped significantly on at least one system. Hopefully it doesn't break a bunch of others... --- app/render/opengl/openglrenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 8c6f2a703..7ef116a21 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -364,7 +364,7 @@ void OpenGLRenderer::Flush() { GL_PREAMBLE; - functions_->glFinish(); + functions_->glFlush(); } Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt) From 920a294387e7dbb4a943f4cd0dcd3446121c17d0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 9 Aug 2022 11:49:11 -0700 Subject: [PATCH 091/107] shape: add rounded rectangle --- app/node/generator/shape/shapenode.cpp | 21 +++++++- app/node/generator/shape/shapenode.h | 7 ++- app/shaders/shape.frag | 71 ++++++++++++++++++++------ 3 files changed, 82 insertions(+), 17 deletions(-) diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp index 7344d6e3d..d4808d768 100644 --- a/app/node/generator/shape/shapenode.cpp +++ b/app/node/generator/shape/shapenode.cpp @@ -25,10 +25,14 @@ namespace olive { #define super ShapeNodeBase QString ShapeNode::kTypeInput = QStringLiteral("type_in"); +QString ShapeNode::kRadiusInput = QStringLiteral("radius_in"); ShapeNode::ShapeNode() { PrependInput(kTypeInput, NodeValue::kCombo); + + AddInput(kRadiusInput, NodeValue::kFloat, 20.0); + SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0); } QString ShapeNode::Name() const @@ -56,9 +60,10 @@ void ShapeNode::Retranslate() super::Retranslate(); SetInputName(kTypeInput, tr("Type")); + SetInputName(kRadiusInput, tr("Radius")); // Coordinate with Type enum - SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse")}); + SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse"), tr("Rounded Rectangle")}); } ShaderCode ShapeNode::GetShaderCode(const ShaderRequest &request) const @@ -81,4 +86,18 @@ void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod PushMergableJob(value, QVariant::fromValue(job), table); } +void ShapeNode::InputValueChangedEvent(const QString &input, int element) +{ + if (input == kTypeInput) { + InputFlags i = GetInputFlags(kRadiusInput); + if (GetStandardValue(kTypeInput).toInt() == kRoundedRectangle) { + i &= InputFlag(~kInputFlagHidden); + } else { + i |= kInputFlagHidden; + } + SetInputFlags(kRadiusInput, i); + } + super::InputValueChangedEvent(input, element); +} + } diff --git a/app/node/generator/shape/shapenode.h b/app/node/generator/shape/shapenode.h index 3dfdccbaf..285fc5bb5 100644 --- a/app/node/generator/shape/shapenode.h +++ b/app/node/generator/shape/shapenode.h @@ -33,7 +33,8 @@ public: enum Type { kRectangle, - kEllipse + kEllipse, + kRoundedRectangle }; NODE_DEFAULT_FUNCTIONS(ShapeNode) @@ -49,6 +50,10 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; static QString kTypeInput; + static QString kRadiusInput; + +protected: + virtual void InputValueChangedEvent(const QString &input, int element) override; }; diff --git a/app/shaders/shape.frag b/app/shaders/shape.frag index f06b2ce7b..51b2c7270 100644 --- a/app/shaders/shape.frag +++ b/app/shaders/shape.frag @@ -3,14 +3,35 @@ in vec2 ove_texcoord; out vec4 frag_color; // Match with ShapeNode::Type -#define SHAPE_RECTANGLE 0 -#define SHAPE_ELLIPSE 1 +const int SHAPE_RECTANGLE = 0; +const int SHAPE_ELLIPSE = 1; +const int SHAPE_ROUNDEDRECT = 2; uniform vec2 pos_in; uniform vec2 size_in; uniform int type_in; uniform vec2 resolution_in; uniform vec4 color_in; +uniform float radius_in; + +vec4 draw_rect(vec2 real_position, vec2 real_size) +{ + if (ove_texcoord.x >= real_position.x && ove_texcoord.y >= real_position.y + && ove_texcoord.x < real_position.x+real_size.x && ove_texcoord.y < real_position.y+real_size.y) { + return color_in; + } else { + return vec4(0.0, 0.0, 0.0, 0.0); + } +} + +vec4 draw_ellipse(vec2 center, float radius, float aspect_ratio) { + vec2 offset = ove_texcoord*resolution_in - center; + offset.x /= aspect_ratio; + float d = length(offset)-radius; + float t = clamp(d, 0.0, 1.0); + + return color_in * (1.0-t); +} void main() { vec2 p = pos_in + resolution_in*0.5 - size_in*0.5; @@ -20,22 +41,42 @@ void main() { vec4 col = vec4(0.0); - if (type_in == SHAPE_RECTANGLE) { - if (ove_texcoord.x >= real_position.x && ove_texcoord.y >= real_position.y - && ove_texcoord.x < real_position.x+real_size.x && ove_texcoord.y < real_position.y+real_size.y) { - col = color_in; - } - } else if (type_in == SHAPE_ELLIPSE) { + switch (type_in) { + case SHAPE_RECTANGLE: + { + col = draw_rect(real_position, real_size); + break; + } + case SHAPE_ELLIPSE: + { vec2 center = p+size_in*0.5; float radius = size_in.y*0.5; float aspect_ratio = size_in.x/size_in.y; - - vec2 offset = ove_texcoord*resolution_in - center; - offset.x /= aspect_ratio; - float d = length(offset)-radius; - float t = clamp(d, 0.0, 1.0); - - col = color_in * (1.0-t); + col = draw_ellipse(center, radius, aspect_ratio); + break; + } + case SHAPE_ROUNDEDRECT: + { + // Limit radius so it is never larger than half the shortest size + float r = min(radius_in, min(size_in.y*0.5, size_in.x*0.5)); + vec2 real_rad = vec2(r / resolution_in.x, r / resolution_in.y); + if (ove_texcoord.x < real_position.x + real_rad.x && ove_texcoord.y < real_position.y + real_rad.y) { + // Top-left + col = draw_ellipse(p + r, r, 1.0); + } else if (ove_texcoord.x > real_position.x+real_size.x - real_rad.x && ove_texcoord.y < real_position.y + real_rad.y) { + // Top-right + col = draw_ellipse(vec2(p.x + size_in.x - r, p.y + r), r, 1.0); + } else if (ove_texcoord.x < real_position.x + real_rad.x && ove_texcoord.y > real_position.y + real_size.y - real_rad.y) { + // Bottom-left + col = draw_ellipse(vec2(p.x + r, p.y + size_in.y - r), r, 1.0); + } else if (ove_texcoord.x > real_position.x+real_size.x - real_rad.x && ove_texcoord.y > real_position.y + real_size.y - real_rad.y) { + // Bottom-right + col = draw_ellipse(vec2(p.x + size_in.x - r, p.y + size_in.y - r), r, 1.0); + } else { + col = draw_rect(real_position, real_size); + } + break; + } } frag_color = col; From a2b400a1dceae789d951be9d67ff657999c290bd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Aug 2022 09:51:02 -0700 Subject: [PATCH 092/107] projectexplorer: add delete context menu entry Fixes #2004 --- app/widget/projectexplorer/projectexplorer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 1a3cc36a4..bdf853d99 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -403,7 +403,12 @@ void ProjectExplorer::ShowContextMenu() auto rename_action = menu.addAction(tr("Rename")); connect(rename_action, &QAction::triggered, this, &ProjectExplorer::RenameSelectedItem); + } + auto delete_action = menu.addAction(tr("Delete")); + connect(delete_action, &QAction::triggered, this, &ProjectExplorer::DeleteSelected); + + if (context_menu_items_.size() == 1) { menu.addSeparator(); QAction* properties_action = menu.addAction(tr("P&roperties")); From 409f24be397c6fdffbd23936535ac84028e52a11 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:37:24 -0700 Subject: [PATCH 093/107] exportdialog: allow restoring parameters --- app/codec/encoder.cpp | 60 +++++++++++- app/codec/encoder.h | 39 +++++++- app/codec/ffmpeg/ffmpegencoder.cpp | 4 +- app/common/qtutils.cpp | 10 ++ app/common/qtutils.h | 9 +- app/dialog/export/codec/cineformsection.cpp | 5 + app/dialog/export/codec/cineformsection.h | 2 + app/dialog/export/codec/codecsection.h | 2 + app/dialog/export/codec/h264section.cpp | 57 ++++++++++- app/dialog/export/codec/h264section.h | 6 ++ app/dialog/export/codec/imagesection.h | 5 + app/dialog/export/export.cpp | 101 ++++++++++++++++--- app/dialog/export/export.h | 10 +- app/dialog/export/exportsubtitlestab.h | 6 ++ app/dialog/export/exportvideotab.cpp | 14 ++- app/dialog/export/exportvideotab.h | 25 +++-- app/node/output/viewer/viewer.h | 6 ++ app/task/export/CMakeLists.txt | 2 - app/task/export/export.cpp | 16 +-- app/task/export/export.h | 6 +- app/task/export/exportparams.cpp | 103 -------------------- app/task/export/exportparams.h | 65 ------------ 22 files changed, 331 insertions(+), 222 deletions(-) delete mode 100644 app/task/export/exportparams.cpp delete mode 100644 app/task/export/exportparams.h diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 8e6642449..b4ef307a3 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -93,7 +93,9 @@ EncodingParams::EncodingParams() : audio_enabled_(false), audio_bit_rate_(0), subtitles_enabled_(false), - subtitles_are_sidecar_(false) + subtitles_are_sidecar_(false), + video_scaling_method_(kStretch), + has_custom_range_(false) { } @@ -142,7 +144,14 @@ void EncodingParams::DisableSubtitles() void EncodingParams::Save(QXmlStreamWriter *writer) const { + writer->writeTextElement(QStringLiteral("version"), QString::number(kEncoderParamsVersion)); + writer->writeTextElement(QStringLiteral("filename"), filename_); + writer->writeTextElement(QStringLiteral("format"), QString::number(format_)); + + writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_)); + writer->writeTextElement(QStringLiteral("customrangein"), custom_range_.in().toString()); + writer->writeTextElement(QStringLiteral("customrangeout"), custom_range_.out().toString()); writer->writeStartElement(QStringLiteral("video")); @@ -156,10 +165,18 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString()); writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider())); writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_)); - writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_max_bit_rate_)); + writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_min_bit_rate_)); writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_)); writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_)); writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_)); + writer->writeTextElement(QStringLiteral("pixfmt"), video_pix_fmt_); + writer->writeTextElement(QStringLiteral("imgseq"), QString::number(video_is_image_sequence_)); + + writer->writeStartElement(QStringLiteral("color")); + writer->writeTextElement(QStringLiteral("output"), color_transform_.output()); + writer->writeEndElement(); // colortransform + + writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_)); if (!video_opts_.isEmpty()) { writer->writeStartElement(QStringLiteral("opts")); @@ -191,6 +208,19 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format())); } + writer->writeStartElement(QStringLiteral("subtitles")); + + writer->writeAttribute(QStringLiteral("enabled"), QString::number(subtitles_enabled_)); + + if (subtitles_enabled_) { + writer->writeTextElement(QStringLiteral("sidecar"), QString::number(subtitles_are_sidecar_)); + writer->writeTextElement(QStringLiteral("sidecarformat"), QString::number(subtitle_sidecar_fmt_)); + + writer->writeTextElement(QStringLiteral("codec"), QString::number(subtitles_codec_)); + } + + writer->writeEndElement(); // subtitles + writer->writeEndElement(); // audio } @@ -255,4 +285,30 @@ std::vector Encoder::GetSampleFormatsForCodec(ExportCodec:: return std::vector(); } +QMatrix4x4 EncodingParams::GenerateMatrix(EncodingParams::VideoScalingMethod method, + int source_width, int source_height, + int dest_width, int dest_height) +{ + QMatrix4x4 preview_matrix; + + if (method == EncodingParams::kStretch) { + return preview_matrix; + } + + float export_ar = static_cast(dest_width) / static_cast(dest_height); + float source_ar = static_cast(source_width) / static_cast(source_height); + + if (qFuzzyCompare(export_ar, source_ar)) { + return preview_matrix; + } + + if ((export_ar > source_ar) == (method == EncodingParams::kFit)) { + preview_matrix.scale(source_ar / export_ar, 1.0F); + } else { + preview_matrix.scale(1.0F, export_ar / source_ar); + } + + return preview_matrix; +} + } diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 2982764ed..a8496065e 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -41,10 +41,22 @@ namespace olive { class Encoder; using EncoderPtr = std::shared_ptr; -class EncodingParams { +class EncodingParams +{ public: + enum VideoScalingMethod { + kFit, + kStretch, + kCrop + }; + EncodingParams(); + bool IsValid() const + { + return video_enabled_ || audio_enabled_ || subtitles_enabled_; + } + void SetFilename(const QString& filename) { filename_ = filename; } void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec); @@ -75,6 +87,8 @@ public: const ExportCodec::Codec& video_codec() const { return video_codec_; } const VideoParams& video_params() const { return video_params_; } const QHash& video_opts() const { return video_opts_; } + QString video_option(const QString &key) const { return video_opts_.value(key); } + bool has_video_opt(const QString &key) const { return video_opts_.contains(key); } const int64_t& video_bit_rate() const { return video_bit_rate_; } const int64_t& video_min_bit_rate() const { return video_min_bit_rate_; } const int64_t& video_max_bit_rate() const { return video_max_bit_rate_; } @@ -99,9 +113,26 @@ public: const rational& GetExportLength() const { return export_length_; } void SetExportLength(const rational& export_length) { export_length_ = export_length; } - virtual void Save(QXmlStreamWriter* writer) const; + void Save(QXmlStreamWriter* writer) const; + + bool has_custom_range() const { return has_custom_range_; } + const TimeRange& custom_range() const { return custom_range_; } + void set_custom_range(const TimeRange& custom_range) + { + has_custom_range_ = true; + custom_range_ = custom_range; + } + + const VideoScalingMethod& video_scaling_method() const { return video_scaling_method_; } + void set_video_scaling_method(const VideoScalingMethod& video_scaling_method) { video_scaling_method_ = video_scaling_method; } + + static QMatrix4x4 GenerateMatrix(VideoScalingMethod method, + int source_width, int source_height, + int dest_width, int dest_height); private: + static const int kEncoderParamsVersion = 1; + QString filename_; ExportFormat::Format format_; @@ -129,6 +160,10 @@ private: ExportCodec::Codec subtitles_codec_; rational export_length_; + VideoScalingMethod video_scaling_method_; + + bool has_custom_range_; + TimeRange custom_range_; }; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index b532d0689..e989db229 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -638,7 +638,9 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV // Set custom options { for (auto i=params().video_opts().begin();i!=params().video_opts().end();i++) { - av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN); + if (!i.key().startsWith(QStringLiteral("ove_"))) { + av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN); + } } if (params().video_bit_rate() > 0) { diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index bb6c23de1..613f8a5e9 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -145,4 +145,14 @@ QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, in return list; } +void QtUtils::SetComboBoxData(QComboBox *cb, int data) +{ + for (int i=0; icount(); i++) { + if (cb->itemData(i).toInt() == data) { + cb->setCurrentIndex(i); + break; + } + } +} + } diff --git a/app/common/qtutils.h b/app/common/qtutils.h index 19e2ea68c..74ff4fcf8 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -21,12 +21,7 @@ #ifndef QTVERSIONABSTRACTION_H #define QTVERSIONABSTRACTION_H -/** - * - * A fairly simple header for reducing the amount of Qt version checks necessary throughout the code - * - */ - +#include #include #include #include @@ -58,6 +53,8 @@ public: static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width); + static void SetComboBoxData(QComboBox *cb, int data); + template static T *GetParentOfType(const QObject *child) { diff --git a/app/dialog/export/codec/cineformsection.cpp b/app/dialog/export/codec/cineformsection.cpp index ba0071e6d..da83eabed 100644 --- a/app/dialog/export/codec/cineformsection.cpp +++ b/app/dialog/export/codec/cineformsection.cpp @@ -82,4 +82,9 @@ void CineformSection::AddOpts(EncodingParams *params) params->set_video_option(QStringLiteral("quality"), QString::number(quality_combobox_->currentIndex())); } +void CineformSection::SetOpts(const EncodingParams *p) +{ + quality_combobox_->setCurrentIndex(p->video_option(QStringLiteral("quality")).toInt()); +} + } diff --git a/app/dialog/export/codec/cineformsection.h b/app/dialog/export/codec/cineformsection.h index edbe8813a..8a3a08491 100644 --- a/app/dialog/export/codec/cineformsection.h +++ b/app/dialog/export/codec/cineformsection.h @@ -35,6 +35,8 @@ public: virtual void AddOpts(EncodingParams* params) override; + virtual void SetOpts(const EncodingParams *p) override; + private: QComboBox *quality_combobox_; diff --git a/app/dialog/export/codec/codecsection.h b/app/dialog/export/codec/codecsection.h index 24be8f1f8..93eebaa1d 100644 --- a/app/dialog/export/codec/codecsection.h +++ b/app/dialog/export/codec/codecsection.h @@ -35,6 +35,8 @@ public: virtual void AddOpts(EncodingParams* params){Q_UNUSED(params)} + virtual void SetOpts(const EncodingParams *p){Q_UNUSED(p)} + }; } diff --git a/app/dialog/export/codec/h264section.cpp b/app/dialog/export/codec/h264section.cpp index 8c015e0f8..732aef346 100644 --- a/app/dialog/export/codec/h264section.cpp +++ b/app/dialog/export/codec/h264section.cpp @@ -60,7 +60,7 @@ H264Section::H264Section(int default_crf, QWidget *parent) : preset_combobox_->addItem(tr("Slow")); preset_combobox_->addItem(tr("Slower")); preset_combobox_->addItem(tr("Very Slow")); - + //Default to "medium" preset_combobox_->setCurrentIndex(5); @@ -105,6 +105,10 @@ void H264Section::AddOpts(EncodingParams *params) CompressionMethod method = static_cast(compression_method_stack_->currentIndex()); + // This option is not used by the encoder (nor is anything with the ove_ prefix), it's to help us + // identify which option was chosen when params are restored + params->set_video_option(QStringLiteral("ove_compressionmethod"), QString::number(method)); + if (method == kConstantRateFactor) { // Simply set CRF value @@ -121,9 +125,12 @@ void H264Section::AddOpts(EncodingParams *params) max_rate = bitrate_section_->GetMaximumBitRate(); } else { // Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second) - target_rate = qRound64(static_cast(filesize_section_->GetFileSize()) / params->GetExportLength().toDouble()); + int64_t target_fs = filesize_section_->GetFileSize(); + target_rate = qRound64(static_cast(target_fs) / params->GetExportLength().toDouble()); min_rate = target_rate; max_rate = target_rate; + + params->set_video_option(QStringLiteral("ove_targetfilesize"), QString::number(target_fs)); } // Disable CRF encoding @@ -135,10 +142,33 @@ void H264Section::AddOpts(EncodingParams *params) params->set_video_buffer_size(2000000); } - + params->set_video_option(QStringLiteral("preset"), QString::number(preset_combobox_->currentIndex())); } +void H264Section::SetOpts(const EncodingParams *p) +{ + CompressionMethod method = static_cast(p->video_option(QStringLiteral("ove_compressionmethod")).toInt()); + + compression_method_stack_->setCurrentIndex(method); + + if (method == kConstantRateFactor) { + crf_section_->SetValue(p->video_option(QStringLiteral("crf")).toInt()); + } else { + int64_t target_rate = p->video_bit_rate(); + int64_t max_rate = p->video_max_bit_rate(); + + if (method == kTargetBitRate) { + // Use user-supplied values for the bit rate + bitrate_section_->SetTargetBitRate(target_rate); + bitrate_section_->SetMaximumBitRate(max_rate); + } else { + // Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second) + filesize_section_->SetFileSize(p->video_option(QStringLiteral("ove_targetfilesize")).toLongLong()); + } + } +} + H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) : QWidget(parent) { @@ -168,6 +198,11 @@ int H264CRFSection::GetValue() const return crf_slider_->value(); } +void H264CRFSection::SetValue(int c) +{ + crf_slider_->setValue(c); +} + H264BitRateSection::H264BitRateSection(QWidget *parent) : QWidget(parent) { @@ -207,11 +242,21 @@ int64_t H264BitRateSection::GetTargetBitRate() const return qRound64(target_rate_->GetValue() * 1000000.0); } +void H264BitRateSection::SetTargetBitRate(int64_t b) +{ + target_rate_->SetValue(double(b) * 0.000001); +} + int64_t H264BitRateSection::GetMaximumBitRate() const { return qRound64(max_rate_->GetValue() * 1000000.0); } +void H264BitRateSection::SetMaximumBitRate(int64_t b) +{ + max_rate_->SetValue(double(b) * 0.000001); +} + H264FileSizeSection::H264FileSizeSection(QWidget *parent) : QWidget(parent) { @@ -243,6 +288,12 @@ int64_t H264FileSizeSection::GetFileSize() const return qRound64(file_size_->GetValue() * 1024.0 * 1024.0 * 8.0); } +void H264FileSizeSection::SetFileSize(int64_t f) +{ + // Convert bits back to megabytes + file_size_->SetValue(double(f) / 8.0 / 1024.0 / 1024.0); +} + H265Section::H265Section(QWidget *parent) : H264Section(H264CRFSection::kDefaultH265CRF, parent) { diff --git a/app/dialog/export/codec/h264section.h b/app/dialog/export/codec/h264section.h index 2ca0eaa59..9fd984ae5 100644 --- a/app/dialog/export/codec/h264section.h +++ b/app/dialog/export/codec/h264section.h @@ -37,6 +37,7 @@ public: H264CRFSection(int default_crf, QWidget* parent = nullptr); int GetValue() const; + void SetValue(int c); static const int kDefaultH264CRF = 18; static const int kDefaultH265CRF = 23; @@ -59,11 +60,13 @@ public: * @brief Get user-selected target bit rate (returns in BITS) */ int64_t GetTargetBitRate() const; + void SetTargetBitRate(int64_t b); /** * @brief Get user-selected maximum bit rate (returns in BITS) */ int64_t GetMaximumBitRate() const; + void SetMaximumBitRate(int64_t b); private: FloatSlider* target_rate_; @@ -82,6 +85,7 @@ public: * @brief Returns file size in BITS */ int64_t GetFileSize() const; + void SetFileSize(int64_t f); private: FloatSlider* file_size_; @@ -103,6 +107,8 @@ public: virtual void AddOpts(EncodingParams* params) override; + virtual void SetOpts(const EncodingParams *p) override; + private: QStackedWidget* compression_method_stack_; diff --git a/app/dialog/export/codec/imagesection.h b/app/dialog/export/codec/imagesection.h index b93f733a0..3575ea5a2 100644 --- a/app/dialog/export/codec/imagesection.h +++ b/app/dialog/export/codec/imagesection.h @@ -39,6 +39,11 @@ public: return image_sequence_checkbox_->isChecked(); } + void SetImageSequenceChecked(bool e) + { + image_sequence_checkbox_->setChecked(e); + } + void SetTimebase(const rational& r) { frame_slider_->SetTimebase(r); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 0968428f8..71b12e992 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -41,8 +41,10 @@ namespace olive { +#define super QDialog + ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : - QDialog(parent), + super(parent), viewer_node_(viewer_node) { QHBoxLayout* layout = new QHBoxLayout(this); @@ -255,6 +257,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled); subtitles_enabled_->setChecked(has_subtitle_codecs); subtitles_enabled_->setEnabled(has_subtitle_codecs); + + // If the viewer already has cached params, use them + if (viewer_node_->GetLastUsedEncodingParams().IsValid()) { + SetParams(viewer_node_->GetLastUsedEncodingParams()); + } } rational ExportDialog::GetSelectedTimebase() const @@ -262,6 +269,11 @@ rational ExportDialog::GetSelectedTimebase() const return video_tab_->GetSelectedFrameRate().flipped(); } +void ExportDialog::SetSelectedTimebase(const rational &r) +{ + video_tab_->SetSelectedFrameRate(r.flipped()); +} + void ExportDialog::StartExport() { if (!video_enabled_->isChecked() && !audio_enabled_->isChecked() && !subtitles_enabled_->isChecked()) { @@ -390,13 +402,6 @@ void ExportDialog::ImageSequenceCheckBoxChanged(bool e) filename_edit_->setText(current_fileinfo.dir().filePath(basename)); } -void ExportDialog::closeEvent(QCloseEvent *e) -{ - preview_viewer_->ConnectViewerNode(nullptr); - - QDialog::closeEvent(e); -} - void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title) { QScrollArea* scroll_area = new QScrollArea(); @@ -522,7 +527,7 @@ bool ExportDialog::SequenceHasSubtitles() const return false; } -ExportParams ExportDialog::GenerateParams() const +EncodingParams ExportDialog::GenerateParams() const { VideoParams video_render_params(static_cast(video_tab_->width_slider()->GetValue()), static_cast(video_tab_->height_slider()->GetValue()), @@ -537,7 +542,7 @@ ExportParams ExportDialog::GenerateParams() const audio_tab_->channel_layout_combobox()->GetChannelLayout(), audio_tab_->sample_format_combobox()->GetSampleFormat()); - ExportParams params; + EncodingParams params; params.set_format(format_combobox_->GetFormat()); params.SetFilename(filename_edit_->text().trimmed()); params.SetExportLength(viewer_node_->GetLength()); @@ -552,7 +557,7 @@ ExportParams ExportDialog::GenerateParams() const } if (video_tab_->scaling_method_combobox()->isEnabled()) { - params.set_video_scaling_method(static_cast(video_tab_->scaling_method_combobox()->currentData().toInt())); + params.set_video_scaling_method(static_cast(video_tab_->scaling_method_combobox()->currentData().toInt())); } if (video_enabled_->isChecked()) { @@ -596,6 +601,76 @@ ExportParams ExportDialog::GenerateParams() const return params; } +void ExportDialog::SetParams(const EncodingParams &e) +{ + format_combobox_->SetFormat(e.format()); + filename_edit_->setText(e.filename()); + + if (e.has_custom_range() && viewer_node_->GetWorkArea()->enabled()) { + range_combobox_->setCurrentIndex(kRangeInToOut); + } + + QtUtils::SetComboBoxData(video_tab_->scaling_method_combobox(), e.video_scaling_method()); + + video_enabled_->setChecked(e.video_enabled()); + if (e.video_enabled()) { + video_tab_->width_slider()->SetValue(e.video_params().width()); + video_tab_->height_slider()->SetValue(e.video_params().height()); + SetSelectedTimebase(e.video_params().time_base()); + video_tab_->pixel_format_field()->SetPixelFormat(e.video_params().format()); + video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(e.video_params().pixel_aspect_ratio()); + video_tab_->interlaced_combobox()->SetInterlaceMode(e.video_params().interlacing()); + + video_tab_->SetSelectedCodec(e.video_codec()); + + video_tab_->SetColorRange(e.video_params().color_range()); + + video_tab_->SetThreads(e.video_threads()); + + if (video_tab_->isVisible()) { + video_tab_->GetCodecSection()->SetOpts(&e); + } + + video_tab_->SetOCIOColorSpace(e.color_transform().output()); + + video_tab_->SetPixFmt(e.video_pix_fmt()); + + video_tab_->SetImageSequence(e.video_is_image_sequence()); + } + + audio_enabled_->setChecked(e.audio_enabled()); + if (e.audio_enabled()) { + audio_tab_->sample_rate_combobox()->SetSampleRate(e.audio_params().sample_rate()); + audio_tab_->channel_layout_combobox()->SetChannelLayout(e.audio_params().channel_layout()); + audio_tab_->sample_format_combobox()->SetSampleFormat(e.audio_params().format()); + + audio_tab_->SetCodec(e.audio_codec()); + + audio_tab_->bit_rate_slider()->SetValue(e.audio_bit_rate() / 1000); + } + + if (subtitles_enabled_->isEnabled()) { + subtitles_enabled_->setChecked(e.subtitles_enabled()); + subtitle_tab_->SetSidecarEnabled(e.subtitles_are_sidecar()); + if (e.subtitles_enabled()) { + subtitle_tab_->SetSubtitleCodec(e.subtitles_codec()); + if (e.subtitles_are_sidecar()) { + subtitle_tab_->SetSidecarFormat(e.subtitle_sidecar_fmt()); + } + } + } +} + +void ExportDialog::done(int r) +{ + qDebug() << "done???"; + preview_viewer_->ConnectViewerNode(nullptr); + + viewer_node_->SetLastUsedEncodingParams(GenerateParams()); + + super::done(r); +} + rational ExportDialog::GetExportLength() const { if (range_combobox_->currentIndex() == kRangeInToOut) { @@ -617,8 +692,8 @@ void ExportDialog::UpdateViewerDimensions() VideoParams vp = viewer_node_->GetVideoParams(); - QMatrix4x4 transform = ExportParams::GenerateMatrix( - static_cast(video_tab_->scaling_method_combobox()->currentData().toInt()), + QMatrix4x4 transform = EncodingParams::GenerateMatrix( + static_cast(video_tab_->scaling_method_combobox()->currentData().toInt()), vp.width(), vp.height(), static_cast(video_tab_->width_slider()->GetValue()), diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 9cd7d274a..abf242575 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -45,6 +45,7 @@ public: ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr); rational GetSelectedTimebase() const; + void SetSelectedTimebase(const rational &r); void SetTime(const rational &time) { @@ -54,8 +55,11 @@ public: preview_viewer_->SetAudioScrubbingEnabled(true); } -protected: - virtual void closeEvent(QCloseEvent *e) override; + EncodingParams GenerateParams() const; + void SetParams(const EncodingParams &e); + +public slots: + virtual void done(int r) override; private: void AddPreferencesTab(QWidget *inner_widget, const QString &title); @@ -65,8 +69,6 @@ private: bool SequenceHasSubtitles() const; - ExportParams GenerateParams() const; - ViewerOutput* viewer_node_; ExportFormat::Format previously_selected_format_; diff --git a/app/dialog/export/exportsubtitlestab.h b/app/dialog/export/exportsubtitlestab.h index faa5d4bdf..dee62f93e 100644 --- a/app/dialog/export/exportsubtitlestab.h +++ b/app/dialog/export/exportsubtitlestab.h @@ -26,6 +26,7 @@ #include #include "codec/exportformat.h" +#include "common/qtutils.h" #include "dialog/export/exportformatcombobox.h" namespace olive { @@ -49,6 +50,11 @@ public: return static_cast(codec_combobox_->currentData().toInt()); } + void SetSubtitleCodec(ExportCodec::Codec c) + { + QtUtils::SetComboBoxData(codec_combobox_, c); + } + private: QCheckBox *sidecar_checkbox_; diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index fd175c8a0..56ecfcdfc 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -29,7 +29,6 @@ #include "core.h" #include "exportadvancedvideodialog.h" #include "node/color/colormanager/colormanager.h" -#include "task/export/exportparams.h" namespace olive { @@ -70,6 +69,13 @@ bool ExportVideoTab::IsImageSequenceSet() const return (img_section && img_section->IsImageSequenceChecked()); } +void ExportVideoTab::SetImageSequence(bool e) const +{ + if (ImageSection* img_section = dynamic_cast(codec_stack_->currentWidget())) { + img_section->SetImageSequenceChecked(e); + } +} + QWidget* ExportVideoTab::SetupResolutionSection() { int row = 0; @@ -107,9 +113,9 @@ QWidget* ExportVideoTab::SetupResolutionSection() scaling_method_combobox_ = new QComboBox(); scaling_method_combobox_->setEnabled(false); - scaling_method_combobox_->addItem(tr("Fit"), ExportParams::kFit); - scaling_method_combobox_->addItem(tr("Stretch"), ExportParams::kStretch); - scaling_method_combobox_->addItem(tr("Crop"), ExportParams::kCrop); + scaling_method_combobox_->addItem(tr("Fit"), EncodingParams::kFit); + scaling_method_combobox_->addItem(tr("Stretch"), EncodingParams::kStretch); + scaling_method_combobox_->addItem(tr("Crop"), EncodingParams::kCrop); layout->addWidget(scaling_method_combobox_, row, 1); // Automatically enable/disable the scaling method depending on maintain aspect ratio diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index f6f997ee2..1a23837f6 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -25,6 +25,7 @@ #include #include +#include "common/qtutils.h" #include "common/rational.h" #include "dialog/export/codec/cineformsection.h" #include "dialog/export/codec/codecstack.h" @@ -46,6 +47,7 @@ public: int SetFormat(ExportFormat::Format format); bool IsImageSequenceSet() const; + void SetImageSequence(bool e) const; rational GetStillImageTime() const { @@ -57,6 +59,11 @@ public: return static_cast(codec_combobox()->currentData().toInt()); } + void SetSelectedCodec(ExportCodec::Codec c) + { + QtUtils::SetComboBoxData(codec_combobox(), c); + } + QComboBox* codec_combobox() const { return codec_combobox_; @@ -98,6 +105,11 @@ public: return color_space_chooser_->input(); } + void SetOCIOColorSpace(const QString &s) + { + color_space_chooser_->set_input(s); + } + CodecSection* GetCodecSection() const { return static_cast(codec_stack_->currentWidget()); @@ -133,15 +145,16 @@ public: return threads_; } - const QString& pix_fmt() const + void SetThreads(int t) { - return pix_fmt_; + threads_ = t; } - VideoParams::ColorRange color_range() const - { - return color_range_; - } + const QString& pix_fmt() const { return pix_fmt_; } + void SetPixFmt(const QString &s) { pix_fmt_ = s; } + + VideoParams::ColorRange color_range() const { return color_range_; } + void SetColorRange(VideoParams::ColorRange c) { color_range_ = c; } public slots: void VideoCodecChanged(); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 8d26f4bd3..81e96f1d2 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -21,6 +21,7 @@ #ifndef VIEWER_H #define VIEWER_H +#include "codec/encoder.h" #include "common/rational.h" #include "node/node.h" #include "node/output/track/track.h" @@ -164,6 +165,9 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + const EncodingParams &GetLastUsedEncodingParams() const { return last_used_encoding_params_; } + void SetLastUsedEncodingParams(const EncodingParams &p) { last_used_encoding_params_ = p; } + static const QString kVideoParamsInput; static const QString kAudioParamsInput; static const QString kSubtitleParamsInput; @@ -216,6 +220,8 @@ private: TimelineWorkArea *workarea_; TimelineMarkerList *markers_; + EncodingParams last_used_encoding_params_; + }; } diff --git a/app/task/export/CMakeLists.txt b/app/task/export/CMakeLists.txt index 5c1bb1f41..7a7fad1bc 100644 --- a/app/task/export/CMakeLists.txt +++ b/app/task/export/CMakeLists.txt @@ -18,7 +18,5 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} task/export/export.h task/export/export.cpp - task/export/exportparams.h - task/export/exportparams.cpp PARENT_SCOPE ) diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 7688860d7..017fc59fb 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -27,7 +27,7 @@ namespace olive { ExportTask::ExportTask(ViewerOutput *viewer_node, ColorManager* color_manager, - const ExportParams& params) : + const EncodingParams& params) : color_manager_(color_manager), params_(params) { @@ -60,7 +60,7 @@ bool ExportTask::Run() // If we're exporting to a sidecar subtitle file, disable the subtitles in the main encoder bool subtitles_enabled = params_.subtitles_enabled(); - ExportParams sidecar_params = params_; + EncodingParams sidecar_params = params_; if (subtitles_enabled && params_.subtitles_are_sidecar()) { params_.DisableSubtitles(); } @@ -126,12 +126,12 @@ bool ExportTask::Run() || video_params().height() != params_.video_params().height()) { video_force_size = QSize(params_.video_params().width(), params_.video_params().height()); - if (params_.video_scaling_method() != ExportParams::kStretch) { - video_force_matrix = ExportParams::GenerateMatrix(params_.video_scaling_method(), - video_params().width(), - video_params().height(), - params_.video_params().width(), - params_.video_params().height()); + if (params_.video_scaling_method() != EncodingParams::kStretch) { + video_force_matrix = EncodingParams::GenerateMatrix(params_.video_scaling_method(), + video_params().width(), + video_params().height(), + params_.video_params().width(), + params_.video_params().height()); } } else { // Disables forcing size in the renderer diff --git a/app/task/export/export.h b/app/task/export/export.h index 4bed7cd8b..7dcd8cf99 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -21,7 +21,7 @@ #ifndef EXPORTTASK_H #define EXPORTTASK_H -#include "exportparams.h" +#include "codec/encoder.h" #include "node/output/viewer/viewer.h" #include "render/colorprocessor.h" #include "task/render/render.h" @@ -33,7 +33,7 @@ class ExportTask : public RenderTask { Q_OBJECT public: - ExportTask(ViewerOutput *viewer_node, ColorManager *color_manager, const ExportParams ¶ms); + ExportTask(ViewerOutput *viewer_node, ColorManager *color_manager, const EncodingParams ¶ms); protected: virtual bool Run() override; @@ -58,7 +58,7 @@ private: ColorManager* color_manager_; - ExportParams params_; + EncodingParams params_; std::shared_ptr encoder_; diff --git a/app/task/export/exportparams.cpp b/app/task/export/exportparams.cpp deleted file mode 100644 index f866f3cbd..000000000 --- a/app/task/export/exportparams.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 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 "exportparams.h" - -namespace olive { - -ExportParams::ExportParams() : - video_scaling_method_(kStretch), - has_custom_range_(false) -{ -} - -bool ExportParams::has_custom_range() const -{ - return has_custom_range_; -} - -const TimeRange &ExportParams::custom_range() const -{ - return custom_range_; -} - -void ExportParams::set_custom_range(const TimeRange &custom_range) -{ - has_custom_range_ = true; - custom_range_ = custom_range; -} - -const ExportParams::VideoScalingMethod &ExportParams::video_scaling_method() const -{ - return video_scaling_method_; -} - -void ExportParams::set_video_scaling_method(const ExportParams::VideoScalingMethod &video_scaling_method) -{ - video_scaling_method_ = video_scaling_method; -} - -QMatrix4x4 ExportParams::GenerateMatrix(ExportParams::VideoScalingMethod method, - int source_width, int source_height, - int dest_width, int dest_height) -{ - QMatrix4x4 preview_matrix; - - if (method == ExportParams::kStretch) { - return preview_matrix; - } - - float export_ar = static_cast(dest_width) / static_cast(dest_height); - float source_ar = static_cast(source_width) / static_cast(source_height); - - if (qFuzzyCompare(export_ar, source_ar)) { - return preview_matrix; - } - - if ((export_ar > source_ar) == (method == ExportParams::kFit)) { - preview_matrix.scale(source_ar / export_ar, 1.0F); - } else { - preview_matrix.scale(1.0F, export_ar / source_ar); - } - - return preview_matrix; -} - -void ExportParams::Save(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("export")); - - writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_)); - - writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_)); - - writer->writeTextElement(QStringLiteral("customrangein"), custom_range_.in().toString()); - - writer->writeTextElement(QStringLiteral("customrangeout"), custom_range_.out().toString()); - - // FIXME: Change this when color chains are implemented - writer->writeTextElement(QStringLiteral("color"), color_transform().output()); - - EncodingParams::Save(writer); - - writer->writeEndElement(); // export -} - -} diff --git a/app/task/export/exportparams.h b/app/task/export/exportparams.h deleted file mode 100644 index 437271eab..000000000 --- a/app/task/export/exportparams.h +++ /dev/null @@ -1,65 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 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 EXPORTPARAMS_H -#define EXPORTPARAMS_H - -#include - -#include "codec/encoder.h" -#include "node/output/viewer/viewer.h" -#include "render/colortransform.h" - -namespace olive { - -class ExportParams : public EncodingParams { -public: - enum VideoScalingMethod { - kFit, - kStretch, - kCrop - }; - - ExportParams(); - - bool has_custom_range() const; - const TimeRange& custom_range() const; - void set_custom_range(const TimeRange& custom_range); - - const VideoScalingMethod& video_scaling_method() const; - void set_video_scaling_method(const VideoScalingMethod& video_scaling_method); - - static QMatrix4x4 GenerateMatrix(ExportParams::VideoScalingMethod method, - int source_width, int source_height, - int dest_width, int dest_height); - - virtual void Save(QXmlStreamWriter* writer) const override; - -private: - VideoScalingMethod video_scaling_method_; - - bool has_custom_range_; - TimeRange custom_range_; - -}; - -} - -#endif // EXPORTPARAMS_H From 3cee10dfcff298c3291edb8b589d1b0be8eeb327 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:56:14 -0700 Subject: [PATCH 094/107] exportdialog: remove debug line --- app/dialog/export/export.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 71b12e992..f9b0ec6a1 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -663,7 +663,6 @@ void ExportDialog::SetParams(const EncodingParams &e) void ExportDialog::done(int r) { - qDebug() << "done???"; preview_viewer_->ConnectViewerNode(nullptr); viewer_node_->SetLastUsedEncodingParams(GenerateParams()); From 9f8ffe1fc0e2522dc2e7aa54f40b9abe7bc9cf39 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 11 Aug 2022 08:35:17 -0700 Subject: [PATCH 095/107] config: rename entry to force refresh on other systems --- app/config/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index ce3888875..1e0ee70a2 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -150,7 +150,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("DefaultSequencePixelAspect"), NodeValue::kRational, QVariant::fromValue(rational(1))); SetEntryInternal(QStringLiteral("DefaultSequenceFrameRate"), NodeValue::kRational, QVariant::fromValue(rational(1001, 30000))); SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeValue::kInt, VideoParams::kInterlaceNone); - SetEntryInternal(QStringLiteral("DefaultSequenceAutoCache"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("DefaultSequenceAutoCache2"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeValue::kInt, 48000); SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeValue::kInt, QVariant::fromValue(static_cast(AV_CH_LAYOUT_STEREO))); From 2130f1799bf30ba90bc91a35f9aa70d545916726 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 11 Aug 2022 10:03:17 -0700 Subject: [PATCH 096/107] renderer: add mutexes around texture cache --- app/render/renderer.cpp | 25 ++++++++++++++++++++++++- app/render/renderer.h | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 85f3aed69..e6754e5c7 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -21,6 +21,7 @@ #include "renderer.h" #include +#include #include #include @@ -36,6 +37,7 @@ TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, QVariant v; if (USE_TEXTURE_CACHE) { + QMutexLocker locker(&texture_cache_lock_); for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { if (it->width == params.effective_width() && it->height == params.effective_height() @@ -64,6 +66,22 @@ TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, void Renderer::DestroyTexture(Texture *texture) { if (USE_TEXTURE_CACHE) { + // HACK: Dirty, dirty hack. OpenGL uses "contexts" to store all of its data, and each context + // can only be used by the thread that created it. However there are also "shared contexts" + // where assets from one context can be used in another. We use shared contexts so that + // textures rendered in the background can be displayed on the screen, travelling from + // a background thread to the main UI thread. However, when that texture is destroyed, it + // comes back here to be placed in the texture cache. But that leads to a race condition + // because it will call the background thread's renderer in the main thread. Since all + // assets are shared, we could technically just get the texture to call "destroy" in the + // viewer's renderer instance, but that would mean all textures would end up stranded + // there unusable by the background renderer, negating the very advantage of the texture + // cache in the first place. Therefore, we simply allow the thread calling to happen, and + // use mutexes to prevent race conditions. + // + // Presumably Vulkan would not have this issue because it allows for application-wide + // instances and multithreading. + texture_cache_lock_.lock(); texture_cache_.push_back({texture->params().effective_width(), texture->params().effective_height(), texture->params().effective_depth(), @@ -71,8 +89,11 @@ void Renderer::DestroyTexture(Texture *texture) texture->params().channel_count(), texture->id(), QDateTime::currentMSecsSinceEpoch()}); + texture_cache_lock_.unlock(); - ClearOldTextures(); + if (QThread::currentThread() == this->thread()) { + ClearOldTextures(); + } } else { DestroyNativeTexture(texture->id()); } @@ -249,6 +270,8 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col void Renderer::ClearOldTextures() { + QMutexLocker locker(&texture_cache_lock_); + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); ) { if (it->accessed < QDateTime::currentMSecsSinceEpoch() - MAX_TEXTURE_LIFE) { DestroyNativeTexture(it->handle); diff --git a/app/render/renderer.h b/app/render/renderer.h index 96141df4e..76651c05c 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -155,6 +155,8 @@ private: QVariant interlace_texture_; + QMutex texture_cache_lock_; + }; } From 87348031d8690bf22eaed207f13e02bab809f371 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Aug 2022 09:01:47 -0700 Subject: [PATCH 097/107] nodeparamview: fix issue where some nodes didn't appear in curve view --- app/widget/nodeparamview/nodeparamview.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index cec7c16db..fee75f8ba 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -860,12 +860,10 @@ void NodeParamView::ToggleSelect(NodeParamViewItem *item) new_sel.append(item); SetSelectedNodes(new_sel, false); - if (item->GetNode()->HasGizmos() || !new_sel.contains(focused_node_)) { - if (item->GetNode()->HasGizmos()) { - focused_node_ = item; - } else { - focused_node_ = nullptr; - } + if (!new_sel.contains(focused_node_)) { + // This node gets sent to both the curve editor and viewer, so we focus it even if it has + // no gizmos + focused_node_ = item; emit FocusedNodeChanged(focused_node_ ? focused_node_->GetNode() : nullptr); } From 402c4e1694385811811701c55f935bd14a5f42aa Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Aug 2022 09:16:12 -0700 Subject: [PATCH 098/107] ui: move zoom scrolling option to preferences --- .../tabs/preferencesbehaviortab.cpp | 5 +++++ .../handmovableview/handmovableview.cpp | 18 ++-------------- app/widget/handmovableview/handmovableview.h | 21 ------------------- app/widget/keyframeview/keyframeview.cpp | 4 ---- app/widget/nodeview/nodeview.cpp | 4 ---- app/widget/timelinewidget/timelinewidget.cpp | 10 --------- app/widget/timelinewidget/timelinewidget.h | 2 -- 7 files changed, 7 insertions(+), 57 deletions(-) diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index a081260fe..df9917a84 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -44,6 +44,11 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() AddItem(tr("Enable slider ladder"), QStringLiteral("UseSliderLadders"), general_group); + AddItem(tr("Scrolling zooms by default"), + QStringLiteral("ScrollZooms"), + tr("By default, scrolling will move the view around, and holding Ctrl/Cmd will make it zoom instead. " + "Enabling this will switch those, scrolling will zoom by default, and holding Ctrl/Cmd will move the view instead."), + general_group); QTreeWidgetItem* audio_group = AddParent(tr("Audio")); AddItem(tr("Enable audio scrubbing"), diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index 5ff36be0a..a44858ad8 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -31,8 +31,7 @@ namespace olive { HandMovableView::HandMovableView(QWidget* parent) : super(parent), - dragging_hand_(false), - scroll_zooms_by_default_(OLIVE_CONFIG("ScrollZooms").toBool()) + dragging_hand_(false) { connect(Core::instance(), &Core::ToolChanged, this, &HandMovableView::ApplicationToolChanged); } @@ -149,7 +148,7 @@ const HandMovableView::DragMode &HandMovableView::GetDefaultDragMode() const bool HandMovableView::WheelEventIsAZoomEvent(QWheelEvent *event) const { - return (static_cast(event->modifiers() & Qt::ControlModifier) == !scroll_zooms_by_default_); + return (static_cast(event->modifiers() & Qt::ControlModifier) == !OLIVE_CONFIG("ScrollZooms").toBool()); } void HandMovableView::wheelEvent(QWheelEvent *event) @@ -179,17 +178,4 @@ void HandMovableView::ZoomIntoCursorPosition(QWheelEvent *event, double multipli Q_UNUSED(cursor_pos) } -QAction *HandMovableView::AddSetScrollZoomsByDefaultActionToMenu(QMenu *m, bool autoconnect) -{ - QAction* ctrl_zoom = m->addAction(tr("Scroll Zooms By Default")); - ctrl_zoom->setCheckable(true); - ctrl_zoom->setChecked(GetScrollZoomsByDefault()); - - if (autoconnect) { - connect(ctrl_zoom, &QAction::triggered, this, &HandMovableView::SetScrollZoomsByDefault); - } - - return ctrl_zoom; -} - } diff --git a/app/widget/handmovableview/handmovableview.h b/app/widget/handmovableview/handmovableview.h index c1f9220e8..e8c01599b 100644 --- a/app/widget/handmovableview/handmovableview.h +++ b/app/widget/handmovableview/handmovableview.h @@ -34,19 +34,6 @@ class HandMovableView : public QGraphicsView public: HandMovableView(QWidget* parent = nullptr); - bool GetScrollZoomsByDefault() const - { - return scroll_zooms_by_default_; - } - - QAction* AddSetScrollZoomsByDefaultActionToMenu(QMenu* menu, bool autoconnect = true); - -public slots: - void SetScrollZoomsByDefault(bool e) - { - scroll_zooms_by_default_ = e; - } - protected: virtual void ToolChangedEvent(Tool::Item tool){Q_UNUSED(tool)} @@ -69,14 +56,6 @@ private: DragMode default_drag_mode_; - /** - * @brief Whether scrolling should perform a scroll or a zoom - * - * If TRUE, scrolling will ZOOM and Ctrl+Scroll with SCROLL. - * If FALSE (default), scrolling will SCROLL and Ctrl+Scroll will ZOOM. - */ - bool scroll_zooms_by_default_; - QPointF transformed_pos_; private slots: diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 688e5d7f8..9fead0fe1 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -569,10 +569,6 @@ void KeyframeView::ShowContextMenu() m.addSeparator(); - AddSetScrollZoomsByDefaultActionToMenu(&m); - - m.addSeparator(); - ContextMenuEvent(m); if (!GetSelectedKeyframes().empty()) { diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index e757e6031..cf1610b02 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -814,10 +814,6 @@ void NodeView::ShowContextMenu(const QPoint &pos) m.addSeparator(); - AddSetScrollZoomsByDefaultActionToMenu(&m); - - m.addSeparator(); - Menu* direction_menu = new Menu(tr("Direction"), &m); m.addMenu(direction_menu); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 00aa7e40e..49dda39bb 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1128,9 +1128,6 @@ void TimelineWidget::ShowContextMenu() show_waveforms->setChecked(views_.first()->view()->GetShowWaveforms()); connect(show_waveforms, &QAction::triggered, this, &TimelineWidget::SetViewWaveformsEnabled); - QAction* scroll_zoom = views_.first()->view()->AddSetScrollZoomsByDefaultActionToMenu(&menu); - connect(scroll_zoom, &QAction::triggered, this, &TimelineWidget::SetScrollZoomsByDefaultOnAllViews); - menu.addSeparator(); QAction* properties_action = menu.addAction(tr("Properties")); @@ -1220,13 +1217,6 @@ void TimelineWidget::TrackIndexChanged(int old, int now) } } -void TimelineWidget::SetScrollZoomsByDefaultOnAllViews(bool e) -{ - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->SetScrollZoomsByDefault(e); - } -} - void TimelineWidget::SignalBlockSelectionChange() { signal_block_change_timer_->stop(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 870d7065b..5123e6006 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -426,8 +426,6 @@ private slots: void TrackIndexChanged(int old, int now); - void SetScrollZoomsByDefaultOnAllViews(bool e); - void SignalBlockSelectionChange(); void RevealInFootageViewer(); From 5853c114c223877a7eb34e921e057641ba9b8da9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Aug 2022 09:16:59 -0700 Subject: [PATCH 099/107] exportdialog: implemented basic preset saving/loading This may not work perfectly yet, but we can build upon it --- app/codec/encoder.cpp | 185 ++++++++++++++++++- app/codec/encoder.h | 9 + app/dialog/export/CMakeLists.txt | 2 + app/dialog/export/export.cpp | 119 ++++++++---- app/dialog/export/export.h | 13 ++ app/dialog/export/exportsavepresetdialog.cpp | 97 ++++++++++ app/dialog/export/exportsavepresetdialog.h | 55 ++++++ 7 files changed, 447 insertions(+), 33 deletions(-) create mode 100644 app/dialog/export/exportsavepresetdialog.cpp create mode 100644 app/dialog/export/exportsavepresetdialog.h diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index b4ef307a3..cb5080402 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -23,6 +23,7 @@ #include #include "common/timecodefunctions.h" +#include "common/xmlutils.h" #include "ffmpeg/ffmpegencoder.h" #include "oiio/oiioencoder.h" @@ -99,6 +100,17 @@ EncodingParams::EncodingParams() : { } +QDir EncodingParams::GetPresetPath() +{ + return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("exportpresets")); +} + +QStringList EncodingParams::GetListOfPresets() +{ + QDir d = EncodingParams::GetPresetPath(); + return d.entryList(QDir::Files); +} + void EncodingParams::EnableVideo(const VideoParams &video_params, const ExportCodec::Codec &vcodec) { video_enabled_ = true; @@ -142,9 +154,49 @@ void EncodingParams::DisableSubtitles() subtitles_enabled_ = false; } +bool EncodingParams::Load(QXmlStreamReader *reader) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("export")) { + int version = 0; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("version")) { + version = attr.value().toInt(); + } + } + + switch (version) { + case 1: + return LoadV1(reader); + } + } else { + reader->skipCurrentElement(); + } + } + + return false; +} + +bool EncodingParams::Load(QIODevice *device) +{ + QXmlStreamReader reader(device); + return Load(&reader); +} + +void EncodingParams::Save(QIODevice *device) const +{ + QXmlStreamWriter writer(device); + Save(&writer); +} + void EncodingParams::Save(QXmlStreamWriter *writer) const { - writer->writeTextElement(QStringLiteral("version"), QString::number(kEncoderParamsVersion)); + writer->writeStartDocument(); + + writer->writeStartElement(QStringLiteral("export")); + + writer->writeAttribute(QStringLiteral("version"), QString::number(kEncoderParamsVersion)); writer->writeTextElement(QStringLiteral("filename"), filename_); writer->writeTextElement(QStringLiteral("format"), QString::number(format_)); @@ -222,6 +274,10 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeEndElement(); // subtitles writer->writeEndElement(); // audio + + writer->writeEndElement(); // export + + writer->writeEndDocument(); } Encoder* Encoder::CreateFromID(Type id, const EncodingParams& params) @@ -311,4 +367,131 @@ QMatrix4x4 EncodingParams::GenerateMatrix(EncodingParams::VideoScalingMethod met return preview_matrix; } +bool EncodingParams::LoadV1(QXmlStreamReader *reader) +{ + rational custom_range_in, custom_range_out; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("filename")) { + filename_ = reader->readElementText(); + } else if (reader->name() == QStringLiteral("format")) { + format_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("range")) { + has_custom_range_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("customrangein")) { + custom_range_in = rational::fromString(reader->readElementText()); + } else if (reader->name() == QStringLiteral("customrangeout")) { + custom_range_out = rational::fromString(reader->readElementText()); + } else if (reader->name() == QStringLiteral("video")) { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("enabled")) { + video_enabled_ = attr.value().toInt(); + } + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("codec")) { + video_codec_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("width")) { + video_params_.set_width(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("height")) { + video_params_.set_height(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("format")) { + video_params_.set_format(static_cast(reader->readElementText().toInt())); + } else if (reader->name() == QStringLiteral("timebase")) { + video_params_.set_time_base(rational::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("divider")) { + video_params_.set_divider(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("bitrate")) { + video_bit_rate_ = reader->readElementText().toLongLong(); + } else if (reader->name() == QStringLiteral("minbitrate")) { + video_min_bit_rate_ = reader->readElementText().toLongLong(); + } else if (reader->name() == QStringLiteral("maxbitrate")) { + video_max_bit_rate_ = reader->readElementText().toLongLong(); + } else if (reader->name() == QStringLiteral("bufsize")) { + video_buffer_size_ = reader->readElementText().toLongLong(); + } else if (reader->name() == QStringLiteral("threads")) { + video_threads_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("pixfmt")) { + video_pix_fmt_ = reader->readElementText(); + } else if (reader->name() == QStringLiteral("imgseq")) { + video_is_image_sequence_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("color")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("output")) { + color_transform_ = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("vscale")) { + video_scaling_method_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("opts")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("entry")) { + QString key, value; + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("key")) { + key = reader->readElementText(); + } else if (reader->name() == QStringLiteral("value")) { + value = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + set_video_option(key, value); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("audio")) { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("enabled")) { + audio_enabled_ = attr.value().toInt(); + } + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("codec")) { + audio_codec_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("samplerate")) { + audio_params_.set_sample_rate(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("channellayout")) { + audio_params_.set_channel_layout(reader->readElementText().toULongLong()); + } else if (reader->name() == QStringLiteral("format")) { + audio_params_.set_format(static_cast(reader->readElementText().toInt())); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("subtitles")) { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("enabled")) { + subtitles_enabled_ = attr.value().toInt(); + } + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("sidecar")) { + subtitles_are_sidecar_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("sidecarformat")) { + subtitle_sidecar_fmt_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("codec")) { + subtitles_codec_ = static_cast(reader->readElementText().toInt()); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + + return true; +} + } diff --git a/app/codec/encoder.h b/app/codec/encoder.h index a8496065e..e33f1de9a 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -52,6 +52,9 @@ public: EncodingParams(); + static QDir GetPresetPath(); + static QStringList GetListOfPresets(); + bool IsValid() const { return video_enabled_ || audio_enabled_ || subtitles_enabled_; @@ -113,6 +116,10 @@ public: const rational& GetExportLength() const { return export_length_; } void SetExportLength(const rational& export_length) { export_length_ = export_length; } + bool Load(QIODevice *device); + bool Load(QXmlStreamReader *reader); + + void Save(QIODevice *device) const; void Save(QXmlStreamWriter* writer) const; bool has_custom_range() const { return has_custom_range_; } @@ -133,6 +140,8 @@ public: private: static const int kEncoderParamsVersion = 1; + bool LoadV1(QXmlStreamReader *reader); + QString filename_; ExportFormat::Format format_; diff --git a/app/dialog/export/CMakeLists.txt b/app/dialog/export/CMakeLists.txt index 2a309a6fc..8847c4ba4 100644 --- a/app/dialog/export/CMakeLists.txt +++ b/app/dialog/export/CMakeLists.txt @@ -26,6 +26,8 @@ set(OLIVE_SOURCES dialog/export/exportaudiotab.h dialog/export/exportformatcombobox.cpp dialog/export/exportformatcombobox.h + dialog/export/exportsavepresetdialog.cpp + dialog/export/exportsavepresetdialog.h dialog/export/exportsubtitlestab.cpp dialog/export/exportsubtitlestab.h dialog/export/exportvideotab.cpp diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index f9b0ec6a1..7f5a66a07 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -33,6 +33,7 @@ #include "common/digit.h" #include "common/qtutils.h" #include "dialog/task/task.h" +#include "exportsavepresetdialog.h" #include "node/project/project.h" #include "node/project/sequence/sequence.h" #include "task/taskmanager.h" @@ -81,21 +82,21 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : QLabel* preset_lbl = new QLabel(tr("Preset:")); preset_lbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); preferences_layout->addWidget(preset_lbl, row, 0); - QComboBox* preset_combobox = new QComboBox(); - preset_combobox->addItem(tr("Same As Source - High Quality")); - preset_combobox->addItem(tr("Same As Source - Medium Quality")); - preset_combobox->addItem(tr("Same As Source - Low Quality")); - preferences_layout->addWidget(preset_combobox, row, 1); + preset_combobox_ = new QComboBox(); + LoadPresets(); + connect(preset_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &ExportDialog::PresetComboBoxChanged); + preferences_layout->addWidget(preset_combobox_, row, 1, 1, 2); - QPushButton* preset_load_btn = new QPushButton(); + /*QPushButton* preset_load_btn = new QPushButton(); preset_load_btn->setIcon(icon::Open); preset_load_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - preferences_layout->addWidget(preset_load_btn, row, 2); + preferences_layout->addWidget(preset_load_btn, row, 2);*/ QPushButton* preset_save_btn = new QPushButton(); preset_save_btn->setIcon(icon::Save); preset_save_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); preferences_layout->addWidget(preset_save_btn, row, 3); + connect(preset_save_btn, &QPushButton::clicked, this, &ExportDialog::SavePreset); row++; @@ -197,25 +198,9 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : // Set defaults previously_selected_format_ = ExportFormat::kFormatMPEG4Video; - format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video); connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged); - FormatChanged(format_combobox_->GetFormat()); VideoParams vp = viewer_node_->GetVideoParams(); - AudioParams ap = viewer_node_->GetAudioParams(); - - video_tab_->width_slider()->SetValue(vp.width()); - video_tab_->width_slider()->SetDefaultValue(vp.width()); - video_tab_->height_slider()->SetValue(vp.height()); - video_tab_->height_slider()->SetDefaultValue(vp.height()); - video_tab_->SetSelectedFrameRate(vp.frame_rate()); - video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(vp.pixel_aspect_ratio()); - video_tab_->pixel_format_field()->SetPixelFormat(static_cast(OLIVE_CONFIG("OnlinePixelFormat").toInt())); - video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing()); - audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate()); - audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false); - audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout()); - video_aspect_ratio_ = static_cast(vp.width()) / static_cast(vp.height()); connect(video_tab_->width_slider(), @@ -247,21 +232,22 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : this, &ExportDialog::ImageSequenceCheckBoxChanged); - // Set viewer to view the node - preview_viewer_->ConnectViewerNode(viewer_node_); - preview_viewer_->SetColorMenuEnabled(false); - preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); - // We don't check if the codec supports subtitles because we can always export to a sidecar file - bool has_subtitle_codecs = SequenceHasSubtitles(); + bool has_subtitle_tracks = SequenceHasSubtitles(); connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled); - subtitles_enabled_->setChecked(has_subtitle_codecs); - subtitles_enabled_->setEnabled(has_subtitle_codecs); + subtitles_enabled_->setEnabled(has_subtitle_tracks); // If the viewer already has cached params, use them if (viewer_node_->GetLastUsedEncodingParams().IsValid()) { SetParams(viewer_node_->GetLastUsedEncodingParams()); + } else { + SetDefaults(); } + + // Set viewer to view the node and set its colorspace + preview_viewer_->ConnectViewerNode(viewer_node_); + preview_viewer_->SetColorMenuEnabled(false); + preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); } rational ExportDialog::GetSelectedTimebase() const @@ -402,6 +388,29 @@ void ExportDialog::ImageSequenceCheckBoxChanged(bool e) filename_edit_->setText(current_fileinfo.dir().filePath(basename)); } +void ExportDialog::SavePreset() +{ + ExportSavePresetDialog d(GenerateParams(), this); + if (d.exec() == QDialog::Accepted) { + LoadPresets(); + preset_combobox_->setCurrentText(d.GetSelectedPresetName()); + } +} + +void ExportDialog::PresetComboBoxChanged() +{ + QComboBox *c = static_cast(sender()); + + int preset_number = c->currentData().toInt(); + if (preset_number == kPresetDefault) { + SetDefaults(); + } else if (preset_number == kPresetLastUsed) { + SetParams(viewer_node_->GetLastUsedEncodingParams()); + } else { + SetParams(presets_.at(preset_number)); + } +} + void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title) { QScrollArea* scroll_area = new QScrollArea(); @@ -494,7 +503,32 @@ void ExportDialog::ResolutionChanged() void ExportDialog::LoadPresets() { + preset_combobox_->clear(); + presets_.clear(); + preset_combobox_->addItem(tr("Default"), kPresetDefault); + + if (viewer_node_->GetLastUsedEncodingParams().IsValid()) { + preset_combobox_->addItem(tr("Last Used"), kPresetLastUsed); + } + + preset_combobox_->insertSeparator(preset_combobox_->count()); + + QStringList l = EncodingParams::GetListOfPresets(); + presets_.reserve(l.size()); + + for (const QString &preset : l) { + EncodingParams p; + + QFile f(EncodingParams::GetPresetPath().filePath(preset)); + if (f.open(QFile::ReadOnly)) { + if (p.Load(&f)) { + preset_combobox_->addItem(preset, int(presets_.size())); + presets_.push_back(p); + } + f.close(); + } + } } void ExportDialog::SetDefaultFilename() @@ -527,6 +561,28 @@ bool ExportDialog::SequenceHasSubtitles() const return false; } +void ExportDialog::SetDefaults() +{ + format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video); + FormatChanged(format_combobox_->GetFormat()); + + VideoParams vp = viewer_node_->GetVideoParams(); + AudioParams ap = viewer_node_->GetAudioParams(); + + video_tab_->width_slider()->SetValue(vp.width()); + video_tab_->width_slider()->SetDefaultValue(vp.width()); + video_tab_->height_slider()->SetValue(vp.height()); + video_tab_->height_slider()->SetDefaultValue(vp.height()); + video_tab_->SetSelectedFrameRate(vp.frame_rate()); + video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(vp.pixel_aspect_ratio()); + video_tab_->pixel_format_field()->SetPixelFormat(static_cast(OLIVE_CONFIG("OnlinePixelFormat").toInt())); + video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing()); + audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate()); + audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false); + audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout()); + subtitles_enabled_->setChecked(SequenceHasSubtitles()); +} + EncodingParams ExportDialog::GenerateParams() const { VideoParams video_render_params(static_cast(video_tab_->width_slider()->GetValue()), @@ -604,7 +660,6 @@ EncodingParams ExportDialog::GenerateParams() const void ExportDialog::SetParams(const EncodingParams &e) { format_combobox_->SetFormat(e.format()); - filename_edit_->setText(e.filename()); if (e.has_custom_range() && viewer_node_->GetWorkArea()->enabled()) { range_combobox_->setCurrentIndex(kRangeInToOut); diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index abf242575..06674ed8d 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -69,6 +69,8 @@ private: bool SequenceHasSubtitles() const; + void SetDefaults(); + ViewerOutput* viewer_node_; ExportFormat::Format previously_selected_format_; @@ -81,9 +83,16 @@ private: kRangeInToOut }; + enum AutoPreset { + kPresetDefault = -1, + kPresetLastUsed = -2, + }; + QTabWidget* preferences_tabs_; + QComboBox* preset_combobox_; QComboBox* range_combobox_; + std::vector presets_; QCheckBox* video_enabled_; QCheckBox* audio_enabled_; @@ -119,6 +128,10 @@ private slots: void ImageSequenceCheckBoxChanged(bool e); + void SavePreset(); + + void PresetComboBoxChanged(); + }; } diff --git a/app/dialog/export/exportsavepresetdialog.cpp b/app/dialog/export/exportsavepresetdialog.cpp new file mode 100644 index 000000000..03052c4ec --- /dev/null +++ b/app/dialog/export/exportsavepresetdialog.cpp @@ -0,0 +1,97 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "exportsavepresetdialog.h" + +#include +#include +#include +#include + +namespace olive { + +ExportSavePresetDialog::ExportSavePresetDialog(const EncodingParams &p, QWidget *parent) : + QDialog(parent), + params_(p) +{ + auto layout = new QVBoxLayout(this); + + name_edit_ = new QLineEdit(); + + // Populate existing list + QStringList l = EncodingParams::GetListOfPresets(); + if (!l.empty()) { + auto list_widget_ = new QListWidget(); + for (const QString &f : l) { + list_widget_->addItem(f); + } + connect(list_widget_, &QListWidget::currentTextChanged, name_edit_, &QLineEdit::setText); + layout->addWidget(list_widget_); + } + + auto name_layout = new QHBoxLayout(); + layout->addLayout(name_layout); + + name_layout->addWidget(new QLabel(tr("Name:"))); + + name_edit_->setFocus(); + name_layout->addWidget(name_edit_); + + auto btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(btns, &QDialogButtonBox::accepted, this, &ExportSavePresetDialog::accept); + connect(btns, &QDialogButtonBox::rejected, this, &ExportSavePresetDialog::reject); + layout->addWidget(btns); + + setWindowTitle(tr("Save Export Preset")); +} + +void ExportSavePresetDialog::accept() +{ + if (name_edit_->text().isEmpty()) { + QMessageBox::critical(this, tr("Invalid Name"), tr("You must enter a name to save an export preset.")); + return; + } + + QDir d(EncodingParams::GetPresetPath()); + if (!d.exists()) { + d.mkpath(QStringLiteral(".")); + } + + QFile f(d.filePath(name_edit_->text())); + if (f.exists()) { + if (QMessageBox::question(this, tr("Overwrite Preset"), tr("A preset with the name \"%1\" already exists. Do you wish to overwrite it?").arg(name_edit_->text()), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + return; + } + } + + if (!f.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Write Error"), tr("Failed to open file \"%1\" for writing.").arg(f.fileName())); + return; + } + + params_.Save(&f); + + f.close(); + + QDialog::accept(); +} + +} diff --git a/app/dialog/export/exportsavepresetdialog.h b/app/dialog/export/exportsavepresetdialog.h new file mode 100644 index 000000000..1f8ca09b9 --- /dev/null +++ b/app/dialog/export/exportsavepresetdialog.h @@ -0,0 +1,55 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 EXPORTSAVEPRESETDIALOG_H +#define EXPORTSAVEPRESETDIALOG_H + +#include +#include +#include + +#include "codec/encoder.h" + +namespace olive { + +class ExportSavePresetDialog : public QDialog +{ + Q_OBJECT +public: + ExportSavePresetDialog(const EncodingParams &p, QWidget *parent = nullptr); + + QString GetSelectedPresetName() const + { + return name_edit_->text(); + } + +public slots: + virtual void accept() override; + +private: + QLineEdit *name_edit_; + + EncodingParams params_; + +}; + +} + +#endif // EXPORTSAVEPRESETDIALOG_H From f18461e69b76be1ab8661f13d76caaaeefe5d666 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 15 Aug 2022 11:06:34 -0700 Subject: [PATCH 100/107] timeline: allow pasting nodes into or over clips --- app/widget/nodeparamview/nodeparamview.cpp | 30 ++++++++++++------ app/widget/nodeparamview/nodeparamview.h | 4 +++ app/widget/timelinewidget/timelinewidget.cpp | 33 ++++++++++++++++++-- app/widget/timelinewidget/timelinewidget.h | 3 ++ 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index fee75f8ba..3177da6a5 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -29,7 +29,6 @@ #include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" -#include "node/project/serializer/serializer.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" #include "widget/timeruler/timeruler.h" @@ -611,26 +610,24 @@ bool NodeParamView::Paste() } } + return Paste(this, std::bind(&NodeParamView::GenerateExistingPasteMap, this, std::placeholders::_1)); +} + +bool NodeParamView::Paste(QWidget *parent, std::function(const ProjectSerializer::Result &)> get_existing_map_function) +{ ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("nodes")); if (res.GetLoadedNodes().isEmpty()) { return false; } // Determine if any nodes of this type are already in the editor - QVector ignore_nodes; - QMap existing_nodes; - for (Node *n : res.GetLoadedNodes()) { - if (Node *existing = GetNodeWithIDAndIgnoreList(n->id(), ignore_nodes)) { - existing_nodes.insert(existing, n); - ignore_nodes.append(existing); - } - } + QHash existing_nodes = get_existing_map_function(res); QVector nodes_to_paste_as_new = res.GetLoadedNodes(); MultiUndoCommand *command = new MultiUndoCommand(); if (!existing_nodes.empty()) { - QMessageBox b(this); + QMessageBox b(parent); b.setWindowTitle(tr("Paste Nodes")); QStringList node_names; @@ -870,6 +867,19 @@ void NodeParamView::ToggleSelect(NodeParamViewItem *item) } } +QHash NodeParamView::GenerateExistingPasteMap(const ProjectSerializer::Result &r) +{ + QVector ignore_nodes; + QHash existing_nodes; + for (Node *n : r.GetLoadedNodes()) { + if (Node *existing = GetNodeWithIDAndIgnoreList(n->id(), ignore_nodes)) { + existing_nodes.insert(existing, n); + ignore_nodes.append(existing); + } + } + return existing_nodes; +} + void NodeParamView::UpdateGlobalScrollBar() { if (keyframe_view_) { diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 479685a21..1b78055a9 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -25,6 +25,7 @@ #include #include "node/node.h" +#include "node/project/serializer/serializer.h" #include "nodeparamviewcontext.h" #include "nodeparamviewdockarea.h" #include "nodeparamviewitem.h" @@ -75,6 +76,7 @@ public: virtual bool CopySelected(bool cut) override; virtual bool Paste() override; + static bool Paste(QWidget *parent, std::function(const ProjectSerializer::Result &)> get_existing_map_function); public slots: void SetContexts(const QVector &contexts); @@ -134,6 +136,8 @@ private: void ToggleSelect(NodeParamViewItem *item); + QHash GenerateExistingPasteMap(const ProjectSerializer::Result &r); + KeyframeView* keyframe_view_; QVector context_items_; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 49dda39bb..18dc32441 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -54,6 +54,7 @@ #include "undo/timelineundoworkarea.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" +#include "widget/nodeparamview/nodeparamview.h" #include "widget/nodeview/nodeviewundo.h" #include "widget/timeruler/timeruler.h" @@ -640,13 +641,23 @@ bool TimelineWidget::CopySelected(bool cut) bool TimelineWidget::Paste() { + // TimeRuler gets first chance (markers, etc.) if (super::Paste()) { return true; - } if (!GetConnectedNode()) { + } + + // Ensure we have a connected node + if (!GetConnectedNode()) { return false; } - return PasteInternal(false); + // Attempt regular clip pasting + if (PasteInternal(false)) { + return true; + } + + // Give last chance to NodeParamView + return NodeParamView::Paste(this, std::bind(&TimelineWidget::GenerateExistingPasteMap, this, std::placeholders::_1)); } void TimelineWidget::PasteInsert() @@ -1707,6 +1718,24 @@ TimelineAndTrackView *TimelineWidget::AddTimelineAndTrackView(Qt::Alignment alig return v; } +QHash TimelineWidget::GenerateExistingPasteMap(const ProjectSerializer::Result &r) +{ + QHash m; + + for (Node *n : r.GetLoadedNodes()) { + for (Block *b : qAsConst(this->selected_blocks_)) { + for (auto it=b->GetContextPositions().cbegin(); it!=b->GetContextPositions().cend(); it++) { + if (it.key()->id() == n->id() && !m.contains(it.key())) { + m.insert(it.key(), n); + break; + } + } + } + } + + return m; +} + QByteArray TimelineWidget::SaveSplitterState() const { return view_splitter_->saveState(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 5123e6006..494f19fba 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -28,6 +28,7 @@ #include "core.h" #include "node/block/transition/transition.h" #include "node/output/viewer/viewer.h" +#include "node/project/serializer/serializer.h" #include "timeline/timelinecommon.h" #include "timelineandtrackview.h" #include "widget/slider/rationalslider.h" @@ -311,6 +312,8 @@ private: TimelineAndTrackView *AddTimelineAndTrackView(Qt::Alignment alignment); + QHash GenerateExistingPasteMap(const ProjectSerializer::Result &r); + QPoint drag_origin_; QRubberBand rubberband_; From 4a933f7764457cd0cc8cba94b7c36c72ea2be5a5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 15 Aug 2022 11:53:39 -0700 Subject: [PATCH 101/107] viewer: makeCurrent before retrieving pixel at cursor Fixes crash if viewer was somehow made not current (e.g. if the export dialog was shown) --- app/widget/viewer/viewerdisplay.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 1357af21a..e420669d8 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -889,6 +889,8 @@ void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e) QPointF pixel_pos = GenerateDisplayTransform().inverted().map(e->pos()); pixel_pos /= texture_->params().divider(); + makeCurrent(); + reference = renderer()->GetPixelFromTexture(texture_.get(), pixel_pos); display = color_service()->ConvertColor(reference); } From c1dd45bb570530c02632f50d1e53ef8c745e005f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 15 Aug 2022 12:51:12 -0700 Subject: [PATCH 102/107] exportdialog: ignore scroll events on widgets Fixes usability issue where scrolling tabs on the export dialog could inadvertently change parameters --- app/dialog/export/export.cpp | 29 ++++++++++++++++++++++++++++- app/dialog/export/export.h | 5 +++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 7f5a66a07..6f2d68c51 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -133,7 +133,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : audio_enabled_ = new QCheckBox(tr("Export Audio")); av_enabled_layout->addWidget(audio_enabled_); - subtitles_enabled_ = new QCheckBox(tr("Export Subtitle")); + subtitles_enabled_ = new QCheckBox(tr("Export Subtitles")); av_enabled_layout->addWidget(subtitles_enabled_); preferences_layout->addLayout(av_enabled_layout, row, 0, 1, 4); @@ -142,6 +142,8 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preferences_tabs_ = new QTabWidget(); + scroll_blocker_ = new NodeParamViewScrollBlocker(this); + color_manager_ = viewer_node_->project()->color_manager(); video_tab_ = new ExportVideoTab(color_manager_); AddPreferencesTab(video_tab_, tr("Video")); @@ -248,6 +250,15 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preview_viewer_->ConnectViewerNode(viewer_node_); preview_viewer_->SetColorMenuEnabled(false); preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); + + qApp->installEventFilter(this); + + connect(video_enabled_, &QCheckBox::toggled, video_tab_, &QWidget::setEnabled); + video_tab_->setEnabled(video_enabled_->isChecked()); + connect(audio_enabled_, &QCheckBox::toggled, audio_tab_, &QWidget::setEnabled); + audio_tab_->setEnabled(audio_enabled_->isChecked()); + connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled); + subtitle_tab_->setEnabled(subtitles_enabled_->isChecked()); } rational ExportDialog::GetSelectedTimebase() const @@ -716,6 +727,22 @@ void ExportDialog::SetParams(const EncodingParams &e) } } +bool ExportDialog::eventFilter(QObject *o, QEvent *e) +{ + // Any parameters in scrollable areas, ignore wheel events so the user doesn't unwittingly change + // them while trying to scroll through the pages + if (e->type() == QEvent::Wheel) { + while ((o = o->parent())) { + if (o == video_tab_ || o == audio_tab_ || o == subtitle_tab_) { + e->ignore(); + return true; + } + } + } + + return super::eventFilter(o, e); +} + void ExportDialog::done(int r) { preview_viewer_->ConnectViewerNode(nullptr); diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 06674ed8d..1c1edea48 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -34,6 +34,7 @@ #include "exportsubtitlestab.h" #include "exportvideotab.h" #include "task/export/export.h" +#include "widget/nodeparamview/nodeparamviewwidgetbridge.h" #include "widget/viewer/viewer.h" namespace olive { @@ -58,6 +59,8 @@ public: EncodingParams GenerateParams() const; void SetParams(const EncodingParams &e); + virtual bool eventFilter(QObject *o, QEvent *e) override; + public slots: virtual void done(int r) override; @@ -113,6 +116,8 @@ private: QWidget* preferences_area_; QCheckBox *export_bkg_box_; + NodeParamViewScrollBlocker *scroll_blocker_; + private slots: void BrowseFilename(); From f091ad5a475b051f8b58940e5115b5c1a919f606 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:24:34 -0700 Subject: [PATCH 103/107] exportdialog: remove unnecessary member --- app/dialog/export/export.cpp | 2 -- app/dialog/export/export.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 6f2d68c51..54a1403a8 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -142,8 +142,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preferences_tabs_ = new QTabWidget(); - scroll_blocker_ = new NodeParamViewScrollBlocker(this); - color_manager_ = viewer_node_->project()->color_manager(); video_tab_ = new ExportVideoTab(color_manager_); AddPreferencesTab(video_tab_, tr("Video")); diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 1c1edea48..b9cd62b73 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -116,8 +116,6 @@ private: QWidget* preferences_area_; QCheckBox *export_bkg_box_; - NodeParamViewScrollBlocker *scroll_blocker_; - private slots: void BrowseFilename(); From 4a22bf71e709e0515197371549cfd41d55228564 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 15 Aug 2022 18:24:17 -0700 Subject: [PATCH 104/107] clip: fix issue with trimming in point of speed adjusted clip --- app/node/block/clip/clip.cpp | 7 ++----- app/node/block/clip/clip.h | 8 ++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 1b8fed631..b94bd0c0e 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -92,7 +92,6 @@ void ClipBlock::set_length_and_media_out(const rational &length) if (reverse()) { // Calculate media_in adjustment - rational proposed_media_in = SequenceToMediaTime(this->length() - length, kSTMIgnoreReverse | kSTMIgnoreLoop); set_media_in(proposed_media_in); } @@ -108,11 +107,9 @@ void ClipBlock::set_length_and_media_in(const rational &length) if (!reverse()) { // Calculate media_in adjustment - rational proposed_media_in = SequenceToMediaTime(this->length() - length, kSTMIgnoreSpeed | kSTMIgnoreLoop); + waveform_.TrimIn(SequenceToMediaTime(this->length() - length, kSTMIgnoreSpeed | kSTMIgnoreLoop) - media_in()); - waveform_.TrimIn(proposed_media_in - media_in()); - - set_media_in(proposed_media_in); + set_media_in(SequenceToMediaTime(this->length() - length, kSTMIgnoreLoop)); } else { // Trim waveform out point waveform_.TrimIn(this->length() - length); diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 6b625eb53..c6571d2b1 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -148,10 +148,10 @@ protected: private: enum SequenceToMediaTimeFlag { - kSTMNone, - kSTMIgnoreReverse, - kSTMIgnoreSpeed, - kSTMIgnoreLoop + kSTMNone = 0x0, + kSTMIgnoreReverse = 0x1, + kSTMIgnoreSpeed = 0x2, + kSTMIgnoreLoop = 0x4 }; rational SequenceToMediaTime(const rational& sequence_time, uint64_t flags = kSTMNone) const; From 29cf63b10acd0ef8b9722f2800ad19a737c756cb Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Tue, 16 Aug 2022 18:31:52 +0200 Subject: [PATCH 105/107] ffmpegdecoder: fix missing include avcodec.h to avoid build failures ffmpegdecoder.h declares a pointer to AVCodecContext ; which is a type defined in libavcodec/avcodec.h. Signed-off-by: Olivier Gayot --- app/codec/ffmpeg/ffmpegdecoder.h | 1 + 1 file changed, 1 insertion(+) diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index c29f45c1a..8df5a67bc 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -25,6 +25,7 @@ #include extern "C" { +#include #include #include #include From 78c262fbc99216ffa9a22351bb97256640d642e8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 17 Aug 2022 16:14:26 -0700 Subject: [PATCH 106/107] timeline: replace all with gaps before placing when nudging Fixes #2012 --- app/widget/timelinewidget/timelinewidget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 18dc32441..31312764d 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1314,6 +1314,9 @@ void TimelineWidget::NudgeInternal(rational amount) foreach (Block* b, selected_blocks_) { command->add_child(new TrackReplaceBlockWithGapCommand(b->track(), b, false)); + } + + foreach (Block* b, selected_blocks_) { command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(b->track()->type()), b->track()->Index(), b, b->in() + amount)); } From b169ad923cdf92d6d83c0add4a46fa7afd27858f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 18 Aug 2022 07:40:49 -0700 Subject: [PATCH 107/107] timeline: don't offset waveform by title height Fixes #2014 --- app/widget/timelinewidget/view/timelineview.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 34cca2fe6..2c622fb4d 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -486,6 +486,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q int text_height = fm.height(); int text_padding = text_height/4; // This ties into the track minimum height being 1.5 int text_total_height = text_height + text_padding + text_padding; + Q_UNUSED(text_total_height) if (foreground) { painter->setBrush(Qt::NoBrush); @@ -522,7 +523,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (ClipBlock *clip = dynamic_cast(block)) { // Draw waveform if (show_waveforms_) { - QRect waveform_rect = r.adjusted(0, text_total_height, 0, 0).toRect(); + QRect waveform_rect = r.toRect(); painter->setPen(shadow_color); AudioVisualWaveform::DrawWaveform(painter, waveform_rect, this->GetScale(), clip->waveform(), SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()));