From 3275dac50f36b407cf81e78d980f00e0dea0df66 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 3 Oct 2022 15:30:52 -0700 Subject: [PATCH 01/34] decoder: fixed longstanding audio sync issue Wow this has probably been a bug for 3 years straight --- app/codec/decoder.cpp | 10 +++++----- app/codec/decoder.h | 4 +++- app/codec/ffmpeg/ffmpegdecoder.cpp | 18 ++++++++++++++++-- app/codec/ffmpeg/ffmpegdecoder.h | 4 +++- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 666cec629..3e6998f88 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -217,14 +217,11 @@ DecoderPtr Decoder::CreateFromID(const QString &id) int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time) { - int64_t t = Timecode::time_to_timestamp(time, timebase); - t += start_time; - return t; + return Timecode::time_to_timestamp(time, timebase); } rational Decoder::GetTimestampInTimeUnits(int64_t time, const rational &timebase, int64_t start_time) { - time -= start_time; return Timecode::timestamp_to_time(time, timebase); } @@ -294,10 +291,13 @@ bool Decoder::ConformAudioInternal(const QVector &filenames, const Audi return false; } -bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange& range, LoopMode loop_mode, const AudioParams &input_params) +bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams &input_params) { PlanarFileDevice input; if (input.open(conform_filenames, QFile::ReadOnly)) { + // Offset range by audio start offset + range -= GetAudioStartOffset(); + qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count(); qint64 write_index = 0; diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 983efb753..c6aac3ecb 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -297,6 +297,8 @@ protected: static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time); static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time); + virtual rational GetAudioStartOffset() const { return 0; } + signals: /** * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if @@ -307,7 +309,7 @@ signals: private: void UpdateLastAccessed(); - bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange &range, LoopMode loop_mode, const AudioParams ¶ms); + bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index e4168479d..179666090 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -143,7 +143,7 @@ bool FFmpegDecoder::OpenInternal() TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) { + if (AVFramePtr f = RetrieveFrame(p.time, p.src_interlacing, p.cancelled)) { if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } @@ -311,6 +311,16 @@ void FFmpegDecoder::CloseInternal() native_output_pix_fmt_ = VideoParams::kFormatInvalid; } +rational FFmpegDecoder::GetAudioStartOffset() const +{ + AVStream *s = this->instance_.avstream(); + if (s) { + return rational(s->start_time * s->time_base.num, s->time_base.den); + } else { + return 0; + } +} + QString FFmpegDecoder::id() const { return QStringLiteral("ffmpeg"); @@ -787,10 +797,14 @@ void FFmpegDecoder::ClearFrameCache() } } -AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancelled) +AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Interlacing interlacing, CancelAtom *cancelled) { int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); + if (interlacing != VideoParams::kInterlaceNone) { + target_ts *= 2; + } + const int64_t min_seek = -instance_.avstream()->start_time; int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); bool still_seeking = false; diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index e018cfc75..cd60c5d05 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -67,6 +67,8 @@ protected: virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled) override; virtual void CloseInternal() override; + virtual rational GetAudioStartOffset() const override; + private: class Instance { @@ -148,7 +150,7 @@ private: void ClearFrameCache(); - AVFramePtr RetrieveFrame(const rational &time, CancelAtom *cancelled); + AVFramePtr RetrieveFrame(const rational &time, VideoParams::Interlacing interlacing, CancelAtom *cancelled); void RemoveFirstFrame(); From 42ba84bf5f069f053c4c5dd167133c55a4e73e87 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 3 Oct 2022 17:24:09 -0700 Subject: [PATCH 02/34] multicam: finished UI implementation --- app/node/input/multicam/multicamnode.cpp | 64 +++++- app/node/input/multicam/multicamnode.h | 27 ++- app/node/node.cpp | 20 ++ app/node/node.h | 198 +++++++++++-------- app/node/traverser.cpp | 8 +- app/render/previewautocacher.cpp | 45 +++-- app/render/previewautocacher.h | 2 + app/render/renderprocessor.cpp | 8 +- app/widget/multicam/multicamwidget.cpp | 18 +- app/widget/multicam/multicamwidget.h | 3 + app/widget/timelinewidget/timelinewidget.cpp | 88 ++++++--- app/widget/timelinewidget/timelinewidget.h | 1 - app/widget/viewer/viewer.cpp | 19 +- app/widget/viewer/viewer.h | 6 +- 14 files changed, 354 insertions(+), 153 deletions(-) diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 8bdb5a5dd..9dd96e6a4 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -1,11 +1,15 @@ #include "multicamnode.h" +#include "node/project/sequence/sequence.h" + namespace olive { #define super Node const QString MultiCamNode::kCurrentInput = QStringLiteral("current_in"); const QString MultiCamNode::kSourcesInput = QStringLiteral("sources_in"); +const QString MultiCamNode::kSequenceInput = QStringLiteral("sequence_in"); +const QString MultiCamNode::kSequenceTypeInput = QStringLiteral("sequence_type_in"); MultiCamNode::MultiCamNode() { @@ -17,6 +21,11 @@ MultiCamNode::MultiCamNode() AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1); + + AddInput(kSequenceInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); + AddInput(kSequenceTypeInput, NodeValue::kCombo, InputFlags(kInputFlagStatic | kInputFlagHidden)); + + sequence_ = nullptr; } QString MultiCamNode::Name() const @@ -43,7 +52,7 @@ Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, { if (input == kSourcesInput) { int src = GetCurrentSource(); - if (src >= 0 && src < InputArraySize(kSourcesInput)) { + if (src >= 0 && src < GetSourceCount()) { Node::ActiveElements a; a.add(src); return a; @@ -71,12 +80,65 @@ void MultiCamNode::IndexToRowCols(int index, int total_rows, int total_cols, int *row = index/total_cols; } +Node *MultiCamNode::GetConnectedRenderOutput(const QString &input, int element) const +{ + if (sequence_ && input == kSourcesInput && element >= 0 && element < GetSourceCount()) { + return GetTrackList()->GetTrackAt(element); + } else { + return Node::GetConnectedRenderOutput(input, element); + } +} + +bool MultiCamNode::IsInputConnectedForRender(const QString &input, int element) const +{ + if (sequence_ && input == kSourcesInput && element >= 0 && element < GetSourceCount()) { + return true; + } else { + return Node::IsInputConnectedForRender(input, element); + } +} + +void MultiCamNode::InputConnectedEvent(const QString &input, int element, Node *output) +{ + if (input == kSequenceInput) { + if (Sequence *s = dynamic_cast(output)) { + SetInputFlags(kSequenceTypeInput, GetInputFlags(kSequenceTypeInput) & InputFlag(~kInputFlagHidden)); + sequence_ = s; + } + } +} + +void MultiCamNode::InputDisconnectedEvent(const QString &input, int element, Node *output) +{ + if (input == kSequenceInput) { + SetInputFlags(kSequenceTypeInput, GetInputFlags(kSequenceTypeInput) | kInputFlagHidden); + sequence_ = nullptr; + } +} + +TrackList *MultiCamNode::GetTrackList() const +{ + return sequence_->track_list(static_cast(GetStandardValue(kSequenceTypeInput).toInt())); +} + void MultiCamNode::Retranslate() { super::Retranslate(); SetInputName(kCurrentInput, tr("Current")); SetInputName(kSourcesInput, tr("Sources")); + SetInputName(kSequenceInput, tr("Sequence")); + SetInputName(kSequenceTypeInput, tr("Sequence Type")); + SetComboBoxStrings(kSequenceTypeInput, {tr("Video"), tr("Audio")}); +} + +int MultiCamNode::GetSourceCount() const +{ + if (sequence_) { + return GetTrackList()->GetTrackCount(); + } else { + return InputArraySize(kSourcesInput); + } } void MultiCamNode::GetRowsAndColumns(int sources, int *rows_in, int *cols_in) diff --git a/app/node/input/multicam/multicamnode.h b/app/node/input/multicam/multicamnode.h index 56a9ed858..cec77a88a 100644 --- a/app/node/input/multicam/multicamnode.h +++ b/app/node/input/multicam/multicamnode.h @@ -2,9 +2,12 @@ #define MULTICAMNODE_H #include "node/node.h" +#include "node/output/track/tracklist.h" namespace olive { +class Sequence; + class MultiCamNode : public Node { Q_OBJECT @@ -26,16 +29,15 @@ public: static const QString kCurrentInput; static const QString kSourcesInput; + static const QString kSequenceInput; + static const QString kSequenceTypeInput; int GetCurrentSource() const { return GetStandardValue(kCurrentInput).toInt(); } - int GetSourceCount() const - { - return InputArraySize(kSourcesInput); - } + int GetSourceCount() const; static void GetRowsAndColumns(int sources, int *rows, int *cols); void GetRowsAndColumns(int *rows, int *cols) const @@ -43,6 +45,11 @@ public: return GetRowsAndColumns(GetSourceCount(), rows, cols); } + void SetSequenceType(Track::Type t) + { + SetStandardValue(kSequenceTypeInput, t); + } + static void IndexToRowCols(int index, int total_rows, int total_cols, int *row, int *col); static int RowsColsToIndex(int row, int col, int total_rows, int total_cols) @@ -50,6 +57,18 @@ public: return col + row * total_cols; } + virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const override; + virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const override; + +protected: + virtual void InputConnectedEvent(const QString &input, int element, Node *output) override; + virtual void InputDisconnectedEvent(const QString &input, int element, Node *output) override; + +private: + TrackList *GetTrackList() const; + + Sequence *sequence_; + }; } diff --git a/app/node/node.cpp b/app/node/node.cpp index c677b130f..1d8cb3f4c 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1287,6 +1287,26 @@ int Node::GetInternalInputArraySize(const QString &input) return array_immediates_.value(input).size(); } +void FindWaysNodeArrivesHereRecursively(const Node *output, const Node *input, QVector &v) +{ + for (auto it=input->input_connections().cbegin(); it!=input->input_connections().cend(); it++) { + if (it->second == output) { + v.append(it->first); + } else { + FindWaysNodeArrivesHereRecursively(output, it->second, v); + } + } +} + +QVector Node::FindWaysNodeArrivesHere(const Node *output) const +{ + QVector v; + + FindWaysNodeArrivesHereRecursively(output, this, v); + + return v; +} + void Node::SetInputName(const QString &id, const QString &name) { Input* i = GetInternalInputData(id); diff --git a/app/node/node.h b/app/node/node.h index 3ac69a6f9..e6ffa724c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -418,6 +418,15 @@ public: return IsInputConnected(input.input(), input.element()); } + virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const + { + return IsInputConnected(input, element); + } + bool IsInputConnectedForRender(const NodeInput& input) const + { + return IsInputConnectedForRender(input.input(), input.element()); + } + bool IsInputStatic(const QString& input, int element = -1) const { return !IsInputConnected(input, element) && !IsInputKeyframing(input, element); @@ -435,6 +444,16 @@ public: return GetConnectedOutput(input.input(), input.element()); } + virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const + { + return GetConnectedOutput(input, element); + } + + Node *GetConnectedRenderOutput(const NodeInput& input) const + { + return GetConnectedRenderOutput(input.input(), input.element()); + } + bool IsUsingStandardValue(const QString& input, int track, int element = -1) const; NodeValue::Type GetInputDataType(const QString& id) const; @@ -796,6 +815,15 @@ public: */ bool InputsFrom(const QString& id, bool recursively) const; + + /** + * @brief Find inputs that `output` outputs to in order to arrive at this node + * + * Traverse this node's inputs recursively looking for `output`, and return a list of + * edges that `output` uses to get to `this` node. + */ + QVector FindWaysNodeArrivesHere(const Node *output) const; + /** * @brief Determines how many paths go from this node out to another node */ @@ -967,6 +995,90 @@ public: folder_ = folder; } + class ArrayInsertCommand : public UndoCommand + { + public: + ArrayInsertCommand(Node* node, const QString& input, int index) : + node_(node), + input_(input), + index_(index) + { + } + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override + { + node_->InputArrayInsert(input_, index_, false); + } + + virtual void undo() override + { + node_->InputArrayRemove(input_, index_, false); + } + + private: + Node* node_; + QString input_; + int index_; + + }; + + class ArrayResizeCommand : public UndoCommand + { + public: + ArrayResizeCommand(Node* node, const QString& input, int size) : + node_(node), + input_(input), + size_(size) + {} + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override + { + old_size_ = node_->InputArraySize(input_); + + if (old_size_ > size_) { + // Decreasing in size, disconnect any extraneous edges + for (int i=size_; iinput_connections().at(input); + + removed_connections_[input] = output; + + DisconnectEdge(output, input); + } catch (std::out_of_range&) {} + } + } + + node_->ArrayResizeInternal(input_, size_); + } + + virtual void undo() override + { + for (auto it=removed_connections_.cbegin(); it!=removed_connections_.cend(); it++) { + ConnectEdge(it->second, it->first); + } + removed_connections_.clear(); + + node_->ArrayResizeInternal(input_, old_size_); + } + + private: + Node* node_; + QString input_; + int size_; + int old_size_; + + InputConnections removed_connections_; + + }; + class ArrayRemoveCommand : public UndoCommand { public: @@ -1203,90 +1315,6 @@ signals: void InputFlagsChanged(const QString &input, const InputFlags &flags); private: - class ArrayInsertCommand : public UndoCommand - { - public: - ArrayInsertCommand(Node* node, const QString& input, int index) : - node_(node), - input_(input), - index_(index) - { - } - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override - { - node_->InputArrayInsert(input_, index_, false); - } - - virtual void undo() override - { - node_->InputArrayRemove(input_, index_, false); - } - - private: - Node* node_; - QString input_; - int index_; - - }; - - class ArrayResizeCommand : public UndoCommand - { - public: - ArrayResizeCommand(Node* node, const QString& input, int size) : - node_(node), - input_(input), - size_(size) - {} - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override - { - old_size_ = node_->InputArraySize(input_); - - if (old_size_ > size_) { - // Decreasing in size, disconnect any extraneous edges - for (int i=size_; iinput_connections().at(input); - - removed_connections_[input] = output; - - DisconnectEdge(output, input); - } catch (std::out_of_range&) {} - } - } - - node_->ArrayResizeInternal(input_, size_); - } - - virtual void undo() override - { - for (auto it=removed_connections_.cbegin(); it!=removed_connections_.cend(); it++) { - ConnectEdge(it->second, it->first); - } - removed_connections_.clear(); - - node_->ArrayResizeInternal(input_, old_size_); - } - - private: - Node* node_; - QString input_; - int size_; - int old_size_; - - InputConnections removed_connections_; - - }; - struct Input { NodeValue::Type type; InputFlags flags; @@ -1540,7 +1568,7 @@ void Node::FindOutputNodeInternal(const Node* n, QVector& list) list.append(cast_test); } - FindOutputNodeInternal(connected); + FindOutputNodeInternal(connected, list); } } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index bb95a6463..355932af1 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -187,12 +187,12 @@ NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams &vparams, const Aud NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) { // If input is connected, retrieve value directly - if (node->IsInputConnected(input)) { + if (node->IsInputConnectedForRender(input)) { TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range); // Value will equal something from the connected node, follow it - Node *output = node->GetConnectedOutput(input); + Node *output = node->GetConnectedRenderOutput(input); NodeValueTable table = GenerateTable(output, adjusted_range, node); return table; @@ -242,8 +242,8 @@ void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const No NodeValueTable& sub_tbl = array_tbl[element]; TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range); - if (node->IsInputConnected(input, element)) { - Node *output = node->GetConnectedOutput(input, element); + if (node->IsInputConnectedForRender(input, element)) { + Node *output = node->GetConnectedRenderOutput(input, element); sub_tbl = GenerateTable(output, adjusted_range, node); } else { QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), element); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 59ff3d3c9..08624be67 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -41,6 +41,7 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : viewer_node_(nullptr), use_custom_range_(false), pause_renders_(false), + pause_thumbnails_(false), single_frame_render_(nullptr), display_color_processor_(nullptr), multicam_(nullptr), @@ -583,6 +584,14 @@ void PreviewAutoCacher::SetRendersPaused(bool e) } } +void PreviewAutoCacher::SetThumbnailsPaused(bool e) +{ + pause_thumbnails_ = e; + if (!e) { + TryRender(); + } +} + void PreviewAutoCacher::NodeAdded(Node *node) { graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); @@ -663,29 +672,31 @@ void PreviewAutoCacher::TryRender() const int max_tasks = 4; // Handle video tasks - while (!pending_video_jobs_.empty()) { - VideoJob &d = pending_video_jobs_.front(); + if (!pause_thumbnails_) { + while (!pending_video_jobs_.empty()) { + VideoJob &d = pending_video_jobs_.front(); - if (Node *copy = copy_map_.value(d.node)) { - // Queue next frames - rational t; - while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { - RenderFrame(copy, t, d.cache, false); + if (Node *copy = copy_map_.value(d.node)) { + // Queue next frames + rational t; + while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { + RenderFrame(copy, t, d.cache, false); - emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); + emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); - if (!d.iterator.HasNext()) { - emit StopCacheProxyTasks(); + if (!d.iterator.HasNext()) { + emit StopCacheProxyTasks(); + } } + } else { + qCritical() << "Failed to find node copy for video job"; } - } else { - qCritical() << "Failed to find node copy for video job"; - } - if (d.iterator.HasNext()) { - break; - } else { - pending_video_jobs_.pop_front(); + if (d.iterator.HasNext()) { + break; + } else { + pending_video_jobs_.pop_front(); + } } } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 28c16deb7..21efb9912 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -89,6 +89,7 @@ public: bool IsRenderingCustomRange() const; void SetRendersPaused(bool e); + void SetThumbnailsPaused(bool e); void SetMulticamNode(MultiCamNode *n) { multicam_ = n; } @@ -178,6 +179,7 @@ private: TimeRange custom_autocache_range_; bool pause_renders_; + bool pause_thumbnails_; RenderTicketPtr single_frame_render_; QMap > video_immediate_passthroughs_; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 2b42f9e67..d723d54e7 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -300,13 +300,11 @@ NodeValueDatabase RenderProcessor::GenerateDatabase(const Node *node, const Time if (const MultiCamNode *multicam = dynamic_cast(node)) { if (Node::ValueToPtr(ticket_->property("multicam")) == multicam) { - int sz = multicam->InputArraySize(multicam->kSourcesInput); - NodeValueTableArray arr; + int sz = multicam->GetSourceCount(); QVector multicam_tex(sz); for (int i=0; ikSourcesInput, i, range); - - NodeValue val = GenerateRowValueElement(multicam, multicam->kSourcesInput, i, &arr.at(i), range); + NodeValueTable t = GenerateTable(multicam->GetConnectedRenderOutput(multicam->kSourcesInput, i), range, multicam); + NodeValue val = GenerateRowValueElement(multicam, multicam->kSourcesInput, i, &t, range); ResolveJobs(val); multicam_tex[i] = val.toTexture(); diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 6afbd9ac8..5156fd4ed 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -58,10 +58,18 @@ MulticamWidget::MulticamWidget(QWidget *parent) : void MulticamWidget::SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip) { - ConnectViewerNode(viewer); - node_ = n; - display_->SetMulticamNode(n); - clip_ = clip; + if (GetConnectedNode() != viewer) { + ConnectViewerNode(viewer); + } + + if (node_ != n) { + node_ = n; + display_->SetMulticamNode(n); + } + + if (clip_ != clip) { + clip_ = clip; + } } void MulticamWidget::SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time) @@ -145,6 +153,8 @@ void MulticamWidget::Switch(int source, bool split_clip) Core::instance()->undo_stack()->push(command); display_->update(); + + emit Switched(); } void MulticamWidget::DisplayClicked(const QPoint &p) diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index 8691c5668..a6e5f311a 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -42,6 +42,9 @@ protected: virtual void DisconnectNodeEvent(ViewerOutput *n) override; virtual void TimeChangedEvent(const rational &t) override; +signals: + void Switched(); + private: void SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 9941d061a..b68881868 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1235,30 +1235,23 @@ void TimelineWidget::ShowContextMenu() reveal_in_project->setData(reinterpret_cast(clip->connected_viewer())); connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject); - /*if (Sequence *sequence = dynamic_cast(clip->connected_viewer())) { - Menu *multicam_menu = new Menu(tr("Multi-Cam"), &menu); - menu.addMenu(multicam_menu); - - QAction *multicam_enabled = multicam_menu->addAction(tr("Enabled")); + if (Sequence *sequence = dynamic_cast(clip->connected_viewer())) { + QAction *multicam_enabled = menu.addAction(tr("Multi-Cam")); multicam_enabled->setCheckable(true); - auto mcn = sequence->FindOutputNode(); - multicam_enabled->setChecked(!mcn.empty()); + MultiCamNode *mcn = nullptr; + auto paths = clip->FindWaysNodeArrivesHere(sequence); - multicam_menu->addSeparator(); - - QAction *multicam_update = multicam_menu->addAction(tr("Update")); - multicam_update->setEnabled(!mcn.empty()); - - if (!mcn.empty()) { - auto n = mcn.first(); - multicam_enabled->setProperty("multicam", Node::PtrToValue(n)); - multicam_update->setProperty("multicam", Node::PtrToValue(n)); + for (const NodeInput &i : paths) { + if ((mcn = dynamic_cast(i.node()))) { + break; + } } + multicam_enabled->setChecked(mcn); + connect(multicam_enabled, &QAction::triggered, this, &TimelineWidget::MulticamEnabledTriggered); - connect(multicam_update, &QAction::triggered, this, &TimelineWidget::MulticamUpdateTriggered); - }*/ + } } } @@ -1492,16 +1485,57 @@ void TimelineWidget::CacheDiscard() void TimelineWidget::MulticamEnabledTriggered(bool e) { - if (e) { - // Add multicam node - } else if (MultiCamNode *m = Node::ValueToPtr(sender()->property("multicam"))) { - // Remove multicam node - } -} + MultiUndoCommand *command = new MultiUndoCommand(); -void TimelineWidget::MulticamUpdateTriggered() -{ - // Update multicam node + for (Block *b : qAsConst(selected_blocks_)) { + if (ClipBlock *c = dynamic_cast(b)) { + if (Sequence *s = dynamic_cast(c->connected_viewer())) { + if (e) { + + // Adding multicams + // Create multicam node and add it to the graph + MultiCamNode *n = new MultiCamNode(); + n->SetSequenceType(c->GetTrackType()); + command->add_child(new NodeAddCommand(s->parent(), n)); + + + // For each output the sequence has to this clip, disconnect it and + // connect to the multicam instead + QVector inputs = c->FindWaysNodeArrivesHere(s); + for (const NodeInput &i : inputs) { + command->add_child(new NodeEdgeRemoveCommand(s, i)); + command->add_child(new NodeEdgeAddCommand(n, i)); + } + + command->add_child(new NodeEdgeAddCommand(s, NodeInput(n, n->kSequenceInput))); + + // Move sequence node one unit back, and place multicam in sequence's spot + QPointF sequence_pos = c->GetNodePositionInContext(s); + command->add_child(new NodeSetPositionCommand(s, c, sequence_pos - QPointF(1, 0))); + command->add_child(new NodeSetPositionCommand(n, c, sequence_pos)); + + } else { + + // Removing multicams + // Locate first multicam that specifically ends up at this clip + QVector inputs = c->FindWaysNodeArrivesHere(s); + for (const NodeInput &i : inputs) { + if (MultiCamNode *mcn = dynamic_cast(i.node())) { + for (auto it=mcn->output_connections().cbegin(); it!=mcn->output_connections().cend(); it++) { + command->add_child(new NodeEdgeRemoveCommand(it->first, it->second)); + command->add_child(new NodeEdgeAddCommand(s, it->second)); + } + + command->add_child(new NodeRemoveAndDisconnectCommand(mcn)); + } + } + + } + } + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); } void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 998e56869..5161f6d55 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -447,7 +447,6 @@ private slots: void CacheDiscard(); void MulticamEnabledTriggered(bool e); - void MulticamUpdateTriggered(); }; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 1ec330c19..32c1f9ebe 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -427,7 +427,15 @@ void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, c void ViewerWidget::ConnectMulticamWidget(MulticamWidget *p) { + if (multicam_panel_) { + disconnect(multicam_panel_, &MulticamWidget::Switched, this, &ViewerWidget::DetectMulticamNodeNow); + } + multicam_panel_ = p; + + if (multicam_panel_) { + connect(multicam_panel_, &MulticamWidget::Switched, this, &ViewerWidget::DetectMulticamNodeNow); + } } FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QUuid &cache_id, const int64_t& time) @@ -607,6 +615,11 @@ void ViewerWidget::SaveFrameAsImage() Core::instance()->OpenExportDialogForViewer(GetConnectedNode(), GetTime(), true); } +void ViewerWidget::DetectMulticamNodeNow() +{ + DetectMulticamNode(GetTime()); +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); @@ -903,7 +916,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) if (viewer != this) { viewer->PauseInternal(); } - viewer->auto_cacher_->SetRendersPaused(true); + viewer->auto_cacher_->SetThumbnailsPaused(true); } RenderManager::instance()->SetAggressiveGarbageCollection(true); @@ -1005,7 +1018,7 @@ void ViewerWidget::PauseInternal() UpdateAudioProcessor(); foreach (ViewerWidget* viewer, instances_) { - viewer->auto_cacher_->SetRendersPaused(false); + viewer->auto_cacher_->SetThumbnailsPaused(false); } UpdateTextureFromNode(); @@ -1796,7 +1809,7 @@ void ViewerWidget::SetZoomFromMenu(QAction *action) void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range) { // If our current frame is within this range, we need to update - if (GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) { + if (!IsPlaying() && GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) { QMetaObject::invokeMethod(this, &ViewerWidget::UpdateTextureFromNode, Qt::QueuedConnection); } } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index c465aabc9..e4f0d7289 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -117,7 +117,7 @@ public: if (!IsPlaying()) { // If is playing, this will happen by the next frame automatically - DetectMulticamNode(GetTime()); + DetectMulticamNodeNow(); UpdateTextureFromNode(); } } @@ -128,7 +128,7 @@ public: if (!IsPlaying()) { // If is playing, this will happen by the next frame automatically - DetectMulticamNode(GetTime()); + DetectMulticamNodeNow(); UpdateTextureFromNode(); } } @@ -421,6 +421,8 @@ private slots: void SaveFrameAsImage(); + void DetectMulticamNodeNow(); + }; } From 5a15e2df50f0657355b885b683c4a78a83396000 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:08:15 -0700 Subject: [PATCH 03/34] core: limit recently opened projects size --- app/core.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/core.cpp b/app/core.cpp index 1dd1317c2..a4aaef6fc 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1365,6 +1365,11 @@ void Core::PushRecentlyOpenedProject(const QString& s) recent_projects_.move(existing_index, 0); } else { recent_projects_.prepend(s); + + const int kMaximumRecentProjects = 10; + while (recent_projects_.size() > kMaximumRecentProjects) { + recent_projects_.removeLast(); + } } emit OpenRecentListChanged(); From 95851c065649ba3f22525ebd70916238093e317b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:08:33 -0700 Subject: [PATCH 04/34] ffmpegdecoder: removed conflicting deinterlacing code --- app/codec/ffmpeg/ffmpegdecoder.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 179666090..6a0030ae7 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -801,10 +801,6 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter { int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); - if (interlacing != VideoParams::kInterlaceNone) { - target_ts *= 2; - } - const int64_t min_seek = -instance_.avstream()->start_time; int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); bool still_seeking = false; @@ -1005,20 +1001,6 @@ 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) { From 75e78ae1e0012dd6777ddd53f4ef218c16de53c9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:29:28 -0700 Subject: [PATCH 05/34] ffmpegdecoder: minor code cleanup --- app/codec/ffmpeg/ffmpegdecoder.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 6a0030ae7..a5ffa9842 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -40,12 +40,9 @@ extern "C" { #include #include "codec/planarfiledevice.h" -#include "common/define.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" #include "common/timecodefunctions.h" -#include "render/framehashcache.h" -#include "render/diskmanager.h" #include "render/renderer.h" #include "render/subtitleparams.h" @@ -1002,10 +999,10 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params AVFilterContext *last_filter = buffersrc_ctx_; // Add scale filter if necessary - int dst_width, dst_height; if (filter_params_.divider > 1) { AVFilterContext* scale_filter; + int dst_width, dst_height; dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider); dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider); @@ -1017,9 +1014,6 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params avfilter_link(last_filter, 0, scale_filter, 0); last_filter = scale_filter; - } else { - dst_width = src_width; - dst_height = src_height; } // Add format filter if necessary @@ -1125,6 +1119,7 @@ int FFmpegDecoder::MaximumQueueSize() FFmpegDecoder::Instance::Instance() : fmt_ctx_(nullptr), codec_ctx_(nullptr), + avstream_(nullptr), opts_(nullptr) { } From 783d9af864dc86d0398525418d1c279c38a67273 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 12:36:19 -0700 Subject: [PATCH 06/34] encoder: fix bug where audio bitrate wasn't serialized --- app/codec/encoder.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index cb5080402..380b06759 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -258,6 +258,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate())); writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout())); writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format())); + writer->writeTextElement(QStringLiteral("bitrate"), QString::number(audio_bit_rate_)); } writer->writeStartElement(QStringLiteral("subtitles")); @@ -464,10 +465,17 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader) audio_params_.set_channel_layout(reader->readElementText().toULongLong()); } else if (reader->name() == QStringLiteral("format")) { audio_params_.set_format(static_cast(reader->readElementText().toInt())); + } else if (reader->name() == QStringLiteral("bitrate")) { + audio_bit_rate_ = reader->readElementText().toLongLong(); } else { reader->skipCurrentElement(); } } + + // HACK: Resolve bug where I forgot to serialize the audio bit rate + if (!audio_bit_rate_) { + audio_bit_rate_ = 320000; + } } else if (reader->name() == QStringLiteral("subtitles")) { XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("enabled")) { From c173bcad2a36e3e5dc81122197dc5aba4f1c75d1 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 20:52:38 -0700 Subject: [PATCH 07/34] mainwindow: remove timeline from "main time panels" when destroyed Fixes potential crash with multiple sequences --- app/window/mainwindow/mainwindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 79b589901..20adf7800 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -555,7 +555,9 @@ void MainWindow::UpdateTitle() void MainWindow::TimelineCloseRequested() { - RemoveTimelinePanel(static_cast(sender())); + TimelinePanel *t = static_cast(sender()); + RemoveTimelinePanel(t); + main_time_panels_.removeOne(t); } void MainWindow::ProjectCloseRequested() From 73e1e60f822d2931de47a4bc9252416041d88efb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 20:52:56 -0700 Subject: [PATCH 08/34] Revert "ffmpegdecoder: removed conflicting deinterlacing code" This reverts commit 95851c065649ba3f22525ebd70916238093e317b. --- app/codec/ffmpeg/ffmpegdecoder.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index a5ffa9842..3721d52ff 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -798,6 +798,10 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter { int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); + if (interlacing != VideoParams::kInterlaceNone) { + target_ts *= 2; + } + const int64_t min_seek = -instance_.avstream()->start_time; int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); bool still_seeking = false; @@ -998,6 +1002,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 if (filter_params_.divider > 1) { AVFilterContext* scale_filter; From ef8996d48f788683878299697a5dfdfe4fa1049e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 21:13:01 -0700 Subject: [PATCH 09/34] nodes: allow ignoring specific inputs for rendering Also ignore sequence input on multicam --- app/node/input/multicam/multicamnode.cpp | 5 +++++ app/node/input/multicam/multicamnode.h | 2 ++ app/node/node.h | 5 +++++ app/node/traverser.cpp | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 9dd96e6a4..2ff887069 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -98,6 +98,11 @@ bool MultiCamNode::IsInputConnectedForRender(const QString &input, int element) } } +QVector MultiCamNode::IgnoreInputsForRendering() const +{ + return {kSequenceInput}; +} + void MultiCamNode::InputConnectedEvent(const QString &input, int element, Node *output) { if (input == kSequenceInput) { diff --git a/app/node/input/multicam/multicamnode.h b/app/node/input/multicam/multicamnode.h index cec77a88a..ead53c63c 100644 --- a/app/node/input/multicam/multicamnode.h +++ b/app/node/input/multicam/multicamnode.h @@ -60,6 +60,8 @@ public: virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const override; virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const override; + virtual QVector IgnoreInputsForRendering() const override; + protected: virtual void InputConnectedEvent(const QString &input, int element, Node *output) override; virtual void InputDisconnectedEvent(const QString &input, int element, Node *output) override; diff --git a/app/node/node.h b/app/node/node.h index e6ffa724c..99e4239e2 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -212,6 +212,11 @@ public: return input_ids_; } + virtual QVector IgnoreInputsForRendering() const + { + return QVector(); + } + class ActiveElements { public: diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 355932af1..ee1fa0fbd 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -38,11 +38,16 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa } // We need to insert tables into the database for each input + auto ignore = node->IgnoreInputsForRendering(); foreach (const QString& input, node->inputs()) { if (IsCancelled()) { return NodeValueDatabase(); } + if (ignore.contains(input)) { + continue; + } + database.Insert(input, ProcessInput(node, input, range)); } From 5d5b2440a16d9d6e2f67cc3c31fe6fd4c8e25f9a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 21:24:05 -0700 Subject: [PATCH 10/34] ffmpegdecoder: improved glsl and swscale interoperability --- app/codec/ffmpeg/ffmpegdecoder.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 3721d52ff..350b54345 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -232,9 +232,9 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) 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, f->color_range == AVCOL_RANGE_JPEG)); + job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, hw_in->color_range == AVCOL_RANGE_JPEG)); - const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->colorspace)); + const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->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])); @@ -243,7 +243,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) 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()); + frame_rate_tb_ = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), hw_in); // Double frame rate for interlaced fields frame_rate_tb_ *= 2; @@ -253,7 +253,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) } 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_); + int64_t frm = Timecode::rescale_timestamp(hw_in->pts - instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb_); bool first = (req == frm); bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst); @@ -798,7 +798,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter { int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); - if (interlacing != VideoParams::kInterlaceNone) { + if (interlacing != VideoParams::kInterlaceNone && !IsPixelFormatGLSLCompatible(static_cast(instance_.avstream()->codecpar->format))) { target_ts *= 2; } @@ -1002,8 +1002,10 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params // Link filters as necessary AVFilterContext *last_filter = buffersrc_ctx_; + bool glsl_available = IsPixelFormatGLSLCompatible(static_cast(input->format)); + // Add deinterlace filter if necessary - if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) { + if (filter_params_.src_interlacing != VideoParams::kInterlaceNone && !glsl_available) { AVFilterContext* deint_filter; snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s", @@ -1035,7 +1037,7 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params } // Add format filter if necessary - if (ideal_pix_fmt != input->format && !IsPixelFormatGLSLCompatible(static_cast(input->format))) { + if (ideal_pix_fmt != input->format && !glsl_available) { AVFilterContext* format_filter; snprintf(filter_args, kFilterArgSz, "pix_fmts=%u", ideal_pix_fmt); From 906f802422da1c600ace85878f5bdd05e3caa1a3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 4 Oct 2022 21:51:05 -0700 Subject: [PATCH 11/34] viewer: only render sequence waveforms when waveform view is visible --- app/node/output/viewer/viewer.cpp | 21 +++++++++++++-------- app/node/output/viewer/viewer.h | 4 +++- app/widget/viewer/viewer.cpp | 4 ++++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 34ec101d6..50d90f0b3 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -39,7 +39,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream video_length_(0), audio_length_(0), autocache_input_video_(false), - autocache_input_audio_(false) + autocache_input_audio_(false), + waveform_requests_enabled_(false) { AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); @@ -231,7 +232,9 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, } } else if (from == kSamplesInput) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength())); - connected->waveform_cache()->Request(range.Intersected(max_range)); + if (waveform_requests_enabled_) { + connected->waveform_cache()->Request(range.Intersected(max_range)); + } if (autocache_input_audio_) { connected->audio_playback_cache()->Request(range.Intersected(max_range)); } @@ -387,13 +390,15 @@ Node::ValueHint ViewerOutput::GetConnectedSampleValueHint() return GetValueHintForInput(kSamplesInput); } -void ViewerOutput::ConnectedToPreviewEvent() +void ViewerOutput::SetWaveformEnabled(bool e) { - if (Node *connected = this->GetConnectedSampleOutput()) { - TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength())); - TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - connected->waveform_cache()->Request(r); + if ((waveform_requests_enabled_ = e)) { + if (Node *connected = this->GetConnectedSampleOutput()) { + TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength())); + TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + connected->waveform_cache()->Request(r); + } } } } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 4a1bf10a2..b99ebc30b 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -182,7 +182,7 @@ public: virtual ValueHint GetConnectedSampleValueHint(); - virtual void ConnectedToPreviewEvent() override; + void SetWaveformEnabled(bool e); bool IsVideoAutoCacheEnabled() const { qDebug() << "sequence ac is a stub"; return false; } void SetVideoAutoCacheEnabled(bool e) { qDebug() << "sequence ac is a stub"; } @@ -251,6 +251,8 @@ private: EncodingParams last_used_encoding_params_; + bool waveform_requests_enabled_; + }; } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 32c1f9ebe..3beb3f44e 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -708,6 +708,10 @@ void ViewerWidget::UpdateWaveformViewFromMode() waveform_view_->setVisible(waveform_mode_ == kWFViewerAndWaveform || waveform_mode_ == kWFWaveformOnly || (waveform_mode_ == kWFAutomatic && prefer_waveform)); waveform_view_->setSizePolicy(QSizePolicy::Expanding, waveform_mode_ == kWFViewerAndWaveform ? QSizePolicy::Maximum : QSizePolicy::Expanding); + + if (GetConnectedNode()) { + GetConnectedNode()->SetWaveformEnabled(waveform_view_->isVisible()); + } } void ViewerWidget::QueueNextAudioBuffer() From 4ea1870098085d0021331ca35572fb947b908cc1 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 5 Oct 2022 16:04:42 -0700 Subject: [PATCH 12/34] exportdialog: fixed bug where format change signal didn't fire when loading preset --- app/dialog/export/export.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 6210b63f4..e714dfd41 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -697,6 +697,7 @@ EncodingParams ExportDialog::GenerateParams() const void ExportDialog::SetParams(const EncodingParams &e) { format_combobox_->SetFormat(e.format()); + FormatChanged(format_combobox_->GetFormat()); if (e.has_custom_range() && viewer_node_->GetWorkArea()->enabled()) { range_combobox_->setCurrentIndex(kRangeInToOut); From d9c50ce1766c112769f5dde5c3cd5a66d9b852fe Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 5 Oct 2022 20:54:17 -0700 Subject: [PATCH 13/34] clip: limit maximum multicam to 1 --- app/node/block/clip/clip.cpp | 2 +- app/node/node.h | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 2d3ac32cb..12d7f4a52 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -543,7 +543,7 @@ TimeRange ClipBlock::media_range() const MultiCamNode *ClipBlock::FindMulticam() { - auto v = FindInputNodesConnectedToInput(NodeInput(this, kBufferIn)); + auto v = FindInputNodesConnectedToInput(NodeInput(this, kBufferIn), 1); if (v.empty()) { return nullptr; } else { diff --git a/app/node/node.h b/app/node/node.h index 99e4239e2..fea4db863 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -859,7 +859,7 @@ public: * @brief Find nodes of a certain type that this Node takes inputs from */ template - static QVector FindInputNodesConnectedToInput(const NodeInput &input); + static QVector FindInputNodesConnectedToInput(const NodeInput &input, int maximum = 0); template /** @@ -1400,10 +1400,10 @@ private: * @brief Find nodes of a certain type that this Node takes inputs from */ template - static void FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector& list); + static void FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector& list, int maximum); template - static void FindInputNodeInternal(const Node* n, QVector& list); + static void FindInputNodeInternal(const Node* n, QVector& list, int maximum); template static void FindOutputNodeInternal(const Node* n, QVector& list); @@ -1512,7 +1512,7 @@ private slots: }; template -void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector &list) +void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector &list, int maximum) { Node* edge = input.GetConnectedOutput(); if (!edge) { @@ -1523,26 +1523,29 @@ void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVecto if (cast_test) { list.append(cast_test); + if (maximum != 0 && list.size() == maximum) { + return; + } } - FindInputNodeInternal(edge, list); + FindInputNodeInternal(edge, list, maximum); } template -QVector Node::FindInputNodesConnectedToInput(const NodeInput &input) +QVector Node::FindInputNodesConnectedToInput(const NodeInput &input, int maximum) { QVector list; - FindInputNodesConnectedToInputInternal(input, list); + FindInputNodesConnectedToInputInternal(input, list, maximum); return list; } template -void Node::FindInputNodeInternal(const Node* n, QVector &list) +void Node::FindInputNodeInternal(const Node* n, QVector &list, int maximum) { for (auto it=n->input_connections_.cbegin(); it!=n->input_connections_.cend(); it++) { - FindInputNodesConnectedToInputInternal(it->first, list); + FindInputNodesConnectedToInputInternal(it->first, list, maximum); } } From 1fe91ada05147a83adb663a818cb1b1cec3d2bd3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:01:04 -0700 Subject: [PATCH 14/34] footage: use null timestamp for invalid footage modified time --- app/node/project/footage/footage.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 307ce247b..fd936faf6 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -544,7 +544,12 @@ void Footage::CheckFootage() if (!fn.isEmpty()) { QFileInfo info(fn); - qint64 current_file_timestamp = info.lastModified().toMSecsSinceEpoch(); + qint64 current_file_timestamp; + if (!info.lastModified().isValid()) { + current_file_timestamp = 0; + } else { + current_file_timestamp = info.lastModified().toMSecsSinceEpoch(); + } if (current_file_timestamp != timestamp()) { // File has changed! From fa8dc8892bddafdb6e246fec00d1a8d712e2ba90 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:01:15 -0700 Subject: [PATCH 15/34] clip: limit input node lookup to 1 --- 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 12d7f4a52..110a26ee4 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -364,7 +364,7 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int } // Find connected viewer node - auto viewers = FindInputNodesConnectedToInput(NodeInput(this, kBufferIn)); + auto viewers = FindInputNodesConnectedToInput(NodeInput(this, kBufferIn), 1); ViewerOutput *new_connected_viewer = viewers.isEmpty() ? nullptr : viewers.first(); if (new_connected_viewer != connected_viewer_) { From 9ccf6fc1ab47cd6688d6f9db3c477a9fcf2fe9e9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:10:55 -0700 Subject: [PATCH 16/34] node: updated incompatible function --- app/node/node.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/node/node.h b/app/node/node.h index fea4db863..0dbfdaa3e 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -853,7 +853,7 @@ public: * @brief Find nodes of a certain type that this Node takes inputs from */ template - QVector FindInputNodes() const; + QVector FindInputNodes(int maximum = 0) const; /** * @brief Find nodes of a certain type that this Node takes inputs from @@ -1550,11 +1550,11 @@ void Node::FindInputNodeInternal(const Node* n, QVector &list, int maximum) } template -QVector Node::FindInputNodes() const +QVector Node::FindInputNodes(int maximum) const { QVector list; - FindInputNodeInternal(this, list); + FindInputNodeInternal(this, list, maximum); return list; } From b2b32805ee30624baa7211220f5c9cb659836a58 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 6 Oct 2022 00:32:24 -0700 Subject: [PATCH 17/34] ffmpegdecoder: ensure scaled frame is unref'd --- app/codec/ffmpeg/ffmpegdecoder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 350b54345..76b25da8e 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -265,6 +265,8 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) tex = p.renderer->CreateTexture(vp); p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); + + av_frame_unref(working_frame_); } } From 2076d0b2c5e1e9f4fdb941d310a871922cbb69b2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 6 Oct 2022 00:46:32 -0700 Subject: [PATCH 18/34] exportdialog: ignore preset combobox change when loading presets --- app/dialog/export/export.cpp | 11 ++++++++++- app/dialog/export/export.h | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index e714dfd41..94bc6fedf 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -47,7 +47,8 @@ namespace olive { ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWidget *parent) : super(parent), viewer_node_(viewer_node), - stills_only_mode_(stills_only_mode) + stills_only_mode_(stills_only_mode), + loading_presets_(false) { QHBoxLayout* layout = new QHBoxLayout(this); @@ -432,6 +433,10 @@ void ExportDialog::SavePreset() void ExportDialog::PresetComboBoxChanged() { + if (loading_presets_) { + return; + } + QComboBox *c = static_cast(sender()); int preset_number = c->currentData().toInt(); @@ -536,6 +541,8 @@ void ExportDialog::ResolutionChanged() void ExportDialog::LoadPresets() { + loading_presets_ = true; + preset_combobox_->clear(); presets_.clear(); @@ -562,6 +569,8 @@ void ExportDialog::LoadPresets() f.close(); } } + + loading_presets_ = false; } void ExportDialog::SetDefaultFilename() diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 307e8b0d4..594b16380 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -125,6 +125,8 @@ private: bool stills_only_mode_; + bool loading_presets_; + private slots: void BrowseFilename(); From 184e43fb68bff1abb0abb2c2bc5f30f4e1c03e31 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 6 Oct 2022 09:42:57 -0700 Subject: [PATCH 19/34] encodingparams: fix bug where pixel aspect wasn't serialized --- app/codec/encoder.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 380b06759..4888ed5a6 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -214,6 +214,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width())); writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height())); writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format())); + writer->writeTextElement(QStringLiteral("pixelaspect"), video_params_.pixel_aspect_ratio().toString()); 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_)); @@ -399,6 +400,8 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader) 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("pixelaspect")) { + video_params_.set_pixel_aspect_ratio(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("timebase")) { video_params_.set_time_base(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("divider")) { @@ -449,6 +452,11 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader) reader->skipCurrentElement(); } } + + // HACK: Resolve bug where I forgot to serialize pixel aspect ratio + if (video_params_.pixel_aspect_ratio().isNull()) { + video_params_.set_pixel_aspect_ratio(1); + } } else if (reader->name() == QStringLiteral("audio")) { XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("enabled")) { From 0eadea86077ca60291c4fccb1745a531d34e51f2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 6 Oct 2022 09:43:24 -0700 Subject: [PATCH 20/34] exportdialog: if using last used, set combobox to reflect that --- app/dialog/export/export.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 94bc6fedf..38e890a4c 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -260,6 +260,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi // If the viewer already has cached params, use them if (!stills_only_mode_ && viewer_node_->GetLastUsedEncodingParams().IsValid()) { SetParams(viewer_node_->GetLastUsedEncodingParams()); + QtUtils::SetComboBoxData(preset_combobox_, kPresetLastUsed); } else { SetDefaults(); } From 5420ceaa6f37f4b9d4a2c06663a555ebd0948b2f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 6 Oct 2022 09:47:04 -0700 Subject: [PATCH 21/34] exportdialog: don't double-set data --- app/dialog/export/export.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 38e890a4c..64573cf4f 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -259,7 +259,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi // If the viewer already has cached params, use them if (!stills_only_mode_ && viewer_node_->GetLastUsedEncodingParams().IsValid()) { - SetParams(viewer_node_->GetLastUsedEncodingParams()); + // This will automatically set the param data QtUtils::SetComboBoxData(preset_combobox_, kPresetLastUsed); } else { SetDefaults(); From 1765b8ab179af91e44fa005b6dcdb846a05f410e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 7 Oct 2022 21:10:45 -0700 Subject: [PATCH 22/34] footage: don't use a divider higher than the global --- 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 fd936faf6..210d448f5 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -276,7 +276,8 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV // Adjust footage job's divider if (globals.vparams().divider() > 1) { // Use a divider appropriate for this target resolution - vp.set_divider(VideoParams::GetDividerForTargetResolution(vp.width(), vp.height(), globals.vparams().effective_width(), globals.vparams().effective_height())); + int calculated = VideoParams::GetDividerForTargetResolution(vp.width(), vp.height(), globals.vparams().effective_width(), globals.vparams().effective_height()); + vp.set_divider(std::min(calculated, globals.vparams().divider())); } else { // Render everything at full res vp.set_divider(1); From fc64f9e8a4ec1afe7013b768357a3e05fdf3daa7 Mon Sep 17 00:00:00 2001 From: elsandosgrande Date: Sat, 8 Oct 2022 15:36:09 +0200 Subject: [PATCH 23/34] [skip ci] .gitignore: Ignore `compile_flags.txt` --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 43c997614..b10c5cddf 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,10 @@ CMakeLists.txt.user* # QtCreator 4.8< compilation database compile_commands.json +# Hand-written compilation database listing compilation flags for clangd to use when parsing the code, similarly to the compilation database above +# https://clangd.llvm.org/design/compile-commands#where-do-compile-commands-come-from +compile_flags.txt + # QtCreator local machine specific files for imported projects *creator.user* From cae71f011d5339bff33e146516985a4cfa06ab77 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 9 Oct 2022 16:19:49 +0100 Subject: [PATCH 24/34] Chromakey: Fix alpha channel bug Fixes bug where elements that are smaller than the main texture were croping layers below them when keyed. --- app/shaders/chromakey.frag | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/shaders/chromakey.frag b/app/shaders/chromakey.frag index dc665b2e4..d3ee70e6c 100644 --- a/app/shaders/chromakey.frag +++ b/app/shaders/chromakey.frag @@ -100,8 +100,7 @@ void main() { mask = 1.0 - mask; } - col.rgb *= mask; - col.w = mask; + col *= mask; if (!mask_only_in) { frag_color = col; From bb63b6968c2050694bf78bb759ae3d67f8d05eed Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:54:10 -0700 Subject: [PATCH 25/34] decoder: use atomic int instead of mutex for access time Prevents main thread from getting hung up if the decoder is doing something slow --- app/codec/decoder.cpp | 12 ------------ app/codec/decoder.h | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 3e6998f88..434376c06 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -45,7 +45,6 @@ Decoder::Decoder() : void Decoder::IncrementAccessTime(qint64 t) { - QMutexLocker locker(&mutex_); last_accessed_ += t; } @@ -156,7 +155,6 @@ Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const Ti qint64 Decoder::GetLastAccessedTime() { - QMutexLocker locker(&mutex_); return last_accessed_; } @@ -215,16 +213,6 @@ DecoderPtr Decoder::CreateFromID(const QString &id) return nullptr; } -int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time) -{ - return Timecode::time_to_timestamp(time, timebase); -} - -rational Decoder::GetTimestampInTimeUnits(int64_t time, const rational &timebase, int64_t start_time) -{ - return Timecode::timestamp_to_time(time, timebase); -} - void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration) { if (duration != AV_NOPTS_VALUE && duration != 0) { diff --git a/app/codec/decoder.h b/app/codec/decoder.h index c6aac3ecb..757b48080 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -315,7 +315,7 @@ private: QMutex mutex_; - qint64 last_accessed_; + std::atomic_int64_t last_accessed_; TexturePtr cached_texture_; rational cached_time_; From 4f6dd6aeb156bd76aa5752bc8d23fe0105a17462 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:54:36 -0700 Subject: [PATCH 26/34] ffmpegdecoder: offset target timestamp by start timestamp Fixes #2054 --- app/codec/decoder.h | 3 --- app/codec/ffmpeg/ffmpegdecoder.cpp | 6 ++++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 757b48080..d0c974846 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -294,9 +294,6 @@ protected: return stream_; } - static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time); - static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time); - virtual rational GetAudioStartOffset() const { return 0; } signals: diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 76b25da8e..65520d34f 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -798,13 +798,15 @@ void FFmpegDecoder::ClearFrameCache() AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Interlacing interlacing, CancelAtom *cancelled) { - int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); + int64_t target_ts = Timecode::time_to_timestamp(time, instance_.avstream()->time_base); if (interlacing != VideoParams::kInterlaceNone && !IsPixelFormatGLSLCompatible(static_cast(instance_.avstream()->codecpar->format))) { target_ts *= 2; } - const int64_t min_seek = -instance_.avstream()->start_time; + target_ts += instance_.avstream()->start_time; + + const int64_t min_seek = 0; int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); bool still_seeking = false; From 6f0e35bcd6dcd0c9d8c53f2cc76bce97d39be299 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:39:17 -0700 Subject: [PATCH 27/34] audioparams: move calculate_channel_count definition to source file --- app/render/audioparams.cpp | 5 +++++ app/render/audioparams.h | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index d5dd635b7..3712f8cde 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -350,4 +350,9 @@ AudioParams::Format AudioParams::GetPlanarEquivalent(Format fmt) return kFormatInvalid; } +void AudioParams::calculate_channel_count() +{ + channel_count_ = av_get_channel_layout_nb_channels(channel_layout()); +} + } diff --git a/app/render/audioparams.h b/app/render/audioparams.h index a58767328..aff223239 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -256,10 +256,7 @@ private: duration_ = 0; } - void calculate_channel_count() - { - channel_count_ = av_get_channel_layout_nb_channels(channel_layout()); - } + void calculate_channel_count(); int sample_rate_; From 92bf60cc72be485d4f6a9998ccb6d579f00b7f22 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:40:23 -0700 Subject: [PATCH 28/34] videoparams: prevent divided depth from becoming 0 --- 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 7ac14904e..58442bcd3 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -262,7 +262,7 @@ void VideoParams::calculate_effective_size() { effective_width_ = GetScaledDimension(width(), divider_); effective_height_ = GetScaledDimension(height(), divider_); - effective_depth_ = GetScaledDimension(depth(), divider_); + effective_depth_ = (depth() == 1) ? depth() : GetScaledDimension(depth(), divider_); calculate_square_pixel_width(); } From 01874ba786dd2200c9d6a8e65be161de934ee51b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 10 Oct 2022 23:24:19 -0700 Subject: [PATCH 29/34] videoparams: add interlacing to == operator --- app/render/videoparams.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index 58442bcd3..77d785d24 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -164,6 +164,7 @@ bool VideoParams::operator==(const VideoParams &rhs) const return width() == rhs.width() && height() == rhs.height() && depth() == rhs.depth() + && interlacing() == rhs.interlacing() && time_base() == rhs.time_base() && format() == rhs.format() && pixel_aspect_ratio() == rhs.pixel_aspect_ratio() From 202342e24fe4a0a71d43f0fb908f4f03d98aae5b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:25:56 -0700 Subject: [PATCH 30/34] ffmpegdecoder: factor in AVFormatContext::start_time --- app/codec/ffmpeg/ffmpegdecoder.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 65520d34f..d398108a2 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -312,9 +312,11 @@ void FFmpegDecoder::CloseInternal() rational FFmpegDecoder::GetAudioStartOffset() const { - AVStream *s = this->instance_.avstream(); - if (s) { - return rational(s->start_time * s->time_base.num, s->time_base.den); + auto f = instance_.fmt_ctx(); + if (f) { + rational fmt_start = rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE); + rational str_start = rational(instance_.avstream()->time_base) * instance_.avstream()->start_time; + return str_start - fmt_start; } else { return 0; } @@ -804,7 +806,9 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter target_ts *= 2; } - target_ts += instance_.avstream()->start_time; + if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) { + target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, AV_TIME_BASE_Q, instance_.avstream()->time_base); + } const int64_t min_seek = 0; int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); From e17a24021f9a6cc84e9764cc804029e39fabb8fd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:27:27 -0700 Subject: [PATCH 31/34] ffmpegdecoder: disable interlaced scaling So for some reason enabling interlaced scaling causes memory corruption. Is it an FFmpeg bug or ours? I have no idea. But it only happens if interlaced scaling is enabled. If it isn't, either by forcing it off or deinterlacing it beforehand, it doesn't happen. Since scaling is only done for low quality preview, I opted to just disable it, but maybe I'll decide against it later. --- 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 d398108a2..069d9d342 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -1034,7 +1034,7 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider); dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider); - snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=-1", + snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=0", dst_width, dst_height); From b76e9cbdbe28943ed27c8757efb547597678cbc7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:31:12 -0700 Subject: [PATCH 32/34] openglrenderer: revert back to glFlush Just can't win with this one. I started with glFinish because it's technically the correct option, however it caused issues on a GPU, so I switched it out for glFlush. Then I started noticing other frame sync oddities that would be fixed by using glFinish, so I switched back. Now, not only is it causing GPU issues on one system, it seems to be causing GPU issues on others too. So now glFlush seems like the lesser of the two evils, and I'm just going to have to find a way around the aforementioned frame sync issues. --- 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 2cb608783..b0c91d7ab 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 fb566f2d9eee4873deaa07f1e3c527a9bc38a602 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:54:39 -0700 Subject: [PATCH 33/34] ci: attempt to make dmg for macOS --- .github/workflows/ci.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9143394d2..91f292b0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -376,7 +376,7 @@ jobs: ctest -C ${{ matrix.build-type }} -V if: matrix.os-arch == 'x86_64' # ARM64 tests naturally won't be able to run on x86_64 runners - - name: Create Package + - name: Bundle Application working-directory: ${{ runner.workspace }}/build shell: bash run: | @@ -444,9 +444,18 @@ jobs: # Sign application codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME + - name: Deploy + shell: bash + working-directory: ${{ runner.workspace }}/build/deploy + run: | + ln -s /Applications Applications + cd .. + hdiutil create img.dmg -volname Olive -fs HFS+ -srcfolder deploy + hdiutil convert img.dmg -format UDZO -o $PKGNAME.dmg + - name: Upload Artifact to GitHub uses: actions/upload-artifact@v2 continue-on-error: true with: name: ${{ env.PKGNAME }} - path: ${{ runner.workspace }}/build/deploy + path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.dmg From 7791dde9543cf28249d126b99b0fba8b748bebcd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:59:12 -0700 Subject: [PATCH 34/34] ffmpegdecoder: use inline avtimebase rather than macro --- 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 069d9d342..d2292f002 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -807,7 +807,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter } if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) { - target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, AV_TIME_BASE_Q, instance_.avstream()->time_base); + target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, {1, AV_TIME_BASE}, instance_.avstream()->time_base); } const int64_t min_seek = 0;