From f39a3b0ece529112f2208a2f6c782679034f36f7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 14 Oct 2022 02:28:25 -0700 Subject: [PATCH 01/85] timeline: check for locked tracks on split at playhead --- 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 b68881868..82c22b2a8 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -383,6 +383,10 @@ void TimelineWidget::SplitAtPlayhead() // Get all blocks at the playhead foreach (Track* track, sequence()->GetTracks()) { + if (track->IsLocked()) { + continue; + } + Block* b = track->BlockContainingTime(playhead_time); if (dynamic_cast(b)) { From 9b0ba2a6c4e25122a7f23edc22433f06dd0012b5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 16 Oct 2022 09:43:02 -0700 Subject: [PATCH 02/85] transition: show in param view --- app/node/block/block.cpp | 2 +- app/node/block/transition/transition.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index f007eee42..fb030e5b5 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -39,7 +39,7 @@ Block::Block() : track_(nullptr), index_(-1) { - AddInput(kLengthInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kLengthInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagHidden)); SetInputProperty(kLengthInput, QStringLiteral("min"), QVariant::fromValue(rational(0, 1))); SetInputProperty(kLengthInput, QStringLiteral("view"), RationalSlider::kTime); SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true); diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index d5c048eeb..f7497af41 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -47,6 +47,8 @@ TransitionBlock::TransitionBlock() : AddInput(kCenterInput, NodeValue::kRational, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); SetInputProperty(kCenterInput, QStringLiteral("view"), RationalSlider::kTime); SetInputProperty(kCenterInput, QStringLiteral("viewlock"), true); + + SetFlags(GetFlags() & ~kDontShowInParamView); } void TransitionBlock::Retranslate() From ca8a257a9b9b846e8f7a9143082d97abdb2d450f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 16 Oct 2022 13:55:31 -0700 Subject: [PATCH 03/85] footagerelinkdialog: disable filename filter because it breaks on Windows --- app/dialog/footagerelink/footagerelinkdialog.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/dialog/footagerelink/footagerelinkdialog.cpp b/app/dialog/footagerelink/footagerelinkdialog.cpp index 2f89de75e..066c163e6 100644 --- a/app/dialog/footagerelink/footagerelinkdialog.cpp +++ b/app/dialog/footagerelink/footagerelinkdialog.cpp @@ -102,8 +102,16 @@ void FootageRelinkDialog::BrowseForFootage() QString new_fn = QFileDialog::getOpenFileName(this, tr("Relink \"%1\"").arg(f->GetLabel()), - info.absolutePath(), - QStringLiteral("%1;;%2 (**)").arg(info.fileName(), tr("All Files"))); + info.absolutePath()); + + // Originally, this function would attempt to filter to the exact filename of the missing file. + // However, this would break on Windows if the filename had any spaces in it. The reason is + // Windows separates its extensions with ';' while Qt separates them with ' '. Qt isn't + // intelligent enough to determine whether it's a list of extensions or a single filename with a + // space in it, it just does a global replace of ' ' to ';'. There's no way around it, outside of + // bypassing Qt entirely and using Win32's GetOpenFileName() directly. As annoying as it is, I've + // just disabled it for now. + //QStringLiteral("%1 (\"%1\");;%2 (*)").arg(info.fileName(), tr("All Files"))); // We received a new filename if (!new_fn.isEmpty()) { From c43d4b7a12c283f7fbb2f25c4ce3ba9ac290b7b0 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 21 Oct 2022 17:48:47 +0100 Subject: [PATCH 04/85] otioload: Improve error reporting --- app/task/project/loadotio/loadotio.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index 202b71a5e..d6c2802fb 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -61,7 +61,8 @@ bool LoadOTIOTask::Run() auto root = OTIO::SerializableObjectWithMetadata::from_json_file(GetFilename().toStdString(), &es); if (es.outcome != OTIO::ErrorStatus::Outcome::OK) { - SetError(tr("Failed to load OpenTimelineIO from file \"%1\"").arg(GetFilename())); + SetError(tr("Failed to load OpenTimelineIO from file \"%1\" \n\nOpenTimelineIO Error:\n\n%2") + .arg(GetFilename(), QString::fromStdString(es.full_description))); return false; } From 70b42926273da6a1811ad469d323d29eb5418345 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 09:36:32 -0700 Subject: [PATCH 05/85] exportdialog: fix bug where files would always be imported after export Fixes #2069 --- 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 64573cf4f..386fa2e1b 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -391,7 +391,7 @@ void ExportDialog::ExportFinished() // If this task was cancelled, we stay open so the user can potentially queue another export } else { // Accept this dialog and close - if (import_file_after_export_) { + if (import_file_after_export_->isEnabled() && import_file_after_export_->isChecked()) { QString filename = filename_edit_->text().trimmed(); emit RequestImportFile(filename); } From b71a976175e7bab5c2ac7693ecc4ce539506f2f2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 10:01:17 -0700 Subject: [PATCH 06/85] core: save recent projects list on change rather than on exit --- app/core.cpp | 24 +++++++++++++++--------- app/core.h | 2 ++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index a4aaef6fc..c13e83009 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -198,15 +198,6 @@ void Core::Stop() // Save Config Config::Save(); - // Save recently opened projects - { - QFile recent_projects_file(GetRecentProjectsFilePath()); - if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) { - recent_projects_file.write(recent_projects_.join('\n').toUtf8()); - recent_projects_file.close(); - } - } - ProjectSerializer::Destroy(); ConformManager::DestroyInstance(); @@ -292,6 +283,7 @@ void Core::SetSelectedTransitionObject(const QString &obj) void Core::ClearOpenRecentList() { recent_projects_.clear(); + SaveRecentProjectsList(); emit OpenRecentListChanged(); } @@ -960,6 +952,16 @@ bool Core::RevertProjectInternal(Project *p, bool by_opening_existing) return false; } +void Core::SaveRecentProjectsList() +{ + // Save recently opened projects + QFile recent_projects_file(GetRecentProjectsFilePath()); + if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) { + recent_projects_file.write(recent_projects_.join('\n').toUtf8()); + recent_projects_file.close(); + } +} + void Core::SaveAutorecovery() { if (OLIVE_CONFIG("AutorecoveryEnabled").toBool()) { @@ -1372,6 +1374,8 @@ void Core::PushRecentlyOpenedProject(const QString& s) } } + SaveRecentProjectsList(); + emit OpenRecentListChanged(); } @@ -1523,6 +1527,8 @@ void Core::OpenProjectFromRecentList(int index) QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { recent_projects_.removeAt(index); + SaveRecentProjectsList(); + emit OpenRecentListChanged(); } } diff --git a/app/core.h b/app/core.h index 0d9d8df0d..8f19c056c 100644 --- a/app/core.h +++ b/app/core.h @@ -559,6 +559,8 @@ private: bool RevertProjectInternal(Project *p, bool by_opening_existing); + void SaveRecentProjectsList(); + /** * @brief Internal main window object */ From 86a5a0fcfdc9115f0f504723d428bebf77fcb030 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 10:01:43 -0700 Subject: [PATCH 07/85] timeline: commit imported footage before connecting Fixes #2065 --- app/widget/timelinewidget/timelinewidget.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 82c22b2a8..943242ee8 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -795,15 +795,20 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange ProjectImportTask task(GetConnectedNode()->project()->root(), {filename}); task.Start(); - MultiUndoCommand *import_command = task.GetCommand(); + auto subimport_command = task.GetCommand(); if (task.GetImportedFootage().empty()) { qCritical() << "Failed to import recorded audio file" << filename; + delete subimport_command; } else { - import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index()); - } + subimport_command->redo_now(); - Core::instance()->undo_stack()->pushIfHasChildren(import_command); + auto import_command = new MultiUndoCommand(); + import_command->add_child(subimport_command); + + import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index()); + Core::instance()->undo_stack()->pushIfHasChildren(import_command); + } } void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord) From 5096278e600f95dd294f6765c31e8811b3c018ac Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 12:15:36 -0700 Subject: [PATCH 08/85] nodes: use much smarter time transform function Fixes #2042 --- app/node/node.cpp | 90 ++++++++----------- app/node/node.h | 12 ++- app/widget/curvewidget/curveview.cpp | 2 +- app/widget/keyframeview/keyframeview.cpp | 6 +- .../nodeparamviewkeyframecontrol.cpp | 4 +- .../nodeparamviewwidgetbridge.cpp | 2 +- .../timebased/timebasedviewselectionmanager.h | 2 +- app/widget/timebased/timebasedwidget.cpp | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 2 +- app/widget/timetarget/timetarget.cpp | 14 +-- app/widget/timetarget/timetarget.h | 4 +- app/widget/viewer/viewerdisplay.cpp | 2 +- 12 files changed, 64 insertions(+), 78 deletions(-) diff --git a/app/node/node.cpp b/app/node/node.cpp index 1d8cb3f4c..d843a460a 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1712,42 +1712,33 @@ QString Node::GetCategoryName(const CategoryID &c) return tr("Uncategorized"); } -QVector Node::TransformTimeTo(const TimeRange &time, Node *target, bool input_dir) +TimeRange Node::TransformTimeTo(TimeRange time, Node *target, TransformTimeDirection dir, int path_index) { - QVector paths_found; + Node *from = this; + Node *to = target; - if (input_dir) { - // If this input is connected, traverse it to see if we stumble across the specified `node` - for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) { - TimeRange input_adjustment = InputTimeAdjustment(it->first.input(), it->first.element(), time); - Node* connected = it->second; + if (dir == kTransformTowardsInput) { + std::swap(from, to); + } - if (connected == target) { - // We found the target, no need to keep traversing - if (!paths_found.contains(input_adjustment)) { - paths_found.append(input_adjustment); - } - } else { - // We did NOT find the target, traverse this - paths_found.append(connected->TransformTimeTo(input_adjustment, target, input_dir)); + std::list path = FindPath(from, to, path_index); + + if (!path.empty()) { + if (dir == kTransformTowardsInput) { + for (auto it=path.crbegin(); it!=path.crend(); it++) { + const NodeInput &i = (*it); + time = i.node()->InputTimeAdjustment(i.input(), i.element(), time); } - } - } else { - // If this input is connected, traverse it to see if we stumble across the specified `node` - foreach (const OutputConnection& conn, output_connections_) { - Node* connected_node = conn.second.node(); - - TimeRange output_adjustment = connected_node->OutputTimeAdjustment(conn.second.input(), conn.second.element(), time); - - if (connected_node == target) { - paths_found.append(output_adjustment); - } else { - paths_found.append(connected_node->TransformTimeTo(output_adjustment, target, input_dir)); + } else { + // Traverse in output direction + for (auto it=path.cbegin(); it!=path.cend(); it++) { + const NodeInput &i = (*it); + time = i.node()->OutputTimeAdjustment(i.input(), i.element(), time); } } } - return paths_found; + return time; } QVariant Node::PtrToValue(void *ptr) @@ -2011,46 +2002,39 @@ void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QV } } -void FindPathInternal(std::list &vec, Node *to, int &path_index) +bool FindPathInternal(std::list &vec, Node *from, Node *to, int &path_index) { - Node *from = vec.back(); + for (auto it=from->output_connections().cbegin(); it!=from->output_connections().cend(); it++) { + const NodeInput &next = it->second; - for (auto it=from->input_connections().cbegin(); it!=from->input_connections().cend(); it++) { - vec.push_back(it->second); - if (it->second == to) { - // Found a path, determine if it's the one we want + vec.push_back(next); + + if (next.node() == to) { + // Found a path! Determine if it's the index we want if (path_index == 0) { // It is! - break; + return true; } else { + // It isn't, keep looking... path_index--; } } - // Recurse to see if we can find it here - FindPathInternal(vec, to, path_index); - if (vec.back() == to) { - // Found through recursion - break; - } else { - // Must not be available through this path - vec.pop_back(); + if (FindPathInternal(vec, next.node(), to, path_index)) { + return true; } + + vec.pop_back(); } + + return false; } -std::list Node::FindPath(Node *from, Node *to, int path_index) +std::list Node::FindPath(Node *from, Node *to, int path_index) { - std::list v; + std::list v; - v.push_back(from); - - FindPathInternal(v, to, path_index); - - if (v.size() == 1) { - // Failed to find path, return empty list - v.pop_back(); - } + FindPathInternal(v, from, to, path_index); return v; } diff --git a/app/node/node.h b/app/node/node.h index 0dbfdaa3e..253788cbe 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -844,10 +844,15 @@ public: */ static QString GetCategoryName(const CategoryID &c); + enum TransformTimeDirection { + kTransformTowardsInput, + kTransformTowardsOutput + }; + /** * @brief Transforms time from this node through the connections it takes to get to the specified node */ - QVector TransformTimeTo(const TimeRange& time, Node* target, bool input_dir); + TimeRange TransformTimeTo(TimeRange time, Node* target, TransformTimeDirection dir, int path_index); /** * @brief Find nodes of a certain type that this Node takes inputs from @@ -1147,7 +1152,10 @@ public: static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); - static std::list FindPath(Node *from, Node *to, int path_index = 0); + /** + * @brief Find path starting at `from` that outputs to arrive at `to` + */ + static std::list FindPath(Node *from, Node *to, int path_index); static const QString kEnabledInput; diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 10300b7ff..6f105a22d 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -519,7 +519,7 @@ void CurveView::ZoomToFitInternal(bool selected_only) rational transformed_time = GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), - false); + Node::kTransformTowardsOutput); qreal key_y = GetUnscaledItemYFromKeyframeValue(key); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 9fead0fe1..abc2b1015 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -228,7 +228,7 @@ bool KeyframeView::Paste(std::function find_node_functi for (NodeKeyframe *key : it.value()) { // Adjust sequence time to node's time rational t = key->time() - min; - t = GetAdjustedTime(GetTimeTarget(), node_with_id, t, true); + t = GetAdjustedTime(GetTimeTarget(), node_with_id, t, Node::kTransformTowardsInput); key->set_time(t); if (NodeKeyframe *existing = node_with_id->GetKeyframeAtTimeOnTrack(key->input(), key->time(), key->track(), key->element())) { @@ -491,12 +491,12 @@ void KeyframeView::DeselectKeyframe(NodeKeyframe *key) rational KeyframeView::GetUnadjustedKeyframeTime(NodeKeyframe *key, const rational &time) { - return GetAdjustedTime(GetTimeTarget(), key->parent(), time, true); + return GetAdjustedTime(GetTimeTarget(), key->parent(), time, Node::kTransformTowardsInput); } rational KeyframeView::GetAdjustedKeyframeTime(NodeKeyframe *key) { - return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); + return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), Node::kTransformTowardsOutput); } double KeyframeView::GetKeyframeSceneX(NodeKeyframe *key) diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp index c431966a8..ea29b036e 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp @@ -122,12 +122,12 @@ void NodeParamViewKeyframeControl::SetButtonsEnabled(bool e) rational NodeParamViewKeyframeControl::GetCurrentTimeAsNodeTime() const { - return GetAdjustedTime(GetTimeTarget(), input_.node(), time_, true); + return GetAdjustedTime(GetTimeTarget(), input_.node(), time_, Node::kTransformTowardsInput); } rational NodeParamViewKeyframeControl::ConvertToViewerTime(const rational &r) const { - return GetAdjustedTime(input_.node(), GetTimeTarget(), r, false); + return GetAdjustedTime(input_.node(), GetTimeTarget(), r, Node::kTransformTowardsOutput); } void NodeParamViewKeyframeControl::ShowButtonsFromKeyframeEnable(bool e) diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index dd33a7659..b5b4db823 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -528,7 +528,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() rational NodeParamViewWidgetBridge::GetCurrentTimeAsNodeTime() const { - return GetAdjustedTime(GetTimeTarget(), GetInnerInput().node(), time_, true); + return GetAdjustedTime(GetTimeTarget(), GetInnerInput().node(), time_, Node::kTransformTowardsInput); } void NodeParamViewWidgetBridge::SetTimebase(const rational& timebase) diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 6d9e6962f..f9ac6faf9 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -216,7 +216,7 @@ public: if (time_target_) { for (size_t i=0; iGetAdjustedTime(parent, time_target_->GetTimeTarget(), copy[i], false); + copy[i] = time_target_->GetAdjustedTime(parent, time_target_->GetTimeTarget(), copy[i], Node::kTransformTowardsOutput); } } } diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index a4beb3bfa..b97521b47 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -868,7 +868,7 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration rational time = key->time(); if (const TimeTargetObject *target = GetKeyframeTimeTarget()) { if (Node *parent = key->parent()) { - time = target->GetAdjustedTime(parent, target->GetTimeTarget(), time, false); + time = target->GetAdjustedTime(parent, target->GetTimeTarget(), time, Node::kTransformTowardsOutput); } } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 943242ee8..1bfea94a2 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1470,7 +1470,7 @@ void TimelineWidget::CacheClipsInOut() for (Block *b : qAsConst(selected_blocks_)) { if (ClipBlock *clip = dynamic_cast(b)) { if (Node *connected = clip->GetConnectedOutput(clip->kBufferIn)) { - TimeRange adjusted = tto.GetAdjustedTime(this->sequence(), connected, r, true); + TimeRange adjusted = tto.GetAdjustedTime(this->sequence(), connected, r, Node::kTransformTowardsInput); clip->RequestInvalidatedFromConnected(true, adjusted); } } diff --git a/app/widget/timetarget/timetarget.cpp b/app/widget/timetarget/timetarget.cpp index 8662999e5..3c98aa7df 100644 --- a/app/widget/timetarget/timetarget.cpp +++ b/app/widget/timetarget/timetarget.cpp @@ -45,28 +45,22 @@ void TimeTargetObject::SetPathIndex(int index) path_index_ = index; } -rational TimeTargetObject::GetAdjustedTime(Node* from, Node* to, const rational &r, bool input_direction) const +rational TimeTargetObject::GetAdjustedTime(Node* from, Node* to, const rational &r, Node::TransformTimeDirection dir) const { if (!from || !to) { return r; } - return GetAdjustedTime(from, to, TimeRange(r, r), input_direction).in(); + return GetAdjustedTime(from, to, TimeRange(r, r), dir).in(); } -TimeRange TimeTargetObject::GetAdjustedTime(Node* from, Node* to, const TimeRange &r, bool input_direction) const +TimeRange TimeTargetObject::GetAdjustedTime(Node* from, Node* to, const TimeRange &r, Node::TransformTimeDirection dir) const { if (!from || !to) { return r; } - QVector adjusted = from->TransformTimeTo(r, to, input_direction); - - if (adjusted.isEmpty()) { - return r; - } - - return adjusted.at(path_index_); + return from->TransformTimeTo(r, to, dir, path_index_); } /*int TimeTargetObject::GetNumberOfPathAdjustments(Node* from, NodeParam::Type direction) const diff --git a/app/widget/timetarget/timetarget.h b/app/widget/timetarget/timetarget.h index f1b2aa285..c561f58b0 100644 --- a/app/widget/timetarget/timetarget.h +++ b/app/widget/timetarget/timetarget.h @@ -35,8 +35,8 @@ public: void SetPathIndex(int index); - rational GetAdjustedTime(Node* from, Node* to, const rational& r, bool input_direction) const; - TimeRange GetAdjustedTime(Node* from, Node* to, const TimeRange& r, bool input_direction) const; + rational GetAdjustedTime(Node* from, Node* to, const rational& r, Node::TransformTimeDirection dir) const; + TimeRange GetAdjustedTime(Node* from, Node* to, const TimeRange& r, Node::TransformTimeDirection dir) const; //int GetNumberOfPathAdjustments(Node* from, NodeParam::Type direction) const; diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index e7ec99fb5..aea700d51 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -596,7 +596,7 @@ void ViewerDisplayWidget::DrawTextWithCrudeShadow(QPainter *painter, const QRect rational ViewerDisplayWidget::GetGizmoTime() { - return GetAdjustedTime(GetTimeTarget(), gizmos_, time_, true); + return GetAdjustedTime(GetTimeTarget(), gizmos_, time_, Node::kTransformTowardsInput); } bool ViewerDisplayWidget::IsHandDrag(QMouseEvent *event) const From 6ce80f4469166264d94d30b7041f9ebfde5dae05 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 12:31:42 -0700 Subject: [PATCH 09/85] multicamnode: use combobox for current source instead of int --- app/node/input/multicam/multicamnode.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 2ff887069..e34220906 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -13,11 +13,7 @@ const QString MultiCamNode::kSequenceTypeInput = QStringLiteral("sequence_type_i MultiCamNode::MultiCamNode() { - AddInput(kCurrentInput, NodeValue::kInt, InputFlags(kInputFlagStatic)); - - // Make current index start at 1 instead of 0 - SetInputProperty(kCurrentInput, QStringLiteral("offset"), 1); - SetInputProperty(kCurrentInput, QStringLiteral("min"), 0); + AddInput(kCurrentInput, NodeValue::kCombo, InputFlags(kInputFlagStatic)); AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1); @@ -135,6 +131,18 @@ void MultiCamNode::Retranslate() SetInputName(kSequenceInput, tr("Sequence")); SetInputName(kSequenceTypeInput, tr("Sequence Type")); SetComboBoxStrings(kSequenceTypeInput, {tr("Video"), tr("Audio")}); + + QStringList names; + int name_count = GetSourceCount(); + names.reserve(name_count); + for (int i=0; iName(); + } + names.append(tr("%1: %2").arg(QString::number(i+1), src_name)); + } + SetComboBoxStrings(kCurrentInput, names); } int MultiCamNode::GetSourceCount() const From 93f88042d1a29c1ab5ac1d2604ac89d73a9d03c0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 19:52:08 -0700 Subject: [PATCH 10/85] math: use operation by default for name --- app/node/math/math/math.cpp | 18 +++++++++++++----- app/node/math/math/mathbase.cpp | 13 +++++++++++++ app/node/math/math/mathbase.h | 2 ++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index 4ddbe2e12..82b07e6e8 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -44,6 +44,14 @@ MathNode::MathNode() QString MathNode::Name() const { + // Default to naming after the operation + if (parent()) { + QString op_name = GetOperationName(GetOperation()); + if (!op_name.isEmpty()) { + return op_name; + } + } + return tr("Math"); } @@ -70,12 +78,12 @@ void MathNode::Retranslate() SetInputName(kParamAIn, tr("Value")); SetInputName(kParamBIn, tr("Value")); - QStringList operations = {tr("Add"), - tr("Subtract"), - tr("Multiply"), - tr("Divide"), + QStringList operations = {GetOperationName(kOpAdd), + GetOperationName(kOpSubtract), + GetOperationName(kOpMultiply), + GetOperationName(kOpDivide), QString(), - tr("Power")}; + GetOperationName(kOpPower)}; SetComboBoxStrings(kMethodIn, operations); } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 4ed0ea5f6..7063c2691 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -167,6 +167,19 @@ void MathNodeBase::PushVector(NodeValueTable *output, olive::NodeValue::Type typ } } +QString MathNodeBase::GetOperationName(Operation o) +{ + switch (o) { + case kOpAdd: return tr("Add"); + case kOpSubtract: return tr("Subtract"); + case kOpMultiply: return tr("Multiply"); + case kOpDivide: return tr("Divide"); + case kOpPower: return tr("Power"); + } + + return QString(); +} + void MathNodeBase::PerformAllOnFloatBuffer(Operation operation, float *a, float b, int start, int end) { for (int j=start;j Date: Sun, 23 Oct 2022 20:23:48 -0700 Subject: [PATCH 11/85] nodes: allow two texture inputs in opacity --- app/node/effect/opacity/opacityeffect.cpp | 13 ++++++++--- app/shaders/opacity_rgb.frag | 27 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 app/shaders/opacity_rgb.frag diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index cc0f2d2c0..ee58fb632 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -39,15 +39,22 @@ void OpacityEffect::Retranslate() ShaderCode OpacityEffect::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(request) - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag")); + if (request.id == QStringLiteral("rgbmult")) { + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity_rgb.frag")); + } else { + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag")); + } } void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // If there's no texture, no need to run an operation if (TexturePtr tex = value[kTextureInput].toTexture()) { - if (!qFuzzyCompare(value[kValueInput].toDouble(), 1.0)) { + if (TexturePtr opacity_tex = value[kValueInput].toTexture()) { + ShaderJob job(value); + job.SetShaderID(QStringLiteral("rgbmult")); + table->Push(NodeValue::kTexture, tex->toJob(job), this); + } else if (!qFuzzyCompare(value[kValueInput].toDouble(), 1.0)) { table->Push(NodeValue::kTexture, tex->toJob(ShaderJob(value)), this); } else { // 1.0 float is a no-op, so just push the texture diff --git a/app/shaders/opacity_rgb.frag b/app/shaders/opacity_rgb.frag new file mode 100644 index 000000000..6d529023e --- /dev/null +++ b/app/shaders/opacity_rgb.frag @@ -0,0 +1,27 @@ +// Inputs +uniform sampler2D tex_in; +uniform sampler2D opacity_in; + +// Input texture coordinate +in vec2 ove_texcoord; +out vec4 frag_color; + +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +void main() { + vec4 value = texture(opacity_in, ove_texcoord); + float v = rgb2hsv(value.rgb).b; + + vec4 c = texture(tex_in, ove_texcoord); + c *= v; + frag_color = c; +} From d1545745d8b697b43f691006c806437973fbbbf5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:56:04 -0700 Subject: [PATCH 12/85] various: moved time from viewer UI to viewer node --- app/core.cpp | 18 ++-- app/core.h | 4 +- app/dialog/export/codec/imagesection.cpp | 1 + app/dialog/export/codec/imagesection.h | 3 + app/dialog/export/export.cpp | 8 +- app/dialog/export/export.h | 8 -- app/dialog/export/exportvideotab.cpp | 1 + app/dialog/export/exportvideotab.h | 2 + app/node/output/viewer/viewer.cpp | 6 ++ app/node/output/viewer/viewer.h | 8 ++ app/panel/timebased/timebased.cpp | 14 --- app/panel/timebased/timebased.h | 8 -- app/widget/curvewidget/curvewidget.cpp | 19 +--- app/widget/curvewidget/curvewidget.h | 5 +- app/widget/keyframeview/keyframeview.cpp | 8 +- app/widget/keyframeview/keyframeview.h | 2 +- app/widget/multicam/multicamwidget.cpp | 6 +- app/widget/nodeparamview/nodeparamview.cpp | 27 +----- app/widget/nodeparamview/nodeparamview.h | 7 +- .../nodeparamviewconnectedlabel.cpp | 31 ++++--- .../nodeparamviewconnectedlabel.h | 4 +- .../nodeparamview/nodeparamviewcontext.cpp | 9 +- .../nodeparamview/nodeparamviewcontext.h | 4 +- .../nodeparamview/nodeparamviewitem.cpp | 23 +---- app/widget/nodeparamview/nodeparamviewitem.h | 18 +--- .../nodeparamviewkeyframecontrol.cpp | 22 ++--- .../nodeparamviewkeyframecontrol.h | 9 +- .../nodeparamviewwidgetbridge.cpp | 25 ++++-- .../nodeparamview/nodeparamviewwidgetbridge.h | 8 +- app/widget/timebased/timebasedview.cpp | 41 +++++---- app/widget/timebased/timebasedview.h | 14 ++- app/widget/timebased/timebasedwidget.cpp | 88 ++++++++----------- app/widget/timebased/timebasedwidget.h | 12 +-- app/widget/timelinewidget/timelinewidget.cpp | 54 +++++------- app/widget/timelinewidget/timelinewidget.h | 3 - .../timelinewidget/view/timelineview.cpp | 13 +-- app/widget/timeruler/seekablewidget.cpp | 8 +- app/widget/timeruler/timeruler.cpp | 2 +- app/widget/timetarget/timetarget.cpp | 13 ++- app/widget/timetarget/timetarget.h | 12 +-- app/widget/viewer/audiowaveformview.cpp | 2 +- app/widget/viewer/footageviewer.cpp | 19 ---- app/widget/viewer/footageviewer.h | 7 -- app/widget/viewer/viewer.cpp | 46 +++++----- app/widget/viewer/viewer.h | 2 +- app/window/mainwindow/mainwindow.cpp | 27 +----- app/window/mainwindow/mainwindow.h | 6 -- 47 files changed, 259 insertions(+), 418 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index c13e83009..78d9b1721 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -379,11 +379,8 @@ void Core::DialogProjectPropertiesShow() void Core::DialogExportShow() { - ViewerOutput* viewer; - rational time; - - if (GetSequenceToExport(&viewer, &time)) { - OpenExportDialogForViewer(viewer, time, false); + if (ViewerOutput* viewer = GetSequenceToExport()) { + OpenExportDialogForViewer(viewer, false); } } @@ -850,7 +847,7 @@ void Core::SaveProjectInternal(Project* project, const QString& override_filenam psm->deleteLater(); } -bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time) +ViewerOutput *Core::GetSequenceToExport() { // First try the most recently focused time based window TimeBasedPanel* time_panel = PanelManager::instance()->MostRecentlyFocused(); @@ -868,9 +865,7 @@ bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time) tr("This Sequence is empty. There is nothing to export."), QMessageBox::Ok); } else { - *viewer = time_panel->GetConnectedViewer(); - *time = time_panel->GetTime(); - return true; + return time_panel->GetConnectedViewer(); } } else { QMessageBox::critical(main_window_, @@ -879,7 +874,7 @@ bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time) QMessageBox::Ok); } - return false; + return nullptr; } QString Core::GetAutoRecoveryIndexFilename() @@ -1249,10 +1244,9 @@ void Core::OpenNodeInViewer(ViewerOutput *viewer) main_window_->OpenNodeInViewer(viewer); } -void Core::OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image) +void Core::OpenExportDialogForViewer(ViewerOutput *viewer, bool start_still_image) { ExportDialog* ed = new ExportDialog(viewer, start_still_image, main_window_); - ed->SetTime(time); connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); ed->open(); connect(ed, &ExportDialog::RequestImportFile, this, &Core::ImportSingleFile); diff --git a/app/core.h b/app/core.h index 8f19c056c..19ffb07dc 100644 --- a/app/core.h +++ b/app/core.h @@ -317,7 +317,7 @@ public: void OpenNodeInViewer(ViewerOutput* viewer); - void OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image); + void OpenExportDialogForViewer(ViewerOutput *viewer, bool start_still_image); public slots: /** @@ -551,7 +551,7 @@ private: /** * @brief Retrieves the currently most active sequence for exporting */ - bool GetSequenceToExport(ViewerOutput **viewer, rational *time); + ViewerOutput *GetSequenceToExport(); static QString GetAutoRecoveryIndexFilename(); diff --git a/app/dialog/export/codec/imagesection.cpp b/app/dialog/export/codec/imagesection.cpp index b2bf50fd8..b87ca0a79 100644 --- a/app/dialog/export/codec/imagesection.cpp +++ b/app/dialog/export/codec/imagesection.cpp @@ -47,6 +47,7 @@ ImageSection::ImageSection(QWidget* parent) : frame_slider_->SetMinimum(0); frame_slider_->SetValue(0); frame_slider_->SetDisplayType(RationalSlider::kTime); + connect(frame_slider_, &RationalSlider::ValueChanged, this, &ImageSection::TimeChanged); layout->addWidget(frame_slider_, row, 1); } diff --git a/app/dialog/export/codec/imagesection.h b/app/dialog/export/codec/imagesection.h index 3575ea5a2..b1d6cac61 100644 --- a/app/dialog/export/codec/imagesection.h +++ b/app/dialog/export/codec/imagesection.h @@ -59,6 +59,9 @@ public: frame_slider_->SetValue(t); } +signals: + void TimeChanged(const rational &t); + private: QCheckBox* image_sequence_checkbox_; diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 386fa2e1b..cf1319488 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -148,6 +148,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi video_tab_ = new ExportVideoTab(color_manager_); AddPreferencesTab(video_tab_, tr("Video")); + // Set video tab time and make connections + connect(viewer_node, &ViewerOutput::PlayheadChanged, video_tab_, &ExportVideoTab::SetTime); + connect(video_tab_, &ExportVideoTab::TimeChanged, viewer_node, &ViewerOutput::SetPlayhead); + video_tab_->SetTime(viewer_node->GetPlayhead()); + audio_tab_ = new ExportAudioTab(); AddPreferencesTab(audio_tab_, tr("Audio")); @@ -206,7 +211,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi 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_); splitter->addWidget(preview_area); @@ -437,7 +441,7 @@ void ExportDialog::PresetComboBoxChanged() if (loading_presets_) { return; } - + QComboBox *c = static_cast(sender()); int preset_number = c->currentData().toInt(); diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 594b16380..b0afc83ac 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -51,14 +51,6 @@ public: rational GetSelectedTimebase() const; void SetSelectedTimebase(const rational &r); - void SetTime(const rational &time) - { - preview_viewer_->SetAudioScrubbingEnabled(false); - preview_viewer_->SetTime(time); - video_tab_->SetTime(time); - preview_viewer_->SetAudioScrubbingEnabled(true); - } - EncodingParams GenerateParams() const; void SetParams(const EncodingParams &e); diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 56ecfcdfc..579aa746c 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -184,6 +184,7 @@ QWidget *ExportVideoTab::SetupCodecSection() codec_layout->addWidget(codec_stack_, row, 0, 1, 2); image_section_ = new ImageSection(); + connect(image_section_, &ImageSection::TimeChanged, this, &ExportVideoTab::TimeChanged); codec_stack_->addWidget(image_section_); h264_section_ = new H264Section(); diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index 1a23837f6..c401721e9 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -166,6 +166,8 @@ signals: void ImageSequenceCheckBoxChanged(bool e); + void TimeChanged(const rational &time); + private: QWidget* SetupResolutionSection(); QWidget* SetupColorSection(); diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 50d90f0b3..3f1f3e683 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -316,6 +316,12 @@ void ViewerOutput::VerifyLength() } } +void ViewerOutput::SetPlayhead(const rational &t) +{ + playhead_ = t; + emit PlayheadChanged(t); +} + void ViewerOutput::InputConnectedEvent(const QString &input, int element, Node *output) { if (input == kTextureInput) { diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index b99ebc30b..93df8d46b 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -97,6 +97,8 @@ public: } } + const rational &GetPlayhead() { return playhead_; } + void SetVideoParams(const VideoParams &video, int index = 0) { SetStandardValue(kVideoParamsInput, QVariant::fromValue(video), index); @@ -219,9 +221,13 @@ signals: void ConnectedWaveformChanged(); + void PlayheadChanged(const rational &t); + public slots: void VerifyLength(); + void SetPlayhead(const rational &t); + protected: virtual void InputConnectedEvent(const QString &input, int element, Node *output) override; @@ -253,6 +259,8 @@ private: bool waveform_requests_enabled_; + rational playhead_; + }; } diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index b10220dab..ae28aff69 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -29,11 +29,6 @@ TimeBasedPanel::TimeBasedPanel(const QString &object_name, QWidget *parent) : { } -rational TimeBasedPanel::GetTime() -{ - return widget_->GetTime(); -} - const rational& TimeBasedPanel::timebase() { return widget_->timebase(); @@ -74,11 +69,6 @@ void TimeBasedPanel::SetTimebase(const rational &timebase) widget_->SetTimebase(timebase); } -void TimeBasedPanel::SetTime(const rational &time) -{ - widget_->SetTime(time); -} - void TimeBasedPanel::GoToPrevCut() { widget_->GoToPrevCut(); @@ -122,16 +112,12 @@ void TimeBasedPanel::ConnectViewerNode(ViewerOutput *node) void TimeBasedPanel::SetTimeBasedWidget(TimeBasedWidget *widget) { if (widget_) { - disconnect(widget_, &TimeBasedWidget::TimeChanged, this, &TimeBasedPanel::TimeChanged); - disconnect(widget_, &TimeBasedWidget::TimebaseChanged, this, &TimeBasedPanel::TimebaseChanged); disconnect(widget_, &TimeBasedWidget::ConnectedNodeChanged, this, &TimeBasedPanel::ConnectedNodeChanged); } widget_ = widget; if (widget_) { - connect(widget_, &TimeBasedWidget::TimeChanged, this, &TimeBasedPanel::TimeChanged); - connect(widget_, &TimeBasedWidget::TimebaseChanged, this, &TimeBasedPanel::TimebaseChanged); connect(widget_, &TimeBasedWidget::ConnectedNodeChanged, this, &TimeBasedPanel::ConnectedNodeChanged); } diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index 4101e6b09..02f1a5b12 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -39,8 +39,6 @@ public: ConnectViewerNode(nullptr); } - rational GetTime(); - // Get the timebase of this panels widget const rational& timebase(); @@ -111,13 +109,7 @@ public: public slots: void SetTimebase(const rational& timebase); - void SetTime(const rational &time); - signals: - void TimeChanged(const rational& time); - - void TimebaseChanged(const rational& timebase); - void PlayPauseRequested(); void PlayInToOutRequested(); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index d85e6d168..4079abd4e 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -59,7 +59,6 @@ CurveWidget::CurveWidget(QWidget *parent) : QHBoxLayout* top_controls = new QHBoxLayout(); key_control_ = new NodeParamViewKeyframeControl(false); - connect(key_control_, &NodeParamViewKeyframeControl::RequestSetTime, this, &CurveWidget::SetTimeAndSignal); top_controls->addWidget(key_control_); top_controls->addStretch(); @@ -99,7 +98,6 @@ CurveWidget::CurveWidget(QWidget *parent) : layout->addLayout(ruler_view_layout); // Connect ruler and view together - connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal); connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged); connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale); connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged); @@ -193,14 +191,6 @@ void CurveWidget::SetNodes(const QVector &nodes) } } -void CurveWidget::TimeChangedEvent(const rational &time) -{ - super::TimeChangedEvent(time); - - view_->SetTime(time); - UpdateBridgeTime(time); -} - void CurveWidget::TimebaseChangedEvent(const rational &timebase) { super::TimebaseChangedEvent(timebase); @@ -215,7 +205,7 @@ void CurveWidget::ScaleChangedEvent(const double &scale) view_->SetScale(scale); } -void CurveWidget::TimeTargetChangedEvent(Node *target) +void CurveWidget::TimeTargetChangedEvent(ViewerOutput *target) { TimeTargetObject::TimeTargetChangedEvent(target); @@ -228,6 +218,8 @@ void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n) { super::ConnectedNodeChangeEvent(n); + key_control_->SetTimeTarget(n); + SetTimeTarget(n); } @@ -252,11 +244,6 @@ void CurveWidget::SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type) hold_button_->setChecked(type == NodeKeyframe::kHold); } -void CurveWidget::UpdateBridgeTime(const rational &time) -{ - key_control_->SetTime(time); -} - void CurveWidget::ConnectInput(Node *node, const QString &input, int element) { if (element == -1 && node->InputIsArray(input)) { diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index eef0ab62f..5ade8eb31 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -65,11 +65,10 @@ public slots: void SetNodes(const QVector &nodes); protected: - virtual void TimeChangedEvent(const rational &) override; virtual void TimebaseChangedEvent(const rational &) override; virtual void ScaleChangedEvent(const double &) override; - virtual void TimeTargetChangedEvent(Node* target) override; + virtual void TimeTargetChangedEvent(ViewerOutput *target) override; virtual void ConnectedNodeChangeEvent(ViewerOutput* n) override; @@ -95,8 +94,6 @@ private: void SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type); - void UpdateBridgeTime(const rational &time); - void ConnectInput(Node *node, const QString &input, int element); void ConnectInputInternal(Node *node, const QString &input, int element); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index abc2b1015..0811f416f 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -204,6 +204,10 @@ bool KeyframeView::CopySelected(bool cut) bool KeyframeView::Paste(std::function find_node_function) { + if (!GetViewerNode()) { + return false; + } + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("keyframes")); if (res == ProjectSerializer::kSuccess) { const ProjectSerializer::SerializedKeyframes &keys = res.GetLoadData().keyframes; @@ -216,7 +220,7 @@ bool KeyframeView::Paste(std::function find_node_functi min = std::min(min, key->time()); } } - min -= GetTime(); + min -= GetViewerNode()->GetPlayhead(); for (auto it=keys.cbegin(); it!=keys.cend(); it++) { const QString &paste_id = it.key(); @@ -454,7 +458,7 @@ void KeyframeView::ScaleChangedEvent(const double &scale) Redraw(); } -void KeyframeView::TimeTargetChangedEvent(Node *target) +void KeyframeView::TimeTargetChangedEvent(ViewerOutput *v) { Redraw(); } diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 0ab17f254..541b81583 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -101,7 +101,7 @@ protected: virtual void ScaleChangedEvent(const double& scale) override; - virtual void TimeTargetChangedEvent(Node*) override; + virtual void TimeTargetChangedEvent(ViewerOutput *v) override; virtual void TimebaseChangedEvent(const rational &timebase) override; diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 5156fd4ed..a11495339 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -74,7 +74,7 @@ void MulticamWidget::SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode void MulticamWidget::SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time) { - if (time.isNaN() || time == GetTime()) { + if (time.isNaN() || !GetConnectedNode() || time == GetConnectedNode()->GetPlayhead()) { SetMulticamNodeInternal(viewer, n, clip); play_queue_.clear(); } else { @@ -125,13 +125,13 @@ void MulticamWidget::Switch(int source, bool split_clip) BlockSplitPreservingLinksCommand *split = nullptr; - if (clip_ && split_clip && clip_->in() < GetTime() && clip_->out() > GetTime()) { + if (clip_ && split_clip && clip_->in() < GetConnectedNode()->GetPlayhead() && clip_->out() > GetConnectedNode()->GetPlayhead()) { QVector blocks; blocks.append(clip_); blocks.append(clip_->block_links()); - split = new BlockSplitPreservingLinksCommand(blocks, {GetTime()}); + split = new BlockSplitPreservingLinksCommand(blocks, {GetConnectedNode()->GetPlayhead()}); split->redo_now(); command->add_child(split); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b0d6f1618..bd6d8594e 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -126,9 +126,6 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : keyframe_area_layout->addWidget(keyframe_view_); // Connect ruler and keyframe view together - connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); - 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); @@ -361,19 +358,6 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase) foreach (NodeParamViewContext* ctx, context_items_) { ctx->SetTimebase(timebase); } - - UpdateItemTime(GetTime()); -} - -void NodeParamView::TimeChangedEvent(const rational &time) -{ - super::TimeChangedEvent(time); - - if (keyframe_view_) { - keyframe_view_->SetTime(time); - } - - UpdateItemTime(time); } void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) @@ -390,7 +374,7 @@ void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) time_target_ = n; } -Node *NodeParamView::GetTimeTarget() const +ViewerOutput *NodeParamView::GetTimeTarget() const { return time_target_; } @@ -683,13 +667,6 @@ bool NodeParamView::Paste(QWidget *parent, std::function(co return true; } -void NodeParamView::UpdateItemTime(const rational &time) -{ - foreach (NodeParamViewContext* item, context_items_) { - item->SetTime(time); - } -} - void NodeParamView::QueueKeyframePositionUpdate() { QMetaObject::invokeMethod(this, &NodeParamView::UpdateElementY, Qt::QueuedConnection); @@ -735,7 +712,6 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) NodeParamViewItem* item = new NodeParamViewItem(n, IsGroupMode() ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context->GetDockArea()); - connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::SelectNodeFromConnectedLink); connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode); connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::InputCheckBoxChanged); @@ -745,7 +721,6 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) item->SetContext(ctx); item->SetTimeTarget(GetTimeTarget()); item->SetTimebase(timebase()); - item->SetTime(GetTime()); context->AddNode(item); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 1b78055a9..f9ecc5fb5 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -48,7 +48,7 @@ public: void CloseContextsBelongingToProject(Project *p); - Node* GetTimeTarget() const; + ViewerOutput *GetTimeTarget() const; void DeleteSelected(); @@ -95,7 +95,6 @@ protected: virtual void ScaleChangedEvent(const double &) override; virtual void TimebaseChangedEvent(const rational&) override; - virtual void TimeChangedEvent(const rational &time) override; virtual void ConnectedNodeChangeEvent(ViewerOutput* n) override; @@ -115,8 +114,6 @@ protected: } private: - void UpdateItemTime(const rational &time); - void QueueKeyframePositionUpdate(); void AddContext(Node *context); @@ -159,7 +156,7 @@ private: NodeParamViewItem* focused_node_; QVector selected_nodes_; - Node *time_target_; + ViewerOutput *time_target_; QVector contexts_; QVector current_contexts_; diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index 937037219..c9454e4a4 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -34,7 +34,8 @@ namespace olive { NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, QWidget *parent) : QWidget(parent), input_(input), - connected_node_(nullptr) + connected_node_(nullptr), + viewer_(nullptr) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); @@ -85,6 +86,20 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, connect(collapse_btn, &CollapseButton::toggled, this, &NodeParamViewConnectedLabel::SetValueTreeVisible); } +void NodeParamViewConnectedLabel::SetViewerNode(ViewerOutput *viewer) +{ + if (viewer_) { + disconnect(viewer_, &ViewerOutput::PlayheadChanged, this, &NodeParamViewConnectedLabel::UpdateValueTree); + } + + viewer_ = viewer; + + if (viewer_) { + connect(viewer_, &ViewerOutput::PlayheadChanged, this, &NodeParamViewConnectedLabel::UpdateValueTree); + UpdateValueTree(); + } +} + void NodeParamViewConnectedLabel::CreateTree() { // Set up table area @@ -92,15 +107,6 @@ void NodeParamViewConnectedLabel::CreateTree() layout()->addWidget(value_tree_); } -void NodeParamViewConnectedLabel::SetTime(const rational &time) -{ - time_ = time; - - if (value_tree_ && value_tree_->isVisible()) { - UpdateValueTree(); - } -} - void NodeParamViewConnectedLabel::InputConnected(Node *output, const NodeInput& input) { if (input_ != input) { @@ -159,8 +165,8 @@ void NodeParamViewConnectedLabel::UpdateLabel() void NodeParamViewConnectedLabel::UpdateValueTree() { - if (value_tree_) { - value_tree_->SetNode(input_, time_); + if (value_tree_ && viewer_ && value_tree_->isVisible()) { + value_tree_->SetNode(input_, viewer_->GetPlayhead()); } } @@ -173,6 +179,7 @@ void NodeParamViewConnectedLabel::SetValueTreeVisible(bool e) if (e) { if (!value_tree_) { CreateTree(); + value_tree_->setVisible(true); } UpdateValueTree(); diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h index 9a7a81ee7..16a7428af 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h @@ -32,7 +32,7 @@ class NodeParamViewConnectedLabel : public QWidget { public: NodeParamViewConnectedLabel(const NodeInput& input, QWidget* parent = nullptr); - void SetTime(const rational &time); + void SetViewerNode(ViewerOutput *viewer); signals: void RequestSelectNode(Node *n); @@ -61,7 +61,7 @@ private: NodeValueTree *value_tree_; - rational time_; + ViewerOutput *viewer_; private slots: void SetValueTreeVisible(bool e); diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp index 1d3a22253..a2438bddd 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.cpp +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -113,20 +113,13 @@ void NodeParamViewContext::SetTimebase(const rational &timebase) } } -void NodeParamViewContext::SetTimeTarget(Node *n) +void NodeParamViewContext::SetTimeTarget(ViewerOutput *n) { foreach (NodeParamViewItem* item, items_) { item->SetTimeTarget(n); } } -void NodeParamViewContext::SetTime(const rational &time) -{ - foreach (NodeParamViewItem* item, items_) { - item->SetTime(time); - } -} - void NodeParamViewContext::SetEffectType(Track::Type type) { type_ = type; diff --git a/app/widget/nodeparamview/nodeparamviewcontext.h b/app/widget/nodeparamview/nodeparamviewcontext.h index 11574e2a5..b89db696a 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.h +++ b/app/widget/nodeparamview/nodeparamviewcontext.h @@ -60,9 +60,7 @@ public: void SetTimebase(const rational &timebase); - void SetTimeTarget(Node *n); - - void SetTime(const rational &time); + void SetTimeTarget(ViewerOutput *n); void SetEffectType(Track::Type type); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index a27cbe824..7d0f6dc7a 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -90,12 +90,10 @@ void NodeParamViewItem::RecreateBody() body_ = new NodeParamViewItemBody(node_, create_checkboxes_, this); connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); - connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); connect(body_, &NodeParamViewItemBody::RequestEditTextInViewer, this, &NodeParamViewItem::RequestEditTextInViewer); body_->Retranslate(); - body_->SetTime(time_); body_->SetTimebase(timebase_); SetBody(body_); } @@ -259,7 +257,6 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const ui_objects.key_control = new NodeParamViewKeyframeControl(this); ui_objects.key_control->SetInput(resolved); layout->addWidget(ui_objects.key_control, row, kKeyControlColumn); - connect(ui_objects.key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItemBody::RequestSetTime); } input_ui_map_.insert(input_ref, ui_objects); @@ -269,31 +266,17 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const } } -void NodeParamViewItemBody::SetTimeTarget(Node *target) +void NodeParamViewItemBody::SetTimeTarget(ViewerOutput *target) { foreach (const InputUI& ui_obj, input_ui_map_) { // Only keyframable inputs have a key control widget if (ui_obj.key_control) { ui_obj.key_control->SetTimeTarget(target); } - - ui_obj.widget_bridge->SetTimeTarget(target); - } -} - -void NodeParamViewItemBody::SetTime(const rational &time) -{ - foreach (const InputUI& ui_obj, input_ui_map_) { - // Only keyframable inputs have a key control widget - if (ui_obj.key_control) { - ui_obj.key_control->SetTime(time); - } - if (ui_obj.connected_label) { - ui_obj.connected_label->SetTime(time); + ui_obj.connected_label->SetViewerNode(target); } - - ui_obj.widget_bridge->SetTime(time); + ui_obj.widget_bridge->SetTimeTarget(target); } } diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 8ec20bb6a..eb52e44d3 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -51,9 +51,7 @@ class NodeParamViewItemBody : public QWidget { public: NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); - void SetTimeTarget(Node* target); - - void SetTime(const rational& time); + void SetTimeTarget(ViewerOutput *target); void Retranslate(); @@ -65,8 +63,6 @@ public: void SetInputChecked(const NodeInput &input, bool e); signals: - void RequestSetTime(const rational& time); - void RequestSelectNode(Node *node); void ArrayExpandedChanged(bool e); @@ -167,18 +163,11 @@ class NodeParamViewItem : public NodeParamViewItemBase public: NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); - void SetTimeTarget(Node* target) + void SetTimeTarget(ViewerOutput* target) { body_->SetTimeTarget(target); } - void SetTime(const rational& time) - { - time_ = time; - - body_->SetTime(time_); - } - void SetTimebase(const rational& timebase) { timebase_ = timebase; @@ -216,8 +205,6 @@ public: } signals: - void RequestSetTime(const rational& time); - void RequestSelectNode(Node *node); void ArrayExpandedChanged(bool e); @@ -238,7 +225,6 @@ private: Node *ctx_; - rational time_; rational timebase_; KeyframeView::NodeConnections keyframe_connections_; diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp index ea29b036e..85bc9f465 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp @@ -96,10 +96,14 @@ void NodeParamViewKeyframeControl::SetInput(const NodeInput& input) } } -void NodeParamViewKeyframeControl::SetTime(const rational &time) +void NodeParamViewKeyframeControl::TimeTargetDisconnectEvent(ViewerOutput *v) { - time_ = time; + disconnect(v, &ViewerOutput::PlayheadChanged, this, &NodeParamViewKeyframeControl::UpdateState); +} +void NodeParamViewKeyframeControl::TimeTargetConnectEvent(ViewerOutput *v) +{ + connect(v, &ViewerOutput::PlayheadChanged, this, &NodeParamViewKeyframeControl::UpdateState); UpdateState(); } @@ -122,7 +126,7 @@ void NodeParamViewKeyframeControl::SetButtonsEnabled(bool e) rational NodeParamViewKeyframeControl::GetCurrentTimeAsNodeTime() const { - return GetAdjustedTime(GetTimeTarget(), input_.node(), time_, Node::kTransformTowardsInput); + return GetAdjustedTime(GetTimeTarget(), input_.node(), GetTimeTarget()->GetPlayhead(), Node::kTransformTowardsInput); } rational NodeParamViewKeyframeControl::ConvertToViewerTime(const rational &r) const @@ -177,7 +181,7 @@ void NodeParamViewKeyframeControl::ToggleKeyframe(bool e) void NodeParamViewKeyframeControl::UpdateState() { - if (!input_.IsValid() || !input_.IsKeyframing()) { + if (!input_.IsValid() || !input_.IsKeyframing() || !GetTimeTarget()) { return; } @@ -197,10 +201,9 @@ void NodeParamViewKeyframeControl::GoToPreviousKey() NodeKeyframe* previous_key = input_.node()->GetClosestKeyframeBeforeTime(input_, node_time); - if (previous_key) { + if (previous_key && GetTimeTarget()) { rational key_time = ConvertToViewerTime(previous_key->time()); - - emit RequestSetTime(key_time); + GetTimeTarget()->SetPlayhead(key_time); } } @@ -210,10 +213,9 @@ void NodeParamViewKeyframeControl::GoToNextKey() NodeKeyframe* next_key = input_.node()->GetClosestKeyframeAfterTime(input_, node_time); - if (next_key) { + if (next_key && GetTimeTarget()) { rational key_time = ConvertToViewerTime(next_key->time()); - - emit RequestSetTime(key_time); + GetTimeTarget()->SetPlayhead(key_time); } } diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h index 0f8ffab7d..44b017b56 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h @@ -46,10 +46,9 @@ public: void SetInput(const NodeInput& input); - void SetTime(const rational& time); - -signals: - void RequestSetTime(const rational& time); +protected: + virtual void TimeTargetDisconnectEvent(ViewerOutput *v) override; + virtual void TimeTargetConnectEvent(ViewerOutput *v) override; private: QPushButton* CreateNewToolButton(const QIcon &icon) const; @@ -67,8 +66,6 @@ private: NodeInput input_; - rational time_; - private slots: void ShowButtonsFromKeyframeEnable(bool e); diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index b5b4db823..5a519fd03 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -57,13 +57,6 @@ NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput input, QObject *p CreateWidgets(); } -void NodeParamViewWidgetBridge::SetTime(const rational &time) -{ - time_ = time; - - UpdateWidgetValues(); -} - int GetSliderCount(NodeValue::Type type) { return NodeValue::get_number_of_keyframe_tracks(type); @@ -528,7 +521,11 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() rational NodeParamViewWidgetBridge::GetCurrentTimeAsNodeTime() const { - return GetAdjustedTime(GetTimeTarget(), GetInnerInput().node(), time_, Node::kTransformTowardsInput); + if (GetTimeTarget()) { + return GetAdjustedTime(GetTimeTarget(), GetInnerInput().node(), GetTimeTarget()->GetPlayhead(), Node::kTransformTowardsInput); + } else { + return 0; + } } void NodeParamViewWidgetBridge::SetTimebase(const rational& timebase) @@ -538,11 +535,21 @@ void NodeParamViewWidgetBridge::SetTimebase(const rational& timebase) } } +void NodeParamViewWidgetBridge::TimeTargetDisconnectEvent(ViewerOutput *v) +{ + disconnect(v, &ViewerOutput::PlayheadChanged, this, &NodeParamViewWidgetBridge::UpdateWidgetValues); +} + +void NodeParamViewWidgetBridge::TimeTargetConnectEvent(ViewerOutput *v) +{ + connect(v, &ViewerOutput::PlayheadChanged, this, &NodeParamViewWidgetBridge::UpdateWidgetValues); +} + void NodeParamViewWidgetBridge::InputValueChanged(const NodeInput &input, const TimeRange &range) { if (GetInnerInput() == input && !dragger_.IsStarted() - && range.in() <= time_ && range.out() >= time_) { + && range.in() <= GetTimeTarget()->GetPlayhead() && range.out() >= GetTimeTarget()->GetPlayhead()) { // We'll need to update the widgets because the values have changed on our current time UpdateWidgetValues(); } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h index 772febbde..457c618c6 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h @@ -42,8 +42,6 @@ class NodeParamViewWidgetBridge : public QObject, public TimeTargetObject public: NodeParamViewWidgetBridge(NodeInput input, QObject* parent); - void SetTime(const rational& time); - const QVector& widgets() const { return widgets_; @@ -59,6 +57,10 @@ signals: void RequestEditTextInViewer(); +protected: + virtual void TimeTargetDisconnectEvent(ViewerOutput *v) override; + virtual void TimeTargetConnectEvent(ViewerOutput *v) override; + private: void CreateWidgets(); @@ -102,8 +104,6 @@ private: QVector widgets_; - rational time_; - NodeInputDragger dragger_; NodeParamViewScrollBlocker scroll_filter_; diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index 310df8866..869bc6d78 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -38,7 +38,8 @@ TimeBasedView::TimeBasedView(QWidget *parent) : snapped_(false), snap_service_(nullptr), y_axis_enabled_(false), - y_scale_(1.0) + y_scale_(1.0), + viewer_(nullptr) { // Sets scene to our scene setScene(&scene_); @@ -142,12 +143,17 @@ void TimeBasedView::SetYScale(const double &y_scale) } } -void TimeBasedView::SetTime(const rational &time) +void TimeBasedView::SetViewerNode(ViewerOutput *v) { - playhead_ = time; + if (viewer_) { + disconnect(viewer_, &ViewerOutput::PlayheadChanged, viewport(), static_cast(&TimeBasedView::update)); + } - // Force redraw for playhead - viewport()->update(); + viewer_ = v; + + if (viewer_) { + connect(viewer_, &ViewerOutput::PlayheadChanged, viewport(), static_cast(&TimeBasedView::update)); + } } void TimeBasedView::drawForeground(QPainter *painter, const QRectF &rect) @@ -203,20 +209,21 @@ bool TimeBasedView::PlayheadMove(QMouseEvent *event) return false; } - QPointF scene_pos = mapToScene(event->pos()); - rational mouse_time = qMax(rational(0), SceneToTime(scene_pos.x())); + if (viewer_) { + QPointF scene_pos = mapToScene(event->pos()); + rational mouse_time = qMax(rational(0), SceneToTime(scene_pos.x())); - if (Core::instance()->snapping() && snap_service_) { - rational movement; + if (Core::instance()->snapping() && snap_service_) { + rational movement; - snap_service_->SnapPoint({mouse_time}, &movement, TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToPlayhead); + snap_service_->SnapPoint({mouse_time}, &movement, TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToPlayhead); - mouse_time += movement; + mouse_time += movement; + } + + viewer_->SetPlayhead(mouse_time); } - SetTime(mouse_time); - emit TimeChanged(mouse_time); - return true; } @@ -237,7 +244,11 @@ bool TimeBasedView::PlayheadRelease(QMouseEvent*) qreal TimeBasedView::GetPlayheadX() { - return TimeToScene(playhead_); + if (viewer_) { + return TimeToScene(viewer_->GetPlayhead()); + } else { + return 0; + } } void TimeBasedView::SetEndTime(const rational &length) diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index 9f708553a..c6459ca52 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -45,8 +45,6 @@ public: return snapped_; } - const rational &GetTime() const { return playhead_; } - TimeBasedWidget *GetSnapService() const { return snap_service_; } void SetSnapService(TimeBasedWidget* service) { snap_service_ = service; } @@ -62,9 +60,11 @@ public: virtual void SelectionManagerSelectEvent(void *obj){} virtual void SelectionManagerDeselectEvent(void *obj){} -public slots: - void SetTime(const rational &time); + ViewerOutput *GetViewerNode() const { return viewer_; } + void SetViewerNode(ViewerOutput *v); + +public slots: void SetEndTime(const rational& length); /** @@ -73,8 +73,6 @@ public slots: void UpdateSceneRect(); signals: - void TimeChanged(const rational& time); - void ScaleChanged(double scale); protected: @@ -109,8 +107,6 @@ protected: private: qreal GetPlayheadX(); - rational playhead_; - double playhead_scene_left_; double playhead_scene_right_; @@ -129,6 +125,8 @@ private: double y_scale_; + ViewerOutput *viewer_; + }; } diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index b97521b47..f5bb38378 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -44,7 +44,7 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu markers_(nullptr) { ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this); - ConnectTimelineView(ruler_, true); + ConnectTimelineView(ruler_); ruler()->SetSnapService(this); connect(ruler(), &TimeRuler::DragReleased, this, static_cast(&TimeBasedWidget::StopCatchUpScrollTimer)); @@ -68,11 +68,6 @@ void TimeBasedWidget::SetScaleAndCenterOnPlayhead(const double &scale) QTimer::singleShot(0, this, &TimeBasedWidget::CenterScrollOnPlayhead); } -const rational &TimeBasedWidget::GetTime() const -{ - return ruler_->GetTime(); -} - ViewerOutput *TimeBasedWidget::GetConnectedNode() const { return viewer_node_; @@ -96,6 +91,7 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) // Disconnect length changed signal disconnect(old, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll); disconnect(old, &ViewerOutput::RemovedFromGraph, this, &TimeBasedWidget::ConnectedNodeRemovedFromGraph); + disconnect(old, &ViewerOutput::PlayheadChanged, this, &TimeBasedWidget::PlayheadTimeChanged); // Disconnect rate change signals if they were connected disconnect(old, &ViewerOutput::FrameRateChanged, this, &TimeBasedWidget::AutoUpdateTimebase); @@ -110,12 +106,16 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) } // Call derivatives + for (TimeBasedView *view : timeline_views_) { + view->SetViewerNode(viewer_node_); + } ConnectedNodeChangeEvent(viewer_node_); if (viewer_node_) { // Connect length changed signal connect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll); connect(viewer_node_, &ViewerOutput::RemovedFromGraph, this, &TimeBasedWidget::ConnectedNodeRemovedFromGraph); + connect(viewer_node_, &ViewerOutput::PlayheadChanged, this, &TimeBasedWidget::PlayheadTimeChanged); // Connect ruler and scrollbar to timeline points ConnectWorkArea(viewer_node_->GetWorkArea()); @@ -209,12 +209,16 @@ void TimeBasedWidget::ScrollBarResizeMoved(int movement) void TimeBasedWidget::PageScrollToPlayhead() { - PageScrollInternal(qRound(TimeToScene(GetTime())), true); + if (GetConnectedNode()) { + PageScrollInternal(qRound(TimeToScene(GetConnectedNode()->GetPlayhead())), true); + } } void TimeBasedWidget::CatchUpScrollToPlayhead() { - CatchUpScrollToPoint(qRound(TimeToScene(GetTime()))); + if (GetConnectedNode()) { + CatchUpScrollToPoint(qRound(TimeToScene(GetConnectedNode()->GetPlayhead()))); + } } void TimeBasedWidget::CatchUpScrollToPoint(int point) @@ -300,12 +304,8 @@ void TimeBasedWidget::resizeEvent(QResizeEvent *event) UpdateMaximumScroll(); } -void TimeBasedWidget::ConnectTimelineView(TimeBasedView *base, bool connect_time_change_event) +void TimeBasedWidget::ConnectTimelineView(TimeBasedView *base) { - if (connect_time_change_event) { - connect(base, &TimeBasedView::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal); - } - timeline_views_.append(base); } @@ -345,7 +345,7 @@ void TimeBasedWidget::StopCatchUpScrollTimer(QScrollBar *b) } } -void TimeBasedWidget::SetTime(const rational &time) +void TimeBasedWidget::PlayheadTimeChanged(const rational &time) { if (UserIsDraggingPlayhead()) { // If the user is dragging the playhead, we will simply nudge over and not use autoscroll rules. @@ -365,8 +365,6 @@ void TimeBasedWidget::SetTime(const rational &time) } } - ruler_->SetTime(time); - TimeChangedEvent(time); } @@ -400,7 +398,7 @@ void TimeBasedWidget::GoToPrevCut() return; } - if (GetTime().isNull()) { + if (GetConnectedNode()->GetPlayhead().isNull()) { return; } @@ -410,7 +408,7 @@ void TimeBasedWidget::GoToPrevCut() rational this_track_closest_cut = 0; for (Block* block : track->Blocks()) { - if (block->out() < GetTime()) { + if (block->out() < GetConnectedNode()->GetPlayhead()) { this_track_closest_cut = block->out(); } else { break; @@ -420,7 +418,7 @@ void TimeBasedWidget::GoToPrevCut() closest_cut = qMax(closest_cut, this_track_closest_cut); } - SetTimeAndSignal(closest_cut); + GetConnectedNode()->SetPlayhead(closest_cut); } void TimeBasedWidget::GoToNextCut() @@ -437,12 +435,12 @@ void TimeBasedWidget::GoToNextCut() for (Track* track : sequence->GetTracks()) { rational this_track_closest_cut = track->track_length(); - if (this_track_closest_cut <= GetTime()) { + if (this_track_closest_cut <= GetConnectedNode()->GetPlayhead()) { this_track_closest_cut = RATIONAL_MAX; } for (Block* block : track->Blocks()) { - if (block->in() > GetTime()) { + if (block->in() > GetConnectedNode()->GetPlayhead()) { this_track_closest_cut = block->in(); break; } @@ -452,57 +450,51 @@ void TimeBasedWidget::GoToNextCut() } if (closest_cut < RATIONAL_MAX) { - SetTimeAndSignal(closest_cut); + GetConnectedNode()->SetPlayhead(closest_cut); } } void TimeBasedWidget::GoToStart() { if (viewer_node_) { - SetTimeAndSignal(0); + viewer_node_->SetPlayhead(0); } } void TimeBasedWidget::PrevFrame() { if (viewer_node_) { - rational proposed_time = Timecode::snap_time_to_timebase(GetTime() - timebase(), timebase(), Timecode::kCeil); - if (proposed_time == GetTime()) { + rational proposed_time = Timecode::snap_time_to_timebase(GetConnectedNode()->GetPlayhead() - timebase(), timebase(), Timecode::kCeil); + if (proposed_time == GetConnectedNode()->GetPlayhead()) { // Catch rounding error, assume this time is snapped and just subtract a timebase proposed_time -= timebase(); } - SetTimeAndSignal(qMax(rational(0), proposed_time)); + viewer_node_->SetPlayhead(qMax(rational(0), proposed_time)); } } void TimeBasedWidget::NextFrame() { if (viewer_node_) { - rational proposed_time = Timecode::snap_time_to_timebase(GetTime() + timebase(), timebase(), Timecode::kFloor); - if (proposed_time == GetTime()) { + rational proposed_time = Timecode::snap_time_to_timebase(GetConnectedNode()->GetPlayhead() + timebase(), timebase(), Timecode::kFloor); + if (proposed_time == GetConnectedNode()->GetPlayhead()) { // Catch rounding error, assume this time is snapped and just add a timebase proposed_time += timebase(); } - SetTimeAndSignal(proposed_time); + viewer_node_->SetPlayhead(proposed_time); } } void TimeBasedWidget::GoToEnd() { if (viewer_node_) { - SetTimeAndSignal(viewer_node_->GetLength()); + viewer_node_->SetPlayhead(viewer_node_->GetLength()); } } -void TimeBasedWidget::SetTimeAndSignal(const rational &t) -{ - SetTime(t); - emit TimeChanged(t); -} - void TimeBasedWidget::CenterScrollOnPlayhead() { - scrollbar_->setValue(qRound(TimeToScene(ruler_->GetTime())) - scrollbar_->width()/2); + scrollbar_->setValue(qRound(TimeToScene(GetConnectedNode()->GetPlayhead())) - scrollbar_->width()/2); } void TimeBasedWidget::SetAutoSetTimebase(bool e) @@ -603,10 +595,6 @@ void TimeBasedWidget::PageScrollInternal(int screen_position, bool whole_page_sc bool TimeBasedWidget::UserIsDraggingPlayhead() const { - if (ruler_->IsDraggingPlayhead()) { - return true; - } - foreach (TimeBasedView* view, timeline_views_) { if (view->IsDraggingPlayhead()) { return true; @@ -618,12 +606,12 @@ bool TimeBasedWidget::UserIsDraggingPlayhead() const void TimeBasedWidget::SetInAtPlayhead() { - SetPoint(Timeline::kTrimIn, GetTime()); + SetPoint(Timeline::kTrimIn, GetConnectedNode()->GetPlayhead()); } void TimeBasedWidget::SetOutAtPlayhead() { - SetPoint(Timeline::kTrimOut, GetTime()); + SetPoint(Timeline::kTrimOut, GetConnectedNode()->GetPlayhead()); } void TimeBasedWidget::ResetIn() @@ -653,14 +641,14 @@ void TimeBasedWidget::SetMarker() TimelineMarkerList *markers = GetConnectedNode()->GetMarkers(); - if (TimelineMarker *existing = markers->GetMarkerAtTime(GetTime())) { + if (TimelineMarker *existing = markers->GetMarkerAtTime(GetConnectedNode()->GetPlayhead())) { // We already have a marker here, so pop open the edit dialog MarkerPropertiesDialog mpd({existing}, timebase(), this); mpd.exec(); } else { // Create a new marker and place it here int color; - if (TimelineMarker *closest = markers->GetClosestMarkerToTime(GetTime())) { + if (TimelineMarker *closest = markers->GetClosestMarkerToTime(GetConnectedNode()->GetPlayhead())) { // Copy color of closest marker to this time color = closest->color(); } else { @@ -668,7 +656,7 @@ void TimeBasedWidget::SetMarker() color = OLIVE_CONFIG("MarkerColor").toInt(); } - TimelineMarker *marker = new TimelineMarker(color, TimeRange(GetTime(), GetTime())); + TimelineMarker *marker = new TimelineMarker(color, TimeRange(GetConnectedNode()->GetPlayhead(), GetConnectedNode()->GetPlayhead())); if (OLIVE_CONFIG("SetNameWithMarker").toBool()) { MarkerPropertiesDialog mpd({marker}, timebase(), this); @@ -704,8 +692,6 @@ void TimeBasedWidget::ToggleShowAll() w = timeline_views_.first()->width(); } - - toggle_show_all_old_scale_ = GetScale(); toggle_show_all_old_scroll_ = scrollbar_->value(); @@ -721,7 +707,7 @@ void TimeBasedWidget::GoToIn() { if (GetConnectedNode()) { if (GetConnectedNode()->GetWorkArea()->enabled()) { - SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->in()); + GetConnectedNode()->SetPlayhead(GetConnectedNode()->GetWorkArea()->in()); } else { GoToStart(); } @@ -732,7 +718,7 @@ void TimeBasedWidget::GoToOut() { if (GetConnectedNode()) { if (GetConnectedNode()->GetWorkArea()->enabled()) { - SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->out()); + GetConnectedNode()->SetPlayhead(GetConnectedNode()->GetWorkArea()->out()); } else { GoToEnd(); } @@ -787,7 +773,7 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration std::vector potential_snaps; if (snap_points & kSnapToPlayhead) { - rational playhead_abs_time = GetTime(); + rational playhead_abs_time = GetConnectedNode()->GetPlayhead(); qreal playhead_pos = TimeToScene(playhead_abs_time); AttemptSnap(potential_snaps, screen_pt, playhead_pos, start_times, playhead_abs_time); } diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 347c079c7..9e152c7d3 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -41,8 +41,6 @@ class TimeBasedWidget : public TimelineScaledWidget public: TimeBasedWidget(bool ruler_text_visible = true, bool ruler_cache_status_visible = false, QWidget* parent = nullptr); - const rational &GetTime() const; - void ZoomIn(); void ZoomOut(); @@ -84,8 +82,6 @@ public: virtual bool Paste(); public slots: - void SetTime(const rational &time); - void SetTimebase(const rational& timebase); void SetScale(const double& scale); @@ -144,7 +140,7 @@ protected: virtual void resizeEvent(QResizeEvent *event) override; - void ConnectTimelineView(TimeBasedView* base, bool connect_time_change_event = true); + void ConnectTimelineView(TimeBasedView* base); void PassWheelEventsToScrollBar(QObject* object); @@ -172,16 +168,12 @@ 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&); - void TimebaseChanged(const rational&); void ConnectedNodeChanged(ViewerOutput* old, ViewerOutput* now); @@ -271,6 +263,8 @@ private slots: void ConnectedNodeRemovedFromGraph(); + void PlayheadTimeChanged(const rational &time); + }; } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 1bfea94a2..ed8dd84d8 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -83,7 +83,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) : timecode_label_->SetDisplayType(RationalSlider::kTime); timecode_label_->setVisible(false); timecode_label_->SetMinimum(0); - connect(timecode_label_, &RationalSlider::ValueChanged, this, &TimelineWidget::SetTimeAndSignal); ruler_and_time_layout->addWidget(timecode_label_); ruler_and_time_layout->addWidget(ruler()); @@ -144,11 +143,10 @@ TimelineWidget::TimelineWidget(QWidget *parent) : view_splitter_->addWidget(tview); - ConnectTimelineView(view, false); + ConnectTimelineView(view); connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); connect(view, &TimelineView::ScaleChanged, this, &TimelineWidget::SetScale); - connect(view, &TimelineView::TimeChanged, this, &TimelineWidget::SetTimeAndSignal); connect(view, &TimelineView::customContextMenuRequested, this, &TimelineWidget::ShowContextMenu); connect(scrollbar(), &QScrollBar::valueChanged, view->horizontalScrollBar(), &QScrollBar::setValue); connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, scrollbar(), &QScrollBar::setValue); @@ -244,15 +242,6 @@ void TimelineWidget::resizeEvent(QResizeEvent *event) UpdateTimecodeWidthFromSplitters(views_.first()->splitter()); } -void TimelineWidget::TimeChangedEvent(const rational &time) -{ - super::TimeChangedEvent(time); - - SetViewTime(time); - - timecode_label_->SetValue(time); -} - void TimelineWidget::ScaleChangedEvent(const double &scale) { super::ScaleChangedEvent(scale); @@ -271,6 +260,9 @@ void TimelineWidget::ConnectNodeEvent(ViewerOutput *n) connect(s, &Sequence::FrameRateChanged, this, &TimelineWidget::FrameRateChanged); connect(s, &Sequence::SampleRateChanged, this, &TimelineWidget::SampleRateChanged); + connect(timecode_label_, &RationalSlider::ValueChanged, s, &Sequence::SetPlayhead); + connect(s, &Sequence::PlayheadChanged, timecode_label_, &RationalSlider::SetValue); + ruler()->SetPlaybackCache(n->video_frame_cache()); SetTimebase(n->GetVideoParams().frame_rate_as_time_base()); @@ -301,6 +293,8 @@ void TimelineWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(s, &Sequence::FrameRateChanged, this, &TimelineWidget::FrameRateChanged); disconnect(s, &Sequence::SampleRateChanged, this, &TimelineWidget::SampleRateChanged); + disconnect(timecode_label_, &RationalSlider::ValueChanged, s, &Sequence::SetPlayhead); + DeselectAll(); foreach (Track* track, s->GetTracks()) { @@ -371,7 +365,7 @@ void TimelineWidget::SplitAtPlayhead() return; } - const rational &playhead_time = GetTime(); + const rational &playhead_time = GetConnectedNode()->GetPlayhead(); QVector selected_blocks = GetSelectedBlocks(); @@ -513,7 +507,7 @@ void TimelineWidget::DeleteSelected(bool ripple) ClearGhosts(); if (ripple_command && ripple_command->HasCommands() && new_playhead != RATIONAL_MAX) { - SetTimeAndSignal(new_playhead); + GetConnectedNode()->SetPlayhead(new_playhead); } } @@ -544,14 +538,14 @@ void TimelineWidget::DecreaseTrackHeight() void TimelineWidget::InsertFootageAtPlayhead(const QVector& footage) { auto command = new MultiUndoCommand(); - import_tool_->PlaceAt(footage, GetTime(), true, command); + import_tool_->PlaceAt(footage, GetConnectedNode()->GetPlayhead(), true, command); Core::instance()->undo_stack()->push(command); } void TimelineWidget::OverwriteFootageAtPlayhead(const QVector &footage) { auto command = new MultiUndoCommand(); - import_tool_->PlaceAt(footage, GetTime(), false, command); + import_tool_->PlaceAt(footage, GetConnectedNode()->GetPlayhead(), false, command); Core::instance()->undo_stack()->push(command); } @@ -714,7 +708,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) false)); if (ripple) { - SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->in()); + GetConnectedNode()->SetPlayhead(GetConnectedNode()->GetWorkArea()->in()); } Core::instance()->undo_stack()->push(command); @@ -1325,14 +1319,6 @@ void TimelineWidget::SetUseAudioTimeUnits(bool use) UpdateViewTimebases(); } -void TimelineWidget::SetViewTime(const rational &time) -{ - for (int i=0;iview()->SetTime(time); - } -} - void TimelineWidget::ToolChanged() { HideSnaps(); @@ -1618,7 +1604,7 @@ void TimelineWidget::MoveToPlayheadInternal(bool out) } foreach (Block *b, selected_blocks_) { - rational shift_amt = GetTime() - earliest_pts.value(b->track()); + rational shift_amt = GetConnectedNode()->GetPlayhead() - earliest_pts.value(b->track()); rational new_in = b->in() + shift_amt; bool can_shift = true; @@ -1641,7 +1627,7 @@ void TimelineWidget::MoveToPlayheadInternal(bool out) // Shift selections TimelineWidgetSelections new_sel = GetSelections(); for (auto it=new_sel.begin(); it!=new_sel.end(); it++) { - rational track_adj = GetTime() - earliest_pts.value(GetTrackFromReference(it.key()), GetTime()); + rational track_adj = GetConnectedNode()->GetPlayhead() - earliest_pts.value(GetTrackFromReference(it.key()), GetConnectedNode()->GetPlayhead()); if (!track_adj.isNull()) { it.value().shift(track_adj); } @@ -1794,7 +1780,7 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode) return; } - rational playhead_time = GetTime(); + rational playhead_time = GetConnectedNode()->GetPlayhead(); QVector tracks = GetEditToInfo(playhead_time, mode); @@ -1842,15 +1828,15 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode) // If we rippled, ump to where new cut is if applicable if (mode == Timeline::kTrimIn) { - SetTimeAndSignal(closest_point_to_playhead); - } else if (mode == Timeline::kTrimOut && closest_point_to_playhead == GetTime()) { - SetTimeAndSignal(playhead_time); + GetConnectedNode()->SetPlayhead(closest_point_to_playhead); + } else if (mode == Timeline::kTrimOut && closest_point_to_playhead == GetConnectedNode()->GetPlayhead()) { + GetConnectedNode()->SetPlayhead(playhead_time); } } void TimelineWidget::EditTo(Timeline::MovementMode mode) { - const rational playhead_time = GetTime(); + const rational playhead_time = GetConnectedNode()->GetPlayhead(); // Get list of unlocked tracks QVector tracks = GetEditToInfo(playhead_time, mode); @@ -1952,10 +1938,10 @@ bool TimelineWidget::PasteInternal(bool insert) command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n)); } - rational paste_start = GetTime(); + rational paste_start = GetConnectedNode()->GetPlayhead(); if (insert) { - rational paste_end = GetTime(); + rational paste_end = paste_start; for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { rational length = static_cast(it.key())->length(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 5161f6d55..74cf4d896 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -291,7 +291,6 @@ protected: virtual void resizeEvent(QResizeEvent *event) override; virtual void TimebaseChangedEvent(const rational &) override; - virtual void TimeChangedEvent(const rational &time) override; virtual void ScaleChangedEvent(const double &) override; virtual void ConnectNodeEvent(ViewerOutput* n) override; @@ -417,8 +416,6 @@ private slots: void SetUseAudioTimeUnits(bool use); - void SetViewTime(const rational &time); - void ToolChanged(); void AddableObjectChanged(); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 5bad687f6..bcba5631d 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -64,15 +64,8 @@ void TimelineView::mousePressEvent(QMouseEvent *event) QPointF scene_pos = mapToScene(event->pos()); for (auto it=clip_marker_rects_.cbegin(); it!=clip_marker_rects_.cend(); it++) { if (it.value().contains(scene_pos)) { - QObject *p = this->parent(); - while (p) { - if (TimelineWidget *timeline = dynamic_cast(p)) { - timeline->SetTime(it.key()->time().in()); - break; - } - - p = p->parent(); - } + GetViewerNode()->SetPlayhead(it.key()->time().in()); + break; } } @@ -353,7 +346,7 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) int x = TimeToScene(recording_coord_.GetFrame()); painter->drawRect(x, GetTrackY(recording_coord_.GetTrack().index()), - TimeToScene(GetTime()) - x, GetTrackHeight(recording_coord_.GetTrack().index())); + TimeToScene(GetViewerNode()->GetPlayhead()) - x, GetTrackHeight(recording_coord_.GetTrack().index())); } // Draw standard TimelineViewBase things (such as playhead) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 8dc21dace..750e1b67c 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -149,7 +149,7 @@ bool SeekableWidget::PasteMarkers() for (auto it=markers.cbegin(); it!=markers.cend(); it++) { min = std::min(min, (*it)->time().in()); } - min -= GetTime(); + min -= GetViewerNode()->GetPlayhead(); for (auto it=markers.cbegin(); it!=markers.cend(); it++) { TimelineMarker *m = *it; @@ -379,10 +379,8 @@ void SeekableWidget::SeekToScenePoint(qreal scene) playhead_time += movement; } - if (playhead_time != GetTime()) { - SetTime(playhead_time); - - emit TimeChanged(playhead_time); + if (playhead_time != GetViewerNode()->GetPlayhead()) { + GetViewerNode()->SetPlayhead(playhead_time); } } diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index fab83276a..603706a82 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -267,7 +267,7 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) } // Draw the playhead if it's on screen at the moment - int playhead_pos = TimeToScene(GetTime()); + int playhead_pos = TimeToScene(GetViewerNode()->GetPlayhead()); p->setPen(Qt::NoPen); p->setBrush(PLAYHEAD_COLOR); DrawPlayhead(p, playhead_pos, line_bottom); diff --git a/app/widget/timetarget/timetarget.cpp b/app/widget/timetarget/timetarget.cpp index 3c98aa7df..3262b7a0a 100644 --- a/app/widget/timetarget/timetarget.cpp +++ b/app/widget/timetarget/timetarget.cpp @@ -28,16 +28,23 @@ TimeTargetObject::TimeTargetObject() : { } -Node *TimeTargetObject::GetTimeTarget() const +ViewerOutput *TimeTargetObject::GetTimeTarget() const { return time_target_; } -void TimeTargetObject::SetTimeTarget(Node *target) +void TimeTargetObject::SetTimeTarget(ViewerOutput *target) { - time_target_ = target; + if (time_target_) { + TimeTargetDisconnectEvent(time_target_); + } + time_target_ = target; TimeTargetChangedEvent(time_target_); + + if (time_target_) { + TimeTargetConnectEvent(time_target_); + } } void TimeTargetObject::SetPathIndex(int index) diff --git a/app/widget/timetarget/timetarget.h b/app/widget/timetarget/timetarget.h index c561f58b0..5f11fc0f1 100644 --- a/app/widget/timetarget/timetarget.h +++ b/app/widget/timetarget/timetarget.h @@ -21,7 +21,7 @@ #ifndef TIMETARGETOBJECT_H #define TIMETARGETOBJECT_H -#include "node/node.h" +#include "node/output/viewer/viewer.h" namespace olive { @@ -30,8 +30,8 @@ class TimeTargetObject public: TimeTargetObject(); - Node* GetTimeTarget() const; - void SetTimeTarget(Node* target); + ViewerOutput* GetTimeTarget() const; + void SetTimeTarget(ViewerOutput* target); void SetPathIndex(int index); @@ -41,10 +41,12 @@ public: //int GetNumberOfPathAdjustments(Node* from, NodeParam::Type direction) const; protected: - virtual void TimeTargetChangedEvent(Node* ){} + virtual void TimeTargetDisconnectEvent(ViewerOutput *){} + virtual void TimeTargetChangedEvent(ViewerOutput *){} + virtual void TimeTargetConnectEvent(ViewerOutput *){} private: - Node* time_target_; + ViewerOutput* time_target_; int path_index_; diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index 10a4c85eb..8ad081e6f 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -96,7 +96,7 @@ void AudioWaveformView::drawForeground(QPainter *p, const QRectF &rect) // Draw playhead p->setPen(PLAYHEAD_COLOR); - int playhead_x = TimeToScene(GetTime()); + int playhead_x = TimeToScene(GetViewerNode()->GetPlayhead()); p->drawLine(playhead_x, 0, playhead_x, height()); } diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index b643f6740..05373b0f2 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -56,25 +56,6 @@ void FootageViewerWidget::ResetWorkArea() } } -void FootageViewerWidget::ConnectNodeEvent(ViewerOutput *n) -{ - super::ConnectNodeEvent(n); - - IgnoreNextScrubEvent(); - SetTime(cached_timestamps_.value(n, 0)); -} - -void FootageViewerWidget::DisconnectNodeEvent(ViewerOutput *n) -{ - // Cache timestamp in case this footage is opened again later - cached_timestamps_.insert(n, GetTime()); - - super::DisconnectNodeEvent(n); - - IgnoreNextScrubEvent(); - SetTime(0); -} - void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enable_audio) { if (!GetConnectedNode()) { diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h index de98866c8..802e6e4d8 100644 --- a/app/widget/viewer/footageviewer.h +++ b/app/widget/viewer/footageviewer.h @@ -35,16 +35,9 @@ public: void OverrideWorkArea(const TimeRange &r); void ResetWorkArea(); -protected: - virtual void ConnectNodeEvent(ViewerOutput *) override; - - virtual void DisconnectNodeEvent(ViewerOutput *) override; - private: void StartFootageDragInternal(bool enable_video, bool enable_audio); - QHash cached_timestamps_; - TimelineWorkArea *override_workarea_; private slots: diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 3beb3f44e..ccb3d79d3 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -113,7 +113,7 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : // Create waveform view when audio is connected and video isn't waveform_view_ = new AudioWaveformView(); - ConnectTimelineView(waveform_view_, true); + ConnectTimelineView(waveform_view_); PassWheelEventsToScrollBar(waveform_view_); layout->addWidget(waveform_view_); @@ -135,7 +135,6 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : connect(controls_, &PlaybackControls::NextFrameClicked, this, &ViewerWidget::NextFrame); connect(controls_, &PlaybackControls::BeginClicked, this, &ViewerWidget::GoToStart); connect(controls_, &PlaybackControls::EndClicked, this, &ViewerWidget::GoToEnd); - connect(controls_, &PlaybackControls::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); layout->addWidget(controls_); // FIXME: Magic number @@ -182,7 +181,6 @@ void ViewerWidget::TimeChangedEvent(const rational &time) } controls_->SetTime(time); - waveform_view_->SetTime(time); if (GetConnectedNode() && last_time_ != time) { if (!IsPlaying()) { @@ -216,6 +214,8 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); connect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); + connect(controls_, &PlaybackControls::TimeChanged, n, &ViewerOutput::SetPlayhead); + VideoParams vp = n->GetVideoParams(); InterlacingChangedSlot(vp.interlacing()); @@ -260,6 +260,8 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); + disconnect(controls_, &PlaybackControls::TimeChanged, n, &ViewerOutput::SetPlayhead); + timeline_selected_blocks_.clear(); node_view_selected_.clear(); if (multicam_panel_) { @@ -417,7 +419,7 @@ void ViewerWidget::SetGizmos(Node *node) void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track) { - SetTimeAndSignal(time.in()); + GetConnectedNode()->SetPlayhead(time.in()); ArmForRecording(); recording_callback_ = source; @@ -480,7 +482,7 @@ void ViewerWidget::SetEmptyImage() void ViewerWidget::UpdateAutoCacher() { - auto_cacher_->SetPlayhead(GetTime()); + auto_cacher_->SetPlayhead(GetConnectedNode()->GetPlayhead()); } void ViewerWidget::DecrementPrequeuedAudio() @@ -523,7 +525,7 @@ void ViewerWidget::CreateAddableAt(const QRectF &f) Track::Type type = Track::kVideo; int track_index = -1; TrackList *list = s->track_list(type); - const rational &in = GetTime(); + const rational &in = GetConnectedNode()->GetPlayhead(); rational length = OLIVE_CONFIG("DefaultStillLength").value(); rational out = in + length; @@ -597,7 +599,7 @@ 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) { + if (next_time > GetConnectedNode()->GetPlayhead() + RenderManager::kDryRunInterval) { QTimer::singleShot(timebase().toDouble() / playback_speed_, this, &ViewerWidget::RequestNextDryRun); } else { RenderTicketWatcher *watcher = new RenderTicketWatcher(this); @@ -612,12 +614,12 @@ void ViewerWidget::RequestNextDryRun() void ViewerWidget::SaveFrameAsImage() { - Core::instance()->OpenExportDialogForViewer(GetConnectedNode(), GetTime(), true); + Core::instance()->OpenExportDialogForViewer(GetConnectedNode(), true); } void ViewerWidget::DetectMulticamNodeNow() { - DetectMulticamNode(GetTime()); + DetectMulticamNode(GetConnectedNode()->GetPlayhead()); } void ViewerWidget::CloseAudioProcessor() @@ -828,7 +830,7 @@ void ViewerWidget::QueueStarved() queue_starved_start_ = now; } else if (now > queue_starved_start_ + kMaximumWaitTimeMs) { if (first_requeue_watcher_) { - if (GetTime() + kMaximumWaitTime < first_requeue_watcher_->property("time").value()) { + if (GetConnectedNode()->GetPlayhead() + kMaximumWaitTime < first_requeue_watcher_->property("time").value()) { // We still have time return; } @@ -874,7 +876,7 @@ void ViewerWidget::UpdateTextureFromNode() return; } - rational time = GetTime(); + rational time = GetConnectedNode()->GetPlayhead(); bool frame_exists_at_time = FrameExistsAtTime(time); bool frame_might_be_still = ViewerMightBeAStill(); @@ -933,11 +935,11 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) // If the playhead is beyond the end, restart at 0 if (!recording_) { rational last_frame = GetConnectedNode()->GetLength() - timebase(); - if (!in_to_out_only && GetTime() >= last_frame) { + if (!in_to_out_only && GetConnectedNode()->GetPlayhead() >= last_frame) { if (speed > 0) { - SetTimeAndSignal(0); + GetConnectedNode()->SetPlayhead(0); } else { - SetTimeAndSignal(last_frame); + GetConnectedNode()->SetPlayhead(last_frame); } } } @@ -977,7 +979,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) static const int prequeue_count = 2; prequeuing_audio_ = prequeue_count; // Queue two buffers ahead of time - audio_playback_queue_time_ = GetTime(); + audio_playback_queue_time_ = GetConnectedNode()->GetPlayhead(); for (int i=0; iSetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval))); + watcher->SetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(GetConnectedNode()->GetPlayhead(), GetConnectedNode()->GetPlayhead() + interval))); } } } @@ -1173,7 +1175,7 @@ void ViewerWidget::FinishPlayPreprocess() prequeued_audio_.clear(); AudioMonitor::StartWaveformOnAll(GetConnectedNode()->GetConnectedWaveform(), - GetTime(), playback_speed_); + GetConnectedNode()->GetPlayhead(), playback_speed_); } display_widget_->ResetFPSTimer(); @@ -1522,7 +1524,7 @@ void ViewerWidget::Play(bool in_to_out_only) if (GetConnectedNode() && GetConnectedNode()->GetWorkArea()->enabled()) { // Jump to in point - SetTimeAndSignal(GetConnectedNode()->GetWorkArea()->in()); + GetConnectedNode()->SetPlayhead(GetConnectedNode()->GetWorkArea()->in()); } else { in_to_out_only = false; } @@ -1632,7 +1634,7 @@ void ViewerWidget::TimebaseChangedEvent(const rational &timebase) controls_->SetTimebase(timebase); - controls_->SetTime(ruler()->GetTime()); + controls_->SetTime(GetConnectedNode() ? GetConnectedNode()->GetPlayhead() : 0); LengthChangedSlot(GetConnectedNode() ? GetConnectedNode()->GetLength() : 0); } @@ -1717,7 +1719,7 @@ void ViewerWidget::PlaybackTimerUpdate() // pausing. Even if we pause it later with `end_of_line`, we prefer pausing after setting the time // so that an audio scrub event, etc. isn't sent. time_changed_from_timer_ = true; - SetTimeAndSignal(time_to_set); + GetConnectedNode()->SetPlayhead(time_to_set); time_changed_from_timer_ = false; if (end_of_line) { // Cache the current speed @@ -1767,7 +1769,7 @@ void ViewerWidget::LengthChangedSlot(const rational &length) controls_->SetEndTime(length); UpdateMinimumScale(); - if (length < last_length_ && GetTime() >= length) { + if (GetConnectedNode() && length < last_length_ && GetConnectedNode()->GetPlayhead() >= length) { UpdateTextureFromNode(); } @@ -1813,7 +1815,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 (!IsPlaying() && GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) { + if (!IsPlaying() && GetConnectedNode()->GetPlayhead() >= range.in() && (GetConnectedNode()->GetPlayhead() < 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 e4f0d7289..de06b62e1 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -229,7 +229,7 @@ protected: private: int64_t GetTimestamp() const { - return Timecode::time_to_timestamp(GetTime(), timebase(), Timecode::kFloor); + return Timecode::time_to_timestamp(GetConnectedNode()->GetPlayhead(), timebase(), Timecode::kFloor); } void UpdateTimeInternal(int64_t i); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 20adf7800..93049db33 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -107,12 +107,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(node_panel_, &NodePanel::NodeSelectionChanged, sequence_viewer_panel_, &ViewerPanel::SetNodeViewSelections); - // Connect time signals together - AddMainTimePanel(multicam_panel_); - AddMainTimePanel(curve_panel_); - AddMainTimePanel(param_panel_); - AddMainTimePanel(sequence_viewer_panel_); - + // Route play/pause/shuttle commands from these panels to the sequence viewer sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_); sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_); sequence_viewer_panel_->ConnectTimeBasedPanel(multicam_panel_); @@ -526,7 +521,7 @@ void MainWindow::RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &r command->add_child(new WorkareaSetRangeCommand(r->GetWorkArea(), range)); Core::instance()->undo_stack()->push(command); - footage_viewer_panel_->SetTime(range.in()); + r->SetPlayhead(range.in()); } #ifdef Q_OS_LINUX @@ -557,7 +552,6 @@ void MainWindow::TimelineCloseRequested() { TimelinePanel *t = static_cast(sender()); RemoveTimelinePanel(t); - main_time_panels_.removeOne(t); } void MainWindow::ProjectCloseRequested() @@ -599,21 +593,6 @@ void MainWindow::FloatingPanelCloseRequested() panel->deleteLater(); } -void MainWindow::AddMainTimePanel(TimeBasedPanel *p) -{ - main_time_panels_.append(p); - connect(p, &TimeBasedPanel::TimeChanged, this, &MainWindow::UpdateMainTimePanels); -} - -void MainWindow::UpdateMainTimePanels(const rational &r) -{ - for (TimeBasedPanel *p : main_time_panels_) { - if (p != sender()) { - p->SetTime(r); - } - } -} - TimelinePanel* MainWindow::AppendTimelinePanel() { TimelinePanel* panel = AppendPanelInternal(timeline_panels_); @@ -624,8 +603,6 @@ TimelinePanel* MainWindow::AppendTimelinePanel() connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject); connect(panel, &TimelinePanel::RevealViewerInFootageViewer, this, &MainWindow::RevealViewerInFootageViewer); - AddMainTimePanel(panel); - sequence_viewer_panel_->ConnectTimeBasedPanel(panel); return panel; diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 7f319e4b1..892d2ca54 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -147,8 +147,6 @@ private: void SelectFootageForProjectPanel(const QVector &e, ProjectPanel *p); - void AddMainTimePanel(TimeBasedPanel *p); - QByteArray premaximized_state_; // Standard panels @@ -176,8 +174,6 @@ private: bool first_show_; - QVector main_time_panels_; - private slots: void FocusedPanelChanged(PanelWidget* panel); @@ -208,8 +204,6 @@ private slots: void RevealViewerInProject(ViewerOutput *r); void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range); - void UpdateMainTimePanels(const rational &r); - }; } From a3f4ebdb6dcb3305cdfc75bcf14e7ffdbaa49139 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:24:49 -0700 Subject: [PATCH 13/85] node/ui: limit amount of selectable nodes for performance --- app/widget/nodeparamview/nodeparamview.cpp | 11 +++++++++-- app/widget/nodeview/nodeview.cpp | 5 +++++ app/widget/nodeview/nodeview.h | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b0d6f1618..d17358fb2 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -697,13 +697,20 @@ void NodeParamView::QueueKeyframePositionUpdate() void NodeParamView::AddContext(Node *ctx) { + NodeParamViewContext *item = GetContextItemFromContext(ctx); + + // TEMP: Creating many NPV items is EXTREMELY slow so limit to one item per context for now. + // I have a better solution in the works to use one UI for several nodes, but I haven't + // done it yet, and this can severely affect productivity. + if (item->GetContexts().size() == 1) { + return; + } + // Queued so that if any further work is done in connecting this node to the context, it'll be // done before our sorting function is called connect(ctx, &Node::NodeAddedToContext, this, &NodeParamView::NodeAddedToContext, Qt::QueuedConnection); connect(ctx, &Node::NodeRemovedFromContext, this, &NodeParamView::NodeRemovedFromContext, Qt::QueuedConnection); - NodeParamViewContext *item = GetContextItemFromContext(ctx); - item->AddContext(ctx); item->setVisible(true); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index cf1610b02..0a9ecee08 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -44,6 +44,7 @@ namespace olive { const double NodeView::kMinimumScale = 0.1; +const int NodeView::kMaximumContexts = 10; NodeView::NodeView(QWidget *parent) : HandMovableView(parent), @@ -102,6 +103,10 @@ void NodeView::SetContexts(const QVector &nodes) // Add contexts that are now in the list foreach (Node *n, nodes) { + if (scene_.context_map().size() >= kMaximumContexts) { + break; + } + if (!contexts_.contains(n)) { AddContext(n); } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index bf2d0ee0d..01e1dd487 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -235,6 +235,8 @@ private: static const double kMinimumScale; + static const int kMaximumContexts; + private slots: /** * @brief Receiver for when the scene's selected items change From 3978a8501a0e3db041f2fcbedaaa2ad23bc5282d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 23 Oct 2022 22:28:35 -0700 Subject: [PATCH 14/85] timeline: implement select to seek and seek to select --- app/config/config.cpp | 2 +- .../tabs/preferencesbehaviortab.cpp | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 41 +++++++++++++++++++ app/widget/timelinewidget/timelinewidget.h | 1 + 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index c9e814a16..56ea47a74 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -82,7 +82,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("InvertTimelineScrollAxes"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PasteSeeks"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("SeekAlsoSelects"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("AutoSeekToBeginning"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("DropFileOnMediaToReplace"), NodeValue::kBoolean, false); diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index df9917a84..6db150edc 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -73,7 +73,7 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() tr("Hold ALT on any UI element to switch scrolling axes"), timeline_group); AddItem(tr("Seek Also Selects"), - QStringLiteral("SelectAlsoSeeks"), + QStringLiteral("SeekAlsoSelects"), timeline_group); AddItem(tr("Seek to the End of Pastes"), QStringLiteral("PasteSeeks"), diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index ed8dd84d8..507f417c6 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -198,6 +198,17 @@ TimelineWidget::TimelineWidget(QWidget *parent) : signal_block_change_timer_->setSingleShot(true); connect(signal_block_change_timer_, &QTimer::timeout, this, [this]{ signal_block_change_timer_->stop(); + + if (OLIVE_CONFIG("SelectAlsoSeeks").toBool()) { + rational start = RATIONAL_MAX; + for (Block *b : selected_blocks_) { + start = std::min(start, b->in()); + } + if (start != RATIONAL_MAX) { + GetConnectedNode()->SetPlayhead(start); + } + } + emit BlockSelectionChanged(selected_blocks_); }); } @@ -242,6 +253,36 @@ void TimelineWidget::resizeEvent(QResizeEvent *event) UpdateTimecodeWidthFromSplitters(views_.first()->splitter()); } +void TimelineWidget::TimeChangedEvent(const rational &t) +{ + if (OLIVE_CONFIG("SeekAlsoSelects").toBool()) { + TimelineWidgetSelections sels; + + QVector new_blocks; + + for (auto it=sequence()->GetTracks().cbegin(); it!=sequence()->GetTracks().cend(); it++) { + Track *track = *it; + if (track->IsLocked()) { + continue; + } + + Block *b = track->VisibleBlockAtTime(sequence()->GetPlayhead()); + if (!b || dynamic_cast(b)) { + continue; + } + + new_blocks.push_back(b); + sels[track->ToReference()].insert(b->range()); + } + + if (selected_blocks_ != new_blocks) { + selected_blocks_ = new_blocks; + SetSelections(sels, false); + SignalBlockSelectionChange(); + } + } +} + void TimelineWidget::ScaleChangedEvent(const double &scale) { super::ScaleChangedEvent(scale); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 74cf4d896..4a49bcfb7 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -290,6 +290,7 @@ signals: protected: virtual void resizeEvent(QResizeEvent *event) override; + virtual void TimeChangedEvent(const rational &) override; virtual void TimebaseChangedEvent(const rational &) override; virtual void ScaleChangedEvent(const double &) override; From 896ba1f6f0bd3bce33dad57903f6b1f3e24204ce Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 24 Oct 2022 12:22:11 +0100 Subject: [PATCH 15/85] diptoblack shader: Fix mix operation The mix function in GLSL is slightly counter intuitively x(1-a)+y(a) Here we re-arrange the inputs slightly to get the correct result. --- app/shaders/diptoblack.frag | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/shaders/diptoblack.frag b/app/shaders/diptoblack.frag index 30a9c918d..499b03b53 100644 --- a/app/shaders/diptoblack.frag +++ b/app/shaders/diptoblack.frag @@ -13,8 +13,9 @@ out vec4 frag_color; void main(void) { if (out_block_in_enabled && in_block_in_enabled) { - vec4 out_block_col = mix(texture(out_block_in, ove_texcoord), color_in, ove_tprog_out); - vec4 in_block_col = mix(texture(in_block_in, ove_texcoord), color_in, 1.0 - ove_tprog_in); + // mix(x, y , a): a(1-x) + b(x) + vec4 out_block_col = mix(color_in, texture(out_block_in, ove_texcoord),ove_tprog_out); + vec4 in_block_col = mix(color_in, texture(in_block_in, ove_texcoord), ove_tprog_in); frag_color = out_block_col + in_block_col; } else if (out_block_in_enabled) { From 02ac71fb8c20c9f9c3950d23a48555df7829c1d8 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 24 Oct 2022 12:38:18 +0100 Subject: [PATCH 16/85] diptoblack: add curve controls to shader --- app/shaders/diptoblack.frag | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/shaders/diptoblack.frag b/app/shaders/diptoblack.frag index 499b03b53..04eeb64d4 100644 --- a/app/shaders/diptoblack.frag +++ b/app/shaders/diptoblack.frag @@ -1,8 +1,13 @@ +#define LINEAR_CURVE 0 +#define EXPONENTIAL_CURVE 1 +#define LOGARITHMIC_CURVE 2 + uniform sampler2D out_block_in; uniform sampler2D in_block_in; uniform bool out_block_in_enabled; uniform bool in_block_in_enabled; uniform vec4 color_in; +uniform int curve_in; uniform float ove_tprog_all; uniform float ove_tprog_out; @@ -11,17 +16,27 @@ uniform float ove_tprog_in; in vec2 ove_texcoord; out vec4 frag_color; +float TransformCurve(float linear) { + if (curve_in == EXPONENTIAL_CURVE) { + return linear * linear; + } else if (curve_in == LOGARITHMIC_CURVE) { + return sqrt(linear); + } else { + return linear; + } +} + void main(void) { if (out_block_in_enabled && in_block_in_enabled) { // mix(x, y , a): a(1-x) + b(x) - vec4 out_block_col = mix(color_in, texture(out_block_in, ove_texcoord),ove_tprog_out); - vec4 in_block_col = mix(color_in, texture(in_block_in, ove_texcoord), ove_tprog_in); + vec4 out_block_col = mix(color_in, texture(out_block_in, ove_texcoord),TransformCurve(ove_tprog_out)); + vec4 in_block_col = mix(color_in, texture(in_block_in, ove_texcoord), TransformCurve(ove_tprog_in)); frag_color = out_block_col + in_block_col; } else if (out_block_in_enabled) { - frag_color = mix(texture(out_block_in, ove_texcoord), color_in, ove_tprog_all); + frag_color = mix(texture(out_block_in, ove_texcoord), color_in, TransformCurve(ove_tprog_all)); } else if (in_block_in_enabled) { - frag_color = mix(texture(in_block_in, ove_texcoord), color_in, 1.0 - ove_tprog_all); + frag_color = mix(texture(in_block_in, ove_texcoord), color_in, TransformCurve(1.0 - ove_tprog_all)); } else { frag_color = vec4(0.0); } From 36a3c663b29d7c76919ccf16e3fdd0669c99b8b6 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 24 Oct 2022 12:45:24 +0100 Subject: [PATCH 17/85] diptoblack: fix mix operations I missed --- app/shaders/diptoblack.frag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/shaders/diptoblack.frag b/app/shaders/diptoblack.frag index 04eeb64d4..371c8b563 100644 --- a/app/shaders/diptoblack.frag +++ b/app/shaders/diptoblack.frag @@ -34,9 +34,9 @@ void main(void) { frag_color = out_block_col + in_block_col; } else if (out_block_in_enabled) { - frag_color = mix(texture(out_block_in, ove_texcoord), color_in, TransformCurve(ove_tprog_all)); + frag_color = mix(color_in, texture(out_block_in, ove_texcoord), TransformCurve(ove_tprog_out)); } else if (in_block_in_enabled) { - frag_color = mix(texture(in_block_in, ove_texcoord), color_in, TransformCurve(1.0 - ove_tprog_all)); + frag_color = mix(texture(in_block_in, ove_texcoord), color_in, TransformCurve(1.0 - ove_tprog_in)); } else { frag_color = vec4(0.0); } From 3d90dc4ecd61ee67d3e52024695b99c0898256cd Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 24 Oct 2022 13:16:12 +0100 Subject: [PATCH 18/85] diptoblack: fix support for arbitary color --- app/shaders/diptoblack.frag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/shaders/diptoblack.frag b/app/shaders/diptoblack.frag index 371c8b563..44d2bb6e4 100644 --- a/app/shaders/diptoblack.frag +++ b/app/shaders/diptoblack.frag @@ -29,7 +29,7 @@ float TransformCurve(float linear) { void main(void) { if (out_block_in_enabled && in_block_in_enabled) { // mix(x, y , a): a(1-x) + b(x) - vec4 out_block_col = mix(color_in, texture(out_block_in, ove_texcoord),TransformCurve(ove_tprog_out)); + vec4 out_block_col = ove_tprog_out==0.0? vec4(0.0) : mix(color_in, texture(out_block_in, ove_texcoord),TransformCurve(ove_tprog_out)); vec4 in_block_col = mix(color_in, texture(in_block_in, ove_texcoord), TransformCurve(ove_tprog_in)); frag_color = out_block_col + in_block_col; From 89677ce7e410410a3d776db47cacf0aab56f16a9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 24 Oct 2022 09:19:44 -0700 Subject: [PATCH 19/85] transition: push null texture if none exists --- app/node/block/transition/transition.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index f7497af41..f0590e5c5 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -173,10 +173,14 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global if (out_buffer.type() != NodeValue::kNone) { job.Insert(kOutBlockInput, out_buffer); + } else { + job.Insert(kOutBlockInput, NodeValue(NodeValue::kTexture, nullptr)); } if (in_buffer.type() != NodeValue::kNone) { job.Insert(kInBlockInput, in_buffer); + } else { + job.Insert(kInBlockInput, NodeValue(NodeValue::kTexture, nullptr)); } job.Insert(kCurveInput, value); From f4011db5b469c326effea123cd888d022b3c5ae4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 24 Oct 2022 09:28:15 -0700 Subject: [PATCH 20/85] diptocolortransition: ensure input name is set --- .../block/transition/diptocolor/diptocolortransition.cpp | 5 +++++ app/node/block/transition/diptocolor/diptocolortransition.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/app/node/block/transition/diptocolor/diptocolortransition.cpp b/app/node/block/transition/diptocolor/diptocolortransition.cpp index 5b262288e..9ed6bff6c 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.cpp +++ b/app/node/block/transition/diptocolor/diptocolortransition.cpp @@ -56,6 +56,11 @@ ShaderCode DipToColorTransition::GetShaderCode(const ShaderRequest &request) con return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString()); } +void DipToColorTransition::Retranslate() +{ + SetInputName(kColorInput, tr("Color")); +} + void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const { job->Insert(kColorInput, value); diff --git a/app/node/block/transition/diptocolor/diptocolortransition.h b/app/node/block/transition/diptocolor/diptocolortransition.h index f0554f6c7..8c84134eb 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.h +++ b/app/node/block/transition/diptocolor/diptocolortransition.h @@ -40,6 +40,8 @@ public: virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void Retranslate() override; + static const QString kColorInput; protected: From 3ae7f0ff7e0e430885156ff89b5b7ca45b7c30b7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 24 Oct 2022 10:05:49 -0700 Subject: [PATCH 21/85] diptocolortransition: ensure base params are retranslated too --- app/node/block/transition/diptocolor/diptocolortransition.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/node/block/transition/diptocolor/diptocolortransition.cpp b/app/node/block/transition/diptocolor/diptocolortransition.cpp index 9ed6bff6c..42d04dd94 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.cpp +++ b/app/node/block/transition/diptocolor/diptocolortransition.cpp @@ -24,6 +24,8 @@ namespace olive { const QString DipToColorTransition::kColorInput = QStringLiteral("color_in"); +#define super TransitionBlock + DipToColorTransition::DipToColorTransition() { AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0, 0, 0))); @@ -58,6 +60,8 @@ ShaderCode DipToColorTransition::GetShaderCode(const ShaderRequest &request) con void DipToColorTransition::Retranslate() { + super::Retranslate(); + SetInputName(kColorInput, tr("Color")); } From 876bc26a5cac364722105126d24ebbc9235a0e99 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 24 Oct 2022 10:13:17 -0700 Subject: [PATCH 22/85] decoder: track divider in cached frame --- app/codec/decoder.cpp | 3 ++- app/codec/decoder.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 434376c06..336aaa6f1 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -112,12 +112,13 @@ TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p) return nullptr; } - if (cached_texture_ && cached_time_ == p.time) { + if (cached_texture_ && cached_time_ == p.time && cached_divider_ == p.divider) { return cached_texture_; } cached_texture_ = RetrieveVideoInternal(p); cached_time_ = p.time; + cached_divider_ = p.divider; return cached_texture_; } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index d0c974846..029cefa25 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -316,6 +316,7 @@ private: TexturePtr cached_texture_; rational cached_time_; + int cached_divider_; }; From f189df91e101135fc9eaf8a58e02a3d77adb1339 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 24 Oct 2022 18:29:42 +0100 Subject: [PATCH 23/85] diptoblack: Stop the second half of the transition effecting the first --- app/shaders/diptoblack.frag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/shaders/diptoblack.frag b/app/shaders/diptoblack.frag index 44d2bb6e4..a5c00a16f 100644 --- a/app/shaders/diptoblack.frag +++ b/app/shaders/diptoblack.frag @@ -29,8 +29,8 @@ float TransformCurve(float linear) { void main(void) { if (out_block_in_enabled && in_block_in_enabled) { // mix(x, y , a): a(1-x) + b(x) - vec4 out_block_col = ove_tprog_out==0.0? vec4(0.0) : mix(color_in, texture(out_block_in, ove_texcoord),TransformCurve(ove_tprog_out)); - vec4 in_block_col = mix(color_in, texture(in_block_in, ove_texcoord), TransformCurve(ove_tprog_in)); + vec4 out_block_col = ove_tprog_out == 0.0 ? vec4(0.0) : mix(color_in, texture(out_block_in, ove_texcoord),TransformCurve(ove_tprog_out)); + vec4 in_block_col = ove_tprog_out != 0.0 ? vec4(0.0) : mix(color_in, texture(in_block_in, ove_texcoord), TransformCurve(ove_tprog_in)); frag_color = out_block_col + in_block_col; } else if (out_block_in_enabled) { From 840fc0f212649579a67d1d749e64688092e30dd6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 24 Oct 2022 10:45:48 -0700 Subject: [PATCH 24/85] timeline: when deleting all gaps, switch to a ripple delete --- app/widget/timelinewidget/timelinewidget.cpp | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 1bfea94a2..cf94883c1 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -452,21 +452,22 @@ void TimelineWidget::DeleteSelected(bool ripple) } QVector selected_list = GetSelectedBlocks(); - QVector blocks_to_delete; - - foreach (Block* b, selected_list) { - blocks_to_delete.append(b); - } // No-op if nothing is selected - if (blocks_to_delete.isEmpty()) { + if (selected_list.isEmpty()) { return; } QVector clips_to_delete; QVector transitions_to_delete; - foreach (Block* b, blocks_to_delete) { + bool all_gaps = true; + + foreach (Block* b, selected_list) { + if (!dynamic_cast(b)) { + all_gaps = false; + } + if (dynamic_cast(b)) { clips_to_delete.append(b); } else if (dynamic_cast(b)) { @@ -474,6 +475,10 @@ void TimelineWidget::DeleteSelected(bool ripple) } } + if (all_gaps) { + ripple = true; + } + MultiUndoCommand* command = new MultiUndoCommand(); // Remove all selections @@ -498,7 +503,7 @@ void TimelineWidget::DeleteSelected(bool ripple) if (ripple) { TimelineRippleDeleteGapsAtRegionsCommand::RangeList range_list; - foreach (Block* b, blocks_to_delete) { + foreach (Block* b, selected_list) { range_list.append({b->track(), b->range()}); new_playhead = qMin(new_playhead, b->in()); } From f81445527f0be79618001df5f706c7e0bb1ce74d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 24 Oct 2022 11:18:42 -0700 Subject: [PATCH 25/85] timebasedwidget: automatically connect scale and scrollbars --- app/widget/curvewidget/curvewidget.cpp | 2 -- app/widget/nodeparamview/nodeparamview.cpp | 5 ----- app/widget/timebased/timebasedwidget.cpp | 20 ++++++++++++++++---- app/widget/timelinewidget/timelinewidget.cpp | 14 -------------- app/widget/viewer/viewer.cpp | 2 -- 5 files changed, 16 insertions(+), 27 deletions(-) diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index d85e6d168..25f2d83a2 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -101,13 +101,11 @@ CurveWidget::CurveWidget(QWidget *parent) : // Connect ruler and view together connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal); 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()); - connect(view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); // Disable collapsing the main curve view (but allow collapsing the tree) splitter->setCollapsible(1, false); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index d17358fb2..15fd86e67 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -132,9 +132,6 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : 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); - splitter->addWidget(keyframe_area); // Set both widgets to 50/50 @@ -148,8 +145,6 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of keyframe_view_->setHorizontalScrollBar(scrollbar()); keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - - connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); } else { keyframe_view_ = nullptr; } diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index b97521b47..2c1e175cd 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -43,14 +43,15 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu workarea_(nullptr), markers_(nullptr) { + scrollbar_ = new ResizableTimelineScrollBar(Qt::Horizontal, this); + connect(scrollbar_, &ResizableScrollBar::ResizeBegan, this, &TimeBasedWidget::ScrollBarResizeBegan); + connect(scrollbar_, &ResizableScrollBar::ResizeMoved, this, &TimeBasedWidget::ScrollBarResizeMoved); + 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); - connect(scrollbar_, &ResizableScrollBar::ResizeMoved, this, &TimeBasedWidget::ScrollBarResizeMoved); + connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); PassWheelEventsToScrollBar(ruler_); @@ -306,6 +307,17 @@ void TimeBasedWidget::ConnectTimelineView(TimeBasedView *base, bool connect_time connect(base, &TimeBasedView::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal); } + connect(base, &TimeBasedView::ScaleChanged, this, &TimeBasedWidget::SetScale); + + connect(scrollbar(), &QScrollBar::valueChanged, base->horizontalScrollBar(), &QScrollBar::setValue); + connect(base->horizontalScrollBar(), &QScrollBar::valueChanged, scrollbar(), &QScrollBar::setValue); + connect(base->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + + for (TimeBasedView *other : qAsConst(timeline_views_)) { + connect(other->horizontalScrollBar(), &QScrollBar::valueChanged, base->horizontalScrollBar(), &QScrollBar::setValue); + connect(base->horizontalScrollBar(), &QScrollBar::valueChanged, other->horizontalScrollBar(), &QScrollBar::setValue); + } + timeline_views_.append(base); } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index cf94883c1..50fc57a5c 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -129,7 +129,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) : tools_.append(import_tool_); // Global scrollbar - connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); connect(views_.first()->view()->horizontalScrollBar(), &QScrollBar::rangeChanged, scrollbar(), &QScrollBar::setRange); vert_layout->addWidget(scrollbar()); @@ -146,12 +145,8 @@ TimelineWidget::TimelineWidget(QWidget *parent) : ConnectTimelineView(view, false); - connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); - connect(view, &TimelineView::ScaleChanged, this, &TimelineWidget::SetScale); connect(view, &TimelineView::TimeChanged, this, &TimelineWidget::SetTimeAndSignal); connect(view, &TimelineView::customContextMenuRequested, this, &TimelineWidget::ShowContextMenu); - connect(scrollbar(), &QScrollBar::valueChanged, view->horizontalScrollBar(), &QScrollBar::setValue); - connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, scrollbar(), &QScrollBar::setValue); connect(view, &TimelineView::MousePressed, this, &TimelineWidget::ViewMousePressed); connect(view, &TimelineView::MouseMoved, this, &TimelineWidget::ViewMouseMoved); @@ -163,15 +158,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) : connect(view, &TimelineView::DragDropped, this, &TimelineWidget::ViewDragDropped); connect(tview->splitter(), &QSplitter::splitterMoved, this, &TimelineWidget::UpdateHorizontalSplitters); - - // Connect each view's scroll to each other - foreach (TimelineAndTrackView* other_tview, views_) { - TimelineView* other_view = other_tview->view(); - - if (view != other_view) { - connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, other_view->horizontalScrollBar(), &QScrollBar::setValue); - } - } } // Split viewer 50/50 diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 3beb3f44e..f390f2a6b 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -122,8 +122,6 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : // Create scrollbar layout->addWidget(scrollbar()); - connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); - connect(scrollbar(), &QScrollBar::valueChanged, waveform_view_, &AudioWaveformView::SetScroll); // Create lower controls controls_ = new PlaybackControls(); From bdbfb56879130a9ca66333862250f5fcf12bec70 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 24 Oct 2022 12:01:47 -0700 Subject: [PATCH 26/85] shift timelineview scroll code into base classes Fixes #2060 --- .../handmovableview/handmovableview.cpp | 51 ++++++++++++++++- app/widget/handmovableview/handmovableview.h | 4 ++ app/widget/timebased/timebasedwidget.cpp | 22 +------ app/widget/timebased/timebasedwidget.h | 6 -- .../timelinewidget/view/timelineview.cpp | 57 +------------------ app/widget/timelinewidget/view/timelineview.h | 2 - app/widget/timeruler/seekablewidget.cpp | 2 + app/widget/viewer/viewer.cpp | 1 - 8 files changed, 61 insertions(+), 84 deletions(-) diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index a44858ad8..f6cc9bc63 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -31,7 +31,8 @@ namespace olive { HandMovableView::HandMovableView(QWidget* parent) : super(parent), - dragging_hand_(false) + dragging_hand_(false), + is_timeline_axes_(false) { connect(Core::instance(), &Core::ToolChanged, this, &HandMovableView::ApplicationToolChanged); } @@ -166,6 +167,54 @@ void HandMovableView::wheelEvent(QWheelEvent *event) ZoomIntoCursorPosition(event, multiplier, cursor_pos); } + } else if (is_timeline_axes_) { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + + QPoint angle_delta = event->angleDelta(); + + if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool() // Check if config is set to invert timeline axes + && event->source() != Qt::MouseEventSynthesizedBySystem) { // Never flip axes on Apple trackpads though + angle_delta = QPoint(angle_delta.y(), angle_delta.x()); + } + + QWheelEvent e( + #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + event->position(), + event->globalPosition(), + #else + event->pos(), + event->globalPos(), + #endif + event->pixelDelta(), + angle_delta, + event->buttons(), + event->modifiers(), + event->phase(), + event->inverted(), + event->source() + ); + +#else + + Qt::Orientation orientation = event->orientation(); + + if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool()) { + orientation = (orientation == Qt::Horizontal) ? Qt::Vertical : Qt::Horizontal; + } + + QWheelEvent e( + event->pos(), + event->globalPos(), + event->pixelDelta(), + event->angleDelta(), + event->delta(), + orientation, + event->buttons(), + event->modifiers() + ); +#endif + + super::wheelEvent(&e); } else { super::wheelEvent(event); } diff --git a/app/widget/handmovableview/handmovableview.h b/app/widget/handmovableview/handmovableview.h index e8c01599b..63c09cb18 100644 --- a/app/widget/handmovableview/handmovableview.h +++ b/app/widget/handmovableview/handmovableview.h @@ -50,6 +50,8 @@ protected: virtual void ZoomIntoCursorPosition(QWheelEvent* event, double multiplier, const QPointF &cursor_pos); + void SetIsTimelineAxes(bool e) { is_timeline_axes_ = e; } + private: bool dragging_hand_; DragMode pre_hand_drag_mode_; @@ -58,6 +60,8 @@ private: QPointF transformed_pos_; + bool is_timeline_axes_; + private slots: void ApplicationToolChanged(Tool::Item tool); diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 2c1e175cd..e0149578f 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -51,9 +51,6 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu ConnectTimelineView(ruler_, true); ruler()->SetSnapService(this); connect(ruler(), &TimeRuler::DragReleased, this, static_cast(&TimeBasedWidget::StopCatchUpScrollTimer)); - connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); - - PassWheelEventsToScrollBar(ruler_); catchup_scroll_timer_ = new QTimer(this); catchup_scroll_timer_->setInterval(250); // Hardcoded 1/4 scroll limit value @@ -307,12 +304,14 @@ void TimeBasedWidget::ConnectTimelineView(TimeBasedView *base, bool connect_time connect(base, &TimeBasedView::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal); } + // Connect scale connect(base, &TimeBasedView::ScaleChanged, this, &TimeBasedWidget::SetScale); + // Main scrollbar to view scrollbar and vice versa connect(scrollbar(), &QScrollBar::valueChanged, base->horizontalScrollBar(), &QScrollBar::setValue); connect(base->horizontalScrollBar(), &QScrollBar::valueChanged, scrollbar(), &QScrollBar::setValue); - connect(base->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + // Connect scrollbar to other scrollbars for (TimeBasedView *other : qAsConst(timeline_views_)) { connect(other->horizontalScrollBar(), &QScrollBar::valueChanged, base->horizontalScrollBar(), &QScrollBar::setValue); connect(base->horizontalScrollBar(), &QScrollBar::valueChanged, other->horizontalScrollBar(), &QScrollBar::setValue); @@ -321,12 +320,6 @@ void TimeBasedWidget::ConnectTimelineView(TimeBasedView *base, bool connect_time timeline_views_.append(base); } -void TimeBasedWidget::PassWheelEventsToScrollBar(QObject *object) -{ - wheel_passthrough_objects_.append(object); - object->installEventFilter(this); -} - void TimeBasedWidget::SetCatchUpScrollValue(QScrollBar *b, int v, int maximum) { CatchUpScrollData &cudata = catchup_scroll_values_[b]; @@ -758,15 +751,6 @@ void TimeBasedWidget::DeleteSelected() } } -bool TimeBasedWidget::eventFilter(QObject *object, QEvent *event) -{ - if (wheel_passthrough_objects_.contains(object) && event->type() == QEvent::Wheel) { - QCoreApplication::sendEvent(scrollbar(), event); - } - - return false; -} - struct SnapData { rational time; rational movement; diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 347c079c7..2789793b1 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -60,8 +60,6 @@ public: TimeRuler* ruler() const; - virtual bool eventFilter(QObject* object, QEvent* event) override; - using SnapMask = uint32_t; enum SnapPoints { kSnapToClips = 0x1, @@ -146,8 +144,6 @@ protected: void ConnectTimelineView(TimeBasedView* base, bool connect_time_change_event = true); - void PassWheelEventsToScrollBar(QObject* object); - void SetCatchUpScrollValue(QScrollBar *b, int v, int maximum); void SetCatchUpScrollValue(int v); void StopCatchUpScrollTimer(QScrollBar *b); @@ -228,8 +224,6 @@ private: bool auto_set_timebase_; - QVector wheel_passthrough_objects_; - int scrollbar_start_width_; double scrollbar_start_value_; double scrollbar_start_scale_; diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 5bad687f6..2ba27f240 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -56,6 +56,8 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : setBackgroundRole(QPalette::Window); setContextMenuPolicy(Qt::CustomContextMenu); viewport()->setMouseTracking(true); + + SetIsTimelineAxes(true); } void TimelineView::mousePressEvent(QMouseEvent *event) @@ -147,61 +149,6 @@ void TimelineView::mouseDoubleClickEvent(QMouseEvent *event) emit MouseDoubleClicked(&timeline_event); } -void TimelineView::wheelEvent(QWheelEvent *event) -{ - if (WheelEventIsAZoomEvent(event)) { - super::wheelEvent(event); - } else { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) - - QPoint angle_delta = event->angleDelta(); - - if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool() // Check if config is set to invert timeline axes - && event->source() != Qt::MouseEventSynthesizedBySystem) { // Never flip axes on Apple trackpads though - angle_delta = QPoint(angle_delta.y(), angle_delta.x()); - } - - QWheelEvent e( - #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - event->position(), - event->globalPosition(), - #else - event->pos(), - event->globalPos(), - #endif - event->pixelDelta(), - angle_delta, - event->buttons(), - event->modifiers(), - event->phase(), - event->inverted(), - event->source() - ); - -#else - - Qt::Orientation orientation = event->orientation(); - - if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool()) { - orientation = (orientation == Qt::Horizontal) ? Qt::Vertical : Qt::Horizontal; - } - - QWheelEvent e( - event->pos(), - event->globalPos(), - event->pixelDelta(), - event->angleDelta(), - event->delta(), - orientation, - event->buttons(), - event->modifiers() - ); -#endif - - super::wheelEvent(&e); - } -} - void TimelineView::dragEnterEvent(QDragEnterEvent *event) { TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), Qt::NoButton, event->keyboardModifiers()); diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 6255af2da..a1e1698a9 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -90,8 +90,6 @@ protected: virtual void mouseReleaseEvent(QMouseEvent *event) override; virtual void mouseDoubleClickEvent(QMouseEvent *event) override; - virtual void wheelEvent(QWheelEvent* event) override; - virtual void dragEnterEvent(QDragEnterEvent *event) override; virtual void dragMoveEvent(QDragMoveEvent *event) override; virtual void dragLeaveEvent(QDragLeaveEvent *event) override; diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 8dc21dace..bb154d59e 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -63,6 +63,8 @@ SeekableWidget::SeekableWidget(QWidget* parent) : setMouseTracking(true); selection_manager_.SetSnapMask(TimeBasedWidget::kSnapAll); + + SetIsTimelineAxes(true); } void SeekableWidget::SetMarkers(TimelineMarkerList *markers) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index f390f2a6b..8447734af 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -114,7 +114,6 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : // Create waveform view when audio is connected and video isn't waveform_view_ = new AudioWaveformView(); ConnectTimelineView(waveform_view_, true); - PassWheelEventsToScrollBar(waveform_view_); layout->addWidget(waveform_view_); // Create time ruler From d0112298644ad72e0e597e223eb1b12d51f0e44d Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 25 Oct 2022 17:24:40 +0100 Subject: [PATCH 27/85] ffmpegdecoder: Catch bad stream duration estimates For some formats, particularly mxf, FFmpeg calculates the duration of the stream incorrectly. Here we try to catch that and force Olive to use it's much slower, but correct fallback method. --- app/codec/ffmpeg/ffmpegdecoder.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index d2292f002..6bff06232 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -350,6 +350,13 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can int64_t footage_duration = fmt_ctx->duration; + bool bad_duration = false; + + if (fmt_ctx->duration_estimation_method == AVFMT_DURATION_FROM_BITRATE) { + bad_duration = true; + qWarning() << "Potentially bad duration estimation, using fallback. This could be slow."; + } + // Dump it into the Footage object for (unsigned int i=0;inb_streams;i++) { @@ -409,8 +416,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can if (ret >= 0) { // Check if we need a manual duration - if (avstream->duration == AV_NOPTS_VALUE) { - if (footage_duration == AV_NOPTS_VALUE) { + if (avstream->duration == AV_NOPTS_VALUE || bad_duration) { + if (footage_duration == AV_NOPTS_VALUE || bad_duration) { // Manually read through file for duration int64_t new_dur; @@ -468,9 +475,9 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); } - if (avstream->duration == AV_NOPTS_VALUE) { + if (avstream->duration == AV_NOPTS_VALUE || bad_duration) { // Loop through stream until we get the whole duration - if (footage_duration == AV_NOPTS_VALUE) { + if (footage_duration == AV_NOPTS_VALUE || bad_duration) { Instance instance; instance.Open(filename_c, avstream->index); From 9bc10e830169859a028c01b0b4d9126a7b0d52e7 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 25 Oct 2022 17:39:45 +0100 Subject: [PATCH 28/85] ffmpegdecoder: add comment --- 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 6bff06232..87f96bb8c 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -352,6 +352,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can bool bad_duration = false; + // Catch when ffmpeg uses its inaccurate method of duration estimation so we can + // use manual calculation if (fmt_ctx->duration_estimation_method == AVFMT_DURATION_FROM_BITRATE) { bad_duration = true; qWarning() << "Potentially bad duration estimation, using fallback. This could be slow."; From ddeab29da37b4cdd2cef1033a6f8fb1cb83b8103 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 26 Oct 2022 21:02:59 +0100 Subject: [PATCH 29/85] ffmepgdecoder: simplify code and improve vairable name --- app/codec/ffmpeg/ffmpegdecoder.cpp | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 87f96bb8c..edb37d273 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -350,14 +350,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can int64_t footage_duration = fmt_ctx->duration; - bool bad_duration = false; - - // Catch when ffmpeg uses its inaccurate method of duration estimation so we can - // use manual calculation - if (fmt_ctx->duration_estimation_method == AVFMT_DURATION_FROM_BITRATE) { - bad_duration = true; - qWarning() << "Potentially bad duration estimation, using fallback. This could be slow."; - } + bool duration_guessed_from_bitrate = (fmt_ctx->duration_estimation_method == AVFMT_DURATION_FROM_BITRATE); // Dump it into the Footage object for (unsigned int i=0;inb_streams;i++) { @@ -418,8 +411,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can if (ret >= 0) { // Check if we need a manual duration - if (avstream->duration == AV_NOPTS_VALUE || bad_duration) { - if (footage_duration == AV_NOPTS_VALUE || bad_duration) { + if (avstream->duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { + if (footage_duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { // Manually read through file for duration int64_t new_dur; @@ -477,9 +470,9 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); } - if (avstream->duration == AV_NOPTS_VALUE || bad_duration) { + if (avstream->duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { // Loop through stream until we get the whole duration - if (footage_duration == AV_NOPTS_VALUE || bad_duration) { + if (footage_duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { Instance instance; instance.Open(filename_c, avstream->index); From 4d80044d6da8461479dd7dc6f4e7bdba2965b2de Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:42:04 -0700 Subject: [PATCH 30/85] upgrade to qt 6 compliance --- CMakeLists.txt | 29 ++++++++++++------- app/CMakeLists.txt | 6 ++-- app/common/html.cpp | 21 ++------------ app/common/memorypool.h | 1 - app/common/ratiodialog.cpp | 3 +- app/common/timecodefunctions.cpp | 3 +- app/crashhandler/CMakeLists.txt | 8 ++--- app/dialog/about/about.cpp | 6 ++-- app/dialog/export/codec/cineformsection.cpp | 2 +- app/dialog/export/codec/h264section.cpp | 8 ++--- app/dialog/export/codec/imagesection.cpp | 2 +- app/dialog/export/export.cpp | 6 ++-- .../videostreamproperties.cpp | 2 +- .../preferences/tabs/preferencesaudiotab.cpp | 2 +- app/dialog/progress/progress.cpp | 2 +- .../sequence/sequencedialogpresettab.cpp | 2 +- app/dialog/task/task.cpp | 2 +- .../transform/transformdistortnode.cpp | 6 ++-- app/node/generator/matrix/matrix.cpp | 4 +-- app/node/math/math/mathbase.cpp | 4 +-- .../project/serializer/serializer220403.cpp | 8 ++--- app/panel/project/project.cpp | 2 +- app/panel/scope/scope.cpp | 2 +- app/panel/timebased/timebased.cpp | 5 ++++ app/panel/timebased/timebased.h | 2 ++ app/render/texture.h | 1 + app/task/project/import/import.cpp | 2 +- app/task/taskmanager.cpp | 2 +- app/ui/style/style.cpp | 5 ---- app/ui/style/style.h | 2 -- app/widget/colorwheel/colorvalueswidget.cpp | 2 +- app/widget/curvewidget/curvewidget.cpp | 4 +-- app/widget/filefield/filefield.cpp | 2 +- app/widget/flowlayout/flowlayout.cpp | 2 +- app/widget/manageddisplay/manageddisplay.cpp | 6 ++-- app/widget/manageddisplay/manageddisplay.h | 9 ++++++ app/widget/menu/menushared.cpp | 2 ++ app/widget/nodeparamview/nodeparamview.cpp | 4 +-- .../nodeparamviewconnectedlabel.cpp | 4 +-- .../nodeparamviewkeyframecontrol.cpp | 2 +- .../nodeparamview/nodeparamviewtextedit.cpp | 2 +- .../nodeparamviewwidgetbridge.cpp | 4 +-- app/widget/nodetableview/nodetablewidget.cpp | 2 +- app/widget/nodeview/nodeviewtoolbar.cpp | 2 +- app/widget/nodeview/nodewidget.cpp | 2 +- app/widget/panel/panel.cpp | 2 +- app/widget/path/pathwidget.cpp | 2 +- app/widget/pixelsampler/pixelsampler.cpp | 2 +- .../playbackcontrols/playbackcontrols.cpp | 10 +++---- .../projectexplorer/projectexplorer.cpp | 2 +- .../projectexplorernavigation.cpp | 2 +- app/widget/projecttoolbar/projecttoolbar.cpp | 2 +- app/widget/slider/base/sliderbase.cpp | 16 ++++++++-- app/widget/slider/base/sliderbase.h | 6 ++-- app/widget/slider/base/sliderladder.cpp | 2 +- app/widget/standardcombos/frameratecombobox.h | 2 +- app/widget/taskview/elapsedcounterwidget.cpp | 2 +- app/widget/taskview/taskview.cpp | 2 +- .../timelinewidget/timelineandtrackview.cpp | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 2 +- .../timelinewidget/trackview/trackview.cpp | 2 +- .../trackview/trackviewitem.cpp | 2 +- app/widget/toolbar/toolbar.cpp | 2 +- app/widget/viewer/viewer.cpp | 7 +++-- app/widget/viewer/viewerdisplay.cpp | 8 ++--- app/widget/viewer/viewerqueue.h | 2 -- app/widget/viewer/viewerwindow.cpp | 2 +- app/window/mainwindow/mainmenu.cpp | 1 + app/window/mainwindow/mainwindow.cpp | 1 - 69 files changed, 152 insertions(+), 130 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61f4adef4..9c15a6b67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,7 +94,7 @@ find_package(OpenEXR REQUIRED) list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES}) list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES}) -# Link Qt 5 +# Link Qt set(QT_LIBRARIES Core Gui @@ -102,25 +102,34 @@ set(QT_LIBRARIES OpenGL LinguistTools Concurrent + OpenGLWidgets ) if (UNIX AND NOT APPLE) list(APPEND QT_LIBRARIES DBus) endif() -find_package(Qt5 5.6 REQUIRED +find_package(QT NAMES Qt6 REQUIRED COMPONENTS ${QT_LIBRARIES} OPTIONAL_COMPONENTS Network ) -if (NOT Qt5Network_FOUND) - message(" Qt5::Network module not found, crash reporting will be disabled.") +find_package(Qt${QT_VERSION_MAJOR} REQUIRED + COMPONENTS + ${QT_LIBRARIES} + OPTIONAL_COMPONENTS + Network +) +message("Qt version: " ${QT_VERSION_MAJOR}) +if (NOT Qt${QT_VERSION_MAJOR}Network_FOUND) + message(" Qt${QT_VERSION_MAJOR}::Network module not found, crash reporting will be disabled.") endif() list(APPEND OLIVE_LIBRARIES - Qt5::Core - Qt5::Gui - Qt5::Widgets - Qt5::OpenGL - Qt5::Concurrent + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::OpenGL + Qt${QT_VERSION_MAJOR}::Concurrent + Qt${QT_VERSION_MAJOR}::OpenGLWidgets ) # Link FFmpeg @@ -186,7 +195,7 @@ if (WIN32) elseif (APPLE) list(APPEND OLIVE_LIBRARIES "-framework IOKit") elseif(UNIX) - list(APPEND OLIVE_LIBRARIES Qt5::DBus) + list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::DBus) endif() # Generate Git hash diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 35a818bc0..e8cf1ec2c 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -44,7 +44,7 @@ add_subdirectory(widget) add_subdirectory(window) # Add translations -qt5_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES}) +qt_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES}) set(QRC_BODY "") foreach(QM_FILE ${OLIVE_QM_FILES}) @@ -64,7 +64,7 @@ add_library(olive-version-obj version.cpp version.h ) -target_link_libraries(olive-version-obj PRIVATE Qt5::Core) +target_link_libraries(olive-version-obj PRIVATE Qt${QT_VERSION_MAJOR}::Core) target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" ) # Add main library @@ -142,6 +142,6 @@ target_include_directories(olive-editor PRIVATE ${OLIVE_INCLUDE_DIRS}) target_include_directories(libolive-editor PRIVATE ${OLIVE_INCLUDE_DIRS}) # Add crash handler -if (GoogleCrashpad_FOUND AND Qt5Network_FOUND) +if (GoogleCrashpad_FOUND AND Qt${QT_VERSION_MAJOR}Network_FOUND) add_subdirectory(crashhandler) endif() diff --git a/app/common/html.cpp b/app/common/html.cpp index d9ad84a28..143d76544 100644 --- a/app/common/html.cpp +++ b/app/common/html.cpp @@ -12,22 +12,7 @@ const QVector Html::kBlockTags = { QStringLiteral("div") }; -inline bool StrEquals(const QString &a, const QStringRef &b) -{ - return !a.compare(b, Qt::CaseInsensitive); -} - -inline bool StrEquals(const QString &a, const QString &b) -{ - return !a.compare(b, Qt::CaseInsensitive); -} - -inline bool StrEquals(const QStringRef &a, const QString &b) -{ - return !a.compare(b, Qt::CaseInsensitive); -} - -inline bool StrEquals(const QStringRef &a, const QStringRef &b) +inline bool StrEquals(const QStringView &a, const QStringView &b) { return !a.compare(b, Qt::CaseInsensitive); } @@ -421,7 +406,7 @@ QMap Html::GetCSSFromStyle(const QString &s) // match. Also commas should be filtered out. QStringList values; const QString &val = kv.at(1); - QChar in_quote = 0; + QChar in_quote(0); QString current_str; for (int i=0; i Html::GetCSSFromStyle(const QString &s) if (!in_quote.isNull()) { // If inside quotes and character isn't quote, indiscriminately append char if (current_char == in_quote) { - in_quote = 0; + in_quote = QChar(0); } else { current_str.append(current_char); } diff --git a/app/common/memorypool.h b/app/common/memorypool.h index a932ad5f3..270e3a0a0 100644 --- a/app/common/memorypool.h +++ b/app/common/memorypool.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include diff --git a/app/common/ratiodialog.cpp b/app/common/ratiodialog.cpp index fd216b6a1..ee85582fa 100644 --- a/app/common/ratiodialog.cpp +++ b/app/common/ratiodialog.cpp @@ -22,6 +22,7 @@ #include #include +#include namespace olive { @@ -49,7 +50,7 @@ double GetFloatRatioFromUser(QWidget* parent, return qSNaN(); } - QStringList ratio_components = s.split(QRegExp(QStringLiteral(":|;|\\/"))); + QStringList ratio_components = s.split(QRegularExpression(QStringLiteral(":|;|\\/"))); if (ratio_components.size() == 1) { bool float_ok; diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index cbd9d4074..1397a4dc7 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -24,6 +24,7 @@ extern "C" { #include } +#include #include #include "config/config.h" @@ -149,7 +150,7 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational case kTimecodeSeconds: { const int kTimecodeElementCount = 4; - QStringList timecode_split = timecode.split(QRegExp("(:)|(;)|(\\.)")); + QStringList timecode_split = timecode.split(QRegularExpression("(:)|(;)|(\\.)")); bool valid; diff --git a/app/crashhandler/CMakeLists.txt b/app/crashhandler/CMakeLists.txt index 0a023275b..f835c73ae 100644 --- a/app/crashhandler/CMakeLists.txt +++ b/app/crashhandler/CMakeLists.txt @@ -39,10 +39,10 @@ target_include_directories( target_link_libraries( olive-crashhandler PRIVATE - Qt5::Core - Qt5::Gui - Qt5::Widgets - Qt5::Network + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Network ${CRASHPAD_LIBRARIES} ) diff --git a/app/dialog/about/about.cpp b/app/dialog/about/about.cpp index bb584af42..4fb526944 100644 --- a/app/dialog/about/about.cpp +++ b/app/dialog/about/about.cpp @@ -44,10 +44,10 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : QFontMetrics fm = fontMetrics(); QVBoxLayout* layout = new QVBoxLayout(this); - layout->setMargin(fm.height()); + layout->setContentsMargins(fm.height(), fm.height(), fm.height(), fm.height()); QHBoxLayout *horiz_layout = new QHBoxLayout(); - horiz_layout->setMargin(fm.height()); + horiz_layout->setContentsMargins(fm.height(), fm.height(), fm.height(), fm.height()); horiz_layout->setSpacing(fm.height()*2); QLabel* icon = new QLabel(QStringLiteral("")); @@ -108,7 +108,7 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : layout->addWidget(new QLabel()); QHBoxLayout *btn_layout = new QHBoxLayout(); - btn_layout->setMargin(0); + btn_layout->setContentsMargins(0, 0, 0, 0); btn_layout->setSpacing(0); if (welcome_dialog) { diff --git a/app/dialog/export/codec/cineformsection.cpp b/app/dialog/export/codec/cineformsection.cpp index da83eabed..9cae58409 100644 --- a/app/dialog/export/codec/cineformsection.cpp +++ b/app/dialog/export/codec/cineformsection.cpp @@ -30,7 +30,7 @@ CineformSection::CineformSection(QWidget *parent) : { QGridLayout *layout = new QGridLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); int row = 0; diff --git a/app/dialog/export/codec/h264section.cpp b/app/dialog/export/codec/h264section.cpp index 732aef346..0d50312e4 100644 --- a/app/dialog/export/codec/h264section.cpp +++ b/app/dialog/export/codec/h264section.cpp @@ -39,7 +39,7 @@ H264Section::H264Section(int default_crf, QWidget *parent) : CodecSection(parent) { QGridLayout* layout = new QGridLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); int row = 0; layout->addWidget(new QLabel(tr("Encode Speed:")), row, 0); @@ -173,7 +173,7 @@ H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); crf_slider_ = new QSlider(Qt::Horizontal); crf_slider_->setMinimum(kMinimumCRF); @@ -207,7 +207,7 @@ H264BitRateSection::H264BitRateSection(QWidget *parent) : QWidget(parent) { QGridLayout* layout = new QGridLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); int row = 0; @@ -261,7 +261,7 @@ H264FileSizeSection::H264FileSizeSection(QWidget *parent) : QWidget(parent) { QGridLayout* layout = new QGridLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); int row = 0; diff --git a/app/dialog/export/codec/imagesection.cpp b/app/dialog/export/codec/imagesection.cpp index b2bf50fd8..e459a0db9 100644 --- a/app/dialog/export/codec/imagesection.cpp +++ b/app/dialog/export/codec/imagesection.cpp @@ -29,7 +29,7 @@ ImageSection::ImageSection(QWidget* parent) : CodecSection(parent) { QGridLayout* layout = new QGridLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); int row = 0; diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 386fa2e1b..b7205c6a9 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -58,7 +58,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi preferences_area_ = new QWidget(); QGridLayout* preferences_layout = new QGridLayout(preferences_area_); - preferences_layout->setMargin(0); + preferences_layout->setContentsMargins(0, 0, 0, 0); int row = 0; @@ -183,7 +183,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi row++; QHBoxLayout *btn_layout = new QHBoxLayout(); - btn_layout->setMargin(0); + btn_layout->setContentsMargins(0, 0, 0, 0); preferences_layout->addLayout(btn_layout, row, 0, 1, 4); btn_layout->addStretch(); @@ -437,7 +437,7 @@ void ExportDialog::PresetComboBoxChanged() if (loading_presets_) { return; } - + QComboBox *c = static_cast(sender()); int preset_number = c->currentData().toInt(); diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp index 6ed1e486f..e2b223621 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp @@ -38,7 +38,7 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) video_premultiply_alpha_(nullptr) { QGridLayout* video_layout = new QGridLayout(this); - video_layout->setMargin(0); + video_layout->setContentsMargins(0, 0, 0, 0); int row = 0; diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index 6f73dbedc..b498e53ae 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -36,7 +36,7 @@ PreferencesAudioTab::PreferencesAudioTab() { // Backend Layout QGridLayout* main_layout = new QGridLayout(); - main_layout->setMargin(0); + main_layout->setContentsMargins(0, 0, 0, 0); int row = 0; diff --git a/app/dialog/progress/progress.cpp b/app/dialog/progress/progress.cpp index c63372f94..45d374498 100644 --- a/app/dialog/progress/progress.cpp +++ b/app/dialog/progress/progress.cpp @@ -55,7 +55,7 @@ ProgressDialog::ProgressDialog(const QString& message, const QString& title, QWi QHBoxLayout* cancel_layout = new QHBoxLayout(); layout->addLayout(cancel_layout); - cancel_layout->setMargin(0); + cancel_layout->setContentsMargins(0, 0, 0, 0); cancel_layout->setSpacing(0); cancel_layout->addStretch(); diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp index eb3942068..68446fd16 100644 --- a/app/dialog/sequence/sequencedialogpresettab.cpp +++ b/app/dialog/sequence/sequencedialogpresettab.cpp @@ -47,7 +47,7 @@ SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) : PresetManager(this, QStringLiteral("sequencepresets")) { QVBoxLayout* outer_layout = new QVBoxLayout(this); - outer_layout->setMargin(0); + outer_layout->setContentsMargins(0, 0, 0, 0); preset_tree_ = new QTreeWidget(); preset_tree_->setColumnCount(1); diff --git a/app/dialog/task/task.cpp b/app/dialog/task/task.cpp index 41cfc55a4..2cc6fc063 100644 --- a/app/dialog/task/task.cpp +++ b/app/dialog/task/task.cpp @@ -56,7 +56,7 @@ void TaskDialog::showEvent(QShowEvent *e) this, &TaskDialog::TaskFinished, Qt::QueuedConnection); // Run task in another thread with QtConcurrent - task_watcher->setFuture(QtConcurrent::run(task_, &Task::Start)); + task_watcher->setFuture(QtConcurrent::run(&Task::Start, task_)); already_shown_ = true; } diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index b19934615..26e1e3577 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -297,7 +297,7 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat adjusted_matrix.scale(2.0 / sequence_res.x(), 2.0 / sequence_res.y(), 1.0); // Apply offset if applicable - adjusted_matrix.translate(offset); + adjusted_matrix.translate(offset.x(), offset.y()); // Adjust by the matrix we generated earlier adjusted_matrix *= mat; @@ -358,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.scale(sequence_half_res.x(), sequence_half_res.y()); rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()), sequence_res, tex_sz, @@ -378,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.scale(sequence_half_res.x(), sequence_half_res.y()); anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false, row[kParentInput].toMatrix()), sequence_res, tex_sz, diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index 81dbdb8f6..0d8a112d4 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -128,7 +128,7 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos, QMatrix4x4 mat) { // Position - mat.translate(pos); + mat.translate(pos.x(), pos.y()); // Rotation mat.rotate(rot, 0, 0, 1); @@ -143,7 +143,7 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos, mat.scale(full_scale); // Anchor Point - mat.translate(-anchor); + mat.translate(-anchor.x(), -anchor.y()); return mat; } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 7063c2691..2ae9594ae 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -141,9 +141,9 @@ QVector4D MathNodeBase::RetrieveVector(const NodeValue &val) // QVariant doesn't know that QVector*D can convert themselves so we do it here switch (val.type()) { case NodeValue::kVec2: - return val.toVec2(); + return QVector4D(val.toVec2()); case NodeValue::kVec3: - return val.toVec3(); + return QVector4D(val.toVec3()); case NodeValue::kVec4: default: return val.toVec4(); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 513990302..6708911df 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -570,13 +570,13 @@ void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, Q } else if (reader->name() == QStringLiteral("caches")) { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("audio")) { - node->audio_playback_cache()->SetUuid(reader->readElementText()); + node->audio_playback_cache()->SetUuid(QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("video")) { - node->video_frame_cache()->SetUuid(reader->readElementText()); + node->video_frame_cache()->SetUuid(QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("thumb")) { - node->thumbnail_cache()->SetUuid(reader->readElementText()); + node->thumbnail_cache()->SetUuid(QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("waveform")) { - node->waveform_cache()->SetUuid(reader->readElementText()); + node->waveform_cache()->SetUuid(QUuid::fromString(reader->readElementText())); } else { reader->skipCurrentElement(); } diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index dcbb72fe3..1b1bf5167 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -40,7 +40,7 @@ ProjectPanel::ProjectPanel(QWidget *parent) : // Create main widget and its layout QWidget* central_widget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(central_widget); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); SetWidgetWithPadding(central_widget); diff --git a/app/panel/scope/scope.cpp b/app/panel/scope/scope.cpp index 27700bb5c..9117734d3 100644 --- a/app/panel/scope/scope.cpp +++ b/app/panel/scope/scope.cpp @@ -36,7 +36,7 @@ ScopePanel::ScopePanel(QWidget* parent) : QVBoxLayout* layout = new QVBoxLayout(central); QHBoxLayout* toolbar_layout = new QHBoxLayout(); - toolbar_layout->setMargin(0); + toolbar_layout->setContentsMargins(0, 0, 0, 0); scope_type_combobox_ = new QComboBox(); diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index b10220dab..27733d569 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -29,6 +29,11 @@ TimeBasedPanel::TimeBasedPanel(const QString &object_name, QWidget *parent) : { } +TimeBasedPanel::~TimeBasedPanel() +{ + delete widget_; +} + rational TimeBasedPanel::GetTime() { return widget_->GetTime(); diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index 4101e6b09..184c69dd8 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -32,6 +32,8 @@ class TimeBasedPanel : public PanelWidget public: TimeBasedPanel(const QString& object_name, QWidget *parent = nullptr); + virtual ~TimeBasedPanel() override; + void ConnectViewerNode(ViewerOutput *node); void DisconnectViewerNode() diff --git a/app/render/texture.h b/app/render/texture.h index 942c0c4f6..a184edb15 100644 --- a/app/render/texture.h +++ b/app/render/texture.h @@ -22,6 +22,7 @@ #define RENDERTEXTURE_H #include +#include #include "render/videoparams.h" diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index db2fd434f..b13be24fb 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -35,7 +35,7 @@ ProjectImportTask::ProjectImportTask(Folder *folder, const QStringList &filename folder_(folder) { foreach (const QString& f, filenames) { - filenames_.append(f); + filenames_.append(QFileInfo(f)); } file_count_ = Core::CountFilesInFileList(filenames_); diff --git a/app/task/taskmanager.cpp b/app/task/taskmanager.cpp index 26730a0f8..d961a7b5a 100644 --- a/app/task/taskmanager.cpp +++ b/app/task/taskmanager.cpp @@ -93,7 +93,7 @@ void TaskManager::AddTask(Task* t) tasks_.insert(watcher, t); // Run task concurrently - watcher->setFuture(QtConcurrent::run(t, &Task::Start)); + watcher->setFuture(QtConcurrent::run(&Task::Start, t)); // Emit signal that a Task was added emit TaskAdded(t); diff --git a/app/ui/style/style.cpp b/app/ui/style/style.cpp index a8038c4c3..3cf32e4bf 100644 --- a/app/ui/style/style.cpp +++ b/app/ui/style/style.cpp @@ -36,7 +36,6 @@ namespace olive { const char* StyleManager::kDefaultStyle = "olive-dark"; QString StyleManager::current_style_; QMap StyleManager::available_themes_; -QPalette StyleManager::platform_palette_; QPalette StyleManager::ParsePalette(const QString& ini_path) { @@ -127,10 +126,6 @@ void StyleManager::ParsePaletteColor(QSettings *ini, QPalette *palette, QPalette void StyleManager::Init() { - // Store standard palette before replacing it with our own - platform_palette_ = qApp->palette(); - platform_palette_.resolve(-1); - qApp->setStyle(QStyleFactory::create("Fusion")); available_themes_.insert(QStringLiteral("olive-dark"), QStringLiteral("Olive Dark")); diff --git a/app/ui/style/style.h b/app/ui/style/style.h index 0ae18332d..860693914 100644 --- a/app/ui/style/style.h +++ b/app/ui/style/style.h @@ -54,8 +54,6 @@ private: static QMap available_themes_; - static QPalette platform_palette_; - }; } diff --git a/app/widget/colorwheel/colorvalueswidget.cpp b/app/widget/colorwheel/colorvalueswidget.cpp index 4ba54df49..bfdaf7d1e 100644 --- a/app/widget/colorwheel/colorvalueswidget.cpp +++ b/app/widget/colorwheel/colorvalueswidget.cpp @@ -44,7 +44,7 @@ ColorValuesWidget::ColorValuesWidget(ColorManager *manager, QWidget *parent) : { QHBoxLayout* preview_layout = new QHBoxLayout(); - preview_layout->setMargin(0); + preview_layout->setContentsMargins(0, 0, 0, 0); preview_layout->addWidget(new QLabel(tr("Preview"))); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 25f2d83a2..44ea426ec 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -53,7 +53,7 @@ CurveWidget::CurveWidget(QWidget *parent) : QWidget* workarea = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(workarea); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); splitter->addWidget(workarea); QHBoxLayout* top_controls = new QHBoxLayout(); @@ -86,7 +86,7 @@ CurveWidget::CurveWidget(QWidget *parent) : // We use a separate layout for the ruler+view combination so that there's no spacing between them QVBoxLayout* ruler_view_layout = new QVBoxLayout(); - ruler_view_layout->setMargin(0); + ruler_view_layout->setContentsMargins(0, 0, 0, 0); ruler_view_layout->setSpacing(0); ruler_view_layout->addWidget(ruler()); diff --git a/app/widget/filefield/filefield.cpp b/app/widget/filefield/filefield.cpp index dde16dd36..4d40eae7c 100644 --- a/app/widget/filefield/filefield.cpp +++ b/app/widget/filefield/filefield.cpp @@ -34,7 +34,7 @@ FileField::FileField(QWidget* parent) : { QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); line_edit_ = new QLineEdit(); connect(line_edit_, &QLineEdit::textChanged, this, &FileField::LineEditChanged); diff --git a/app/widget/flowlayout/flowlayout.cpp b/app/widget/flowlayout/flowlayout.cpp index 2c9aed8ac..c226a2f2f 100644 --- a/app/widget/flowlayout/flowlayout.cpp +++ b/app/widget/flowlayout/flowlayout.cpp @@ -146,7 +146,7 @@ QSize FlowLayout::minimumSize() const foreach (item, itemList) size = size.expandedTo(item->minimumSize()); - size += QSize(2*margin(), 2*margin()); + size += QSize(2*contentsMargins().left(), 2*contentsMargins().top()); return size; } diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index a5446162a..2e26be7c8 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -38,7 +38,7 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : { QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { // Create OpenGL widget @@ -312,11 +312,11 @@ bool ManagedDisplayWidget::eventFilter(QObject *o, QEvent *e) { // HACK: QWindows don't seem to receive ContextMenu events on right click (only when pressing // the menu button on the keyboard) so we handle it manually here - QMouseEvent *ev = static_cast(e); + /*QMouseEvent *ev = static_cast(e); if (ev->button() == Qt::RightButton) { emit customContextMenuRequested(ev->pos()); return true; - } + }*/ break; } default: diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index c26008e77..5dceb3fc1 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -48,6 +48,15 @@ class ManagedDisplayWidgetOpenGL public: ManagedDisplayWidgetOpenGL() = default; + virtual ~ManagedDisplayWidgetOpenGL() override + { + if (context()) { + DestroyListener(); + disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, + this, &ManagedDisplayWidgetOpenGL::DestroyListener); + } + } + signals: // Render signals void OnInit(); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 1ff70f954..7e6ee594f 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -20,6 +20,8 @@ #include "menushared.h" +#include + #include "core.h" #include "common/timecodefunctions.h" #include "panel/panelmanager.h" diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 15fd86e67..7cd492b7b 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -46,7 +46,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); QSplitter* splitter = new QSplitter(Qt::Horizontal); layout->addWidget(splitter); @@ -113,7 +113,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : QWidget* keyframe_area = new QWidget(); QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area); keyframe_area_layout->setSpacing(0); - keyframe_area_layout->setMargin(0); + keyframe_area_layout->setContentsMargins(0, 0, 0, 0); // Create ruler object keyframe_area_layout->addWidget(ruler()); diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index 937037219..a94d8e9c2 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -37,7 +37,7 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, connected_node_(nullptr) { QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); QSizePolicy p = sizePolicy(); p.setHorizontalStretch(1); @@ -47,7 +47,7 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, // Set up label area QHBoxLayout *label_layout = new QHBoxLayout(); label_layout->setSpacing(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" "))); - label_layout->setMargin(0); + label_layout->setContentsMargins(0, 0, 0, 0); layout->addLayout(label_layout); CollapseButton *collapse_btn = new CollapseButton(this); diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp index ea29b036e..7208e5fc7 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp @@ -33,7 +33,7 @@ NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align, QWi QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); if (right_align) { diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index 6e3b291cc..95c533bd5 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -31,7 +31,7 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); line_edit_ = new QPlainTextEdit(); line_edit_->setUndoRedoEnabled(true); diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index b5b4db823..4df011b58 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -567,7 +567,7 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant & } } else { // set specific track/widget bool ok; - int element = key.midRef(7).toInt(&ok); + int element = key.mid(7).toInt(&ok); int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); if (ok && element >= 0 && element < tracks) { @@ -686,7 +686,7 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant & } } else { bool ok; - int element = key.midRef(5).toInt(&ok); + int element = key.mid(5).toInt(&ok); if (ok && element >= 0 && element < tracks) { static_cast(widgets_.at(element))->SetColor(c); } diff --git a/app/widget/nodetableview/nodetablewidget.cpp b/app/widget/nodetableview/nodetablewidget.cpp index 48091357f..2ebb1eac8 100644 --- a/app/widget/nodetableview/nodetablewidget.cpp +++ b/app/widget/nodetableview/nodetablewidget.cpp @@ -29,7 +29,7 @@ NodeTableWidget::NodeTableWidget(QWidget* parent) : { QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); view_ = new NodeTableView(); layout->addWidget(view_); diff --git a/app/widget/nodeview/nodeviewtoolbar.cpp b/app/widget/nodeview/nodeviewtoolbar.cpp index 6ffaa6d35..7f3712a5b 100644 --- a/app/widget/nodeview/nodeviewtoolbar.cpp +++ b/app/widget/nodeview/nodeviewtoolbar.cpp @@ -13,7 +13,7 @@ NodeViewToolBar::NodeViewToolBar(QWidget *parent) : QWidget(parent) { QHBoxLayout *layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); add_node_btn_ = new QPushButton(); connect(add_node_btn_, &QPushButton::clicked, this, &NodeViewToolBar::AddNodeClicked); diff --git a/app/widget/nodeview/nodewidget.cpp b/app/widget/nodeview/nodewidget.cpp index 7ee2d4097..7fe526a8e 100644 --- a/app/widget/nodeview/nodewidget.cpp +++ b/app/widget/nodeview/nodewidget.cpp @@ -28,7 +28,7 @@ NodeWidget::NodeWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *outer_layout = new QVBoxLayout(this); - outer_layout->setMargin(0); + outer_layout->setContentsMargins(0, 0, 0, 0); toolbar_ = new NodeViewToolBar(); outer_layout->addWidget(toolbar_); diff --git a/app/widget/panel/panel.cpp b/app/widget/panel/panel.cpp index f3840fc06..bcc3365e2 100644 --- a/app/widget/panel/panel.cpp +++ b/app/widget/panel/panel.cpp @@ -158,7 +158,7 @@ void PanelWidget::SetWidgetWithPadding(QWidget *widget) { QWidget* wrapper = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(wrapper); - layout->setMargin(layout->margin() / 2); + layout->setContentsMargins(layout->contentsMargins() / 2); layout->addWidget(widget); setWidget(wrapper); } diff --git a/app/widget/path/pathwidget.cpp b/app/widget/path/pathwidget.cpp index 79504273f..3035822ac 100644 --- a/app/widget/path/pathwidget.cpp +++ b/app/widget/path/pathwidget.cpp @@ -32,7 +32,7 @@ PathWidget::PathWidget(const QString &path, QWidget *parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); path_edit_ = new QLineEdit(); path_edit_->setText(path); diff --git a/app/widget/pixelsampler/pixelsampler.cpp b/app/widget/pixelsampler/pixelsampler.cpp index 81ee86d6c..5a3128e1a 100644 --- a/app/widget/pixelsampler/pixelsampler.cpp +++ b/app/widget/pixelsampler/pixelsampler.cpp @@ -68,7 +68,7 @@ ManagedPixelSamplerWidget::ManagedPixelSamplerWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); display_view_ = new PixelSamplerWidget(); display_view_->setTitle(tr("Display")); diff --git a/app/widget/playbackcontrols/playbackcontrols.cpp b/app/widget/playbackcontrols/playbackcontrols.cpp index ea598b734..a87904e9e 100644 --- a/app/widget/playbackcontrols/playbackcontrols.cpp +++ b/app/widget/playbackcontrols/playbackcontrols.cpp @@ -37,7 +37,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) : // Create lower controls QHBoxLayout* lower_control_layout = new QHBoxLayout(this); lower_control_layout->setSpacing(0); - lower_control_layout->setMargin(0); + lower_control_layout->setContentsMargins(0, 0, 0, 0); QSizePolicy lower_container_size_policy(QSizePolicy::Maximum, QSizePolicy::Expanding); lower_container_size_policy.setHorizontalStretch(1); @@ -51,7 +51,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) : QHBoxLayout* lower_left_layout = new QHBoxLayout(lower_left_container_); lower_left_layout->setSpacing(0); - lower_left_layout->setMargin(0); + lower_left_layout->setContentsMargins(0, 0, 0, 0); cur_tc_lbl_ = new RationalSlider(); cur_tc_lbl_->SetDisplayType(RationalSlider::kTime); @@ -73,7 +73,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) : QHBoxLayout* lower_middle_layout = new QHBoxLayout(lower_middle_container); lower_middle_layout->setSpacing(0); - lower_middle_layout->setMargin(0); + lower_middle_layout->setContentsMargins(0, 0, 0, 0); lower_middle_layout->addStretch(); QSizePolicy btn_sz_policy(QSizePolicy::Maximum, QSizePolicy::Preferred); @@ -124,7 +124,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) : av_btn_widget->setSizePolicy(lower_container_size_policy); QHBoxLayout* av_btn_layout = new QHBoxLayout(av_btn_widget); av_btn_layout->setSpacing(0); - av_btn_layout->setMargin(0); + av_btn_layout->setContentsMargins(0, 0, 0, 0); video_drag_btn_ = new DragButton(); connect(video_drag_btn_, &QPushButton::clicked, this, &PlaybackControls::VideoClicked); connect(video_drag_btn_, &DragButton::MousePressed, this, &PlaybackControls::VideoPressed); @@ -143,7 +143,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) : QHBoxLayout* lower_right_layout = new QHBoxLayout(lower_right_container_); lower_right_layout->setSpacing(0); - lower_right_layout->setMargin(0); + lower_right_layout->setContentsMargins(0, 0, 0, 0); lower_right_layout->addStretch(); end_tc_lbl_ = new QLabel(); diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 48cc8e3aa..e6089e1c1 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -53,7 +53,7 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) : // Create layout QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); // Set up navigation bar nav_bar_ = new ProjectExplorerNavigation(this); diff --git a/app/widget/projectexplorer/projectexplorernavigation.cpp b/app/widget/projectexplorer/projectexplorernavigation.cpp index ed635f19a..35c5e2210 100644 --- a/app/widget/projectexplorer/projectexplorernavigation.cpp +++ b/app/widget/projectexplorer/projectexplorernavigation.cpp @@ -33,7 +33,7 @@ ProjectExplorerNavigation::ProjectExplorerNavigation(QWidget *parent) : { // Create widget layout QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); // Create "directory up" button dir_up_btn_ = new QPushButton(this); diff --git a/app/widget/projecttoolbar/projecttoolbar.cpp b/app/widget/projecttoolbar/projecttoolbar.cpp index a99a7340c..47c61a69a 100644 --- a/app/widget/projecttoolbar/projecttoolbar.cpp +++ b/app/widget/projecttoolbar/projecttoolbar.cpp @@ -33,7 +33,7 @@ ProjectToolbar::ProjectToolbar(QWidget *parent) : { QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); new_button_ = new QPushButton(); connect(new_button_, &QPushButton::clicked, this, &ProjectToolbar::NewClicked); diff --git a/app/widget/slider/base/sliderbase.cpp b/app/widget/slider/base/sliderbase.cpp index 1f132e3e6..ef58ce8ed 100644 --- a/app/widget/slider/base/sliderbase.cpp +++ b/app/widget/slider/base/sliderbase.cpp @@ -103,14 +103,26 @@ void SliderBase::changeEvent(QEvent *e) super::changeEvent(e); } +bool SliderBase::GetLabelSubstitution(const QVariant &v, QString *out) const +{ + for (auto it=label_substitutions_.constBegin(); it!=label_substitutions_.constEnd(); it++) { + if (it->first == v) { + *out = it->second; + return true; + } + } + + return false; +} + void SliderBase::UpdateLabel() { QString s; if (tristate_) { s = tr("---"); - } else if (label_substitutions_.contains(GetValueInternal())) { - s = label_substitutions_.value(GetValueInternal()); + } else if (GetLabelSubstitution(GetValueInternal(), &s)) { + // String will already be set, just pass through } else { s = GetFormattedValueToString(); } diff --git a/app/widget/slider/base/sliderbase.h b/app/widget/slider/base/sliderbase.h index 3bf9eb901..9828bb848 100644 --- a/app/widget/slider/base/sliderbase.h +++ b/app/widget/slider/base/sliderbase.h @@ -51,7 +51,7 @@ public: void InsertLabelSubstitution(const QVariant &value, const QString &label) { - label_substitutions_.insert(value, label); + label_substitutions_.append({value, label}); UpdateLabel(); } @@ -90,6 +90,8 @@ protected: virtual void changeEvent(QEvent* e) override; private: + bool GetLabelSubstitution(const QVariant &v, QString *out) const; + SliderLabel* label_; FocusableLineEdit* editor_; @@ -103,7 +105,7 @@ private: bool format_plural_; - QMap label_substitutions_; + QVector > label_substitutions_; private slots: void LineEditConfirmed(); diff --git a/app/widget/slider/base/sliderladder.cpp b/app/widget/slider/base/sliderladder.cpp index 5c927f61a..0ad993039 100644 --- a/app/widget/slider/base/sliderladder.cpp +++ b/app/widget/slider/base/sliderladder.cpp @@ -42,7 +42,7 @@ SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values, QString QFrame(parent, Qt::Popup) { QVBoxLayout* layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); setFrameShape(QFrame::Box); diff --git a/app/widget/standardcombos/frameratecombobox.h b/app/widget/standardcombos/frameratecombobox.h index d5ed19c05..ddd12d47a 100644 --- a/app/widget/standardcombos/frameratecombobox.h +++ b/app/widget/standardcombos/frameratecombobox.h @@ -43,7 +43,7 @@ public: QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(inner_); RepopulateList(); diff --git a/app/widget/taskview/elapsedcounterwidget.cpp b/app/widget/taskview/elapsedcounterwidget.cpp index cc191239b..288bae977 100644 --- a/app/widget/taskview/elapsedcounterwidget.cpp +++ b/app/widget/taskview/elapsedcounterwidget.cpp @@ -34,7 +34,7 @@ ElapsedCounterWidget::ElapsedCounterWidget(QWidget* parent) : { QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(layout->spacing() * 8); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); elapsed_lbl_ = new QLabel(); layout->addWidget(elapsed_lbl_); diff --git a/app/widget/taskview/taskview.cpp b/app/widget/taskview/taskview.cpp index ff0cd0e54..92a414191 100644 --- a/app/widget/taskview/taskview.cpp +++ b/app/widget/taskview/taskview.cpp @@ -37,7 +37,7 @@ TaskView::TaskView(QWidget* parent) : // Create layout for central widget layout_ = new QVBoxLayout(central_widget_); layout_->setSpacing(0); - layout_->setMargin(0); + layout_->setContentsMargins(0, 0, 0, 0); // Add a "stretch" so that TaskViewItems don't try to expand all the way to the bottom layout_->addStretch(); diff --git a/app/widget/timelinewidget/timelineandtrackview.cpp b/app/widget/timelinewidget/timelineandtrackview.cpp index a9ce5bbcd..d5fb5cbb8 100644 --- a/app/widget/timelinewidget/timelineandtrackview.cpp +++ b/app/widget/timelinewidget/timelineandtrackview.cpp @@ -30,7 +30,7 @@ TimelineAndTrackView::TimelineAndTrackView(Qt::Alignment vertical_alignment, QWi { QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); splitter_ = new QSplitter(Qt::Horizontal); splitter_->setChildrenCollapsible(false); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 50fc57a5c..562faa7e8 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -73,7 +73,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : { QVBoxLayout* vert_layout = new QVBoxLayout(this); vert_layout->setSpacing(0); - vert_layout->setMargin(0); + vert_layout->setContentsMargins(0, 0, 0, 0); QHBoxLayout* ruler_and_time_layout = new QHBoxLayout(); vert_layout->addLayout(ruler_and_time_layout); diff --git a/app/widget/timelinewidget/trackview/trackview.cpp b/app/widget/timelinewidget/trackview/trackview.cpp index 562c68d55..65bf429ec 100644 --- a/app/widget/timelinewidget/trackview/trackview.cpp +++ b/app/widget/timelinewidget/trackview/trackview.cpp @@ -42,7 +42,7 @@ TrackView::TrackView(Qt::Alignment vertical_alignment, QWidget *parent) : setWidgetResizable(true); QVBoxLayout* layout = new QVBoxLayout(central); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); if (alignment_ == Qt::AlignBottom) { diff --git a/app/widget/timelinewidget/trackview/trackviewitem.cpp b/app/widget/timelinewidget/trackview/trackviewitem.cpp index 6a5ba698e..e0b1eab5d 100644 --- a/app/widget/timelinewidget/trackview/trackviewitem.cpp +++ b/app/widget/timelinewidget/trackview/trackviewitem.cpp @@ -40,7 +40,7 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) : { QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); stack_ = new QStackedWidget(); layout->addWidget(stack_); diff --git a/app/widget/toolbar/toolbar.cpp b/app/widget/toolbar/toolbar.cpp index 900e6532b..50989d68e 100644 --- a/app/widget/toolbar/toolbar.cpp +++ b/app/widget/toolbar/toolbar.cpp @@ -38,7 +38,7 @@ Toolbar::Toolbar(QWidget *parent) : super(parent) { layout_ = new FlowLayout(this); - layout_->setMargin(0); + layout_->setContentsMargins(0, 0, 0, 0); // Create standard tool buttons btn_pointer_tool_ = CreateToolButton(Tool::kPointer); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 8447734af..8cb4681b5 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -82,7 +82,7 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : { // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); // Create main OpenGL-based view and sizer sizer_ = new ViewerSizer(); @@ -166,6 +166,9 @@ ViewerWidget::~ViewerWidget() foreach (ViewerWindow* window, windows) { delete window; } + + delete display_widget_; + display_widget_ = nullptr; } void ViewerWidget::TimeChangedEvent(const rational &time) @@ -1146,7 +1149,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t) // Frame has been cached, grab the frame RenderTicketPtr ticket = std::make_shared(); ticket->setProperty("time", QVariant::fromValue(t)); - QtConcurrent::run(ViewerWidget::DecodeCachedImage, ticket, GetConnectedNode()->video_frame_cache()->GetCacheDirectory(), GetConnectedNode()->video_frame_cache()->GetUuid(), Timecode::time_to_timestamp(t, timebase(), Timecode::kFloor)); + QtConcurrent::run(static_cast(ViewerWidget::DecodeCachedImage), ticket, GetConnectedNode()->video_frame_cache()->GetCacheDirectory(), GetConnectedNode()->video_frame_cache()->GetUuid(), Timecode::time_to_timestamp(t, timebase(), Timecode::kFloor)); return ticket; } } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index aea700d51..6e42addde 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -1051,7 +1051,7 @@ void ViewerDisplayWidget::DrawSubtitleTracks() f.setFamily(family); } - f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt()); + f.setWeight(static_cast(OLIVE_CONFIG("DefaultSubtitleWeight").toInt())); bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); @@ -1145,7 +1145,7 @@ void ViewerDisplayWidget::ForwardDragEventToTextEdit(T *e) bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool check_if_outside) { // Transform screen mouse coords to world mouse coords - QPointF local_pos = GetVirtualPosForTextEdit(event->localPos()); + QPointF local_pos = GetVirtualPosForTextEdit(event->position()); if (check_if_outside) { if (local_pos.x() < 0 || local_pos.x() >= text_edit_->width() || local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) { @@ -1156,8 +1156,8 @@ bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool c local_pos = AdjustPosByVAlign(local_pos); - event->setLocalPos(local_pos); - return ForwardEventToTextEdit(event); + QMouseEvent derived(event->type(), local_pos, event->scenePosition(), event->globalPosition(), event->button(), event->buttons(), event->modifiers(), event->source(), event->pointingDevice()); + return ForwardEventToTextEdit(&derived); } bool ViewerDisplayWidget::ForwardEventToTextEdit(QEvent *event) diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h index 053d8f013..1f529bf55 100644 --- a/app/widget/viewer/viewerqueue.h +++ b/app/widget/viewer/viewerqueue.h @@ -21,8 +21,6 @@ #ifndef VIEWERQUEUE_H #define VIEWERQUEUE_H -#include - #include "codec/frame.h" namespace olive { diff --git a/app/widget/viewer/viewerwindow.cpp b/app/widget/viewer/viewerwindow.cpp index be582b139..41e43462f 100644 --- a/app/widget/viewer/viewerwindow.cpp +++ b/app/widget/viewer/viewerwindow.cpp @@ -32,7 +32,7 @@ ViewerWindow::ViewerWindow(QWidget *parent) : pixel_aspect_(1) { QVBoxLayout* layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); display_widget_ = new ViewerDisplayWidget(); diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 22ae0e70d..873c746d7 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -20,6 +20,7 @@ #include "mainmenu.h" +#include #include #include #include diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 20adf7800..8c766e444 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include From d5f645730ae97b8004b6fefcdb353540ff367d82 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:46:48 -0700 Subject: [PATCH 31/85] cmake: allow qt6 and qt5 matching --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c15a6b67..97567dd55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,7 @@ set(QT_LIBRARIES if (UNIX AND NOT APPLE) list(APPEND QT_LIBRARIES DBus) endif() -find_package(QT NAMES Qt6 REQUIRED +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS ${QT_LIBRARIES} OPTIONAL_COMPONENTS From 5c8b955b72b8c36efdde9146f84fab2b033e0485 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:52:05 -0700 Subject: [PATCH 32/85] cmake: only match OpenGLWidgets on qt6 --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97567dd55..556db474d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,7 +119,6 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED OPTIONAL_COMPONENTS Network ) -message("Qt version: " ${QT_VERSION_MAJOR}) if (NOT Qt${QT_VERSION_MAJOR}Network_FOUND) message(" Qt${QT_VERSION_MAJOR}::Network module not found, crash reporting will be disabled.") endif() @@ -129,9 +128,14 @@ list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGL Qt${QT_VERSION_MAJOR}::Concurrent - Qt${QT_VERSION_MAJOR}::OpenGLWidgets ) +if (${QT_VERSION_MAJOR} EQUAL "6") + list(APPEND OLIVE_LIBRARIES + Qt${QT_VERSION_MAJOR}::OpenGLWidgets + ) +endif() + # Link FFmpeg find_package(FFMPEG 3.0 REQUIRED COMPONENTS From 6b806ba3a02afdf646ece58cf4d32060611998fd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:57:26 -0700 Subject: [PATCH 33/85] cmake: only find OpenGLWidgets on qt6 --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 556db474d..c793ca083 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,7 +102,6 @@ set(QT_LIBRARIES OpenGL LinguistTools Concurrent - OpenGLWidgets ) if (UNIX AND NOT APPLE) list(APPEND QT_LIBRARIES DBus) @@ -131,6 +130,11 @@ list(APPEND OLIVE_LIBRARIES ) if (${QT_VERSION_MAJOR} EQUAL "6") + find_package(Qt${QT_VERSION_MAJOR} + REQUIRED + OpenGLWidgets + ) + list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::OpenGLWidgets ) From ce650085a374208f0af3a8692653c93d0ff38162 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 29 Oct 2022 09:46:22 -0700 Subject: [PATCH 34/85] text: use fontFamilies() instead of fontFamily() --- app/common/html.cpp | 7 ++++--- app/widget/viewer/viewertexteditor.cpp | 11 ++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/common/html.cpp b/app/common/html.cpp index 143d76544..74cc805f9 100644 --- a/app/common/html.cpp +++ b/app/common/html.cpp @@ -208,8 +208,9 @@ void Html::WriteCSSProperty(QString *style, const QString &key, const QStringLis void Html::WriteCharFormat(QString *style, const QTextCharFormat &fmt) { - if (!fmt.fontFamily().isEmpty()) { - WriteCSSProperty(style, QStringLiteral("font-family"), fmt.fontFamily()); + QStringList families = fmt.fontFamilies().toStringList(); + if (!families.isEmpty()) { + WriteCSSProperty(style, QStringLiteral("font-family"), families.first()); } if (fmt.hasProperty(QTextFormat::FontPointSize)) { @@ -287,7 +288,7 @@ QTextCharFormat Html::ReadCharFormat(const QXmlStreamAttributes &attributes) const QString &first_val = it.value().first(); if (it.key() == QStringLiteral("font-family")) { - fmt.setFontFamily(first_val); + fmt.setFontFamilies({first_val}); } else if (it.key() == QStringLiteral("font-size")) { if (first_val.endsWith(QStringLiteral("pt"), Qt::CaseInsensitive)) { fmt.setFontPointSize(first_val.chopped(2).toDouble()); diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 99da81967..06a41afb4 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -152,15 +152,16 @@ void ViewerTextEditor::paintEvent(QPaintEvent *e) void ViewerTextEditor::UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTextCharFormat &f, const QTextBlockFormat &b, Qt::Alignment alignment) { - QFontDatabase fd; - - QString family = f.fontFamily(); - if (family.isEmpty()) { + QStringList families = f.fontFamilies().toStringList(); + QString family; + if (families.isEmpty()) { family = qApp->font().family(); + } else { + family = families.first(); } QString style = f.fontStyleName().toString(); - QStringList styles = fd.styles(family); + QStringList styles = QFontDatabase::styles(family); if (!styles.isEmpty() && (style.isEmpty() || !styles.contains(style))) { // There seems to be no better way to find the "regular" style outside of this heuristic. // Feel free to add more if a font isn't working right. From 86573630377916de15dc87bfa19eb14cf595ac50 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 29 Oct 2022 09:46:57 -0700 Subject: [PATCH 35/85] update some deprecated qt api calls --- app/widget/handmovableview/handmovableview.cpp | 8 ++++++-- app/widget/viewer/viewerdisplay.cpp | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index f6cc9bc63..231a75a65 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -118,10 +118,14 @@ bool HandMovableView::HandRelease(QMouseEvent *event) if (dragging_hand_) { // Transform mouse event to act like the left button is pressed QMouseEvent transformed(event->type(), - event->localPos(), + event->position(), + event->scenePosition(), + event->globalPosition(), Qt::LeftButton, Qt::LeftButton, - event->modifiers()); + event->modifiers(), + event->source(), + event->pointingDevice()); super::mouseReleaseEvent(&transformed); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 6e42addde..c114b6620 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -1122,11 +1122,11 @@ void ViewerDisplayWidget::ForwardDragEventToTextEdit(T *e) if constexpr (std::is_same_v) { text_edit_->dragLeaveEvent(e); } else { - T relay(AdjustPosByVAlign(GetVirtualPosForTextEdit(e->posF())).toPoint(), + T relay(AdjustPosByVAlign(GetVirtualPosForTextEdit(e->position())).toPoint(), e->possibleActions(), e->mimeData(), - e->mouseButtons(), - e->keyboardModifiers()); + e->buttons(), + e->modifiers()); if (e->type() == QEvent::DragEnter) { text_edit_->dragEnterEvent(static_cast(&relay)); From 5fe6faf15ad95dfc515726bbde2cc667dd154849 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 29 Oct 2022 09:59:45 -0700 Subject: [PATCH 36/85] ffmpeg: slightly improve performance of yuv2rgb --- app/codec/ffmpeg/ffmpegdecoder.cpp | 8 ++++---- app/shaders/yuv2rgb.frag | 19 +++++++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index edb37d273..377fb5912 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -235,10 +235,10 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, hw_in->color_range == AVCOL_RANGE_JPEG)); 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])); - job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1])); + job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kFloat, yuv_coeffs[0]/65536.0)); + job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kFloat, yuv_coeffs[2]/65536.0)); + job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kFloat, yuv_coeffs[3]/65536.0)); + job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kFloat, yuv_coeffs[1]/65536.0)); int interlacing = 0; if (p.src_interlacing != VideoParams::kInterlaceNone) { diff --git a/app/shaders/yuv2rgb.frag b/app/shaders/yuv2rgb.frag index 8c40b5918..623badc4f 100644 --- a/app/shaders/yuv2rgb.frag +++ b/app/shaders/yuv2rgb.frag @@ -5,10 +5,10 @@ uniform sampler2D v_channel; uniform int bits_per_pixel; uniform bool full_range; -uniform int yuv_crv; -uniform int yuv_cgu; -uniform int yuv_cgv; -uniform int yuv_cbu; +uniform float yuv_crv; +uniform float yuv_cgu; +uniform float yuv_cgv; +uniform float yuv_cbu; uniform int interlacing; uniform int pixel_height; @@ -51,15 +51,10 @@ void main() yuv.b = yuv.b - 0.5; // 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; - 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; + rgba.r = yuv.r + yuv_crv * yuv.b; + rgba.g = yuv.r - yuv_cgu * yuv.g - yuv_cgv * yuv.b; + rgba.b = yuv.r + yuv_cbu * yuv.g; // If the expected value is full range, transform to full range here if (full_range) { From b5bd73c31406ef4b2202a3b40c1fca665713ade3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 29 Oct 2022 10:05:09 -0700 Subject: [PATCH 37/85] viewer: destroy text edit explicitly Fix assert failure --- app/widget/viewer/viewerdisplay.cpp | 7 +++++++ app/widget/viewer/viewerdisplay.h | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index c114b6620..fcbc6553d 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -81,6 +81,13 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : inner_widget()->setAcceptDrops(true); } +ViewerDisplayWidget::~ViewerDisplayWidget() +{ + delete text_edit_; + + MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR_INNER; +} + void ViewerDisplayWidget::SetMatrixTranslate(const QMatrix4x4 &mat) { translate_matrix_ = mat; diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index f2e89102f..a82bad747 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -68,7 +68,7 @@ public: */ ViewerDisplayWidget(QWidget* parent = nullptr); - MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(ViewerDisplayWidget) + virtual ~ViewerDisplayWidget() override; const ViewerSafeMarginInfo& GetSafeMargin() const; void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin); From 274687dbb3894220609081ffb90acd4d6ee83365 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 29 Oct 2022 11:24:12 -0700 Subject: [PATCH 38/85] viewer: clean up some font related functions --- app/widget/viewer/viewertexteditor.cpp | 11 +++-------- app/widget/viewer/viewertexteditor.h | 4 ---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 06a41afb4..8dcbdf6bc 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -40,7 +40,6 @@ ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) : super(parent), transparent_clone_(nullptr), block_update_toolbar_signal_(false), - listen_to_focus_events_(false), forced_default_(false) { // Ensure default text color is white @@ -208,10 +207,7 @@ void ViewerTextEditor::SetFamily(const QString &s) ViewerTextEditorToolBar *toolbar = static_cast(sender()); QTextCharFormat f; - f.setFontFamily(s); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) f.setFontFamilies({s}); -#endif ApplyStyle(&f, s, toolbar->GetFontStyleName()); @@ -271,9 +267,8 @@ void ViewerTextEditor::ApplyStyle(QTextCharFormat *format, const QString &family { // NOTE: Windows appears to require setting weight and italic manually, while macOS and Linux are // perfectly fine with just the style name - QFontDatabase fd; - format->setFontWeight(fd.weight(family, style)); - format->setFontItalic(fd.italic(family, style)); + format->setFontWeight(QFontDatabase::weight(family, style)); + format->setFontItalic(QFontDatabase::italic(family, style)); format->setFontStyleName(style); } @@ -541,7 +536,7 @@ void ViewerTextEditorToolBar::UpdateFontStyleList(const QString &family) style_combo_->blockSignals(true); style_combo_->clear(); - QStringList l = QFontDatabase().styles(family); + QStringList l = QFontDatabase::styles(family); foreach (const QString &style, l) { style_combo_->addItem(style); } diff --git a/app/widget/viewer/viewertexteditor.h b/app/widget/viewer/viewertexteditor.h index 8bef901b9..b58db6565 100644 --- a/app/widget/viewer/viewertexteditor.h +++ b/app/widget/viewer/viewertexteditor.h @@ -151,8 +151,6 @@ public: void ConnectToolBar(ViewerTextEditorToolBar *toolbar); - void SetListenToFocusEvents(bool e) { listen_to_focus_events_ = e; } - void Paint(QPainter *p, Qt::Alignment valign); virtual void dragEnterEvent(QDragEnterEvent *e) override { return QTextEdit::dragEnterEvent(e); } @@ -178,8 +176,6 @@ private: bool block_update_toolbar_signal_; - bool listen_to_focus_events_; - bool forced_default_; QTextCharFormat default_fmt_; From 972ec1e34038ad852a9573a04556162508637d55 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 29 Oct 2022 11:36:09 -0700 Subject: [PATCH 39/85] ffmpegencoder: catch error on sample array alloc --- app/codec/ffmpeg/ffmpegencoder.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index e989db229..a0f08ede6 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -289,11 +289,16 @@ bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) input_sample_count = audio.sample_count(); int input_linesize; - av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(), - input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), 0); + int r = av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(), + input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), 0); - for (int i=0; i Date: Sat, 29 Oct 2022 12:07:55 -0700 Subject: [PATCH 40/85] ffmpegencoder: split sample buffer when writing audio Avoids integer overflow in FFmpeg Fixes #2072 --- app/codec/ffmpeg/ffmpegencoder.cpp | 32 ++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index a0f08ede6..403bfab71 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -280,13 +280,20 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) { + if (!audio.is_allocated()) { + return true; + } + bool result = true; - // Create input buffer - int input_sample_count = 0; - uint8_t** input_data = nullptr; - if (audio.is_allocated()) { - input_sample_count = audio.sample_count(); + size_t start = 0; + size_t end = audio.sample_count(); + const size_t max_frame = 48000; + + while (start < end) { + // Create input buffer + uint8_t** input_data = nullptr; + size_t input_sample_count = std::min(end - start, max_frame); int input_linesize; int r = av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(), @@ -296,17 +303,20 @@ bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) FFmpegError(tr("Failed to allocate sample array"), r); return false; } else { + int bpsc = audio.audio_params().bytes_per_sample_per_channel(); for (int i=0; i(input_data), input_sample_count); + result = WriteAudioData(audio.audio_params().is_valid() ? audio.audio_params() : params().audio_params(), const_cast(input_data), input_sample_count); - if (input_data) { - av_freep(&input_data[0]); - av_freep(&input_data); + if (input_data) { + av_freep(&input_data[0]); + av_freep(&input_data); + } } return result; From c5a86af181c741ee6420c3d75c542faf1974f55b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 31 Oct 2022 18:18:49 -0700 Subject: [PATCH 41/85] various: ensure qt 5 compatibility --- CMakeLists.txt | 6 +++++- app/dialog/task/task.cpp | 8 +++++++- app/task/taskmanager.cpp | 8 +++++++- app/widget/handmovableview/handmovableview.cpp | 17 ++++++++--------- app/widget/viewer/viewerdisplay.cpp | 11 ++++++----- app/widget/viewer/viewertexteditor.cpp | 8 ++++---- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c793ca083..04fc8ce8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,11 @@ set(QT_LIBRARIES if (UNIX AND NOT APPLE) list(APPEND QT_LIBRARIES DBus) endif() -find_package(QT NAMES Qt6 Qt5 REQUIRED +find_package(QT + NAMES + Qt6 + Qt5 + REQUIRED COMPONENTS ${QT_LIBRARIES} OPTIONAL_COMPONENTS diff --git a/app/dialog/task/task.cpp b/app/dialog/task/task.cpp index 2cc6fc063..aab2b2979 100644 --- a/app/dialog/task/task.cpp +++ b/app/dialog/task/task.cpp @@ -56,7 +56,13 @@ void TaskDialog::showEvent(QShowEvent *e) this, &TaskDialog::TaskFinished, Qt::QueuedConnection); // Run task in another thread with QtConcurrent - task_watcher->setFuture(QtConcurrent::run(&Task::Start, task_)); + task_watcher->setFuture( +#if QT_VERSION_MAJOR >= 6 + QtConcurrent::run(&Task::Start, task_) +#else + QtConcurrent::run(task_, &Task::Start) +#endif + ); already_shown_ = true; } diff --git a/app/task/taskmanager.cpp b/app/task/taskmanager.cpp index d961a7b5a..5d2be1036 100644 --- a/app/task/taskmanager.cpp +++ b/app/task/taskmanager.cpp @@ -93,7 +93,13 @@ void TaskManager::AddTask(Task* t) tasks_.insert(watcher, t); // Run task concurrently - watcher->setFuture(QtConcurrent::run(&Task::Start, t)); + watcher->setFuture( +#if QT_VERSION_MAJOR >= 6 + QtConcurrent::run(&Task::Start, t) +#else + QtConcurrent::run(t, &Task::Start) +#endif + ); // Emit signal that a Task was added emit TaskAdded(t); diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index 231a75a65..d7b748195 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -61,7 +61,7 @@ bool HandMovableView::HandPress(QMouseEvent *event) // Transform mouse event to act like the left button is pressed QMouseEvent transformed(event->type(), - event->localPos(), + event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); @@ -83,15 +83,15 @@ bool HandMovableView::HandMove(QMouseEvent *event) QPoint adjustment(0, 0); QMouseEvent transformed(event->type(), - event->localPos() - transformed_pos_, + event->pos() - transformed_pos_, Qt::LeftButton, Qt::LeftButton, event->modifiers()); - if (event->localPos().x() < 0) { + if (event->pos().x() < 0) { transformed_pos_.setX(transformed_pos_.x() + width()); adjustment.setX(width()); - } else if (event->localPos().x() >= width()) { + } else if (event->pos().x() >= width()) { transformed_pos_.setX(transformed_pos_.x() - width()); adjustment.setX(-width()); } @@ -118,14 +118,13 @@ bool HandMovableView::HandRelease(QMouseEvent *event) if (dragging_hand_) { // Transform mouse event to act like the left button is pressed QMouseEvent transformed(event->type(), - event->position(), - event->scenePosition(), - event->globalPosition(), + event->localPos(), + event->windowPos(), + event->screenPos(), Qt::LeftButton, Qt::LeftButton, event->modifiers(), - event->source(), - event->pointingDevice()); + event->source()); super::mouseReleaseEvent(&transformed); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index fcbc6553d..cd75d91e2 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -1129,11 +1129,12 @@ void ViewerDisplayWidget::ForwardDragEventToTextEdit(T *e) if constexpr (std::is_same_v) { text_edit_->dragLeaveEvent(e); } else { - T relay(AdjustPosByVAlign(GetVirtualPosForTextEdit(e->position())).toPoint(), + + T relay(AdjustPosByVAlign(GetVirtualPosForTextEdit(e->pos())).toPoint(), e->possibleActions(), e->mimeData(), - e->buttons(), - e->modifiers()); + e->mouseButtons(), + e->keyboardModifiers()); if (e->type() == QEvent::DragEnter) { text_edit_->dragEnterEvent(static_cast(&relay)); @@ -1152,7 +1153,7 @@ void ViewerDisplayWidget::ForwardDragEventToTextEdit(T *e) bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool check_if_outside) { // Transform screen mouse coords to world mouse coords - QPointF local_pos = GetVirtualPosForTextEdit(event->position()); + QPointF local_pos = GetVirtualPosForTextEdit(event->pos()); if (check_if_outside) { if (local_pos.x() < 0 || local_pos.x() >= text_edit_->width() || local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) { @@ -1163,7 +1164,7 @@ bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool c local_pos = AdjustPosByVAlign(local_pos); - QMouseEvent derived(event->type(), local_pos, event->scenePosition(), event->globalPosition(), event->button(), event->buttons(), event->modifiers(), event->source(), event->pointingDevice()); + QMouseEvent derived(event->type(), local_pos, event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers(), event->source()); return ForwardEventToTextEdit(&derived); } diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 8dcbdf6bc..7c8c27fad 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -160,7 +160,7 @@ void ViewerTextEditor::UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTe } QString style = f.fontStyleName().toString(); - QStringList styles = QFontDatabase::styles(family); + QStringList styles = QFontDatabase().styles(family); if (!styles.isEmpty() && (style.isEmpty() || !styles.contains(style))) { // There seems to be no better way to find the "regular" style outside of this heuristic. // Feel free to add more if a font isn't working right. @@ -267,8 +267,8 @@ void ViewerTextEditor::ApplyStyle(QTextCharFormat *format, const QString &family { // NOTE: Windows appears to require setting weight and italic manually, while macOS and Linux are // perfectly fine with just the style name - format->setFontWeight(QFontDatabase::weight(family, style)); - format->setFontItalic(QFontDatabase::italic(family, style)); + format->setFontWeight(QFontDatabase().weight(family, style)); + format->setFontItalic(QFontDatabase().italic(family, style)); format->setFontStyleName(style); } @@ -536,7 +536,7 @@ void ViewerTextEditorToolBar::UpdateFontStyleList(const QString &family) style_combo_->blockSignals(true); style_combo_->clear(); - QStringList l = QFontDatabase::styles(family); + QStringList l = QFontDatabase().styles(family); foreach (const QString &style, l) { style_combo_->addItem(style); } From 5e92930de0e9881dae88684711e44c91509ab8e6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:15:51 -0700 Subject: [PATCH 42/85] nodes: fix loop mode regression --- app/codec/decoder.cpp | 2 +- app/codec/decoder.h | 9 +-- .../speedduration/speeddurationdialog.cpp | 10 +-- app/node/block/clip/clip.cpp | 2 + app/node/block/clip/clip.h | 6 +- app/node/globals.h | 13 ++- app/node/project/footage/footage.cpp | 10 +-- app/node/project/footage/footage.h | 2 +- app/node/traverser.cpp | 13 +-- app/node/traverser.h | 10 +-- app/render/CMakeLists.txt | 1 + app/render/job/footagejob.h | 10 ++- app/render/loopmode.h | 14 ++++ .../timelinewidget/view/timelineview.cpp | 12 +-- app/widget/viewer/viewerdisplay.cpp | 81 ++++++++++--------- app/widget/viewer/viewerdisplay.h | 2 + 16 files changed, 111 insertions(+), 86 deletions(-) create mode 100644 app/render/loopmode.h diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 336aaa6f1..6fb2a2676 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -293,7 +293,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 == kLoopModeLoop) { + if (loop_mode == LoopMode::kLoopModeLoop) { while (read_index >= input.size()) { read_index -= input.size(); } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 029cefa25..471958bc9 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -31,12 +31,11 @@ extern "C" { #include #include -#include "codec/frame.h" #include "codec/samplebuffer.h" #include "common/rational.h" #include "node/block/block.h" #include "node/project/footage/footagedescription.h" -#include "task/task.h" +#include "render/cancelatom.h" namespace olive { @@ -71,12 +70,6 @@ public: kIndexUnavailable }; - enum LoopMode { - kLoopModeOff, - kLoopModeLoop, - kLoopModeClamp - }; - Decoder(); /** diff --git a/app/dialog/speedduration/speeddurationdialog.cpp b/app/dialog/speedduration/speeddurationdialog.cpp index 7214ca27c..8f186ed20 100644 --- a/app/dialog/speedduration/speeddurationdialog.cpp +++ b/app/dialog/speedduration/speeddurationdialog.cpp @@ -100,9 +100,9 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, cons 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_combo_->addItem(tr("None"), int(LoopMode::kLoopModeOff)); + loop_combo_->addItem(tr("Loop"), int(LoopMode::kLoopModeLoop)); + loop_combo_->addItem(tr("Clamp"), int(LoopMode::kLoopModeClamp)); loop_layout->addWidget(loop_combo_, row, 1); } @@ -117,7 +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(); + start_loop_ = int(clips.first()->loop_mode()); for (int i=1; i &clips, cons start_maintain_audio_pitch_ = -1; } - if (start_loop_ != -1 && c->loop_mode() != start_loop_) { + if (start_loop_ != -1 && int(c->loop_mode()) != start_loop_) { start_loop_ = -1; } } diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 110a26ee4..4d4bf1ebd 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -453,6 +453,8 @@ void ClipBlock::InputValueChangedEvent(const QString &input, int element) } } } + } else if (input == kLoopModeInput) { + emit PreviewChanged(); } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 15cadcfb7..ddbbd881a 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -188,12 +188,12 @@ public: /** * @brief Get currently set loop mode */ - Decoder::LoopMode loop_mode() const + LoopMode loop_mode() const { - return static_cast(GetStandardValue(kLoopModeInput).toInt()); + return static_cast(GetStandardValue(kLoopModeInput).toInt()); } - void set_loop_mode(Decoder::LoopMode l) + void set_loop_mode(LoopMode l) { SetStandardValue(kLoopModeInput, int(l)); } diff --git a/app/node/globals.h b/app/node/globals.h index c7ec1ce59..9c8eb26a9 100644 --- a/app/node/globals.h +++ b/app/node/globals.h @@ -25,6 +25,7 @@ #include "common/timerange.h" #include "render/audioparams.h" +#include "render/loopmode.h" #include "render/videoparams.h" namespace olive { @@ -34,10 +35,16 @@ class NodeGlobals public: NodeGlobals(){} - NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time) : + NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time, LoopMode loop_mode) : video_params_(vparam), audio_params_(aparam), - time_(time) + time_(time), + loop_mode_(loop_mode) + { + } + + NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const rational &time, LoopMode loop_mode) : + NodeGlobals(vparam, aparam, TimeRange(time, time + vparam.frame_rate_as_time_base()), loop_mode) { } @@ -46,11 +53,13 @@ public: const AudioParams &aparams() const { return audio_params_; } const VideoParams &vparams() const { return video_params_; } const TimeRange &time() const { return time_; } + LoopMode loop_mode() const { return loop_mode_; } private: VideoParams video_params_; AudioParams audio_params_; TimeRange time_; + LoopMode loop_mode_; }; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 210d448f5..26e41a5de 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -265,7 +265,7 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV // Push each stream as a footage job for (int i=0; i= length; } -rational Footage::AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase) +rational Footage::AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase) { if (type == VideoParams::kVideoTypeStill) { // No looping for still images @@ -346,15 +346,15 @@ rational Footage::AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mod if (TimeIsOutOfBounds(time, length)) { switch (loop_mode) { - case Decoder::kLoopModeOff: + case LoopMode::kLoopModeOff: // Return no time to indicate no frame should be shown here time = rational::NaN; break; - case Decoder::kLoopModeClamp: + case LoopMode::kLoopModeClamp: // Clamp footage time to length time = clamp(time, rational(0), length - timebase); break; - case Decoder::kLoopModeLoop: + case LoopMode::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 7eb38f3c5..321afa829 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -173,7 +173,7 @@ public: virtual Node *GetConnectedSampleOutput() override; - static rational AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); + static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); virtual void LoadFinishedEvent() override; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index ee1fa0fbd..e99be2d4f 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -32,7 +32,7 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa NodeValueDatabase database; // HACK: Pick up loop mode from clips - Decoder::LoopMode old_loop_mode = loop_mode_; + LoopMode old_loop_mode = loop_mode_; if (const ClipBlock *clip = dynamic_cast(node)) { loop_mode_ = clip->loop_mode(); } @@ -184,11 +184,6 @@ void NodeTraverser::Transform(QTransform *transform, const Node *start, const No transform_ = nullptr; } -NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time) -{ - return NodeGlobals(vparams, aparams, time); -} - NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) { // If input is connected, retrieve value directly @@ -259,7 +254,7 @@ void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const No NodeTraverser::NodeTraverser() : cancel_(nullptr), transform_(nullptr), - loop_mode_(Decoder::kLoopModeOff) + loop_mode_(LoopMode::kLoopModeOff) { } @@ -309,7 +304,7 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang table = database.Merge(); // By this point, the node should have all the inputs it needs to render correctly - NodeGlobals globals = GenerateGlobals(video_params_, audio_params_, range); + NodeGlobals globals(video_params_, audio_params_, range, loop_mode_); n->Value(row, globals, &table); // `transform_now_` is the next node in the path that needs to be traversed. It only ever goes @@ -430,7 +425,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val) } else if (FootageJob *fj = dynamic_cast(base_job)) { - rational footage_time = Footage::AdjustTimeByLoopMode(fj->time().in(), loop_mode_, fj->length(), fj->video_params().video_type(), fj->video_params().frame_rate_as_time_base()); + rational footage_time = Footage::AdjustTimeByLoopMode(fj->time().in(), fj->loop_mode(), fj->length(), fj->video_params().video_type(), fj->video_params().frame_rate_as_time_base()); TexturePtr tex; diff --git a/app/node/traverser.h b/app/node/traverser.h index 4f2041d57..84284a0d8 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -54,12 +54,6 @@ public: void Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range); - static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time); - static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const rational &time) - { - return GenerateGlobals(vparams, aparams, TimeRange(time, time + vparams.frame_rate_as_time_base())); - } - const VideoParams& GetCacheVideoParams() const { return video_params_; @@ -144,7 +138,7 @@ protected: return block_stack_.empty() ? nullptr : block_stack_.back(); } - Decoder::LoopMode loop_mode() const { return loop_mode_; } + LoopMode loop_mode() const { return loop_mode_; } virtual bool UseCache() const { return false; } @@ -163,7 +157,7 @@ private: std::list block_stack_; - Decoder::LoopMode loop_mode_; + LoopMode loop_mode_; QHash > value_cache_; QHash resolved_texture_cache_; diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 2c779e2e8..068552763 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -38,6 +38,7 @@ set(OLIVE_SOURCES render/framehashcache.h render/framemanager.cpp render/framemanager.h + render/loopmode.h render/managedcolor.cpp render/managedcolor.h render/playbackcache.cpp diff --git a/app/render/job/footagejob.h b/app/render/job/footagejob.h index dcafee554..3fbd7e267 100644 --- a/app/render/job/footagejob.h +++ b/app/render/job/footagejob.h @@ -33,12 +33,13 @@ public: { } - FootageJob(const TimeRange &time, const QString& decoder, const QString& filename, Track::Type type, const rational& length) : + FootageJob(const TimeRange &time, const QString& decoder, const QString& filename, Track::Type type, const rational& length, LoopMode loop_mode) : time_(time), decoder_(decoder), filename_(filename), type_(type), - length_(length) + length_(length), + loop_mode_(loop_mode) { } @@ -99,6 +100,9 @@ public: const TimeRange &time() const { return time_; } + LoopMode loop_mode() const { return loop_mode_; } + void set_loop_mode(LoopMode m) { loop_mode_ = m; } + private: TimeRange time_; @@ -116,6 +120,8 @@ private: rational length_; + LoopMode loop_mode_; + }; } diff --git a/app/render/loopmode.h b/app/render/loopmode.h new file mode 100644 index 000000000..9e4927727 --- /dev/null +++ b/app/render/loopmode.h @@ -0,0 +1,14 @@ +#ifndef LOOPMODE_H +#define LOOPMODE_H + +namespace olive { + +enum class LoopMode { + kLoopModeOff, + kLoopModeLoop, + kLoopModeClamp +}; + +} + +#endif // LOOPMODE_H diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 2ba27f240..a8198029d 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -532,18 +532,18 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q qreal zebra_right = TimeToScene(clip->in() - clip->media_in()); switch (clip->loop_mode()) { - case Decoder::kLoopModeOff: + case LoopMode::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: + case LoopMode::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: + case LoopMode::kLoopModeClamp: painter->drawLine(zebra_right, block_top, zebra_right, block_top + block_height); break; } @@ -552,18 +552,18 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); switch (clip->loop_mode()) { - case Decoder::kLoopModeOff: + case LoopMode::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: + case LoopMode::kLoopModeLoop: for (qreal i=zebra_left; iconnected_viewer()->GetLength())) { painter->drawLine(i, block_top, i, block_top + block_height); } break; - case Decoder::kLoopModeClamp: + case LoopMode::kLoopModeClamp: painter->drawLine(zebra_left, block_top, zebra_left, block_top + block_height); break; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index cd75d91e2..a2f036cd8 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -380,15 +380,7 @@ void ViewerDisplayWidget::OnPaint() VideoParams device_params = GetViewportParams(); if (push_mode_ == kPushBlank) { - if (blank_shader_.isNull()) { - blank_shader_ = renderer()->CreateNativeShader(ShaderCode()); - } - - ShaderJob job; - job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, combined_matrix_flipped_)); - job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix_)); - - renderer()->Blit(blank_shader_, job, device_params, false); + DrawBlank(device_params); } else if (color_service()) { if (FramePtr frame = load_frame_.value()) { // This is a CPU frame, upload it now @@ -415,35 +407,39 @@ void ViewerDisplayWidget::OnPaint() TexturePtr texture_to_draw = texture_; - if (deinterlace_) { - if (deinterlace_shader_.isNull()) { - deinterlace_shader_ = renderer()->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/deinterlace.frag")))); + if (!texture_to_draw || texture_to_draw->IsDummy()) { + DrawBlank(device_params); + } else { + if (deinterlace_) { + if (deinterlace_shader_.isNull()) { + deinterlace_shader_ = renderer()->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/deinterlace.frag")))); + } + + if (!deinterlace_texture_ + || deinterlace_texture_->params() != texture_to_draw->params()) { + // (Re)create texture + deinterlace_texture_ = renderer()->CreateTexture(texture_to_draw->params()); + } + + ShaderJob job; + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture_to_draw->width(), texture_to_draw->height()))); + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_to_draw))); + + renderer()->BlitToTexture(deinterlace_shader_, job, deinterlace_texture_.get()); + + texture_to_draw = deinterlace_texture_; } - if (!deinterlace_texture_ - || deinterlace_texture_->params() != texture_to_draw->params()) { - // (Re)create texture - deinterlace_texture_ = renderer()->CreateTexture(texture_to_draw->params()); - } + ColorTransformJob ctj; + ctj.SetColorProcessor(color_service()); + ctj.SetInputTexture(texture_to_draw); + ctj.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); + ctj.SetClearDestinationEnabled(false); + ctj.SetTransformMatrix(combined_matrix_flipped_); + ctj.SetCropMatrix(crop_matrix_); - ShaderJob job; - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture_to_draw->width(), texture_to_draw->height()))); - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_to_draw))); - - renderer()->BlitToTexture(deinterlace_shader_, job, deinterlace_texture_.get()); - - texture_to_draw = deinterlace_texture_; + renderer()->BlitColorManaged(ctj, device_params); } - - ColorTransformJob ctj; - ctj.SetColorProcessor(color_service()); - ctj.SetInputTexture(texture_to_draw); - ctj.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); - ctj.SetClearDestinationEnabled(false); - ctj.SetTransformMatrix(combined_matrix_flipped_); - ctj.SetCropMatrix(crop_matrix_); - - renderer()->BlitColorManaged(ctj, device_params); } } @@ -455,7 +451,7 @@ void ViewerDisplayWidget::OnPaint() p.setWorldTransform(gizmo_last_draw_transform_); - gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_)); + gizmos_->UpdateGizmoPositions(gizmo_db_, NodeGlobals(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_, LoopMode::kLoopModeOff)); foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { if (gizmo->IsVisible()) { gizmo->Draw(&p); @@ -831,7 +827,7 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) // Handle gizmo click gizmo_start_drag_ = event->pos(); gizmo_last_drag_ = gizmo_start_drag_; - current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_audio_params_, GenerateGizmoTime())); + current_gizmo_->SetGlobals(NodeGlobals(gizmo_params_, gizmo_audio_params_, GenerateGizmoTime(), LoopMode::kLoopModeOff)); } else { @@ -1213,6 +1209,19 @@ void ViewerDisplayWidget::GenerateGizmoTransforms() gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(); } +void ViewerDisplayWidget::DrawBlank(const VideoParams &device_params) +{ + if (blank_shader_.isNull()) { + blank_shader_ = renderer()->CreateNativeShader(ShaderCode()); + } + + ShaderJob job; + job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, combined_matrix_flipped_)); + job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix_)); + + renderer()->Blit(blank_shader_, job, device_params, false); +} + void ViewerDisplayWidget::SetShowFPS(bool e) { show_fps_ = e; diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index a82bad747..913d5d93e 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -306,6 +306,8 @@ private: void GenerateGizmoTransforms(); + void DrawBlank(const VideoParams &device_params); + /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ From ca47792dc75308272b86390567048f2be44d79ea Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:44:01 -0700 Subject: [PATCH 43/85] nodes: clamp footage retrieval times for clips Fixes #2070 --- app/codec/ffmpeg/ffmpegencoder.cpp | 2 +- app/node/block/clip/clip.cpp | 19 +++++++++++++++---- app/node/block/clip/clip.h | 2 +- app/node/block/transition/transition.cpp | 4 ++-- app/node/block/transition/transition.h | 2 +- app/node/node.cpp | 4 ++-- app/node/node.h | 2 +- app/node/output/track/track.cpp | 12 ++++++------ app/node/output/track/track.h | 2 +- app/node/output/viewer/viewer.cpp | 6 +++--- app/node/time/timeoffset/timeoffsetnode.cpp | 4 ++-- app/node/time/timeoffset/timeoffsetnode.h | 2 +- app/node/time/timeremap/timeremap.cpp | 4 ++-- app/node/time/timeremap/timeremap.h | 2 +- app/node/traverser.cpp | 6 +++--- 15 files changed, 42 insertions(+), 31 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 403bfab71..da6c8b510 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -290,7 +290,7 @@ bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) size_t end = audio.sample_count(); const size_t max_frame = 48000; - while (start < end) { + while (result && start < end) { // Create input buffer uint8_t** input_data = nullptr; size_t input_sample_count = std::min(end - start, max_frame); diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 4d4bf1ebd..684d0112d 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -458,15 +458,26 @@ void ClipBlock::InputValueChangedEvent(const QString &input, int element) } } -TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const +TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const { Q_UNUSED(element) if (input == kBufferIn) { - return TimeRange(SequenceToMediaTime(input_time.in()), SequenceToMediaTime(input_time.out())); + rational in = input_time.in(); + rational out = input_time.out(); + + if (clamp) { + in = std::max(in, rational(0)); + out = std::min(out, length()); + } + + in = SequenceToMediaTime(in); + out = SequenceToMediaTime(out); + + return TimeRange(in, out); } - return super::InputTimeAdjustment(input, element, input_time); + return super::InputTimeAdjustment(input, element, input_time, clamp); } TimeRange ClipBlock::OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const @@ -540,7 +551,7 @@ void ClipBlock::ConnectedToPreviewEvent() TimeRange ClipBlock::media_range() const { - return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length()), false); } MultiCamNode *ClipBlock::FindMulticam() diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index ddbbd881a..19987c7fb 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -68,7 +68,7 @@ public: virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override; - virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; + virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override; virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index f0590e5c5..3eda77d5f 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -287,7 +287,7 @@ void TransitionBlock::InputDisconnectedEvent(const QString &input, int element, } } -TimeRange TransitionBlock::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const +TimeRange TransitionBlock::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time, bool clamp) const { if (input == kInBlockInput || input == kOutBlockInput) { Block* block = dynamic_cast(GetConnectedOutput(input)); @@ -296,7 +296,7 @@ TimeRange TransitionBlock::InputTimeAdjustment(const QString &input, int element } } - return super::InputTimeAdjustment(input, element, input_time); + return super::InputTimeAdjustment(input, element, input_time, clamp); } TimeRange TransitionBlock::OutputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 554a7c469..d16a195bd 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -83,7 +83,7 @@ protected: virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; - virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; + virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override; virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; diff --git a/app/node/node.cpp b/app/node/node.cpp index d843a460a..4d4f362cf 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -959,7 +959,7 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem SendInvalidateCache(range, options); } -TimeRange Node::InputTimeAdjustment(const QString &, int, const TimeRange &input_time) const +TimeRange Node::InputTimeAdjustment(const QString &, int, const TimeRange &input_time, bool clamp) const { // Default behavior is no time adjustment at all return input_time; @@ -1727,7 +1727,7 @@ TimeRange Node::TransformTimeTo(TimeRange time, Node *target, TransformTimeDirec if (dir == kTransformTowardsInput) { for (auto it=path.crbegin(); it!=path.crend(); it++) { const NodeInput &i = (*it); - time = i.node()->InputTimeAdjustment(i.input(), i.element(), time); + time = i.node()->InputTimeAdjustment(i.input(), i.element(), time, false); } } else { // Traverse in output direction diff --git a/app/node/node.h b/app/node/node.h index 253788cbe..f5e5a8485 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -907,7 +907,7 @@ public: * If this node modifies the `time` (i.e. a clip converting sequence time to media time), this function should be * overridden to do so. Also make sure to override OutputTimeAdjustment() to provide the inverse function. */ - virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const; + virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const; /** * @brief The inverse of InputTimeAdjustment() diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index f2bed9064..be6843eaf 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -146,7 +146,7 @@ void Track::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeVal } } -TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const +TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const { if (input == kBlockInput && element >= 0) { int cache_index = GetCacheIndexFromArrayIndex(element); @@ -156,7 +156,7 @@ TimeRange Track::InputTimeAdjustment(const QString& input, int element, const Ti } } - return Node::InputTimeAdjustment(input, element, input_time); + return Node::InputTimeAdjustment(input, element, input_time, clamp); } TimeRange Track::OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const @@ -647,16 +647,16 @@ void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &glob TimeRange range_for_block(qMax(b->in(), range.in()), qMin(b->out(), range.out())); + qint64 source_offset = 0; qint64 destination_offset = globals.aparams().time_to_samples(range_for_block.in() - range.in()); qint64 max_dest_sz = globals.aparams().time_to_samples(range_for_block.length()); // Destination buffer SampleBuffer samples_from_this_block = it->second.toSamples(); - ClipBlock *clip_cast = dynamic_cast(b); if (samples_from_this_block.is_allocated()) { // If this is a clip, we might have extra speed/reverse information - if (clip_cast) { + if (ClipBlock *clip_cast = dynamic_cast(b)) { double speed_value = clip_cast->speed(); bool reversed = clip_cast->reverse(); @@ -711,11 +711,11 @@ void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &glob } } - qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count() - destination_offset)); + qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count() - source_offset)); // Copy samples into destination buffer for (int i=0; ithumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); if (autocache_input_video_) { - TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength())); + TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()), false); connected->video_frame_cache()->Request(range.Intersected(max_range)); } } else if (from == kSamplesInput) { - TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength())); + TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()), false); if (waveform_requests_enabled_) { connected->waveform_cache()->Request(range.Intersected(max_range)); } @@ -394,7 +394,7 @@ void ViewerOutput::SetWaveformEnabled(bool e) { if ((waveform_requests_enabled_ = e)) { if (Node *connected = this->GetConnectedSampleOutput()) { - TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength())); + TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()), false); TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); for (const TimeRange &r : invalid) { connected->waveform_cache()->Request(r); diff --git a/app/node/time/timeoffset/timeoffsetnode.cpp b/app/node/time/timeoffset/timeoffsetnode.cpp index 98fc2c6d0..1a6201b5e 100644 --- a/app/node/time/timeoffset/timeoffsetnode.cpp +++ b/app/node/time/timeoffset/timeoffsetnode.cpp @@ -46,12 +46,12 @@ void TimeOffsetNode::Retranslate() SetInputName(kInputInput, QStringLiteral("Input")); } -TimeRange TimeOffsetNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const +TimeRange TimeOffsetNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time, bool clamp) const { if (input == kInputInput) { return TimeRange(GetRemappedTime(input_time.in()), GetRemappedTime(input_time.out())); } else { - return super::InputTimeAdjustment(input, element, input_time); + return super::InputTimeAdjustment(input, element, input_time, clamp); } } diff --git a/app/node/time/timeoffset/timeoffsetnode.h b/app/node/time/timeoffset/timeoffsetnode.h index 56f1c0711..f1890924f 100644 --- a/app/node/time/timeoffset/timeoffsetnode.h +++ b/app/node/time/timeoffset/timeoffsetnode.h @@ -52,7 +52,7 @@ public: return tr("Offset time passing through the graph."); } - virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; + virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override; virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; virtual void Retranslate() override; diff --git a/app/node/time/timeremap/timeremap.cpp b/app/node/time/timeremap/timeremap.cpp index 2c1d0d2c5..d3035bb3d 100644 --- a/app/node/time/timeremap/timeremap.cpp +++ b/app/node/time/timeremap/timeremap.cpp @@ -58,12 +58,12 @@ QString TimeRemapNode::Description() const return tr("Arbitrarily remap time through the nodes."); } -TimeRange TimeRemapNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const +TimeRange TimeRemapNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time, bool clamp) const { if (input == kInputInput) { return TimeRange(GetRemappedTime(input_time.in()), GetRemappedTime(input_time.out())); } else { - return super::InputTimeAdjustment(input, element, input_time); + return super::InputTimeAdjustment(input, element, input_time, clamp); } } diff --git a/app/node/time/timeremap/timeremap.h b/app/node/time/timeremap/timeremap.h index 8efd60ed0..3ba9cd8e1 100644 --- a/app/node/time/timeremap/timeremap.h +++ b/app/node/time/timeremap/timeremap.h @@ -38,7 +38,7 @@ public: virtual QVector Category() const override; virtual QString Description() const override; - virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; + virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override; virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; virtual void Retranslate() override; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index e99be2d4f..86e91ea2d 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -189,7 +189,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu // If input is connected, retrieve value directly if (node->IsInputConnectedForRender(input)) { - TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range); + TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range, true); // Value will equal something from the connected node, follow it Node *output = node->GetConnectedRenderOutput(input); @@ -224,7 +224,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu } else { // Not connected or an array, just pull the immediate - TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range); + TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range, true); return_val = node->GetValueAtTime(input, adjusted_range.in()); @@ -240,7 +240,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const Node *node, const QString &input, int element, const TimeRange &range) { NodeValueTable& sub_tbl = array_tbl[element]; - TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range); + TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range, true); if (node->IsInputConnectedForRender(input, element)) { Node *output = node->GetConnectedRenderOutput(input, element); From d6bb3cf5b656fb4874637264adb8c524c6468e40 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:48:32 -0700 Subject: [PATCH 44/85] removed inappropriate optional argument --- app/node/output/track/track.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 8b48fe12c..2ed697e70 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -57,7 +57,7 @@ public: virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp = false) const override; + virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override; virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; From d52d5b13b28db89b597e19694b36111879990980 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:48:48 -0700 Subject: [PATCH 45/85] seekablewidget: allow rubberband select while holding ctrl Fixes #2061 --- app/widget/timeruler/seekablewidget.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index bb154d59e..f3df0b146 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -179,6 +179,8 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) if (HandPress(event)) { return; + } else if (event->modifiers() & Qt::ControlModifier) { + selection_manager_.RubberBandStart(event); } else if (resize_item_) { // Handle selection, even though we won't be using it for dragging if (!(event->modifiers() & Qt::ShiftModifier)) { @@ -203,6 +205,9 @@ void SeekableWidget::mouseMoveEvent(QMouseEvent *event) { if (HandMove(event)) { return; + } else if (selection_manager_.IsRubberBanding()) { + selection_manager_.RubberBandMove(event); + viewport()->update(); } else if (selection_manager_.IsDragging()) { selection_manager_.DragMove(event); } else if (dragging_) { @@ -228,6 +233,11 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) return; } + if (selection_manager_.IsRubberBanding()) { + selection_manager_.RubberBandStop(); + return; + } + if (selection_manager_.IsDragging()) { MultiUndoCommand *command = new MultiUndoCommand(); selection_manager_.DragStop(command); From 073e9fdb0ee6dd770dc4c16685c123ca2c68147f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 1 Nov 2022 21:04:44 -0700 Subject: [PATCH 46/85] fixed transition regression --- app/node/block/clip/clip.cpp | 16 ++++++++++++++-- app/node/block/transition/transition.cpp | 7 ++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 684d0112d..70e9a7304 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -21,6 +21,7 @@ #include "clip.h" #include "config/config.h" +#include "node/block/transition/transition.h" #include "node/output/track/track.h" #include "node/output/viewer/viewer.h" #include "widget/slider/floatslider.h" @@ -467,8 +468,19 @@ TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, cons rational out = input_time.out(); if (clamp) { - in = std::max(in, rational(0)); - out = std::min(out, length()); + rational minimum = 0; + rational maximum = length(); + + if (in_transition_) { + minimum -= in_transition_->length(); + } + + if (out_transition_) { + maximum += out_transition_->length(); + } + + in = std::max(in, minimum); + out = std::min(out, maximum); } in = SequenceToMediaTime(in); diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 3eda77d5f..c78796393 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -292,7 +292,12 @@ TimeRange TransitionBlock::InputTimeAdjustment(const QString &input, int element if (input == kInBlockInput || input == kOutBlockInput) { Block* block = dynamic_cast(GetConnectedOutput(input)); if (block) { - return input_time + in() - block->in(); + TimeRange range = input_time; + if (clamp) { + range.set_range(std::max(rational(0), range.in()), std::min(this->length(), range.out())); + } + range = range + in() - block->in(); + return range; } } From c06d17cf059feca8bdb98de70b978f56ed38d7c5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 1 Nov 2022 21:04:55 -0700 Subject: [PATCH 47/85] timeline: improve transition tool behavior --- app/widget/timelinewidget/tool/transition.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index 3146142a8..73a89d02c 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -194,12 +194,16 @@ bool TransitionTool::GetBlocksAtCoord(const TimelineCoordinate &coord, ClipBlock return false; } + ClipBlock *adjacent = dynamic_cast(block_at_time->previous()); + if (adjacent) { + tenth_point = std::min(tenth_point, adjacent->length()/10); + } + transition_start_point = block_at_time->in(); trim_mode = Timeline::kTrimIn; - if (cursor_frame < (block_at_time->in() + tenth_point) - && dynamic_cast(block_at_time->previous())) { - other_block = block_at_time->previous(); + if (cursor_frame < (block_at_time->in() + tenth_point) && adjacent) { + other_block = adjacent; } } else { if (static_cast(block_at_time)->out_transition()) { @@ -207,11 +211,15 @@ bool TransitionTool::GetBlocksAtCoord(const TimelineCoordinate &coord, ClipBlock return false; } + ClipBlock *adjacent = dynamic_cast(block_at_time->next()); + if (adjacent) { + tenth_point = std::min(tenth_point, adjacent->length()/10); + } + transition_start_point = block_at_time->out(); trim_mode = Timeline::kTrimOut; - if (cursor_frame > block_at_time->out() - tenth_point - && dynamic_cast(block_at_time->next())) { + if (cursor_frame > block_at_time->out() - tenth_point && adjacent) { other_block = block_at_time->next(); } } From c357b04e8230ed94503d2c202f3769247911b703 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 10:13:04 -0700 Subject: [PATCH 48/85] viewer: don't attempt to update still image Fixes crash when playing audio with muxed still --- app/widget/viewer/viewer.cpp | 14 ++++++++++---- app/widget/viewer/viewer.h | 2 ++ app/widget/viewer/viewerdisplay.cpp | 8 +++++--- app/widget/viewer/viewerdisplay.h | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 6c93d064f..32d1a5bd9 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -388,7 +388,7 @@ void ViewerWidget::SetFullScreen(QScreen *screen) (*vw->display_widget()->queue()) = *playback_devices_.first()->queue(); if (IsPlaying()) { - vw->display_widget()->Play(GetTimestamp(), playback_speed_, timebase()); + vw->display_widget()->Play(GetTimestamp(), playback_speed_, timebase(), true); } windows_.insert(screen, vw); @@ -702,6 +702,12 @@ void ViewerWidget::DetectMulticamNode(const rational &time) } } +bool ViewerWidget::IsVideoVisible() const +{ + return GetConnectedNode()->GetVideoParams().video_type() != VideoParams::kVideoTypeStill + && (display_widget_->isVisible() || !windows_.isEmpty()); +} + void ViewerWidget::UpdateWaveformViewFromMode() { bool prefer_waveform = ShouldForceWaveform(); @@ -954,7 +960,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) queue_starved_start_ = 0; // Attempt to fill playback queue - if (display_widget_->isVisible() || !windows_.isEmpty()) { + if (IsVideoVisible()) { prequeue_length_ = DeterminePlaybackQueueSize(); if (prequeue_length_ > 0) { @@ -1181,7 +1187,7 @@ void ViewerWidget::FinishPlayPreprocess() display_widget_->ResetFPSTimer(); foreach (ViewerDisplayWidget *dw, playback_devices_) { - dw->Play(playback_start_time, playback_speed_, timebase()); + dw->Play(playback_start_time, playback_speed_, timebase(), IsVideoVisible()); } // This is our timer for loading the queue and setting the time @@ -1731,7 +1737,7 @@ void ViewerWidget::PlaybackTimerUpdate() } } - if (IsPlaying()) { + if (IsPlaying() && IsVideoVisible()) { while ((int(display_widget_->queue()->size()) + queue_watchers_.size()) < DeterminePlaybackQueueSize()) { if (!RequestNextFrameForQueue()) { // Prevent infinite loop diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index de06b62e1..203a03801 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -282,6 +282,8 @@ private: void DetectMulticamNode(const rational &time); + bool IsVideoVisible() const; + ViewerSizer* sizer_; int playback_speed_; diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index a2f036cd8..832cf2a92 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -1241,16 +1241,18 @@ void ViewerDisplayWidget::RequestStartEditingText() } } -void ViewerDisplayWidget::Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase) +void ViewerDisplayWidget::Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase, bool start_updating) { playback_timebase_ = timebase; playback_speed_ = playback_speed; timer_.Start(start_timestamp, playback_speed, timebase.toDouble()); - connect(this, &ViewerDisplayWidget::frameSwapped, this, &ViewerDisplayWidget::UpdateFromQueue); + if (start_updating) { + connect(this, &ViewerDisplayWidget::frameSwapped, this, &ViewerDisplayWidget::UpdateFromQueue); - update(); + update(); + } } void ViewerDisplayWidget::Pause() diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 913d5d93e..37567dabc 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -123,7 +123,7 @@ public: return texture_; } - void Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase); + void Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase, bool start_updating); void Pause(); From 1575b82fd24ad7b37995ce0725e9f54cf75177f9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 11:56:39 -0700 Subject: [PATCH 49/85] core: add debugging "magic" function Just a UI way of turning on debug functionality --- app/core.cpp | 3 ++- app/core.h | 12 ++++++++++++ app/window/mainwindow/mainmenu.cpp | 8 ++++++++ app/window/mainwindow/mainmenu.h | 4 ++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/core.cpp b/app/core.cpp index 78d9b1721..2523c00e0 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -88,7 +88,8 @@ Core::Core(const CoreParams& params) : snapping_(true), core_params_(params), pixel_sampling_users_(0), - shown_cache_full_warning_(false) + shown_cache_full_warning_(false), + magic_(false) { // Store reference to this object, making the assumption that Core will only ever be made in // main(). This will obviously break if not. diff --git a/app/core.h b/app/core.h index 19ffb07dc..cc161d87f 100644 --- a/app/core.h +++ b/app/core.h @@ -319,6 +319,8 @@ public: void OpenExportDialogForViewer(ViewerOutput *viewer, bool start_still_image); + bool IsMagicEnabled() const { return magic_; } + public slots: /** * @brief Starts an open file dialog to load a project from file @@ -449,6 +451,11 @@ public slots: void WarnCacheFull(); + void SetMagic(bool e) + { + magic_ = e; + } + signals: /** * @brief Signal emitted when a project is opened @@ -626,6 +633,11 @@ private: */ QVector autorecovered_projects_; + /** + * @brief Do something debug related + */ + bool magic_; + /** * @brief How many widgets currently need pixel sampling access */ diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 873c746d7..6d5daa527 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -282,6 +282,11 @@ MainMenu::MainMenu(MainWindow *parent) : tools_preferences_item_ = tools_menu_->AddItem("prefs", Core::instance(), &Core::DialogPreferencesShow, tr("Ctrl+,")); +#ifndef NDEBUG + tools_magic_item_ = tools_menu_->AddItem("magic", Core::instance(), &Core::SetMagic); + tools_magic_item_->setCheckable(true); +#endif + // // HELP MENU // @@ -787,6 +792,9 @@ void MainMenu::Retranslate() tools_record_item_->setText(tr("Record Tool")); tools_snapping_item_->setText(tr("Enable Snapping")); tools_preferences_item_->setText(tr("Preferences")); +#ifndef NDEBUG + tools_magic_item_->setText("Magic"); +#endif // Help menu help_menu_->setTitle(tr("&Help")); diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index a4dc36b81..8b88927e0 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -287,6 +287,10 @@ private: QAction* tools_snapping_item_; QAction* tools_preferences_item_; +#ifndef NDEBUG + QAction* tools_magic_item_; +#endif + Menu* help_menu_; QAction* help_action_search_item_; QAction* help_feedback_item_; From 38517c8c31aaab82d463e5c624fcd6ecc05fad13 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 11:58:38 -0700 Subject: [PATCH 50/85] node: removed slow unrecommended functions --- app/node/node.cpp | 80 ----------------------- app/node/node.h | 64 ------------------ app/widget/nodeview/nodeview.cpp | 10 +-- app/widget/timelinewidget/tool/import.cpp | 2 +- 4 files changed, 6 insertions(+), 150 deletions(-) diff --git a/app/node/node.cpp b/app/node/node.cpp index 4d4f362cf..b26a95d93 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1555,64 +1555,6 @@ void Node::GenerateFrame(FramePtr frame, const GenerateJob &job) const Q_UNUSED(job) } -bool Node::OutputsTo(Node *n, bool recursively, const OutputConnections &ignore_edges, const OutputConnection &added_edge) const -{ - for (const OutputConnection& conn : output_connections_) { - if (std::find(ignore_edges.cbegin(), ignore_edges.cend(), conn) != ignore_edges.cend()) { - // If this edge is in the "ignore edges" list, skip it - continue; - } - - Node* connected = conn.second.node(); - - if (connected == n) { - return true; - } else if (recursively && connected->OutputsTo(n, recursively, ignore_edges, added_edge)) { - return true; - } else if (added_edge.first == this) { - Node *proposed_connected = added_edge.second.node(); - - if (proposed_connected == n) { - return true; - } else if (recursively && proposed_connected->OutputsTo(n, recursively, ignore_edges, added_edge)) { - return true; - } - } - } - - return false; -} - -bool Node::OutputsTo(const QString &id, bool recursively) const -{ - for (const OutputConnection& conn : output_connections_) { - Node* connected = conn.second.node(); - - if (connected->id() == id) { - return true; - } else if (recursively && connected->OutputsTo(id, recursively)) { - return true; - } - } - - return false; -} - -bool Node::OutputsTo(const NodeInput &input, bool recursively) const -{ - for (const OutputConnection& conn : output_connections_) { - const NodeInput& connected = conn.second; - - if (connected == input) { - return true; - } else if (recursively && connected.node()->OutputsTo(input, recursively)) { - return true; - } - } - - return false; -} - bool Node::InputsFrom(Node *n, bool recursively) const { for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) { @@ -1643,28 +1585,6 @@ bool Node::InputsFrom(const QString &id, bool recursively) const return false; } -int Node::GetNumberOfRoutesTo(Node *n) const -{ - bool outputs_directly = false; - int routes = 0; - - foreach (const OutputConnection& conn, output_connections_) { - Node* connected_node = conn.second.node(); - - if (connected_node == n) { - outputs_directly = true; - } else { - routes += connected_node->GetNumberOfRoutesTo(n); - } - } - - if (outputs_directly) { - routes++; - } - - return routes; -} - void Node::DisconnectAll() { // Disconnect inputs (copy map since internal map will change as we disconnect) diff --git a/app/node/node.h b/app/node/node.h index f5e5a8485..8fa933979 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -786,30 +786,6 @@ public: */ virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const; - /** - * @brief Returns whether this Node outputs to `n` - * - * @param n - * - * The node instance to check. - * - * @param recursively - * - * Whether to keep traversing down outputs to find this node (TRUE) or stick to immediate outputs - * (FALSE). - */ - bool OutputsTo(Node* n, bool recursively, const OutputConnections &ignore_edges = OutputConnections(), const OutputConnection &added_edge = OutputConnection()) const; - - /** - * @brief Same as OutputsTo(Node*), but for a node ID rather than a specific instance. - */ - bool OutputsTo(const QString& id, bool recursively) const; - - /** - * @brief Same as OutputsTo(Node*), but for a specific node input rather than just a node. - */ - bool OutputsTo(const NodeInput &input, bool recursively) const; - /** * @brief Returns whether this node ever receives an input from a particular node instance */ @@ -820,7 +796,6 @@ public: */ bool InputsFrom(const QString& id, bool recursively) const; - /** * @brief Find inputs that `output` outputs to in order to arrive at this node * @@ -829,11 +804,6 @@ public: */ QVector FindWaysNodeArrivesHere(const Node *output) const; - /** - * @brief Determines how many paths go from this node out to another node - */ - int GetNumberOfRoutesTo(Node* n) const; - /** * @brief Severs all input and output connections */ @@ -866,12 +836,6 @@ public: template static QVector FindInputNodesConnectedToInput(const NodeInput &input, int maximum = 0); - template - /** - * @brief Find a node of a certain type that this Node outputs to - */ - QVector FindOutputNode(); - /** * @brief Convert a pointer to a value that can be sent between NodeParams */ @@ -1413,9 +1377,6 @@ private: template static void FindInputNodeInternal(const Node* n, QVector& list, int maximum); - template - static void FindOutputNodeInternal(const Node* n, QVector& list); - QVector GetDependenciesInternal(bool traverse, bool exclusive_only) const; void ParameterValueChanged(const QString &input, int element, const olive::TimeRange &range); @@ -1573,31 +1534,6 @@ T* Node::ValueToPtr(const QVariant &ptr) return reinterpret_cast(ptr.value()); } -template -void Node::FindOutputNodeInternal(const Node* n, QVector& list) -{ - foreach (const OutputConnection& output, n->output_connections_) { - Node* connected = output.second.node(); - T* cast_test = dynamic_cast(connected); - - if (cast_test) { - list.append(cast_test); - } - - FindOutputNodeInternal(connected, list); - } -} - -template -QVector Node::FindOutputNode() -{ - QVector list; - - FindOutputNodeInternal(this, list); - - return list; -} - using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 0a9ecee08..97a818fe3 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -1043,7 +1043,7 @@ void NodeView::ProcessMovingAttachedNodes(const QPoint &pos) } } - if (new_drop_edge->input().node()->OutputsTo(attached_node, true)) { + if (attached_node->InputsFrom(new_drop_edge->input().node(), true)) { drop_input_.Reset(); } @@ -1079,7 +1079,7 @@ QVector NodeView::ProcessDroppingAttachedNodes(MultiUndoCommand *command, for (int i=0; iOutputsTo(ai.node, true)) { + if (ai.node->InputsFrom(select_context, true)) { attached.removeAt(i); } else if (select_context->ContextContainsNode(ai.node)) { select_nodes.append(ai.node); @@ -1118,7 +1118,7 @@ QVector NodeView::ProcessDroppingAttachedNodes(MultiUndoCommand *command, Node* dropping_node = nullptr; foreach (const AttachedItem &ai, attached) { - if (ai.item && !select_context->OutputsTo(ai.node, true)) { + if (ai.item && !ai.node->InputsFrom(select_context, true)) { dropping_node = ai.node; break; } @@ -1325,7 +1325,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) // Filter out connecting to a node that connects to us or an item of the same type if (item_at_cursor - && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) + && ((create_edge_from_output_ && source_item->GetNode()->InputsFrom(item_at_cursor->GetNode(), true)) || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)) || (create_edge_from_output_ == item_at_cursor->IsOutputItem()))) { item_at_cursor = nullptr; @@ -1412,7 +1412,7 @@ void NodeView::GroupNodes() // Default to the first node we find that doesn't output to a node inside the group output_passthrough = nodes_to_group.first(); foreach (Node *potential_in, nodes_to_group) { - if (potential_in != n && !n->OutputsTo(potential_in, false)) { + if (potential_in != n && !potential_in->InputsFrom(n, false)) { output_passthrough = n; break; } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index caa0037ed..214056f2c 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -221,7 +221,7 @@ void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData for (auto it=sorted.cbegin(); it!=sorted.cend(); it++) { ViewerOutput* footage = it->first; - if (footage == sequence() || (sequence() && sequence()->OutputsTo(footage, true))) { + if (footage == sequence() || (sequence() && footage->InputsFrom(sequence(), true))) { // Prevent cyclical dependency continue; } From 1057020d4fdc688892ac5e0ef26ddf71c889798a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 12:27:56 -0700 Subject: [PATCH 51/85] render: separate off project copying functionality from PreviewAutoCacher --- app/render/CMakeLists.txt | 2 + app/render/previewautocacher.cpp | 268 ++++--------------------------- app/render/previewautocacher.h | 65 +------- app/render/projectcopier.cpp | 259 +++++++++++++++++++++++++++++ app/render/projectcopier.h | 125 ++++++++++++++ 5 files changed, 419 insertions(+), 300 deletions(-) create mode 100644 app/render/projectcopier.cpp create mode 100644 app/render/projectcopier.h diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 068552763..e2e310fcc 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -47,6 +47,8 @@ set(OLIVE_SOURCES render/previewaudiodevice.h render/previewautocacher.cpp render/previewautocacher.h + render/projectcopier.cpp + render/projectcopier.h render/renderer.cpp render/renderer.h render/rendercache.h diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 08624be67..44112b55c 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -28,11 +28,6 @@ #include "node/inputdragger.h" #include "node/project/project.h" #include "render/diskmanager.h" -#include "render/renderprocessor.h" -#include "task/customcache/customcachetask.h" -#include "task/taskmanager.h" -#include "widget/slider/base/numericsliderbase.h" -#include "widget/viewer/viewer.h" namespace olive { @@ -47,6 +42,10 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : multicam_(nullptr), ignore_cache_requests_(false) { + copier_ = new ProjectCopier(this); + connect(copier_, &ProjectCopier::AddedNode, this, &PreviewAutoCacher::ConnectToNodeCache); + connect(copier_, &ProjectCopier::RemovedNode, this, &PreviewAutoCacher::DisconnectFromNodeCache); + // Set defaults SetPlayhead(0); @@ -157,7 +156,7 @@ void PreviewAutoCacher::AudioRendered() if (running_audio_tasks_.removeOne(watcher)) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket TimeRange range = watcher->property("time").value(); - Node *node = copy_map_.key(Node::ValueToPtr(watcher->property("node"))); + Node *node = copier_->GetOriginal(Node::ValueToPtr(watcher->property("node"))); if (watcher->HasResult() && node) { if (PlaybackCache *cache = Node::ValueToPtr(watcher->property("cache"))) { @@ -252,140 +251,12 @@ void PreviewAutoCacher::VideoRendered() delete watcher; } -void PreviewAutoCacher::ProcessUpdateQueue() -{ - // Iterate everything that happened to the graph and do the same thing on our end - while (!graph_update_queue_.empty()) { - QueuedJob job = graph_update_queue_.front(); - graph_update_queue_.pop_front(); - - switch (job.type) { - case QueuedJob::kNodeAdded: - AddNode(job.node); - break; - case QueuedJob::kNodeRemoved: - RemoveNode(job.node); - break; - case QueuedJob::kEdgeAdded: - AddEdge(job.output, job.input); - break; - case QueuedJob::kEdgeRemoved: - RemoveEdge(job.output, job.input); - break; - case QueuedJob::kValueChanged: - CopyValue(job.input); - break; - case QueuedJob::kValueHintChanged: - CopyValueHint(job.input); - break; - } - } - - // Indicate that we have synchronized to this point, which is compared with the graph change - // time to see if our copied graph is up to date - UpdateLastSyncedValue(); -} - -void PreviewAutoCacher::AddNode(Node *node) -{ - if (dynamic_cast(node)) { - // Group nodes are just dummy nodes, no need to copy them - return; - } - - // Copy node - Node* copy = node->copy(); - - // Add to project - copy->setParent(&copied_project_); - - // Disable caches for copy - copy->SetCachesEnabled(false); - - // Copy cache UUIDs - copy->CopyCacheUuidsFrom(node); - - // Insert into map - InsertIntoCopyMap(node, copy); - - // Keep track of our nodes - created_nodes_.append(copy); -} - -void PreviewAutoCacher::RemoveNode(Node *node) -{ - // Find our copy and remove it - Node* copy = copy_map_.take(node); - - // Disconnect from node's caches - DisconnectFromNodeCache(node); - - // Remove from created list - created_nodes_.removeOne(copy); - - // Delete it - delete copy; -} - -void PreviewAutoCacher::AddEdge(Node *output, const NodeInput &input) -{ - // Create same connection with our copied graph - Node* our_output = copy_map_.value(output); - Node* our_input = copy_map_.value(input.node()); - - Node::ConnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); -} - -void PreviewAutoCacher::RemoveEdge(Node *output, const NodeInput &input) -{ - // Remove same connection with our copied graph - Node* our_output = copy_map_.value(output); - Node* our_input = copy_map_.value(input.node()); - - Node::DisconnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); -} - -void PreviewAutoCacher::CopyValue(const NodeInput &input) -{ - if (dynamic_cast(input.node())) { - // Group nodes are just dummy nodes, no need to copy them - return; - } - - // Copy all values to our graph - Node* our_input = copy_map_.value(input.node()); - Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element()); -} - -void PreviewAutoCacher::CopyValueHint(const NodeInput &input) -{ - if (dynamic_cast(input.node())) { - // Group nodes are just dummy nodes, no need to copy them - return; - } - - // Copy value hint to our graph - Node* our_input = copy_map_.value(input.node()); - Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element()); - our_input->SetValueHintForInput(input.input(), hint, input.element()); -} - -void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) -{ - // Insert into map - copy_map_.insert(node, copy); - - // Copy parameters - Node::CopyInputs(node, copy, false); - - // Connect to node's cache - if (!ignore_cache_requests_) { - ConnectToNodeCache(node); - } -} - void PreviewAutoCacher::ConnectToNodeCache(Node *node) { + if (!ignore_cache_requests_) { + return; + } + connect(node->video_frame_cache(), &PlaybackCache::Requested, this, @@ -455,16 +326,6 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) &PreviewAutoCacher::CancelForCache); } -void PreviewAutoCacher::UpdateGraphChangeValue() -{ - graph_changed_time_.Acquire(); -} - -void PreviewAutoCacher::UpdateLastSyncedValue() -{ - last_update_time_.Acquire(); -} - void PreviewAutoCacher::CancelQueuedSingleFrameRender() { if (single_frame_render_) { @@ -477,7 +338,7 @@ void PreviewAutoCacher::CancelQueuedSingleFrameRender() void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker) { range_list->insert(range); - tracker->insert(range, graph_changed_time_); + tracker->insert(range, copier_->GetGraphChangeTime()); } void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeRange &range) @@ -494,7 +355,7 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR TimeRangeListFrameIterator iterator({range}, using_tb); pending_video_jobs_.push_back({node, cache, range, iterator}); - video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_); + video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), copier_->GetGraphChangeTime()); TryRender(); } @@ -505,7 +366,7 @@ void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeR cache->ClearRequestRange(range); pending_audio_jobs_.push_back({node, cache, range}); - audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); + audio_cache_data_[cache].job_tracker.insert(range, copier_->GetGraphChangeTime()); TryRender(); } @@ -592,47 +453,11 @@ void PreviewAutoCacher::SetThumbnailsPaused(bool e) } } -void PreviewAutoCacher::NodeAdded(Node *node) -{ - graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::NodeRemoved(Node *node) -{ - graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::ValueChanged(const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr}); - UpdateGraphChangeValue(); -} - -void PreviewAutoCacher::ValueHintChanged(const NodeInput &input) -{ - graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); - UpdateGraphChangeValue(); -} - void PreviewAutoCacher::TryRender() { delayed_requeue_timer_.stop(); - if (!graph_update_queue_.empty()) { + if (copier_->HasUpdatesInQueue()) { // Check if we have jobs running in other threads that shouldn't be interrupted right now // NOTE: We don't check for downloads because, while they run in another thread, they don't // require any access to the graph and therefore don't risk race conditions. @@ -642,7 +467,7 @@ void PreviewAutoCacher::TryRender() } // No jobs are active, we can process the update queue - ProcessUpdateQueue(); + copier_->ProcessUpdateQueue(); } if (single_frame_render_) { @@ -653,7 +478,7 @@ void PreviewAutoCacher::TryRender() // Check if already caching this Node *n = Node::ValueToPtr(t->property("node")); - Node *copy = copy_map_.value(n); + Node *copy = copier_->GetCopy(n); if (copy) { RenderTicketWatcher *watcher = RenderFrame(copy, @@ -676,7 +501,7 @@ void PreviewAutoCacher::TryRender() while (!pending_video_jobs_.empty()) { VideoJob &d = pending_video_jobs_.front(); - if (Node *copy = copy_map_.value(d.node)) { + if (Node *copy = copier_->GetCopy(d.node)) { // Queue next frames rational t; while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { @@ -707,7 +532,7 @@ void PreviewAutoCacher::TryRender() bool pop = true; // Start job - if (Node *copy = copy_map_.value(d.node)) { + if (Node *copy = copier_->GetCopy(d.node)) { TimeRange &queued_range = d.range; TimeRange use_range = queued_range; @@ -736,7 +561,7 @@ void PreviewAutoCacher::TryRender() RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, PlaybackCache *cache, bool dry) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); - watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + watcher->setProperty("job", QVariant::fromValue(copier_->GetLastUpdateTime())); watcher->setProperty("cache", Node::PtrToValue(cache)); watcher->setProperty("time", QVariant::fromValue(time)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); @@ -768,7 +593,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& rvp.use_cache = true; // Multicam - rvp.multicam = static_cast(copy_map_.value(multicam_)); + rvp.multicam = copier_->GetCopy(multicam_); watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp)); @@ -778,7 +603,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); - watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + watcher->setProperty("job", QVariant::fromValue(copier_->GetLastUpdateTime())); watcher->setProperty("node", Node::PtrToValue(node)); watcher->setProperty("cache", Node::PtrToValue(cache)); watcher->setProperty("time", QVariant::fromValue(r)); @@ -861,16 +686,13 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) video_immediate_passthroughs_.clear(); // Disconnect from all node cache's - for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { + for (auto it=copier_->GetNodeMap().cbegin(); it!=copier_->GetNodeMap().cend(); it++) { DisconnectFromNodeCache(it.key()); } // Delete all of our copied nodes - qDeleteAll(created_nodes_); - created_nodes_.clear(); - copy_map_.clear(); + copier_->SetProject(nullptr); copied_viewer_node_ = nullptr; - graph_update_queue_.clear(); // Ensure all cache data is cleared video_cache_data_.clear(); @@ -878,59 +700,25 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) // Clear multicam reference multicam_ = nullptr; - - // Disconnect signals for future node additions/deletions - NodeGraph* graph = viewer_node_->parent(); - - disconnect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded); - disconnect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved); - disconnect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded); - disconnect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved); - disconnect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged); - disconnect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged); } viewer_node_ = viewer_node; if (viewer_node_) { - // Copy graph - NodeGraph* graph = viewer_node_->parent(); + // Copy graph (this should always be a Project) + Project* graph = static_cast(viewer_node_->parent()); SetRendersPaused(true); - // Add all nodes - for (int i=0; inodes().at(i), copied_project_.nodes().at(i)); - } - for (int i=copied_project_.nodes().size(); inodes().size(); i++) { - AddNode(graph->nodes().at(i)); - } + copier_->SetProject(graph); + for (int i=0; inodes().size(); i++) { graph->nodes().at(i)->ConnectedToPreviewEvent(); } // Find copied viewer node - copied_viewer_node_ = static_cast(copy_map_.value(viewer_node_)); - copied_color_manager_ = static_cast(copy_map_.value(viewer_node_->project()->color_manager())); - - // Add all connections - foreach (Node* node, graph->nodes()) { - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - AddEdge(it->second, it->first); - } - } - - // Ensure graph change value is just before the sync value - UpdateGraphChangeValue(); - UpdateLastSyncedValue(); - - // Connect signals for future node additions/deletions - connect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded, Qt::DirectConnection); - connect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved, Qt::DirectConnection); - connect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded, Qt::DirectConnection); - connect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved, Qt::DirectConnection); - connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged, Qt::DirectConnection); - connect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged, Qt::DirectConnection); + copied_viewer_node_ = copier_->GetCopy(viewer_node_); + copied_color_manager_ = copier_->GetCopy(graph->color_manager()); SetRendersPaused(false); } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 21efb9912..4aabca518 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -31,6 +31,7 @@ #include "node/output/viewer/viewer.h" #include "node/project/project.h" #include "render/audioparams.h" +#include "render/projectcopier.h" #include "render/renderjobtracker.h" #include "render/rendermanager.h" @@ -113,29 +114,9 @@ private: RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache *cache); - /** - * @brief Process all changes to internal NodeGraph copy - * - * PreviewAutoCacher staggers updates to its internal NodeGraph copy, only applying them when the - * RenderManager is not reading from it. This function is called when such an opportunity arises. - */ - void ProcessUpdateQueue(); - - void AddNode(Node* node); - void RemoveNode(Node* node); - void AddEdge(Node *output, const NodeInput& input); - void RemoveEdge(Node *output, const NodeInput& input); - void CopyValue(const NodeInput& input); - void CopyValueHint(const NodeInput& input); - - void InsertIntoCopyMap(Node* node, Node* copy); - void ConnectToNodeCache(Node *node); void DisconnectFromNodeCache(Node *node); - void UpdateGraphChangeValue(); - void UpdateLastSyncedValue(); - void CancelQueuedSingleFrameRender(); void StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker); @@ -145,33 +126,9 @@ private: void VideoInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range); void AudioInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range); - class QueuedJob { - public: - enum Type { - kNodeAdded, - kNodeRemoved, - kEdgeAdded, - kEdgeRemoved, - kValueChanged, - kValueHintChanged - }; - - Type type; - Node* node; - NodeInput input; - Node *output; - }; - ViewerOutput* viewer_node_; - Project copied_project_; - - std::list graph_update_queue_; - QHash copy_map_; - QHash graph_map_; - ViewerOutput* copied_viewer_node_; - ColorManager* copied_color_manager_; - QVector created_nodes_; + ProjectCopier *copier_; TimeRange cache_range_; @@ -184,9 +141,6 @@ private: RenderTicketPtr single_frame_render_; QMap > video_immediate_passthroughs_; - JobTime graph_changed_time_; - JobTime last_update_time_; - QTimer delayed_requeue_timer_; JobTime last_conform_task_; @@ -194,6 +148,9 @@ private: QVector running_video_tasks_; QVector running_audio_tasks_; + ViewerOutput* copied_viewer_node_; + ColorManager* copied_color_manager_; + struct VideoJob { Node *node; PlaybackCache *cache; @@ -251,18 +208,6 @@ private slots: */ void VideoRendered(); - void NodeAdded(Node* node); - - void NodeRemoved(Node* node); - - void EdgeAdded(Node *output, const NodeInput& input); - - void EdgeRemoved(Node *output, const NodeInput& input); - - void ValueChanged(const NodeInput& input); - - void ValueHintChanged(const NodeInput &input); - /** * @brief Generic function called whenever the frames to render need to be (re)queued */ diff --git a/app/render/projectcopier.cpp b/app/render/projectcopier.cpp new file mode 100644 index 000000000..89ff9f1e4 --- /dev/null +++ b/app/render/projectcopier.cpp @@ -0,0 +1,259 @@ +/*** + + 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 "projectcopier.h" + +namespace olive { + +ProjectCopier::ProjectCopier(QObject *parent) : + QObject(parent) +{ + original_ = nullptr; + copy_ = new Project(); + copy_->setParent(this); +} + +void ProjectCopier::SetProject(Project *project) +{ + if (original_) { + // Clear current project + qDeleteAll(created_nodes_); + created_nodes_.clear(); + copy_map_.clear(); + graph_update_queue_.clear(); + + disconnect(original_, &NodeGraph::NodeAdded, this, &ProjectCopier::QueueNodeAdd); + disconnect(original_, &NodeGraph::NodeRemoved, this, &ProjectCopier::QueueNodeRemove); + disconnect(original_, &NodeGraph::InputConnected, this, &ProjectCopier::QueueEdgeAdd); + disconnect(original_, &NodeGraph::InputDisconnected, this, &ProjectCopier::QueueEdgeRemove); + disconnect(original_, &NodeGraph::ValueChanged, this, &ProjectCopier::QueueValueChange); + disconnect(original_, &NodeGraph::InputValueHintChanged, this, &ProjectCopier::QueueValueHintChange); + } + + original_ = project; + + if (original_) { + // Add all nodes + for (int i=0; inodes().size(); i++) { + InsertIntoCopyMap(original_->nodes().at(i), copy_->nodes().at(i)); + } + + for (int i=copy_->nodes().size(); inodes().size(); i++) { + DoNodeAdd(original_->nodes().at(i)); + } + + // Add all connections + foreach (Node* node, original_->nodes()) { + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + DoEdgeAdd(it->second, it->first); + } + } + + // Ensure graph change value is just before the sync value + UpdateGraphChangeValue(); + UpdateLastSyncedValue(); + + // Connect signals for future node additions/deletions + connect(original_, &NodeGraph::NodeAdded, this, &ProjectCopier::QueueNodeAdd, Qt::DirectConnection); + connect(original_, &NodeGraph::NodeRemoved, this, &ProjectCopier::QueueNodeRemove, Qt::DirectConnection); + connect(original_, &NodeGraph::InputConnected, this, &ProjectCopier::QueueEdgeAdd, Qt::DirectConnection); + connect(original_, &NodeGraph::InputDisconnected, this, &ProjectCopier::QueueEdgeRemove, Qt::DirectConnection); + connect(original_, &NodeGraph::ValueChanged, this, &ProjectCopier::QueueValueChange, Qt::DirectConnection); + connect(original_, &NodeGraph::InputValueHintChanged, this, &ProjectCopier::QueueValueHintChange, Qt::DirectConnection); + } +} + +void ProjectCopier::ProcessUpdateQueue() +{ + // Iterate everything that happened to the graph and do the same thing on our end + while (!graph_update_queue_.empty()) { + QueuedJob job = graph_update_queue_.front(); + graph_update_queue_.pop_front(); + + switch (job.type) { + case QueuedJob::kNodeAdded: + DoNodeAdd(job.node); + break; + case QueuedJob::kNodeRemoved: + DoNodeRemove(job.node); + break; + case QueuedJob::kEdgeAdded: + DoEdgeAdd(job.output, job.input); + break; + case QueuedJob::kEdgeRemoved: + DoEdgeRemove(job.output, job.input); + break; + case QueuedJob::kValueChanged: + DoValueChange(job.input); + break; + case QueuedJob::kValueHintChanged: + DoValueHintChange(job.input); + break; + } + } + + // Indicate that we have synchronized to this point, which is compared with the graph change + // time to see if our copied graph is up to date + UpdateLastSyncedValue(); +} + +void ProjectCopier::DoNodeAdd(Node *node) +{ + if (dynamic_cast(node)) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + + // Copy node + Node* copy = node->copy(); + + // Add to project + copy->setParent(copy_); + + // Disable caches for copy + copy->SetCachesEnabled(false); + + // Copy cache UUIDs + copy->CopyCacheUuidsFrom(node); + + // Insert into map + InsertIntoCopyMap(node, copy); + + // Keep track of our nodes + created_nodes_.append(copy); +} + +void ProjectCopier::DoNodeRemove(Node *node) +{ + // Find our copy and remove it + Node* copy = copy_map_.take(node); + + // Disconnect from node's caches + emit RemovedNode(node); + + // Remove from created list + created_nodes_.removeOne(copy); + + // Delete it + delete copy; +} + +void ProjectCopier::DoEdgeAdd(Node *output, const NodeInput &input) +{ + // Create same connection with our copied graph + Node* our_output = copy_map_.value(output); + Node* our_input = copy_map_.value(input.node()); + + Node::ConnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); +} + +void ProjectCopier::DoEdgeRemove(Node *output, const NodeInput &input) +{ + // Remove same connection with our copied graph + Node* our_output = copy_map_.value(output); + Node* our_input = copy_map_.value(input.node()); + + Node::DisconnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); +} + +void ProjectCopier::DoValueChange(const NodeInput &input) +{ + if (dynamic_cast(input.node())) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + + // Copy all values to our graph + Node* our_input = copy_map_.value(input.node()); + Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element()); +} + +void ProjectCopier::DoValueHintChange(const NodeInput &input) +{ + if (dynamic_cast(input.node())) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + + // Copy value hint to our graph + Node* our_input = copy_map_.value(input.node()); + Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element()); + our_input->SetValueHintForInput(input.input(), hint, input.element()); +} + +void ProjectCopier::InsertIntoCopyMap(Node *node, Node *copy) +{ + // Insert into map + copy_map_.insert(node, copy); + + // Copy parameters + Node::CopyInputs(node, copy, false); + + // Connect to node's cache + emit AddedNode(node); +} + +void ProjectCopier::QueueNodeAdd(Node *node) +{ + graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueNodeRemove(Node *node) +{ + graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueEdgeAdd(Node *output, const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueEdgeRemove(Node *output, const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueValueChange(const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::QueueValueHintChange(const NodeInput &input) +{ + graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); + UpdateGraphChangeValue(); +} + +void ProjectCopier::UpdateGraphChangeValue() +{ + graph_changed_time_.Acquire(); +} + +void ProjectCopier::UpdateLastSyncedValue() +{ + last_update_time_.Acquire(); +} + +} diff --git a/app/render/projectcopier.h b/app/render/projectcopier.h new file mode 100644 index 000000000..42fd14daa --- /dev/null +++ b/app/render/projectcopier.h @@ -0,0 +1,125 @@ +/*** + + 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 PROJECTCOPIER_H +#define PROJECTCOPIER_H + +#include "node/project/project.h" + +namespace olive { + +class ProjectCopier : public QObject +{ + Q_OBJECT +public: + ProjectCopier(QObject *parent = nullptr); + + void SetProject(Project *project); + + template + T *GetCopy(T *original) + { + return static_cast(copy_map_.value(original)); + } + + template + T *GetOriginal(T *copy) + { + return static_cast(copy_map_.key(copy)); + } + + const QHash &GetNodeMap() const { return copy_map_; } + + const JobTime &GetGraphChangeTime() const { return graph_changed_time_; } + const JobTime &GetLastUpdateTime() const { return last_update_time_; } + + bool HasUpdatesInQueue() const { return !graph_update_queue_.empty(); } + + /** + * @brief Process all changes to internal NodeGraph copy + * + * PreviewAutoCacher staggers updates to its internal NodeGraph copy, only applying them when the + * RenderManager is not reading from it. This function is called when such an opportunity arises. + */ + void ProcessUpdateQueue(); + +signals: + void AddedNode(Node *n); + void RemovedNode(Node *n); + +private: + void DoNodeAdd(Node* node); + void DoNodeRemove(Node* node); + void DoEdgeAdd(Node *output, const NodeInput& input); + void DoEdgeRemove(Node *output, const NodeInput& input); + void DoValueChange(const NodeInput& input); + void DoValueHintChange(const NodeInput& input); + + void InsertIntoCopyMap(Node* node, Node* copy); + + void UpdateGraphChangeValue(); + void UpdateLastSyncedValue(); + + Project *original_; + Project *copy_; + + class QueuedJob { + public: + enum Type { + kNodeAdded, + kNodeRemoved, + kEdgeAdded, + kEdgeRemoved, + kValueChanged, + kValueHintChanged + }; + + Type type; + Node* node; + NodeInput input; + Node *output; + }; + + std::list graph_update_queue_; + QHash copy_map_; + QHash graph_map_; + QVector created_nodes_; + + JobTime graph_changed_time_; + JobTime last_update_time_; + +private slots: + void QueueNodeAdd(Node* node); + + void QueueNodeRemove(Node* node); + + void QueueEdgeAdd(Node *output, const NodeInput& input); + + void QueueEdgeRemove(Node *output, const NodeInput& input); + + void QueueValueChange(const NodeInput& input); + + void QueueValueHintChange(const NodeInput &input); + +}; + +} + +#endif // PROJECTCOPIER_H From 3853ef023ece97b0d00a4079173c17c44c69ef39 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 12:37:38 -0700 Subject: [PATCH 52/85] exporttask: make copy of project Fixes #2037 --- app/task/export/export.cpp | 8 ++++++-- app/task/export/export.h | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 017fc59fb..397a0eda8 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -28,10 +28,14 @@ namespace olive { ExportTask::ExportTask(ViewerOutput *viewer_node, ColorManager* color_manager, const EncodingParams& params) : - color_manager_(color_manager), params_(params) { - set_viewer(viewer_node); + // Create a copy of the project + copier_ = new ProjectCopier(this); + copier_->SetProject(viewer_node->project()); + + set_viewer(copier_->GetCopy(viewer_node)); + color_manager_ = copier_->GetCopy(color_manager); // Adjust video params to have no divider VideoParams vp = viewer_node->GetVideoParams(); diff --git a/app/task/export/export.h b/app/task/export/export.h index 7dcd8cf99..7a367c6b2 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -24,6 +24,7 @@ #include "codec/encoder.h" #include "node/output/viewer/viewer.h" #include "render/colorprocessor.h" +#include "render/projectcopier.h" #include "task/render/render.h" #include "task/task.h" @@ -52,6 +53,8 @@ protected: private: bool WriteAudioLoop(const TimeRange &time, const SampleBuffer &samples); + ProjectCopier *copier_; + QHash time_map_; QHash audio_map_; From 1c4149a0fdae80a39d0799aaa7d9eef16737a225 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 12:49:30 -0700 Subject: [PATCH 53/85] preferences: add option to use glFinish i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl i hate opengl --- app/config/config.cpp | 1 + app/dialog/preferences/tabs/preferencesbehaviortab.cpp | 5 +++++ app/render/opengl/openglrenderer.cpp | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 56ea47a74..192fcdb74 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -104,6 +104,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailInOut); SetEntryInternal(QStringLiteral("TimelineWaveformMode"), NodeValue::kInt, Timeline::kWaveformsEnabled); diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index 6db150edc..ad184c7d1 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -107,6 +107,11 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() tr("Multiple clips can share the same nodes. Disable this to automatically share node " "dependencies among clips when copying or splitting them."), node_group); + + QTreeWidgetItem* opengl_group = AddParent(tr("OpenGL")); + AddItem(tr("Use glFinish"), + QStringLiteral("UseGLFinish"), + opengl_group); } void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index b0c91d7ab..3340c64d4 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -25,6 +25,8 @@ #include #include +#include "config/config.h" + namespace olive { const int OpenGLRenderer::kTextureCacheMaxSize = 5000; @@ -364,7 +366,11 @@ void OpenGLRenderer::Flush() { GL_PREAMBLE; - functions_->glFlush(); + if (OLIVE_CONFIG("UseGLFinish").toBool()) { + functions_->glFinish(); + } else { + functions_->glFlush(); + } } Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt) From 49ad3a1e3407b32eea0961a639b794bb08988323 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 5 Nov 2022 13:45:21 -0700 Subject: [PATCH 54/85] timecode: fix issue converting seconds to timecode and vice versa Fixes #2091 --- app/common/timecodefunctions.cpp | 157 ++++++++++--------- app/common/timecodefunctions.h | 6 +- app/widget/slider/CMakeLists.txt | 2 - app/widget/slider/rationalslider.cpp | 26 +-- app/widget/slider/timeslider.cpp | 63 -------- app/widget/slider/timeslider.h | 50 ------ app/widget/timelinewidget/timelinewidget.cpp | 1 + 7 files changed, 102 insertions(+), 203 deletions(-) delete mode 100644 app/widget/slider/timeslider.cpp delete mode 100644 app/widget/slider/timeslider.h diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index 1397a4dc7..ca6e8503e 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -35,16 +35,13 @@ QString padded(int64_t arg, int padding) { return QStringLiteral("%1").arg(arg, padding, 10, QChar('0')); } -QString Timecode::timestamp_to_timecode(const int64_t ×tamp, - const rational& timebase, - const Display& display, - bool show_plus_if_positive) +QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive) { if (timebase.isNull()) { return QStringLiteral("INVALID TIMEBASE"); } - double timestamp_dbl = (rational(timestamp) * timebase).toDouble(); + double time_dbl = time.toDouble(); switch (display) { case kTimecodeNonDropFrame: @@ -53,21 +50,21 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp, { QString prefix; - if (timestamp_dbl < 0) { + if (time_dbl < 0) { prefix = "-"; } else if (show_plus_if_positive) { prefix = "+"; } if (display == kTimecodeSeconds) { - timestamp_dbl = qAbs(timestamp_dbl); + time_dbl = qAbs(time_dbl); - int64_t total_seconds = qFloor(timestamp_dbl); + int64_t total_seconds = qFloor(time_dbl); int64_t hours = total_seconds / 3600; int64_t mins = total_seconds / 60 - hours * 60; int64_t secs = total_seconds - mins * 60; - int64_t fraction = qRound64((timestamp_dbl - static_cast(total_seconds)) * 1000); + int64_t fraction = qRound64((time_dbl - static_cast(total_seconds)) * 1000); return QStringLiteral("%1%2:%3:%4.%5").arg(prefix, padded(hours, 2), @@ -80,7 +77,7 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp, double frame_rate = timebase.flipped().toDouble(); int rounded_frame_rate = qRound(frame_rate); int64_t frames, secs, mins, hours; - int64_t f = qAbs(timestamp); + int64_t f = qAbs(time_to_timestamp(time, timebase)); if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) { frame_token = ";"; @@ -128,18 +125,36 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp, } } case kFrames: - return QString::number(timestamp); + return QString::number(time_to_timestamp(time, timebase)); case kMilliseconds: - return QString::number(qRound(timestamp_dbl * 1000)); + return QString::number(qRound(time_dbl * 1000)); } return QStringLiteral("INVALID TIMECODE MODE"); } -int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational &timebase, const Display &display, bool* ok) +int64_t StrToInt64EmptyTolerant(const QString &s, bool *ok) { - double timebase_dbl = timebase.toDouble(); + if (s.isEmpty()) { + if (ok) *ok = true; + return 0; + } else { + return s.toLongLong(ok); + } +} +double StrToDoubleEmptyTolerant(const QString &s, bool *ok) +{ + if (s.isEmpty()) { + if (ok) *ok = true; + return 0; + } else { + return s.toDouble(ok); + } +} + +rational Timecode::timecode_to_time(const QString &timecode, const rational &timebase, const Timecode::Display &display, bool *ok) +{ if (timecode.isEmpty()) { goto err_fatal; } @@ -149,71 +164,73 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational case kTimecodeDropFrame: case kTimecodeSeconds: { - const int kTimecodeElementCount = 4; - QStringList timecode_split = timecode.split(QRegularExpression("(:)|(;)|(\\.)")); + QStringList timecode_split = timecode.split(QRegularExpression("(:)|(;)")); - bool valid; + const int element_count = display == kTimecodeSeconds ? 3 : 4; - // We only deal with HH, MM, SS, and FF. Any values after that are ignored. - while (timecode_split.size() > kTimecodeElementCount) { + // Remove excess tokens (we're only interested in HH:MM:SS.FF) + while (timecode_split.size() > element_count) { timecode_split.removeLast(); } - // Convert values to integers - QList timecode_numbers; + // For easier index calculations, ensure minimum size + while (timecode_split.size() < element_count) { + timecode_split.prepend(QString()); + } bool negative = timecode.trimmed().startsWith('-'); - foreach (const QString& element, timecode_split) { - valid = true; - - timecode_numbers.append((element.isEmpty()) ? 0 : qAbs(element.toLong(&valid))); - - // If element cannot be converted to a number, - if (!valid) { - goto err_fatal; - } - } - - // Ensure value size is always 4 - while (timecode_numbers.size() < 4) { - timecode_numbers.prepend(0); - } - double frame_rate = timebase.flipped().toDouble(); int rounded_frame_rate = qRound(frame_rate); - int64_t hours = timecode_numbers.at(0); - int64_t mins = timecode_numbers.at(1); - int64_t secs = timecode_numbers.at(2); - int64_t frames = timecode_numbers.at(3); + bool valid; + rational time; - int64_t sec_count = (hours*3600 + mins*60 + secs); - int64_t timestamp = sec_count*rounded_frame_rate + frames; + int64_t hours = StrToInt64EmptyTolerant(timecode_split.at(0), &valid); + if (!valid) goto err_fatal; + int64_t mins = StrToInt64EmptyTolerant(timecode_split.at(1), &valid); + if (!valid) goto err_fatal; - if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) { + if (display == kTimecodeSeconds) { + double secs = StrToDoubleEmptyTolerant(timecode_split.at(2), &valid); + if (!valid) goto err_fatal; - // Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate - int64_t dropFrames = qRound64(frame_rate * (2.0/30.0)); + time = rational::fromDouble(hours * 3600 + mins * 60 + secs); + } else { + int64_t secs = StrToInt64EmptyTolerant(timecode_split.at(2), &valid); + if (!valid) goto err_fatal; + int64_t frames = StrToInt64EmptyTolerant(timecode_split.at(3), &valid); + if (!valid) goto err_fatal; - // d and m need to be calculated from - int64_t real_fr_ts = qRound64(static_cast(sec_count)*frame_rate) + frames; + int64_t sec_count = (hours*3600 + mins*60 + secs); + int64_t frame_count = sec_count*rounded_frame_rate + frames; - int64_t framesPer10Minutes = qRound(frame_rate * 600); - int64_t d = real_fr_ts / framesPer10Minutes; - int64_t m = real_fr_ts % framesPer10Minutes; + if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) { - if (m > dropFrames) { - timestamp -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames)); + // Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int64_t dropFrames = qRound64(frame_rate * (2.0/30.0)); + + // d and m need to be calculated from + int64_t real_fr_ts = qRound64(static_cast(sec_count)*frame_rate) + frames; + + int64_t framesPer10Minutes = qRound(frame_rate * 600); + int64_t d = real_fr_ts / framesPer10Minutes; + int64_t m = real_fr_ts % framesPer10Minutes; + + if (m > dropFrames) { + frame_count -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames)); + } + frame_count -= dropFrames*9*d; } - timestamp -= dropFrames*9*d; + + time = timestamp_to_time(frame_count, timebase); } if (ok) *ok = true; - if (negative) timestamp = -timestamp; + if (negative) time = -time; - return timestamp; + return time; } case kMilliseconds: { @@ -224,18 +241,23 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational // Convert milliseconds to seconds timecode_secs *= 0.001; - // Convert seconds to frames - timecode_secs /= timebase_dbl; - - if (ok) *ok = true; - return qRound(timecode_secs); + // Convert seconds to rational + return rational::fromDouble(timecode_secs, ok); } else { goto err_fatal; } } case kFrames: + { + bool valid; + int64_t ts = timecode.toLongLong(&valid); + if (!valid) { + goto err_fatal; + } + if (ok) *ok = true; - return timecode.toLong(ok); + return timestamp_to_time(ts, timebase); + } } err_fatal: @@ -243,12 +265,6 @@ err_fatal: return 0; } -rational Timecode::timecode_to_time(const QString &timecode, const rational &timebase, const Timecode::Display &display, bool *ok) -{ - int64_t timestamp = timecode_to_timestamp(timecode, timebase, display, ok); - return timestamp_to_time(timestamp, timebase); -} - rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase, Rounding floor) { // Just convert to a timestamp in timebase units and back @@ -269,11 +285,6 @@ rational Timecode::timestamp_to_time(const int64_t ×tamp, const rational &t return rational(num_r, den_r); } -QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive) -{ - return timestamp_to_timecode(time_to_timestamp(time, timebase), timebase, display, show_plus_if_positive); -} - bool Timecode::TimebaseIsDropFrame(const rational &timebase) { return (timebase.numerator() != 1); diff --git a/app/common/timecodefunctions.h b/app/common/timecodefunctions.h index fe5273acb..0cdabd242 100644 --- a/app/common/timecodefunctions.h +++ b/app/common/timecodefunctions.h @@ -56,9 +56,7 @@ public: /** * @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation */ - static QString timestamp_to_timecode(const int64_t ×tamp, const rational& timebase, const Display &display, bool show_plus_if_positive = false); - - static int64_t timecode_to_timestamp(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr); + static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false); static rational timecode_to_time(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr); static rational snap_time_to_timebase(const rational& time, const rational& timebase, Rounding floor = kRound); @@ -71,8 +69,6 @@ public: static rational timestamp_to_time(const int64_t& timestamp, const rational& timebase); - static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false); - static bool TimebaseIsDropFrame(const rational& timebase); static QString TimeToString(int64_t ms); diff --git a/app/widget/slider/CMakeLists.txt b/app/widget/slider/CMakeLists.txt index d20c999e6..b90c18ab5 100644 --- a/app/widget/slider/CMakeLists.txt +++ b/app/widget/slider/CMakeLists.txt @@ -26,7 +26,5 @@ set(OLIVE_SOURCES widget/slider/rationalslider.cpp widget/slider/stringslider.h widget/slider/stringslider.cpp - widget/slider/timeslider.h - widget/slider/timeslider.cpp PARENT_SCOPE ) diff --git a/app/widget/slider/rationalslider.cpp b/app/widget/slider/rationalslider.cpp index 3cf9e1e85..8ac41bf0d 100644 --- a/app/widget/slider/rationalslider.cpp +++ b/app/widget/slider/rationalslider.cpp @@ -98,18 +98,24 @@ void RationalSlider::DisableDisplayType(RationalSlider::DisplayType type) QString RationalSlider::ValueToString(const QVariant &v) const { - double val = v.value().toDouble() + GetOffset().value().toDouble(); + rational r = v.value(); - switch (display_type_) { - case kTime: - return Timecode::time_to_timecode(v.value(), timebase_, Core::instance()->GetTimecodeDisplay()); - case kFloat: - return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces()); - case kRational: - return v.value().toString(); + if (r.isNaN()) { + return tr("NaN"); + } else { + double val = r.toDouble() + GetOffset().value().toDouble(); + + switch (display_type_) { + case kTime: + return Timecode::time_to_timecode(r, timebase_, Core::instance()->GetTimecodeDisplay()); + case kFloat: + return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces()); + case kRational: + return v.value().toString(); + } + + return v.toString(); } - - return v.toString(); } QVariant RationalSlider::StringToValue(const QString &s, bool *ok) const diff --git a/app/widget/slider/timeslider.cpp b/app/widget/slider/timeslider.cpp deleted file mode 100644 index 57fc557b5..000000000 --- a/app/widget/slider/timeslider.cpp +++ /dev/null @@ -1,63 +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 "timeslider.h" - -#include "common/timecodefunctions.h" -#include "core.h" - -namespace olive { - -#define super IntegerSlider - -TimeSlider::TimeSlider(QWidget *parent) : - super(parent) -{ - SetMinimum(0); - - connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &TimeSlider::UpdateLabel); -} - -void TimeSlider::SetTimebase(const rational &timebase) -{ - timebase_ = timebase; - - // Refresh label since we have a new timebase to generate a timecode with - UpdateLabel(); -} - -QString TimeSlider::ValueToString(const QVariant &v) const -{ - if (timebase_.isNull()) { - // We can't generate a timecode without a timebase, so we just return the number - return super::ValueToString(v); - } - - return Timecode::timestamp_to_timecode(v.toLongLong() + GetOffset().toLongLong(), - timebase_, - Core::instance()->GetTimecodeDisplay()); -} - -QVariant TimeSlider::StringToValue(const QString &s, bool *ok) const -{ - return QVariant::fromValue(Timecode::timecode_to_timestamp(s, timebase_, Core::instance()->GetTimecodeDisplay(), ok) - GetOffset().toLongLong()); -} - -} diff --git a/app/widget/slider/timeslider.h b/app/widget/slider/timeslider.h deleted file mode 100644 index b22bb325c..000000000 --- a/app/widget/slider/timeslider.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 TIMESLIDER_H -#define TIMESLIDER_H - -#include "common/rational.h" -#include "integerslider.h" - -namespace olive { - -class TimeSlider : public IntegerSlider -{ - Q_OBJECT -public: - TimeSlider(QWidget* parent = nullptr); - -public slots: - void SetTimebase(const rational& timebase); - -protected: - virtual QString ValueToString(const QVariant& v) const override; - - virtual QVariant StringToValue(const QString& s, bool* ok) const override; - -private: - rational timebase_; - -}; - -} - -#endif // TIMESLIDER_H diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index e4e2f7710..c342e8c32 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -289,6 +289,7 @@ void TimelineWidget::ConnectNodeEvent(ViewerOutput *n) connect(timecode_label_, &RationalSlider::ValueChanged, s, &Sequence::SetPlayhead); connect(s, &Sequence::PlayheadChanged, timecode_label_, &RationalSlider::SetValue); + timecode_label_->SetValue(s->GetPlayhead()); ruler()->SetPlaybackCache(n->video_frame_cache()); From 6307f8d9e38821ea6e1526fa013cce155911d9dd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 6 Nov 2022 11:11:38 -0800 Subject: [PATCH 55/85] core: fix warning breaking linux builds --- app/core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 2523c00e0..a8ae02e47 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -87,9 +87,9 @@ Core::Core(const CoreParams& params) : addable_object_(Tool::kAddableEmpty), snapping_(true), core_params_(params), + magic_(false), pixel_sampling_users_(0), - shown_cache_full_warning_(false), - magic_(false) + shown_cache_full_warning_(false) { // Store reference to this object, making the assumption that Core will only ever be made in // main(). This will obviously break if not. From 812bdca2caf02a3c18c1a38df9500ffe7f6a6826 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 6 Nov 2022 21:04:39 -0800 Subject: [PATCH 56/85] core: strip drive letters from windows paths Qt on non-Windows chooses not to recognize Windows paths, so we translate them to a Unix-esque path --- app/core.cpp | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index a8ae02e47..4c01573bd 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1689,27 +1689,52 @@ void Core::CacheActiveSequence(bool in_out_only) } } +QString StripWindowsDriveLetter(QString s) +{ + // HACK: On Windows, absolute paths are saved with a drive letter (e.g. "C:\video.mp4"). Below, + // we use Qt's relative path system to resolve when an entire project may be in a different + // folder, but the files are all in the same place relatively to the project. Unfortunately, + // Qt chooses not to understand paths from Windows on non-Windows platforms, which causes + // this to break when a project is moving from Windows to non-Windows. To resolve that, if + // we're on a non-Windows platform and we detect a Windows path (i.e. a path with a drive + // letter at the start), we strip it off. We also convert any back-slashes to forward-slashes + // because on Windows they are interchangeable and on non-Windows they are not. +#ifndef Q_OS_WINDOWS + if (s.size() >= 2) { + if (s.at(0).isLetter() && s.at(1) == ':') { + s = s.mid(2); + s.replace('\\', '/'); + } + } +#endif + + return s; +} + bool Core::ValidateFootageInLoadedProject(Project* project, const QString& project_saved_url) { QVector project_footage = project->root()->ListChildrenOfType(); QVector footage_we_couldnt_validate; foreach (Footage* footage, project_footage) { - if (!QFileInfo::exists(footage->filename()) && !project_saved_url.isEmpty()) { + QString footage_fn = StripWindowsDriveLetter(footage->filename()); + QString project_fn = StripWindowsDriveLetter(project_saved_url); + + if (!QFileInfo::exists(footage_fn) && !project_saved_url.isEmpty()) { // If the footage doesn't exist, it might have moved with the project const QString& project_current_url = project->filename(); - if (project_current_url != project_saved_url) { + if (project_current_url != project_fn) { // Project has definitely moved, try to resolve relative paths - QDir saved_dir(QFileInfo(project_saved_url).dir()); + QDir saved_dir(QFileInfo(project_fn).dir()); QDir true_dir(QFileInfo(project_current_url).dir()); - QString relative_filename = saved_dir.relativeFilePath(footage->filename()); + QString relative_filename = saved_dir.relativeFilePath(footage_fn); QString transformed_abs_filename = true_dir.filePath(relative_filename); if (QFileInfo::exists(transformed_abs_filename)) { // Use this file instead - qInfo() << "Resolved" << footage->filename() << "relatively to" << transformed_abs_filename; + qInfo() << "Resolved" << footage_fn << "relatively to" << transformed_abs_filename; footage->set_filename(transformed_abs_filename); } } From 6a394b4e48b02ea67abd8e1823e6d398f6ca6c69 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 6 Nov 2022 21:04:53 -0800 Subject: [PATCH 57/85] viewer: only detect multicam nodes if a viewer is connected Fixes null ref crash --- app/widget/viewer/viewer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 32d1a5bd9..b84d0dec2 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -619,7 +619,9 @@ void ViewerWidget::SaveFrameAsImage() void ViewerWidget::DetectMulticamNodeNow() { - DetectMulticamNode(GetConnectedNode()->GetPlayhead()); + if (GetConnectedNode()) { + DetectMulticamNode(GetConnectedNode()->GetPlayhead()); + } } void ViewerWidget::CloseAudioProcessor() From a6cafe59a5338eff194105b52ba0f6e02fd3e93a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 6 Nov 2022 21:44:48 -0800 Subject: [PATCH 58/85] previewautocacher: fix bad if statement --- app/render/previewautocacher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 44112b55c..149a1b83c 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -253,7 +253,7 @@ void PreviewAutoCacher::VideoRendered() void PreviewAutoCacher::ConnectToNodeCache(Node *node) { - if (!ignore_cache_requests_) { + if (ignore_cache_requests_) { return; } From 373717629e52887fd4200d022dec2e422365f39f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 7 Nov 2022 10:42:41 -0800 Subject: [PATCH 59/85] task: improve threading --- app/task/task.h | 3 +++ app/task/taskmanager.cpp | 5 +++-- app/widget/taskview/elapsedcounterwidget.cpp | 3 ++- app/widget/taskview/elapsedcounterwidget.h | 3 ++- app/widget/taskview/taskviewitem.cpp | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/task/task.h b/app/task/task.h index 835c009fd..a2b516c1f 100644 --- a/app/task/task.h +++ b/app/task/task.h @@ -93,6 +93,7 @@ public slots: bool Start() { start_time_ = QDateTime::currentMSecsSinceEpoch(); + emit Started(start_time_); bool ret = Run(); @@ -150,6 +151,8 @@ protected: } signals: + void Started(qint64 start_time); + /** * @brief Signal emitted whenever progress is made * diff --git a/app/task/taskmanager.cpp b/app/task/taskmanager.cpp index 5d2be1036..9165bb135 100644 --- a/app/task/taskmanager.cpp +++ b/app/task/taskmanager.cpp @@ -29,6 +29,7 @@ TaskManager* TaskManager::instance_ = nullptr; TaskManager::TaskManager() { + thread_pool_.setMaxThreadCount(1); } TaskManager::~TaskManager() @@ -95,9 +96,9 @@ void TaskManager::AddTask(Task* t) // Run task concurrently watcher->setFuture( #if QT_VERSION_MAJOR >= 6 - QtConcurrent::run(&Task::Start, t) + QtConcurrent::run(&thread_pool_, &Task::Start, t) #else - QtConcurrent::run(t, &Task::Start) + QtConcurrent::run(&thread_pool_, t, &Task::Start) #endif ); diff --git a/app/widget/taskview/elapsedcounterwidget.cpp b/app/widget/taskview/elapsedcounterwidget.cpp index 288bae977..8300be9f4 100644 --- a/app/widget/taskview/elapsedcounterwidget.cpp +++ b/app/widget/taskview/elapsedcounterwidget.cpp @@ -22,6 +22,7 @@ #include #include +#include #include "common/timecodefunctions.h" @@ -80,7 +81,7 @@ void ElapsedCounterWidget::UpdateTimers() double ms_per_progress_unit = elapsed_ms / last_progress_; double remaining_progress = 1.0 - last_progress_; - remaining_ms = qRound64(ms_per_progress_unit * remaining_progress); + remaining_ms = std::ceil(ms_per_progress_unit * remaining_progress); } else { elapsed_ms = 0; remaining_ms = 0; diff --git a/app/widget/taskview/elapsedcounterwidget.h b/app/widget/taskview/elapsedcounterwidget.h index 809853fd0..ad1901c60 100644 --- a/app/widget/taskview/elapsedcounterwidget.h +++ b/app/widget/taskview/elapsedcounterwidget.h @@ -37,8 +37,9 @@ public: void SetProgress(double d); - void Start(); +public slots: void Start(qint64 start_time); + void Start(); public slots: void Stop(); diff --git a/app/widget/taskview/taskviewitem.cpp b/app/widget/taskview/taskviewitem.cpp index d63e8f42b..eaa7f04e0 100644 --- a/app/widget/taskview/taskviewitem.cpp +++ b/app/widget/taskview/taskviewitem.cpp @@ -72,9 +72,9 @@ TaskViewItem::TaskViewItem(Task* task, QWidget *parent) : // Set up elapsed timer status_stack_->setCurrentWidget(elapsed_timer_lbl_); - elapsed_timer_lbl_->Start(task_->GetStartTime()); // Connect to the task + connect(task_, &Task::Started, elapsed_timer_lbl_, qOverload(&ElapsedCounterWidget::Start)); connect(task_, &Task::ProgressChanged, this, &TaskViewItem::UpdateProgress); connect(cancel_btn_, &QPushButton::clicked, this, [this] { emit TaskCancelled(task_); }); } From 2aaa2fb87cdab2b9fd51ded329cce987f25639b8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 7 Nov 2022 22:01:52 -0800 Subject: [PATCH 60/85] nodeparamviewwidgetbridge: null check time target --- app/widget/nodeparamview/nodeparamview.cpp | 10 +--------- app/widget/nodeparamview/nodeparamview.h | 4 ---- app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp | 3 ++- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 590758aeb..c734af945 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -40,7 +40,6 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : super(true, false, parent), last_scroll_val_(0), focused_node_(nullptr), - time_target_(nullptr), show_all_nodes_(false) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) @@ -365,13 +364,6 @@ void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) foreach (NodeParamViewContext* item, context_items_) { item->SetTimeTarget(n); } - - time_target_ = n; -} - -ViewerOutput *NodeParamView::GetTimeTarget() const -{ - return time_target_; } void ReconnectOutputsIfNotDeletingNode(MultiUndoCommand *c, NodeViewDeleteCommand *dc, Node *output, Node *deleting, Node *context) @@ -721,7 +713,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) connect(item, &NodeParamViewItem::RequestEditTextInViewer, this, &NodeParamView::RequestEditTextInViewer); item->SetContext(ctx); - item->SetTimeTarget(GetTimeTarget()); + item->SetTimeTarget(GetConnectedNode()); item->SetTimebase(timebase()); context->AddNode(item); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index f9ecc5fb5..a542fcc56 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -48,8 +48,6 @@ public: void CloseContextsBelongingToProject(Project *p); - ViewerOutput *GetTimeTarget() const; - void DeleteSelected(); void SelectAll() @@ -156,8 +154,6 @@ private: NodeParamViewItem* focused_node_; QVector selected_nodes_; - ViewerOutput *time_target_; - QVector contexts_; QVector current_contexts_; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index cdc80f417..693e66bd4 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -547,7 +547,8 @@ void NodeParamViewWidgetBridge::TimeTargetConnectEvent(ViewerOutput *v) void NodeParamViewWidgetBridge::InputValueChanged(const NodeInput &input, const TimeRange &range) { - if (GetInnerInput() == input + if (GetTimeTarget() + && GetInnerInput() == input && !dragger_.IsStarted() && range.in() <= GetTimeTarget()->GetPlayhead() && range.out() >= GetTimeTarget()->GetPlayhead()) { // We'll need to update the widgets because the values have changed on our current time From 4f218522413b3f53e6ac8298f4cc26086a5dc263 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 9 Nov 2022 13:31:45 -0800 Subject: [PATCH 61/85] core: resolve all footage nodes in a project regardless of their connection to root --- app/core.cpp | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 4c01573bd..b66087d48 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1713,38 +1713,39 @@ QString StripWindowsDriveLetter(QString s) bool Core::ValidateFootageInLoadedProject(Project* project, const QString& project_saved_url) { - QVector project_footage = project->root()->ListChildrenOfType(); QVector footage_we_couldnt_validate; - foreach (Footage* footage, project_footage) { - QString footage_fn = StripWindowsDriveLetter(footage->filename()); - QString project_fn = StripWindowsDriveLetter(project_saved_url); + for (Node *n : project->nodes()) { + if (Footage *footage = dynamic_cast(n)) { + QString footage_fn = StripWindowsDriveLetter(footage->filename()); + QString project_fn = StripWindowsDriveLetter(project_saved_url); - if (!QFileInfo::exists(footage_fn) && !project_saved_url.isEmpty()) { - // If the footage doesn't exist, it might have moved with the project - const QString& project_current_url = project->filename(); + if (!QFileInfo::exists(footage_fn) && !project_saved_url.isEmpty()) { + // If the footage doesn't exist, it might have moved with the project + const QString& project_current_url = project->filename(); - if (project_current_url != project_fn) { - // Project has definitely moved, try to resolve relative paths - QDir saved_dir(QFileInfo(project_fn).dir()); - QDir true_dir(QFileInfo(project_current_url).dir()); + if (project_current_url != project_fn) { + // Project has definitely moved, try to resolve relative paths + QDir saved_dir(QFileInfo(project_fn).dir()); + QDir true_dir(QFileInfo(project_current_url).dir()); - QString relative_filename = saved_dir.relativeFilePath(footage_fn); - QString transformed_abs_filename = true_dir.filePath(relative_filename); + QString relative_filename = saved_dir.relativeFilePath(footage_fn); + QString transformed_abs_filename = true_dir.filePath(relative_filename); - if (QFileInfo::exists(transformed_abs_filename)) { - // Use this file instead - qInfo() << "Resolved" << footage_fn << "relatively to" << transformed_abs_filename; - footage->set_filename(transformed_abs_filename); + if (QFileInfo::exists(transformed_abs_filename)) { + // Use this file instead + qInfo() << "Resolved" << footage_fn << "relatively to" << transformed_abs_filename; + footage->set_filename(transformed_abs_filename); + } } } - } - if (QFileInfo::exists(footage->filename())) { - // Assume valid - footage->SetValid(); - } else { - footage_we_couldnt_validate.append(footage); + if (QFileInfo::exists(footage->filename())) { + // Assume valid + footage->SetValid(); + } else { + footage_we_couldnt_validate.append(footage); + } } } From 435971c623083905ce44f8ee7042551eaa3ec2c3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 9 Nov 2022 14:11:46 -0800 Subject: [PATCH 62/85] core: don't add autorecoveries projects to recent projects --- app/core.cpp | 14 ++++++++------ app/core.h | 17 +++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index b66087d48..26a8d68dc 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -468,7 +468,7 @@ void Core::CreateNewSequence() } } -void Core::AddOpenProject(Project* p) +void Core::AddOpenProject(Project* p, bool add_to_recents) { // Ensure project is not open at the moment foreach (Project* already_open, open_projects_) { @@ -487,12 +487,14 @@ void Core::AddOpenProject(Project* p) connect(p, &Project::ModifiedChanged, this, &Core::ProjectWasModified); open_projects_.append(p); - PushRecentlyOpenedProject(p->filename()); + if (!p->filename().isEmpty() && add_to_recents) { + PushRecentlyOpenedProject(p->filename()); + } emit ProjectOpened(p); } -bool Core::AddOpenProjectFromTask(Task *task) +bool Core::AddOpenProjectFromTask(Task *task, bool add_to_recents) { ProjectLoadBaseTask* load_task = static_cast(task); @@ -500,7 +502,7 @@ bool Core::AddOpenProjectFromTask(Task *task) Project* project = load_task->GetLoadedProject(); if (ValidateFootageInLoadedProject(project, project->GetSavedURL())) { - AddOpenProject(project); + AddOpenProject(project, add_to_recents); main_window_->LoadLayout(project->GetLayoutInfo()); return true; @@ -723,7 +725,7 @@ void Core::OpenStartupProject() void Core::AddRecoveryProjectFromTask(Task *task) { - if (AddOpenProjectFromTask(task)) { + if (AddOpenProjectFromTask(task, false)) { ProjectLoadBaseTask* load_task = static_cast(task); Project* project = load_task->GetLoadedProject(); @@ -1417,7 +1419,7 @@ void Core::OpenProjectInternal(const QString &filename, bool recovery_project) if (recovery_project) { connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddRecoveryProjectFromTask); } else { - connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTask); + connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTaskAndAddToRecents); } task_dialog->open(); diff --git a/app/core.h b/app/core.h index cc161d87f..297db9a47 100644 --- a/app/core.h +++ b/app/core.h @@ -568,6 +568,13 @@ private: void SaveRecentProjectsList(); + /** + * @brief Adds a project to the "open projects" list + */ + void AddOpenProject(olive::Project* p, bool add_to_recents = false); + + bool AddOpenProjectFromTask(Task* task, bool add_to_recents); + /** * @brief Internal main window object */ @@ -650,12 +657,10 @@ private slots: void ProjectSaveSucceeded(Task *task); - /** - * @brief Adds a project to the "open projects" list - */ - void AddOpenProject(olive::Project* p); - - bool AddOpenProjectFromTask(Task* task); + bool AddOpenProjectFromTaskAndAddToRecents(Task* task) + { + return AddOpenProjectFromTask(task, true); + } void ImportTaskComplete(Task *task); From de8d50eab0dcd7e27b31d44ea2924313386fc572 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 10 Nov 2022 09:02:29 -0800 Subject: [PATCH 63/85] yuv2rgb: correctly center uv based on bpp --- app/shaders/yuv2rgb.frag | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/shaders/yuv2rgb.frag b/app/shaders/yuv2rgb.frag index 623badc4f..ce004736c 100644 --- a/app/shaders/yuv2rgb.frag +++ b/app/shaders/yuv2rgb.frag @@ -36,9 +36,18 @@ void main() // 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) { + if (bits_per_pixel == 8) { + // Convert 0.0-1.0 to -0.5-0.5 + yuv.gb -= (128.0/255.0); + } else if (bits_per_pixel == 10) { + // Convert 0.0-1.0 to -0.5-0.5 + yuv.gb -= (512.0/1023.0); + yuv *= 64.0; } else if (bits_per_pixel == 12) { + // Convert 0.0-1.0 to -0.5-0.5 + yuv.gb -= (2048.0/4095.0); + yuv *= 16.0; } @@ -46,10 +55,6 @@ void main() 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; - // Use coefficients to weigh YUV into RGB vec4 rgba; rgba.r = yuv.r + yuv_crv * yuv.b; From 4c69f1260af158de63ede5ba3f5f34b174f139d9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 10 Nov 2022 10:14:02 -0800 Subject: [PATCH 64/85] pixelsampler: show more detail --- app/widget/pixelsampler/pixelsampler.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/widget/pixelsampler/pixelsampler.cpp b/app/widget/pixelsampler/pixelsampler.cpp index 5a3128e1a..8c47f7c14 100644 --- a/app/widget/pixelsampler/pixelsampler.cpp +++ b/app/widget/pixelsampler/pixelsampler.cpp @@ -54,14 +54,18 @@ void PixelSamplerWidget::UpdateLabelInternal() box_->SetColor(color_); label_->setText(tr("" - "R: %1
" - "G: %2
" - "B: %3
" - "A: %4" + "R: %1 (%5)
" + "G: %2 (%6)
" + "B: %3 (%7)
" + "A: %4 (%8)" "").arg(QString::number(color_.red()), QString::number(color_.green()), QString::number(color_.blue()), - QString::number(color_.alpha()))); + QString::number(color_.alpha()), + QString::number(int(color_.red()*255.0)), + QString::number(int(color_.green()*255.0)), + QString::number(int(color_.blue()*255.0)), + QString::number(int(color_.alpha()*255.0)))); } ManagedPixelSamplerWidget::ManagedPixelSamplerWidget(QWidget *parent) : From 28b8b22437c28723528bb0314f09bbfde86c5b32 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 11 Nov 2022 09:58:26 -0800 Subject: [PATCH 65/85] footage: don't assume non-video tracks are audio tracks Fixes #2093 --- app/node/project/footage/footage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 26e41a5de..96428f345 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -286,7 +286,7 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV job.set_video_params(vp); table->Push(NodeValue::kTexture, Texture::Job(vp, job), this, ref.ToString()); - } else { + } else if (ref.type() == Track::kAudio) { AudioParams ap = GetAudioParams(ref.index()); job.set_audio_params(ap); job.set_cache_path(project()->cache_path()); From b4e8d74d758b803c842df628bff484fbd262b6b0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 11 Nov 2022 10:02:04 -0800 Subject: [PATCH 66/85] viewer: use qt::tool for viewer text toolbar --- app/widget/viewer/viewerdisplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 832cf2a92..0fcb70e40 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -745,7 +745,7 @@ void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) // Create toolbar text_toolbar_ = new ViewerTextEditorToolBar(text_edit_); - text_toolbar_->setWindowFlags(Qt::Dialog| Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint); + text_toolbar_->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); connect(text_toolbar_, &ViewerTextEditorToolBar::VerticalAlignmentChanged, text, &TextGizmo::SetVerticalAlignment); connect(text, &TextGizmo::VerticalAlignmentChanged, text_toolbar_, &ViewerTextEditorToolBar::SetVerticalAlignment); text_toolbar_->SetVerticalAlignment(text->GetVerticalAlignment()); From 2cf0b4aa8ad2e03129894d7c74f3c7efc2cc0c76 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 11 Nov 2022 10:15:00 -0800 Subject: [PATCH 67/85] viewer: remove padding from viewer Isn't really necessary, probably added for "aesthetics" but is ultimately a waste of screen real estate --- app/widget/viewer/viewersizer.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/widget/viewer/viewersizer.cpp b/app/widget/viewer/viewersizer.cpp index d8ae52b84..a766ec76f 100644 --- a/app/widget/viewer/viewersizer.cpp +++ b/app/widget/viewer/viewersizer.cpp @@ -175,11 +175,6 @@ void ViewerSizer::UpdateSize() double zoom_diff = (zoom_ * 0.01) / current_scale; child_matrix.scale(zoom_diff, zoom_diff, 1.0); - } else { - - // Fit - add a small amount of padding - child_matrix.scale(0.95f, 0.95f); - } emit RequestScale(child_matrix); From f6cbcf8ef9fe3601a5975f9e34cd29e540264774 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:06:31 -0800 Subject: [PATCH 68/85] viewer: respond to wheel events for moving and zooming --- .../handmovableview/handmovableview.cpp | 9 ++- app/widget/handmovableview/handmovableview.h | 6 +- app/widget/viewer/viewer.cpp | 6 +- app/widget/viewer/viewersizer.cpp | 56 ++++++++++++++++++- app/widget/viewer/viewersizer.h | 6 ++ 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index d7b748195..83052dea3 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -150,16 +150,21 @@ const HandMovableView::DragMode &HandMovableView::GetDefaultDragMode() const return default_drag_mode_; } -bool HandMovableView::WheelEventIsAZoomEvent(QWheelEvent *event) const +bool HandMovableView::WheelEventIsAZoomEvent(QWheelEvent *event) { return (static_cast(event->modifiers() & Qt::ControlModifier) == !OLIVE_CONFIG("ScrollZooms").toBool()); } +qreal HandMovableView::GetScrollZoomMultiplier(QWheelEvent *event) +{ + return 1.0 + (static_cast(event->angleDelta().x() + event->angleDelta().y()) * 0.001); +} + void HandMovableView::wheelEvent(QWheelEvent *event) { if (WheelEventIsAZoomEvent(event)) { if (!event->angleDelta().isNull()) { - qreal multiplier = 1.0 + (static_cast(event->angleDelta().x() + event->angleDelta().y()) * 0.001); + qreal multiplier = GetScrollZoomMultiplier(event); QPointF cursor_pos; #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) diff --git a/app/widget/handmovableview/handmovableview.h b/app/widget/handmovableview/handmovableview.h index 63c09cb18..15fd8fc38 100644 --- a/app/widget/handmovableview/handmovableview.h +++ b/app/widget/handmovableview/handmovableview.h @@ -34,6 +34,10 @@ class HandMovableView : public QGraphicsView public: HandMovableView(QWidget* parent = nullptr); + static bool WheelEventIsAZoomEvent(QWheelEvent* event); + + static qreal GetScrollZoomMultiplier(QWheelEvent* event); + protected: virtual void ToolChangedEvent(Tool::Item tool){Q_UNUSED(tool)} @@ -44,8 +48,6 @@ protected: void SetDefaultDragMode(DragMode mode); const DragMode& GetDefaultDragMode() const; - bool WheelEventIsAZoomEvent(QWheelEvent* event) const; - virtual void wheelEvent(QWheelEvent* event) override; virtual void ZoomIntoCursorPosition(QWheelEvent* event, double multiplier, const QPointF &cursor_pos); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index b84d0dec2..c461743b9 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1373,10 +1373,10 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) Menu* zoom_menu = new Menu(tr("Zoom"), &menu); menu.addMenu(zoom_menu); - int zoom_levels[] = {10, 25, 50, 75, 100, 150, 200, 400}; zoom_menu->addAction(tr("Fit"))->setData(0); - for (int i=0;i<8;i++) { - zoom_menu->addAction(tr("%1%").arg(zoom_levels[i]))->setData(zoom_levels[i]); + for (int i=0;iaddAction(tr("%1%").arg(z))->setData(z); } connect(zoom_menu, &QMenu::triggered, this, &ViewerWidget::SetZoomFromMenu); diff --git a/app/widget/viewer/viewersizer.cpp b/app/widget/viewer/viewersizer.cpp index a766ec76f..505cb4eb9 100644 --- a/app/widget/viewer/viewersizer.cpp +++ b/app/widget/viewer/viewersizer.cpp @@ -20,7 +20,12 @@ #include "viewersizer.h" +#include +#include #include +#include + +#include "widget/handmovableview/handmovableview.h" namespace olive { @@ -30,7 +35,8 @@ ViewerSizer::ViewerSizer(QWidget *parent) : width_(0), height_(0), pixel_aspect_(1), - zoom_(0) + zoom_(0), + current_widget_scale_(0) { horiz_scrollbar_ = new QScrollBar(Qt::Horizontal, this); horiz_scrollbar_->setVisible(false); @@ -52,6 +58,7 @@ void ViewerSizer::SetWidget(QWidget *widget) if (widget_ != nullptr) { widget_->setParent(this); + widget_->installEventFilter(this); UpdateSize(); } @@ -90,6 +97,51 @@ void ViewerSizer::HandDragMove(int x, int y) } } +bool ViewerSizer::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == widget_) { + if (event->type() == QEvent::Wheel) { + QWheelEvent *w = static_cast(event); + + if (HandMovableView::WheelEventIsAZoomEvent(w)) { + int x = w->angleDelta().x() + w->angleDelta().y(); + + int current_percent = zoom_; + if (current_percent == 0) { + // Currently set to "fit" + current_percent = current_widget_scale_; + } + + if (x > 0) { + // Zoom in + for (int i=kZoomLevelCount-2; i>=0; i--) { + if (current_percent >= kZoomLevels[i]) { + SetZoom(kZoomLevels[i+1]); + break; + } + } + } else if (x < 0) { + // Zoom out + for (int i=1; ipixelDelta(); + horiz_scrollbar_->setValue(horiz_scrollbar_->value() - p.x()); + vert_scrollbar_->setValue(vert_scrollbar_->value() - p.y()); + } + return true; + } + } + + return QWidget::eventFilter(watched, event); +} + void ViewerSizer::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); @@ -169,6 +221,8 @@ void ViewerSizer::UpdateSize() } + current_widget_scale_ = current_scale * 100; + if (zoom_ > 0) { // Scale to get to the requested zoom diff --git a/app/widget/viewer/viewersizer.h b/app/widget/viewer/viewersizer.h index b4afe5015..21cb532f1 100644 --- a/app/widget/viewer/viewersizer.h +++ b/app/widget/viewer/viewersizer.h @@ -51,6 +51,9 @@ public: */ void SetWidget(QWidget* widget); + static constexpr int kZoomLevelCount = 8; + static constexpr int kZoomLevels[kZoomLevelCount] = {10, 25, 50, 75, 100, 150, 200, 400}; + public slots: /** * @brief Set resolution to use @@ -73,6 +76,8 @@ public slots: void HandDragMove(int x, int y); + virtual bool eventFilter(QObject *watched, QEvent *event) override; + signals: void RequestScale(const QMatrix4x4& matrix); @@ -111,6 +116,7 @@ private: * @brief Internal zoom value */ int zoom_; + int current_widget_scale_; QScrollBar* horiz_scrollbar_; QScrollBar* vert_scrollbar_; From 0e9cedf6904cfe9e4af11b8f9d09727a79565a50 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:08:35 -0800 Subject: [PATCH 69/85] timeline: use last track's height when appending a new one --- .../timelinewidget/undo/timelineundogeneral.cpp | 3 +++ app/widget/timelinewidget/view/timelineview.cpp | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index 634188e65..d46da4fb8 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -126,6 +126,9 @@ void TimelineAddTrackCommand::redo() // Add track to sequence track_->setParent(timeline_->GetParentGraph()); + if (timeline_->GetTrackCount() > 0) { + track_->SetTrackHeight(timeline_->GetTrackAt(timeline_->GetTrackCount()-1)->GetTrackHeight()); + } timeline_->ArrayAppend(); Node::ConnectEdge(track_, timeline_->track_input(timeline_->ArraySize() - 1)); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 18acf5484..468e7461e 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -722,10 +722,22 @@ int TimelineView::GetTrackY(int track_index) const int TimelineView::GetTrackHeight(int track_index) const { - if (!connected_track_list_ || track_index >= connected_track_list_->GetTrackCount()) { + if (!connected_track_list_ || connected_track_list_->GetTrackCount() == 0) { + // Handle null or empty track list return Track::GetDefaultTrackHeightInPixels(); } + if (track_index >= connected_track_list_->GetTrackCount()) { + // Handle new track at the end of the list + return connected_track_list_->GetTrackAt(connected_track_list_->GetTrackCount()-1)->GetTrackHeightInPixels(); + } + + if (track_index < 0) { + // Handle new track at the beginning of the list + return connected_track_list_->GetTrackAt(0)->GetTrackHeightInPixels(); + } + + // Track definitely exists, return its actual height return connected_track_list_->GetTrackAt(track_index)->GetTrackHeightInPixels(); } From 3302c3633c665fd32152217f254b53654945f2dd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 11 Nov 2022 12:29:52 -0800 Subject: [PATCH 70/85] track: emit virtual height rather than pixel height --- app/node/output/track/track.cpp | 2 +- app/node/output/track/track.h | 2 +- app/node/output/track/tracklist.cpp | 5 +++-- app/widget/timelinewidget/timelinewidget.cpp | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index be6843eaf..7afde43ea 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -180,7 +180,7 @@ const double &Track::GetTrackHeight() const void Track::SetTrackHeight(const double &height) { track_height_ = height; - emit TrackHeightChangedInPixels(GetTrackHeightInPixels()); + emit TrackHeightChanged(track_height_); } void Track::InputConnectedEvent(const QString &input, int element, Node *output) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 2ed697e70..ad1eaa5b4 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -425,7 +425,7 @@ signals: /** * @brief Signal emitted when the height of the track has changed */ - void TrackHeightChangedInPixels(int pixel_height); + void TrackHeightChanged(qreal virtual_height); /** * @brief Signal emitted when the muted setting changes diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index 6759d54b5..94c90b6ec 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -81,8 +81,9 @@ void TrackList::TrackConnected(Node *node, int element) UpdateTrackIndexesFrom(cache_index); connect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); - connect(track, &Track::TrackHeightChangedInPixels, this, [this](int height){ - emit TrackHeightChanged(static_cast(sender()), height); + connect(track, &Track::TrackHeightChanged, this, [this](){ + Track *t = static_cast(sender()); + emit TrackHeightChanged(t, t->GetTrackHeightInPixels()); }); track->set_type(type_); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index c342e8c32..1ca6e3569 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1171,7 +1171,7 @@ void TimelineWidget::AddTrack(Track *track) connect(track, &Track::IndexChanged, this, &TimelineWidget::TrackUpdated); connect(track, &Track::IndexChanged, this, &TimelineWidget::TrackIndexChanged); connect(track, &Track::BlocksRefreshed, this, &TimelineWidget::TrackUpdated); - connect(track, &Track::TrackHeightChangedInPixels, this, &TimelineWidget::TrackUpdated); + connect(track, &Track::TrackHeightChanged, this, &TimelineWidget::TrackUpdated); connect(track, &Track::BlockAdded, this, &TimelineWidget::AddBlock); connect(track, &Track::BlockRemoved, this, &TimelineWidget::RemoveBlock); } @@ -1181,7 +1181,7 @@ void TimelineWidget::RemoveTrack(Track *track) disconnect(track, &Track::IndexChanged, this, &TimelineWidget::TrackUpdated); disconnect(track, &Track::IndexChanged, this, &TimelineWidget::TrackIndexChanged); disconnect(track, &Track::BlocksRefreshed, this, &TimelineWidget::TrackUpdated); - disconnect(track, &Track::TrackHeightChangedInPixels, this, &TimelineWidget::TrackUpdated); + disconnect(track, &Track::TrackHeightChanged, this, &TimelineWidget::TrackUpdated); disconnect(track, &Track::BlockAdded, this, &TimelineWidget::AddBlock); disconnect(track, &Track::BlockRemoved, this, &TimelineWidget::RemoveBlock); From 2d3ce55fd75a0dacc36cbb10dddc257e73916d45 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 16 Nov 2022 13:14:37 -0800 Subject: [PATCH 71/85] timebasedview: fix playhead dragging regression --- app/widget/timebased/timebasedview.h | 2 +- app/widget/timeruler/seekablewidget.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index c6459ca52..cb149b445 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -51,7 +51,7 @@ public: const double& GetYScale() const; void SetYScale(const double& y_scale); - bool IsDraggingPlayhead() const + virtual bool IsDraggingPlayhead() const { return dragging_playhead_; } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index ef90f872e..326da52e8 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -47,7 +47,7 @@ public: void SetMarkers(TimelineMarkerList *markers); void SetWorkArea(TimelineWorkArea *workarea); - bool IsDraggingPlayhead() const + virtual bool IsDraggingPlayhead() const override { return dragging_; } From 87d9afb7e119c626ce5ce310ce99f927fe14238c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:08:45 -0800 Subject: [PATCH 72/85] ffmpeg: use array rather than vector Should be faster --- app/common/ffmpegutils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index 565f0269c..489fdf080 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -24,7 +24,7 @@ namespace olive { AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, VideoParams::Format maximum) { - std::vector possible_pix_fmts(3); + AVPixelFormat possible_pix_fmts[3]; possible_pix_fmts[0] = AV_PIX_FMT_RGBA; @@ -35,7 +35,7 @@ AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt possible_pix_fmts[2] = AV_PIX_FMT_NONE; } - return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts.data(), + return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, pix_fmt, 1, nullptr); From 6b6f164c44f2cd3f9469d0fa8a328360e2cc6927 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:00:45 -0800 Subject: [PATCH 73/85] ffmpeg: refactor so swscale is an optional step before glsl --- app/codec/ffmpeg/ffmpegdecoder.cpp | 686 +++++++++++------------------ app/codec/ffmpeg/ffmpegdecoder.h | 26 +- app/common/ffmpegutils.h | 4 + app/shaders/deinterlace2.frag | 21 + app/shaders/yuv2rgb.frag | 19 +- 5 files changed, 306 insertions(+), 450 deletions(-) create mode 100644 app/shaders/deinterlace2.frag diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 377fb5912..3317843a1 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -49,15 +49,10 @@ extern "C" { namespace olive { QVariant Yuv2RgbShader; +QVariant DeinterlaceShader; FFmpegDecoder::FFmpegDecoder() : - filter_graph_(nullptr), - buffersrc_ctx_(nullptr), - buffersink_ctx_(nullptr), - input_fmt_(AV_PIX_FMT_NONE), - native_internal_pix_fmt_(VideoParams::kFormatInvalid), - native_output_pix_fmt_(VideoParams::kFormatInvalid), - working_frame_(nullptr), + sws_ctx_(nullptr), working_packet_(nullptr), cache_at_zero_(false), cache_at_eof_(false) @@ -72,217 +67,195 @@ bool FFmpegDecoder::OpenInternal() // Store one second in the source's timebase second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base))); - working_frame_ = av_frame_alloc(); working_packet_ = av_packet_alloc(); - - frame_rate_tb_ = rational::NaN; return true; } return false; } -/*FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r) +TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original) { - // This is a still image - QString img_filename = stream().filename(); + // Determine native format + AVPixelFormat ideal_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(f->format)); + VideoParams::Format native_fmt = GetNativePixelFormat(ideal_fmt); + int native_channels = GetNativeChannelCount(ideal_fmt); - int64_t ts; + // Set up video params + VideoParams vp(original->width, + original->height, + native_fmt, + native_channels, + av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr), + VideoParams::kInterlaceNone, + p.divider); - // If it's an image sequence, we'll probably need to transform the filename - if (stream().GetStream().video_type() == Track::kVideoTypeImageSequence) { - ts = stream().GetTimeInTimebaseUnits(timecode); + // Create texture + TexturePtr tex = p.renderer->CreateTexture(vp); - img_filename = TransformImageSequenceFileName(stream().filename(), ts); - } else { - ts = 0; + switch (f->format) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUV420P10LE: + case AV_PIX_FMT_YUV422P10LE: + case AV_PIX_FMT_YUV444P10LE: + case AV_PIX_FMT_YUV420P12LE: + case AV_PIX_FMT_YUV422P12LE: + case AV_PIX_FMT_YUV444P12LE: + { + // Run through YUV to RGB shader + if (Yuv2RgbShader.isNull()) { + // Compile shader + Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); + if (Yuv2RgbShader.isNull()) { + return nullptr; + } + } + + int px_size; + int bits_per_pixel; + switch (f->format) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + default: + px_size = 1; + bits_per_pixel = 8; + break; + case AV_PIX_FMT_YUV420P10LE: + case AV_PIX_FMT_YUV422P10LE: + case AV_PIX_FMT_YUV444P10LE: + px_size = 2; + bits_per_pixel = 10; + break; + case AV_PIX_FMT_YUV420P12LE: + case AV_PIX_FMT_YUV422P12LE: + case AV_PIX_FMT_YUV444P12LE: + px_size = 2; + bits_per_pixel = 12; + break; + } + + AVFrame *hw_in = f.get(); + + VideoParams plane_params = vp; + plane_params.set_channel_count(1); + plane_params.set_format(native_fmt); + + TexturePtr y_plane = p.renderer->CreateTexture(plane_params, hw_in->data[0], hw_in->linesize[0] / px_size); + + switch (f->format) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV420P10LE: + case AV_PIX_FMT_YUV422P10LE: + case AV_PIX_FMT_YUV420P12LE: + case AV_PIX_FMT_YUV422P12LE: + plane_params.set_width(plane_params.width()/2); + break; + } + + switch (f->format) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV420P10LE: + case AV_PIX_FMT_YUV420P12LE: + plane_params.set_height(plane_params.height()/2); + break; + } + + TexturePtr u_plane = p.renderer->CreateTexture(plane_params, hw_in->data[1], hw_in->linesize[1] / px_size); + TexturePtr v_plane = p.renderer->CreateTexture(plane_params, hw_in->data[2], hw_in->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, hw_in->color_range == AVCOL_RANGE_JPEG)); + + const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace)); + job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kFloat, yuv_coeffs[0]/65536.0)); + job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kFloat, yuv_coeffs[2]/65536.0)); + job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kFloat, yuv_coeffs[3]/65536.0)); + job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kFloat, yuv_coeffs[1]/65536.0)); + + tex = p.renderer->CreateTexture(vp); + p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); + break; + } + case AV_PIX_FMT_RGBA: + case AV_PIX_FMT_RGBA64LE: + // RGBA can be uploaded directly to the texture + tex->Upload(f->data[0], f->linesize[0] / vp.GetBytesPerPixel()); + break; } - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); - FramePtr output_frame = nullptr; + // Deinterlace if necessary + if (p.src_interlacing != VideoParams::kInterlaceNone) { + if (DeinterlaceShader.isNull()) { + // Compile shader + DeinterlaceShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/deinterlace2.frag")))); + if (DeinterlaceShader.isNull()) { + return nullptr; + } + } - Instance i; - i.Open(img_filename.toUtf8(), stream().GetRealStreamIndex()); + rational frame_rate_tb = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), original.get()); - int ret = i.GetFrame(pkt, frame); + // Double frame rate for interlaced fields + frame_rate_tb *= 2; - if (ret >= 0) { - VideoParams video_params = stream().video_params(); + // Flip frame rate so it can be used as a timebase + frame_rate_tb.flip(); - // Create frame to return - output_frame = Frame::Create(); - output_frame->set_video_params(VideoParams(frame->width, - frame->height, - native_pix_fmt_, - native_channel_count_, - video_params.pixel_aspect_ratio(), - video_params.interlacing(), - divider)); - output_frame->set_timestamp(timecode); - output_frame->allocate(); + int64_t req = Timecode::time_to_timestamp(p.time + rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE), frame_rate_tb); + int64_t frm = Timecode::rescale_timestamp(original->pts, instance_.avstream()->time_base, frame_rate_tb); - uint8_t* copy_data = reinterpret_cast(output_frame->data()); - int copy_linesize = output_frame->linesize_bytes(); + bool first = (req == frm); + bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst); - FFmpegBufferToNativeBuffer(frame->data, frame->linesize, ©_data, ©_linesize); - } else { - qWarning() << "Failed to retrieve still image from decoder"; + int interlacing = (first == top_first) ? 1 : 2; + + TexturePtr deinterlaced = p.renderer->CreateTexture(tex->params()); + + ShaderJob job; + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, tex)); + job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing)); + job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, original->height)); + + p.renderer->BlitToTexture(DeinterlaceShader, job, deinterlaced.get(), false); + + tex = deinterlaced; } - i.Close(); - - av_frame_free(&frame); - av_packet_free(&pkt); - - return output_frame; -}*/ + return tex; +} TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - if (AVFramePtr f = RetrieveFrame(p.time, p.src_interlacing, p.cancelled)) { + if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) { if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } - int &src_fmt = f.get()->format; - src_fmt = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast(src_fmt)); + AVFramePtr original = f; + // Disregard "JPEG" pixel formats because we allow the user to override that + f->format = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast(f->format)); + + // Force frame's color range to whatever it's set to in Olive 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, - p.divider); - - TexturePtr tex = nullptr; - - // Attempt to use GLSL shader for faster YUV to RGB conversion - if (IsPixelFormatGLSLCompatible(static_cast(src_fmt))) { - if (Yuv2RgbShader.isNull()) { - // Compile shader - Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); - } - - if (!Yuv2RgbShader.isNull()) { - int px_size; - int bits_per_pixel; - switch (src_fmt) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: - default: - px_size = 1; - bits_per_pixel = 8; - break; - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV444P10LE: - px_size = 2; - bits_per_pixel = 10; - break; - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV422P12LE: - case AV_PIX_FMT_YUV444P12LE: - px_size = 2; - bits_per_pixel = 12; - break; - } - - AVFrame *hw_in = f.get(); - - VideoParams plane_params = vp; - plane_params.set_channel_count(1); - plane_params.set_format(native_internal_pix_fmt_); - - if (p.divider != 1) { - ApplyScaler(f.get()); - hw_in = working_frame_; - } else { - // Fallback: shouldn't ever really get here, but just in case - plane_params.set_divider(1); - } - - TexturePtr y_plane = p.renderer->CreateTexture(plane_params, hw_in->data[0], hw_in->linesize[0] / px_size); - - if (src_fmt == AV_PIX_FMT_YUV420P - || src_fmt == AV_PIX_FMT_YUV422P - || src_fmt == AV_PIX_FMT_YUV420P10LE - || src_fmt == AV_PIX_FMT_YUV422P10LE - || src_fmt == AV_PIX_FMT_YUV420P12LE - || src_fmt == AV_PIX_FMT_YUV422P12LE) { - plane_params.set_width(plane_params.width()/2); - } - - if (src_fmt == AV_PIX_FMT_YUV420P - || src_fmt == AV_PIX_FMT_YUV420P10LE - || src_fmt == AV_PIX_FMT_YUV420P12LE) { - plane_params.set_height(plane_params.height()/2); - } - - TexturePtr u_plane = p.renderer->CreateTexture(plane_params, hw_in->data[1], hw_in->linesize[1] / px_size); - TexturePtr v_plane = p.renderer->CreateTexture(plane_params, hw_in->data[2], hw_in->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, hw_in->color_range == AVCOL_RANGE_JPEG)); - - const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace)); - job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kFloat, yuv_coeffs[0]/65536.0)); - job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kFloat, yuv_coeffs[2]/65536.0)); - job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kFloat, yuv_coeffs[3]/65536.0)); - job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kFloat, yuv_coeffs[1]/65536.0)); - - 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(), hw_in); - - // 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(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); - - 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); - - av_frame_unref(working_frame_); - } - } - - if (!tex) { - // Fallback to software pixel format conversion - if (!ApplyScaler(f.get())) { - return nullptr; - } - - tex = p.renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel()); - - av_frame_unref(working_frame_); - } - - return tex; + // Perform any CPU processing required + f = PreProcessFrame(f, p); + if (!f) { + // Error occurred while software scaling + return nullptr; } + + // Finally, perform any GPU processing required + return ProcessFrameIntoTexture(f, p, original); } return nullptr; @@ -295,19 +268,10 @@ void FFmpegDecoder::CloseInternal() working_packet_ = nullptr; } - if (working_frame_) { - av_frame_free(&working_frame_); - working_frame_ = nullptr; - } - ClearFrameCache(); FreeScaler(); instance_.Close(); - - input_fmt_ = AV_PIX_FMT_NONE; - native_internal_pix_fmt_ = VideoParams::kFormatInvalid; - native_output_pix_fmt_ = VideoParams::kFormatInvalid; } rational FFmpegDecoder::GetAudioStartOffset() const @@ -723,73 +687,26 @@ const char *FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing i bool FFmpegDecoder::IsPixelFormatGLSLCompatible(AVPixelFormat f) { - return f == AV_PIX_FMT_YUV420P - || f == AV_PIX_FMT_YUV422P - || f == AV_PIX_FMT_YUV444P - || f == AV_PIX_FMT_YUV420P10LE - || f == AV_PIX_FMT_YUV422P10LE - || f == AV_PIX_FMT_YUV444P10LE - || f == AV_PIX_FMT_YUV420P12LE - || f == AV_PIX_FMT_YUV422P12LE - || f == AV_PIX_FMT_YUV444P12LE; -} - -/* OLD UNUSED CODE: Keeping this around in case the code proves useful - -void FFmpegDecoder::CacheFrameToDisk(AVFrame *f) -{ - QFile save_frame(GetIndexFilename().append(QString::number(f->pts))); - if (save_frame.open(QFile::WriteOnly)) { - - // Save frame to media index - int cached_buffer_sz = av_image_get_buffer_size(static_cast(f->format), - f->width, - f->height, - 1); - - QByteArray cached_frame(cached_buffer_sz, Qt::Uninitialized); - - av_image_copy_to_buffer(reinterpret_cast(cached_frame.data()), - cached_frame.size(), - f->data, - f->linesize, - static_cast(f->format), - f->width, - f->height, - 1); - - save_frame.write(qCompress(cached_frame, 1)); - save_frame.close(); - - DiskManager::instance()->CreatedFile(save_frame.fileName(), QByteArray()); - } - - // See if we stored this frame in the disk cache - - QByteArray frame_loader; - if (!got_frame) { - QFile compressed_frame(GetIndexFilename().append(QString::number(target_ts))); - if (compressed_frame.exists() - && compressed_frame.size() > 0 - && compressed_frame.open(QFile::ReadOnly)) { - DiskManager::instance()->Accessed(compressed_frame.fileName()); - - // Read data - frame_loader = qUncompress(compressed_frame.readAll()); - - av_image_fill_arrays(input_data, - input_linesize, - reinterpret_cast(frame_loader.data()), - static_cast(avstream_->codecpar->format), - avstream_->codecpar->width, - avstream_->codecpar->height, - 1); - - got_frame = true; - } + return false; + // NOTE: We don't include RGB24 or RGB48 here because those are slow on the GPU and performance + // should be better if we convert to RGBA on the CPU beforehand + switch (f) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUV420P10LE: + case AV_PIX_FMT_YUV422P10LE: + case AV_PIX_FMT_YUV444P10LE: + case AV_PIX_FMT_YUV420P12LE: + case AV_PIX_FMT_YUV422P12LE: + case AV_PIX_FMT_YUV444P12LE: + case AV_PIX_FMT_RGBA: + case AV_PIX_FMT_RGBA64LE: + return true; + default: + return false; } } -*/ void FFmpegDecoder::ClearFrameCache() { @@ -800,14 +717,94 @@ void FFmpegDecoder::ClearFrameCache() } } -AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Interlacing interlacing, CancelAtom *cancelled) +AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, const RetrieveVideoParams &p) +{ + // In pre-processing, we try to achieve the following: + // - If a divider is being used, scale down the image + // - If a pixel format is not compatible with the GLSL shader, convert it to RGBA ourselves + + if (p.divider == 1 && IsPixelFormatGLSLCompatible(static_cast(f->format))) { + // No CPU processing required, the user wants this in full resolution and the pixel format can + // be converted on the GPU + return f; + } + + // Some scaling and/or format conversion needs to be done + AVFramePtr dest = CreateAVFramePtr(); + + dest->width = f->width; + dest->height = f->height; + dest->format = f->format; + dest->color_range = f->color_range; + dest->colorspace = f->colorspace; + + if (p.divider > 1) { + dest->width = VideoParams::GetScaledDimension(dest->width, p.divider); + dest->height = VideoParams::GetScaledDimension(dest->height, p.divider); + } + + if (!IsPixelFormatGLSLCompatible(static_cast(dest->format))) { + dest->format = FFmpegUtils::GetCompatiblePixelFormat(static_cast(dest->format), p.maximum_format); + } + + int r = av_frame_get_buffer(dest.get(), 0); + if (r < 0) { + FFmpegError(r); + return nullptr; + } + + if (!sws_ctx_ + || sws_src_width_ != f->width + || sws_src_height_ != f->height + || sws_src_format_ != f->format + || sws_dst_width_ != dest->width + || sws_dst_height_ != dest->height + || sws_dst_format_ != dest->format) { + // SwsContext must be recreated, destroy current if it exists + FreeScaler(); + + // Cache info + sws_src_width_ = f->width; + sws_src_height_ = f->height; + sws_src_format_ = static_cast(f->format); + sws_dst_width_ = dest->width; + sws_dst_height_ = dest->height; + sws_dst_format_ = static_cast(dest->format); + + // Create new scaler + sws_ctx_ = sws_getContext(sws_src_width_, + sws_src_height_, + sws_src_format_, + sws_dst_width_, + sws_dst_height_, + sws_dst_format_, + SWS_POINT, + nullptr, + nullptr, + nullptr); + + // Set swscale's colorspace details + sws_setColorspaceDetails(sws_ctx_, + sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f->colorspace)), + f->color_range == AVCOL_RANGE_JPEG ? 1 : 0, + sws_getCoefficients(SWS_CS_DEFAULT), + 1, + 0, 0x10000, 0x10000); + } + + r = sws_scale(sws_ctx_, f->data, f->linesize, 0, f->height, dest->data, dest->linesize); + if (r < 0) { + FFmpegError(r); + return nullptr; + } + + return dest; +} + +AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancelled) { 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; - } - if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) { target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, {1, AV_TIME_BASE}, instance_.avstream()->time_base); } @@ -822,9 +819,6 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter || (target_ts < cached_frames_.front()->pts || target_ts > cached_frames_.back()->pts + 2*second_ts_)) { ClearFrameCache(); - // Filter graph may rely on "continuous" video frames, so we free the scaler here - //ResetScaler(); - instance_.Seek(seek_ts); if (seek_ts == min_seek) { cache_at_zero_ = true; @@ -851,7 +845,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter } if (!filtered) { - filtered = CreateAVFramePtr(av_frame_alloc()); + filtered = CreateAVFramePtr(); } // Pull from the decoder @@ -940,143 +934,11 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter return return_frame; } -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 - return true; - } - - // We need to (re)create the filter, delete current if necessary - ClearFrameCache(); - FreeScaler(); - - // Set our params to this - filter_params_ = params; - input_fmt_ = static_cast(input->format); - if (input_fmt_ == AV_PIX_FMT_NONE) { - return false; - } - - // Get an Olive compatible AVPixelFormat - AVPixelFormat ideal_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(input_fmt_), params.maximum_format); - - // Determine which Olive native pixel format we retrieved - // Note that FFmpeg doesn't support float formats - native_output_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt); - native_channel_count_ = GetNativeChannelCount(ideal_pix_fmt); - - AVPixelFormat ideal_internal_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(input_fmt_)); - native_internal_pix_fmt_ = GetNativePixelFormat(ideal_internal_pix_fmt); - - if (native_output_pix_fmt_ == VideoParams::kFormatInvalid - || native_internal_pix_fmt_ == VideoParams::kFormatInvalid - || native_channel_count_ == 0) { - qCritical() << "Failed to find valid native pixel format for" << ideal_pix_fmt; - return false; - } - - // Allocate filter graph - filter_graph_ = avfilter_graph_alloc(); - if (!filter_graph_) { - qWarning() << "Failed to allocate filter graph"; - return false; - } - - AVStream* s = instance_.avstream(); - - int src_width = s->codecpar->width; - int src_height = s->codecpar->height; - - // Define filter parameters - static const int kFilterArgSz = 1024; - char filter_args[kFilterArgSz]; - snprintf(filter_args, kFilterArgSz, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - src_width, - src_height, - input->format, - s->time_base.num, - s->time_base.den, - s->codecpar->sample_aspect_ratio.num, - s->codecpar->sample_aspect_ratio.den); - - // Create path in and out of the filter graph (the buffer in and the buffersink out) - avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, filter_graph_); - avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, filter_graph_); - - // 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 && !glsl_available) { - 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; - - int dst_width, dst_height; - 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=0", - dst_width, - dst_height); - - avfilter_graph_create_filter(&scale_filter, avfilter_get_by_name("scale"), "scale", filter_args, nullptr, filter_graph_); - - avfilter_link(last_filter, 0, scale_filter, 0); - last_filter = scale_filter; - } - - // Add format filter if necessary - if (ideal_pix_fmt != input->format && !glsl_available) { - AVFilterContext* format_filter; - - snprintf(filter_args, kFilterArgSz, "pix_fmts=%u", ideal_pix_fmt); - - avfilter_graph_create_filter(&format_filter, avfilter_get_by_name("format"), "format", filter_args, nullptr, filter_graph_); - - avfilter_link(last_filter, 0, format_filter, 0); - last_filter = format_filter; - } - - // Finally, link the last filter with the buffersink - avfilter_link(last_filter, 0, buffersink_ctx_, 0); - - // Configure graph - if (int ret = avfilter_graph_config(filter_graph_, nullptr) < 0) { - qCritical() << "Failed to configure graph:" << FFmpegError(ret); - return false; - } - - return true; -} - void FFmpegDecoder::FreeScaler() { - if (filter_graph_) { - avfilter_graph_free(&filter_graph_); - filter_graph_ = nullptr; - buffersrc_ctx_ = nullptr; - buffersink_ctx_ = nullptr; + if (sws_ctx_) { + sws_freeContext(sws_ctx_); + sws_ctx_ = nullptr; } } @@ -1121,22 +983,6 @@ void FFmpegDecoder::RemoveFirstFrame() cache_at_zero_ = false; } -bool FFmpegDecoder::ApplyScaler(AVFrame *in) -{ - int r; - - r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in, AV_BUFFERSRC_FLAG_KEEP_REF); - if (r < 0) { - return false; - } - r = av_buffersink_get_frame(buffersink_ctx_, working_frame_); - if (r < 0) { - return false; - } - - return true; -} - int FFmpegDecoder::MaximumQueueSize() { // Fairly arbitrary size. This used to need to be the number of current threads to ensure any diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index cd60c5d05..ad3ad8070 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -134,7 +134,6 @@ private: */ static QString FFmpegError(int error_code); - bool InitScaler(AVFrame *input, const RetrieveVideoParams ¶ms); void FreeScaler(); static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt); @@ -150,25 +149,24 @@ private: void ClearFrameCache(); - AVFramePtr RetrieveFrame(const rational &time, VideoParams::Interlacing interlacing, CancelAtom *cancelled); + AVFramePtr PreProcessFrame(AVFramePtr f, const RetrieveVideoParams &p); + + TexturePtr ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original); + + AVFramePtr RetrieveFrame(const rational &time, CancelAtom *cancelled); void RemoveFirstFrame(); - bool ApplyScaler(AVFrame *in); - static int MaximumQueueSize(); - RetrieveVideoParams filter_params_; - AVFilterGraph* filter_graph_; - AVFilterContext* buffersrc_ctx_; - AVFilterContext* buffersink_ctx_; - AVPixelFormat input_fmt_; - VideoParams::Format native_internal_pix_fmt_; - VideoParams::Format native_output_pix_fmt_; - int native_channel_count_; - rational frame_rate_tb_; + SwsContext *sws_ctx_; + int sws_src_width_; + int sws_src_height_; + AVPixelFormat sws_src_format_; + int sws_dst_width_; + int sws_dst_height_; + AVPixelFormat sws_dst_format_; - AVFrame *working_frame_; AVPacket *working_packet_; int64_t second_ts_; diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index 7d13b8642..66fafdd49 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -82,6 +82,10 @@ inline AVFramePtr CreateAVFramePtr(AVFrame *f) { return std::shared_ptr(f, [](AVFrame *g){ av_frame_free(&g); }); } +inline AVFramePtr CreateAVFramePtr() +{ + return CreateAVFramePtr(av_frame_alloc()); +} } diff --git a/app/shaders/deinterlace2.frag b/app/shaders/deinterlace2.frag new file mode 100644 index 000000000..37b6002ce --- /dev/null +++ b/app/shaders/deinterlace2.frag @@ -0,0 +1,21 @@ +uniform sampler2D ove_maintex; + +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; + } + + frag_color = texture(ove_maintex, real_coord); +} diff --git a/app/shaders/yuv2rgb.frag b/app/shaders/yuv2rgb.frag index ce004736c..7f0d87a16 100644 --- a/app/shaders/yuv2rgb.frag +++ b/app/shaders/yuv2rgb.frag @@ -10,29 +10,16 @@ uniform float yuv_cgu; uniform float yuv_cgv; uniform float 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, real_coord).r; - yuv.g = texture(u_channel, real_coord).r; - yuv.b = texture(v_channel, real_coord).r; + 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 From 7e604e7433691efec978d9897faf859ba76ae2e4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:07:33 -0800 Subject: [PATCH 74/85] ffmpeg: re-enable glsl and track colspace and colrange --- app/codec/ffmpeg/ffmpegdecoder.cpp | 15 +++++++++------ app/codec/ffmpeg/ffmpegdecoder.h | 2 ++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 3317843a1..4981f1119 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -687,7 +687,6 @@ const char *FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing i bool FFmpegDecoder::IsPixelFormatGLSLCompatible(AVPixelFormat f) { - return false; // NOTE: We don't include RGB24 or RGB48 here because those are slow on the GPU and performance // should be better if we convert to RGBA on the CPU beforehand switch (f) { @@ -759,7 +758,9 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, const RetrieveVideoParam || sws_src_format_ != f->format || sws_dst_width_ != dest->width || sws_dst_height_ != dest->height - || sws_dst_format_ != dest->format) { + || sws_dst_format_ != dest->format + || sws_colrange_ != dest->color_range + || sws_colspace_ != dest->colorspace) { // SwsContext must be recreated, destroy current if it exists FreeScaler(); @@ -770,6 +771,8 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, const RetrieveVideoParam sws_dst_width_ = dest->width; sws_dst_height_ = dest->height; sws_dst_format_ = static_cast(dest->format); + sws_colrange_ = dest->color_range; + sws_colspace_ = dest->colorspace; // Create new scaler sws_ctx_ = sws_getContext(sws_src_width_, @@ -785,10 +788,10 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, const RetrieveVideoParam // Set swscale's colorspace details sws_setColorspaceDetails(sws_ctx_, - sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f->colorspace)), - f->color_range == AVCOL_RANGE_JPEG ? 1 : 0, - sws_getCoefficients(SWS_CS_DEFAULT), - 1, + sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(dest->colorspace)), + dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, + sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(dest->colorspace)), + dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, 0, 0x10000, 0x10000); } diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index ad3ad8070..a695f0b31 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -166,6 +166,8 @@ private: int sws_dst_width_; int sws_dst_height_; AVPixelFormat sws_dst_format_; + AVColorRange sws_colrange_; + AVColorSpace sws_colspace_; AVPacket *working_packet_; From 9a0e16f504594f4b4b5e316014eee7a6eee7d65c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:20:03 -0800 Subject: [PATCH 75/85] move clamping code from clip to track Fixes severe regression related to audio transitions --- app/node/block/clip/clip.cpp | 24 +----------------------- app/node/block/transition/transition.cpp | 8 ++------ app/node/output/track/track.cpp | 9 ++++++++- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 70e9a7304..7ce334697 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -464,29 +464,7 @@ TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, cons Q_UNUSED(element) if (input == kBufferIn) { - rational in = input_time.in(); - rational out = input_time.out(); - - if (clamp) { - rational minimum = 0; - rational maximum = length(); - - if (in_transition_) { - minimum -= in_transition_->length(); - } - - if (out_transition_) { - maximum += out_transition_->length(); - } - - in = std::max(in, minimum); - out = std::min(out, maximum); - } - - in = SequenceToMediaTime(in); - out = SequenceToMediaTime(out); - - return TimeRange(in, out); + return TimeRange(SequenceToMediaTime(input_time.in()), SequenceToMediaTime(input_time.out())); } return super::InputTimeAdjustment(input, element, input_time, clamp); diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index c78796393..1b05f3ba7 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -292,12 +292,8 @@ TimeRange TransitionBlock::InputTimeAdjustment(const QString &input, int element if (input == kInBlockInput || input == kOutBlockInput) { Block* block = dynamic_cast(GetConnectedOutput(input)); if (block) { - TimeRange range = input_time; - if (clamp) { - range.set_range(std::max(rational(0), range.in()), std::min(this->length(), range.out())); - } - range = range + in() - block->in(); - return range; + // Retransform time as if it came from the track + return input_time + in() - block->in(); } } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 7afde43ea..884ad5b65 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -152,7 +152,14 @@ TimeRange Track::InputTimeAdjustment(const QString& input, int element, const Ti int cache_index = GetCacheIndexFromArrayIndex(element); if (cache_index > -1) { - return TransformRangeForBlock(blocks_.at(cache_index), input_time); + TimeRange r = input_time; + Block *b = blocks_.at(cache_index); + + if (clamp) { + r.set_range(std::max(r.in(), b->in()), std::min(r.out(), b->out())); + } + + return TransformRangeForBlock(b, r); } } From b33260dce819012e8bd98053eb461870edfbfa80 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:30:18 -0800 Subject: [PATCH 76/85] timebased: only snap to workarea if enabled Fixes #2106 --- app/widget/timebased/timebasedwidget.cpp | 2 +- app/widget/timeruler/seekablewidget.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 01da6beaa..a87b6dbbb 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -828,7 +828,7 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration } } - if ((snap_points & kSnapToWorkarea) && ruler()->GetWorkArea()) { + if ((snap_points & kSnapToWorkarea) && ruler()->GetWorkArea() && ruler()->GetWorkArea()->enabled()) { const rational &workarea_in = ruler()->GetWorkArea()->in(); const rational &workarea_out = ruler()->GetWorkArea()->out(); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index d7e7b3293..dbe67fa51 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -491,7 +491,7 @@ bool SeekableWidget::FindResizeHandle(QMouseEvent *event) rational max = SceneToTimeNoGrid(scene.x() + border); // Test for workarea - if (workarea_) { + if (workarea_ && workarea_->enabled()) { if (workarea_->in() >= min && workarea_->in() < max) { resize_mode_ = kResizeIn; } else if (workarea_->out() >= min && workarea_->out() < max) { From eae02ac28b9f3e8c4e4524590fa92616a1519e7e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:35:51 -0800 Subject: [PATCH 77/85] prioritize dragging markers over workarea --- app/widget/timeruler/seekablewidget.cpp | 6 +++--- app/widget/timeruler/timeruler.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index dbe67fa51..54085042f 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -181,6 +181,8 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) return; } else if (event->modifiers() & Qt::ControlModifier) { selection_manager_.RubberBandStart(event); + } else if (marker_editing_enabled_ && (initial = selection_manager_.MousePress(event))) { + selection_manager_.DragStart(initial, event); } else if (resize_item_) { // Handle selection, even though we won't be using it for dragging if (!(event->modifiers() & Qt::ShiftModifier)) { @@ -191,8 +193,6 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) } dragging_ = true; resize_start_ = mapToScene(event->pos()); - } 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()); dragging_ = true; @@ -219,7 +219,7 @@ void SeekableWidget::mouseMoveEvent(QMouseEvent *event) } } else { // Look for resize points - if (FindResizeHandle(event)) { + if (!selection_manager_.GetObjectAtPoint(event->pos()) && FindResizeHandle(event)) { setCursor(Qt::SizeHorCursor); } else { unsetCursor(); diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 603706a82..3d3a00808 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -102,8 +102,8 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) // Draw timeline points if connected int marker_height = TimelineMarker::GetMarkerHeight(p->fontMetrics()); - DrawMarkers(p, marker_height); DrawWorkArea(p); + DrawMarkers(p, marker_height); double width_of_frame = timebase_dbl() * GetScale(); double width_of_second = 0; From d164a9a59af6b4e2edf51d097e9ee4c5f125b3f7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:50:07 -0800 Subject: [PATCH 78/85] seekable: prioritize dragging playhead over workarea --- app/widget/timeruler/seekablewidget.cpp | 20 ++++++++++++++------ app/widget/timeruler/seekablewidget.h | 4 ++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 54085042f..d40015680 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -219,10 +219,13 @@ void SeekableWidget::mouseMoveEvent(QMouseEvent *event) } } else { // Look for resize points - if (!selection_manager_.GetObjectAtPoint(event->pos()) && FindResizeHandle(event)) { + if (!last_playhead_shape_.containsPoint(event->pos(), Qt::OddEvenFill) + && !selection_manager_.GetObjectAtPoint(event->pos()) + && FindResizeHandle(event)) { setCursor(Qt::SizeHorCursor); } else { unsetCursor(); + ClearResizeHandle(); } } } @@ -425,16 +428,16 @@ void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) int half_text_height = text_height() / 3; - QPoint points[] = { + last_playhead_shape_ = QPolygon({ QPoint(x, y), QPoint(x - half_width, y - half_text_height), QPoint(x - half_width, y - text_height()), QPoint(x + 1 + half_width, y - text_height()), QPoint(x + 1 + half_width, y - half_text_height), QPoint(x + 1, y), - }; + }); - p->drawPolygon(points, 6); + p->drawPolygon(last_playhead_shape_); p->setRenderHint(QPainter::Antialiasing, false); } @@ -482,8 +485,7 @@ bool SeekableWidget::FindResizeHandle(QMouseEvent *event) return false; } - resize_item_ = nullptr; - resize_mode_ = kResizeNone; + ClearResizeHandle(); QPointF scene = mapToScene(event->pos()); const int border = 10; @@ -531,6 +533,12 @@ bool SeekableWidget::FindResizeHandle(QMouseEvent *event) return resize_item_; } +void SeekableWidget::ClearResizeHandle() +{ + resize_item_ = nullptr; + resize_mode_ = kResizeNone; +} + void SeekableWidget::DragResizeHandle(const QPointF &scene) { qreal diff = scene.x() - resize_start_.x(); diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 326da52e8..47e730542 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -125,6 +125,8 @@ private: bool FindResizeHandle(QMouseEvent *event); + void ClearResizeHandle(); + void DragResizeHandle(const QPointF &scene_pos); void CommitResizeHandle(); @@ -153,6 +155,8 @@ private: bool marker_editing_enabled_; + QPolygon last_playhead_shape_; + private slots: void SetMarkerColor(int c); From 61d2215b07364cd68017725ddb56566bbdd1df3b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:55:11 -0800 Subject: [PATCH 79/85] projectexplorer: if label = filename, rename on replace Fixes #2098 --- app/widget/projectexplorer/projectexplorer.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index e6089e1c1..a9858f19a 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -39,6 +39,7 @@ #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "widget/nodeparamview/nodeparamviewundo.h" +#include "widget/nodeview/nodeviewundo.h" #include "window/mainwindow/mainwindow.h" #include "window/mainwindow/mainwindowundo.h" #include "widget/nodeview/nodeviewundo.h" @@ -478,8 +479,17 @@ void ProjectExplorer::ReplaceSelectedFootage() QString file = QFileDialog::getOpenFileName(this, tr("Replace Footage")); if (!file.isEmpty()) { - auto c = new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(footage, Footage::kFilenameInput)), file); - Core::instance()->undo_stack()->push(c); + auto p = new MultiUndoCommand(); + + // Change filename parameter + p->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(footage, Footage::kFilenameInput)), file)); + + if (QFileInfo(footage->filename()).fileName() == footage->GetLabel()) { + // Footage label == filename, change label too + p->add_child(new NodeRenameCommand(footage, QFileInfo(file).fileName())); + } + + Core::instance()->undo_stack()->push(p); } } From 5fce683e0d78c8db7dbd1604fab6a1899c085571 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:11:36 -0800 Subject: [PATCH 80/85] ci: use macos-11.0 for intel build too Let's see if this breaks anything --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91f292b0a..2324d394c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -287,7 +287,7 @@ jobs: compiler-name: Clang LLVM os-name: macOS os-arch: x86_64 - os: macos-10.15 + os: macos-11.0 cmake-gen: Ninja min-deploy: 10.13 - build-type: RelWithDebInfo From 42a7cc10c80cb4b2d6b29f263af57adc5e367ac0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 19 Nov 2022 17:35:15 -0800 Subject: [PATCH 81/85] timeline: improved and optimized drawing of large amounts of clips --- .../timelinewidget/view/timelineview.cpp | 305 +++++++++--------- 1 file changed, 159 insertions(+), 146 deletions(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 468e7461e..5554a3677 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -418,8 +418,17 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q QColor shadow_color = block->is_enabled() ? block->color().toQColor().darker() : QColor(Qt::darkGray).darker(); - if (r.width() <= 3) { - painter->fillRect(r, shadow_color); + const qreal MINIMUM_RECT_WIDTH = 2; + const qreal MINIMUM_DETAIL_WIDTH = 8; + + if (r.width() <= MINIMUM_RECT_WIDTH) { + if (!foreground) { + // Just draw a green background + // Width is likely fractional, so we ceil it and add 1 to ensure the entire width of the + // rect is painted + r.setWidth(std::ceil(r.width())+1); + painter->fillRect(r, shadow_color); + } } else { QFontMetrics fm = fontMetrics(); int text_height = fm.height(); @@ -429,19 +438,21 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (foreground) { painter->setBrush(Qt::NoBrush); - QString using_label = block->GetLabelOrName(); + if (r.width() > MINIMUM_DETAIL_WIDTH) { + 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); - painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignTop, using_label); + QRectF text_rect = r.adjusted(text_padding, text_padding, -text_padding, -text_padding); + painter->setPen(block->is_enabled() ? ColorCoding::GetUISelectorColor(block->color()) : Qt::lightGray); + painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignTop, using_label); - if (block->HasLinks()) { - int text_width = qMin(qRound(text_rect.width()), - QtUtils::QFontMetricsWidth(fm, using_label)); + if (block->HasLinks()) { + int text_width = qMin(qRound(text_rect.width()), + QtUtils::QFontMetricsWidth(fm, using_label)); - int underline_y = text_rect.y() + text_height; + int underline_y = text_rect.y() + text_height; - painter->drawLine(text_rect.x(), underline_y, text_width + text_rect.x(), underline_y); + painter->drawLine(text_rect.x(), underline_y, text_width + text_rect.x(), underline_y); + } } qreal line_bottom = block_top+block_height-1; @@ -458,177 +469,179 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q painter->setBrush(block->is_enabled() ? block->brush(block_top, block_top + block_height) : Qt::gray); painter->drawRect(r); - if (ClipBlock *clip = dynamic_cast(block)) { - QRect preview_rect = r.toRect(); + if (r.width() > MINIMUM_DETAIL_WIDTH) { + if (ClipBlock *clip = dynamic_cast(block)) { + QRect preview_rect = r.toRect(); - // Draw clip thumbnails - if (clip->GetTrackType() == Track::kVideo - && OLIVE_CONFIG("TimelineThumbnailMode").toInt() != Timeline::kThumbnailOff - && preview_rect.height() > r.height()/3) { - if (const FrameHashCache *thumbs = clip->thumbnails()) { - // Start thumbnails underneath clip name - preview_rect.adjust(0, text_total_height, 0, 0); + // Draw clip thumbnails + if (clip->GetTrackType() == Track::kVideo + && OLIVE_CONFIG("TimelineThumbnailMode").toInt() != Timeline::kThumbnailOff + && preview_rect.height() > r.height()/3) { + if (const FrameHashCache *thumbs = clip->thumbnails()) { + // Start thumbnails underneath clip name + preview_rect.adjust(0, text_total_height, 0, 0); - QRect thumb_rect; - painter->setRenderHint(QPainter::SmoothPixmapTransform); - painter->setClipRect(preview_rect); + QRect thumb_rect; + painter->setRenderHint(QPainter::SmoothPixmapTransform); + painter->setClipRect(preview_rect); - if (OLIVE_CONFIG("TimelineThumbnailMode") == Timeline::kThumbnailOn) { + if (OLIVE_CONFIG("TimelineThumbnailMode") == Timeline::kThumbnailOn) { + + Sequence *s = clip->track()->sequence(); + int width = s->GetVideoParams().width(); + int height = s->GetVideoParams().height(); + int start; + if (height > 0) { // Prevent divide by zero/invalid params + double scale = double(preview_rect.height())/double(height); + thumb_rect.setWidth(width * scale); + start = (((preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width()) * thumb_rect.width()) + qFloor(block_in); + } else { + start = preview_rect.left(); + } + + for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; + DrawThumbnail(painter, thumbs, time_here, i, preview_rect, &thumb_rect); + } - Sequence *s = clip->track()->sequence(); - int width = s->GetVideoParams().width(); - int height = s->GetVideoParams().height(); - int start; - if (height > 0) { // Prevent divide by zero/invalid params - double scale = double(preview_rect.height())/double(height); - thumb_rect.setWidth(width * scale); - start = (((preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width()) * thumb_rect.width()) + qFloor(block_in); } else { - start = preview_rect.left(); + + rational time = clip->media_range().in(); + time = Timecode::snap_time_to_timebase(time, thumbs->GetTimebase(), Timecode::kFloor); + DrawThumbnail(painter, thumbs, time, block_left, preview_rect, &thumb_rect); + } - for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; - DrawThumbnail(painter, thumbs, time_here, i, preview_rect, &thumb_rect); - } - - } else { - - rational time = clip->media_range().in(); - time = Timecode::snap_time_to_timebase(time, thumbs->GetTimebase(), Timecode::kFloor); - DrawThumbnail(painter, thumbs, time, block_left, preview_rect, &thumb_rect); + painter->setClipping(false); } - - painter->setClipping(false); - } - } - // Draw waveform - if (clip->GetTrackType() == Track::kAudio - && OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { - if (const AudioWaveformCache *wave = clip->waveform()) { - rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; - painter->setPen(shadow_color); + // Draw waveform + if (clip->GetTrackType() == Track::kAudio + && OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { + if (const AudioWaveformCache *wave = clip->waveform()) { + rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; + painter->setPen(shadow_color); - wave->Draw(painter, preview_rect, this->GetScale(), waveform_start); + wave->Draw(painter, preview_rect, this->GetScale(), waveform_start); + } } - } - // Draw zebra stripes and markers - if (clip->connected_viewer()) { - if (!clip->connected_viewer()->GetLength().isNull()) { - painter->setPen(shadow_color); + // Draw zebra stripes and markers + if (clip->connected_viewer()) { + if (!clip->connected_viewer()->GetLength().isNull()) { + painter->setPen(shadow_color); - if (clip->media_in() < 0) { - qreal zebra_right = TimeToScene(clip->in() - clip->media_in()); + if (clip->media_in() < 0) { + qreal zebra_right = TimeToScene(clip->in() - clip->media_in()); - switch (clip->loop_mode()) { - case LoopMode::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)); + switch (clip->loop_mode()) { + case LoopMode::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 LoopMode::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 LoopMode::kLoopModeClamp: + painter->drawLine(zebra_right, block_top, zebra_right, block_top + block_height); + break; } - break; - case LoopMode::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); + } + + if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { + qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); + switch (clip->loop_mode()) { + case LoopMode::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 LoopMode::kLoopModeLoop: + for (qreal i=zebra_left; iconnected_viewer()->GetLength())) { + painter->drawLine(i, block_top, i, block_top + block_height); + } + break; + case LoopMode::kLoopModeClamp: + painter->drawLine(zebra_left, block_top, zebra_left, block_top + block_height); + break; } - break; - case LoopMode::kLoopModeClamp: - painter->drawLine(zebra_right, block_top, zebra_right, block_top + block_height); - break; } } - if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { - qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); - switch (clip->loop_mode()) { - case LoopMode::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)); + TimelineMarkerList *marker_list = clip->connected_viewer()->GetMarkers(); + if (!marker_list->empty()) { + + clip_marker_rects_.clear(); + + for (auto it=marker_list->cbegin(); it!=marker_list->cend(); it++) { + TimelineMarker *marker = *it; + // Make sure marker is within In/Out points of the clip + 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, -1, GetScale(), false); + clip_marker_rects_.insert(marker, marker_rect); + painter->setClipping(false); } - break; - case LoopMode::kLoopModeLoop: - for (qreal i=zebra_left; iconnected_viewer()->GetLength())) { - painter->drawLine(i, block_top, i, block_top + block_height); - } - break; - case LoopMode::kLoopModeClamp: - painter->drawLine(zebra_left, block_top, zebra_left, block_top + block_height); - break; } } } - TimelineMarkerList *marker_list = clip->connected_viewer()->GetMarkers(); - if (!marker_list->empty()) { - - clip_marker_rects_.clear(); - - for (auto it=marker_list->cbegin(); it!=marker_list->cend(); it++) { - TimelineMarker *marker = *it; - // Make sure marker is within In/Out points of the clip - 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, -1, GetScale(), false); - clip_marker_rects_.insert(marker, marker_rect); - painter->setClipping(false); - } + if (const FrameHashCache *cache = clip->connected_video_cache()) { + if (cache->HasValidatedRanges()) { + QRect cache_rect = r.adjusted(0, r.height() - PlaybackCache::GetCacheIndicatorHeight(), 0, 0).toRect(); + cache->Draw(painter, clip->media_in(), GetScale(), cache_rect); } } } - if (const FrameHashCache *cache = clip->connected_video_cache()) { - if (cache->HasValidatedRanges()) { - QRect cache_rect = r.adjusted(0, r.height() - PlaybackCache::GetCacheIndicatorHeight(), 0, 0).toRect(); - cache->Draw(painter, clip->media_in(), GetScale(), cache_rect); + // For transitions, show lines representing a transition + if (TransitionBlock* transition = dynamic_cast(block)) { + QVector lines; + + if (transition->connected_in_block()) { + lines.append(QLineF(r.bottomLeft(), r.topRight())); } - } - } - // For transitions, show lines representing a transition - if (TransitionBlock* transition = dynamic_cast(block)) { - QVector lines; + if (transition->connected_out_block()) { + lines.append(QLineF(r.topLeft(), r.bottomRight())); + } - if (transition->connected_in_block()) { - lines.append(QLineF(r.bottomLeft(), r.topRight())); + painter->setPen(shadow_color); + painter->drawLines(lines); } - if (transition->connected_out_block()) { - lines.append(QLineF(r.topLeft(), r.bottomRight())); + if (transition_overlay_out_ == block || transition_overlay_in_ == block) { + QRectF transition_overlay_rect = r; + + qreal transition_overlay_width = TimeToScene(block->length()) * 0.5; + if (transition_overlay_out_ && transition_overlay_in_) { + // This is a dual transition, use the smallest width + Block *other_block = (transition_overlay_out_ == block) ? transition_overlay_in_ : transition_overlay_out_; + + qreal other_width = TimeToScene(other_block->length()) * 0.5; + + transition_overlay_width = qMin(transition_overlay_width, other_width); + } + + if (transition_overlay_out_ == block) { + transition_overlay_rect.setLeft(transition_overlay_rect.right() - transition_overlay_width); + } else { + transition_overlay_rect.setRight(transition_overlay_rect.left() + transition_overlay_width); + } + + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(0, 0, 0, 64)); + + painter->drawRect(transition_overlay_rect); } - - painter->setPen(shadow_color); - painter->drawLines(lines); - } - - if (transition_overlay_out_ == block || transition_overlay_in_ == block) { - QRectF transition_overlay_rect = r; - - qreal transition_overlay_width = TimeToScene(block->length()) * 0.5; - if (transition_overlay_out_ && transition_overlay_in_) { - // This is a dual transition, use the smallest width - Block *other_block = (transition_overlay_out_ == block) ? transition_overlay_in_ : transition_overlay_out_; - - qreal other_width = TimeToScene(other_block->length()) * 0.5; - - transition_overlay_width = qMin(transition_overlay_width, other_width); - } - - if (transition_overlay_out_ == block) { - transition_overlay_rect.setLeft(transition_overlay_rect.right() - transition_overlay_width); - } else { - transition_overlay_rect.setRight(transition_overlay_rect.left() + transition_overlay_width); - } - - painter->setPen(Qt::NoPen); - painter->setBrush(QColor(0, 0, 0, 64)); - - painter->drawRect(transition_overlay_rect); } } } From 5bcb42648895f4dee7b1fe836de7372b484d4b83 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 19 Nov 2022 17:36:16 -0800 Subject: [PATCH 82/85] ffmpeg: ensure duration detection responds to cancel action --- app/codec/ffmpeg/ffmpegdecoder.cpp | 7 +++++-- app/node/project/footage/footage.cpp | 6 ++++-- app/task/project/import/import.cpp | 7 ++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 4981f1119..5e2bdddbe 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -315,6 +315,9 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can int64_t footage_duration = fmt_ctx->duration; bool duration_guessed_from_bitrate = (fmt_ctx->duration_estimation_method == AVFMT_DURATION_FROM_BITRATE); + if (duration_guessed_from_bitrate) { + qWarning() << "Unreliable duration detected - we will manually determine it ourselves (this may take some time)"; + } // Dump it into the Footage object for (unsigned int i=0;inb_streams;i++) { @@ -383,7 +386,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can do { new_dur = frame->best_effort_timestamp; - } while (instance.GetFrame(pkt, frame) >= 0); + } while (instance.GetFrame(pkt, frame) >= 0 && (!cancelled || !cancelled->IsCancelled())); avstream->duration = new_dur; @@ -447,7 +450,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can do { new_dur = frame->best_effort_timestamp; - } while (instance.GetFrame(pkt, frame) >= 0); + } while (instance.GetFrame(pkt, frame) >= 0 && (!cancelled || !cancelled->IsCancelled())); avstream->duration = new_dur; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 96428f345..f99ea8bc5 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -474,8 +474,10 @@ void Footage::Reprobe() } } - if (!footage_info.Save(meta_cache_file)) { - qWarning() << "Failed to save stream cache, footage will have to be re-probed"; + if (!cancelled_ || !cancelled_->HeardCancel()) { + if (!footage_info.Save(meta_cache_file)) { + qWarning() << "Failed to save stream cache, footage will have to be re-probed"; + } } } diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index b13be24fb..91572757b 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -106,10 +106,15 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte } else { - Footage* footage = new Footage(file_info.absoluteFilePath()); + Footage* footage = new Footage(); + footage->SetCancelPointer(this->GetCancelAtom()); + + footage->set_filename(file_info.absoluteFilePath()); footage->SetLabel(file_info.fileName()); + footage->SetCancelPointer(nullptr); + if (footage->IsValid()) { // See if this footage is an image sequence ValidateImageSequence(footage, import, i); From fbfa0c28727c4b8b4a28b85f48cf353aa9894d84 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 19 Nov 2022 17:36:38 -0800 Subject: [PATCH 83/85] core: show successful project save message in status bar --- app/core.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/core.cpp b/app/core.cpp index 26a8d68dc..07684d63c 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1040,6 +1040,8 @@ void Core::ProjectSaveSucceeded(Task* task) autorecovered_projects_.removeOne(p->GetUuid()); SaveUnrecoveredList(); + + ShowStatusBarMessage(tr("Saved to \"%1\" successfully").arg(p->filename())); } Project* Core::GetActiveProject() const From cfbc37cd959df6efdb811062c22f9725e0c8d92e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:23:54 -0800 Subject: [PATCH 84/85] previewautocacher: ignore render requests when params are invalid --- app/render/previewautocacher.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 149a1b83c..827b02c98 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -372,6 +372,11 @@ void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeR void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const TimeRange &range) { + // Ignore render requests if no video is present + if (!viewer_node_ || !viewer_node_->GetVideoParams().is_valid()) { + return; + } + // Stop any current render tasks because a) they might be out of date now anyway, and b) we // want to dedicate all our rendering power to realtime feedback for the user //CancelVideoTasks(node); @@ -386,6 +391,11 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const Tim void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const TimeRange &range) { + // Ignore render requests if no video is present + if (!viewer_node_ || !viewer_node_->GetAudioParams().is_valid()) { + return; + } + // We don't stop rendering audio because currently there's no system of requeuing audio if it's // cancelled, so some areas may end up unrendered forever // ClearAudioQueue(); From 4c00387ab220fe2d2ba35da3e181f99d894d0129 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 23 Nov 2022 08:23:30 -0800 Subject: [PATCH 85/85] track: fix bug where incorrect clip occasionally appeared --- app/node/output/track/track.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 884ad5b65..e1bdde7f3 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -113,6 +113,10 @@ Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const end = blocks_.size()-1; } + if (blocks_.at(end)->in() == r.out()) { + end--; + } + ActiveElements a; for (int i=start; i<=end; i++) { Block *b = blocks_.at(i);