Files
oak-editor/app/render/projectcopier.cpp
T
Mike-Solar 818b76b64e Sync Footage proxy state to render worker copy project
Proxy state (enabled/path/state/etc.) is stored as Footage member data,
not as a Node input, so ProjectCopier did not synchronize it to the
internal copy project used by worker processes. This caused toggling
Use Proxy in the UI to have no effect on rendered output.

Add a Footage::ProxySettingsChanged signal and have ProjectCopier sync
the proxy state to the copied Footage node, marking the copy project
modified so RenderWorkerPool writes a fresh graph snapshot.
2026-07-13 10:19:30 +08:00

362 lines
10 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 <http://www.gnu.org/licenses/>.
***/
#include "projectcopier.h"
#include "node/group/group.h"
#include "node/project/footage/footage.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_, &Project::NodeAdded, this,
&ProjectCopier::QueueNodeAdd);
disconnect(original_, &Project::NodeRemoved, this,
&ProjectCopier::QueueNodeRemove);
disconnect(original_, &Project::InputConnected, this,
&ProjectCopier::QueueEdgeAdd);
disconnect(original_, &Project::InputDisconnected, this,
&ProjectCopier::QueueEdgeRemove);
disconnect(original_, &Project::ValueChanged, this,
&ProjectCopier::QueueValueChange);
disconnect(original_, &Project::InputValueHintChanged, this,
&ProjectCopier::QueueValueHintChange);
disconnect(original_, &Project::SettingChanged, this,
&ProjectCopier::QueueProjectSettingChange);
}
original_ = project;
if (original_) {
// The copied project is only used as an in-memory render proxy. Mark it so
// downstream code (e.g. RenderWorkerPool) knows it is safe to reset its
// modified flag after serializing a snapshot.
copy_->setProperty("_oak_render_proxy", true);
// Add all nodes
for (int i = 0; i < copy_->nodes().size(); i++) {
InsertIntoCopyMap(original_->nodes().at(i), copy_->nodes().at(i));
}
for (int i = copy_->nodes().size(); i < original_->nodes().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);
}
}
// Copy project settings
Project::CopySettings(original_, copy_);
// Ensure graph change value is just before the sync value
UpdateGraphChangeValue();
UpdateLastSyncedValue();
// Connect signals for future node additions/deletions
connect(original_, &Project::NodeAdded, this,
&ProjectCopier::QueueNodeAdd, Qt::DirectConnection);
connect(original_, &Project::NodeRemoved, this,
&ProjectCopier::QueueNodeRemove, Qt::DirectConnection);
connect(original_, &Project::InputConnected, this,
&ProjectCopier::QueueEdgeAdd, Qt::DirectConnection);
connect(original_, &Project::InputDisconnected, this,
&ProjectCopier::QueueEdgeRemove, Qt::DirectConnection);
connect(original_, &Project::ValueChanged, this,
&ProjectCopier::QueueValueChange, Qt::DirectConnection);
connect(original_, &Project::InputValueHintChanged, this,
&ProjectCopier::QueueValueHintChange, Qt::DirectConnection);
connect(original_, &Project::SettingChanged, this,
&ProjectCopier::QueueProjectSettingChange,
Qt::DirectConnection);
}
}
void ProjectCopier::ProcessUpdateQueue()
{
bool copy_changed = false;
// 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();
copy_changed = true;
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;
case QueuedJob::kProjectSettingChanged:
DoProjectSettingChange(job.key, job.value);
break;
}
}
// The copied project is not saved, so its modified flag is only used by the
// render worker pool to decide whether the serialized graph snapshot is stale.
// Mark it modified whenever the copy has actually changed.
if (copy_changed) {
copy_->set_modified(true);
}
// 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<NodeGroup *>(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<NodeGroup *>(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<NodeGroup *>(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::DoProjectSettingChange(const QString &key,
const QString &value)
{
copy_->SetSetting(key, value);
}
void ProjectCopier::InsertIntoCopyMap(Node *node, Node *copy)
{
// Insert into map
copy_map_.insert(node, copy);
// Copy parameters
Node::CopyInputs(node, copy, false);
// Sync Footage proxy state (which is not stored as a Node input)
if (Footage *src_footage = dynamic_cast<Footage *>(node)) {
if (dynamic_cast<Footage *>(copy)) {
connect(src_footage, &Footage::ProxySettingsChanged, this,
[this, src_footage]() { SyncFootageProxySettings(src_footage); });
SyncFootageProxySettings(src_footage);
}
}
// Connect to node's cache
emit AddedNode(node);
}
void ProjectCopier::SyncFootageProxySettings(Footage *source)
{
Footage *copy = GetCopy(source);
if (!copy) {
return;
}
copy->SetProxy(source->proxy_path(), source->proxy_state(),
source->proxy_video_stream_index(),
source->proxy_preset_version(), source->proxy_enabled());
if (Project *cp = copy->project()) {
cp->set_modified(true);
}
}
void ProjectCopier::QueueNodeAdd(Node *node)
{
graph_update_queue_.push_back({ QueuedJob::kNodeAdded, node, NodeInput(),
nullptr, QString(), QString() });
UpdateGraphChangeValue();
}
void ProjectCopier::QueueNodeRemove(Node *node)
{
graph_update_queue_.push_back({ QueuedJob::kNodeRemoved, node, NodeInput(),
nullptr, QString(), QString() });
UpdateGraphChangeValue();
}
void ProjectCopier::QueueEdgeAdd(Node *output, const NodeInput &input)
{
graph_update_queue_.push_back({ QueuedJob::kEdgeAdded, nullptr, input,
output, QString(), QString() });
UpdateGraphChangeValue();
}
void ProjectCopier::QueueEdgeRemove(Node *output, const NodeInput &input)
{
graph_update_queue_.push_back({ QueuedJob::kEdgeRemoved, nullptr, input,
output, QString(), QString() });
UpdateGraphChangeValue();
}
void ProjectCopier::QueueValueChange(const NodeInput &input)
{
/*for (auto it = graph_update_queue_.begin(); it != graph_update_queue_.end(); ) {
if (it->type == QueuedJob::kValueChanged && it->input == input) {
it = graph_update_queue_.erase(it);
} else {
it++;
}
}*/
graph_update_queue_.push_back({ QueuedJob::kValueChanged, nullptr, input,
nullptr, QString(), QString() });
UpdateGraphChangeValue();
}
void ProjectCopier::QueueValueHintChange(const NodeInput &input)
{
graph_update_queue_.push_back({ QueuedJob::kValueHintChanged, nullptr,
input, nullptr, QString(), QString() });
UpdateGraphChangeValue();
}
void ProjectCopier::QueueProjectSettingChange(const QString &key,
const QString &value)
{
graph_update_queue_.push_back({ QueuedJob::kProjectSettingChanged, nullptr,
NodeInput(), nullptr, key, value });
UpdateGraphChangeValue();
}
void ProjectCopier::UpdateGraphChangeValue()
{
graph_changed_time_.Acquire();
}
void ProjectCopier::UpdateLastSyncedValue()
{
last_update_time_.Acquire();
}
}