build: split the engine into liboakengine.so; worker drops the UI entirely
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).
- oak-render-worker now links liboakengine instead of the whole
libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
../app but backends now live in engine/; a stale pre-split liboakgl
in the build tree got dlopened instead, re-initialized and later
destroyed the interposed engine statics (full-suite segfault at
DialogSequenceParameterTab, found via gdb watchpoint)
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(folder)
|
||||
add_subdirectory(footage)
|
||||
add_subdirectory(sequence)
|
||||
add_subdirectory(serializer)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/project/folder/folder.h
|
||||
node/project/folder/folder.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,187 @@
|
||||
/***
|
||||
|
||||
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 "folder.h"
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "ui/icons/icons.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Node
|
||||
|
||||
const QString Folder::k_child_input = QStringLiteral("child_in");
|
||||
|
||||
Folder::Folder()
|
||||
{
|
||||
set_flag(k_is_item);
|
||||
|
||||
add_input(k_child_input, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_array | k_input_flag_not_keyframable));
|
||||
}
|
||||
|
||||
QVariant Folder::data(const DataType &d) const
|
||||
{
|
||||
if (d == icon) {
|
||||
return icon::folder;
|
||||
}
|
||||
|
||||
return super::data(d);
|
||||
}
|
||||
|
||||
void Folder::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_child_input, tr("Children"));
|
||||
}
|
||||
|
||||
Node *get_child_with_name_internal(const Folder *n, const QString &s)
|
||||
{
|
||||
for (int i = 0; i < n->item_child_count(); i++) {
|
||||
Node *child = n->item_child(i);
|
||||
|
||||
if (child->get_label() == s) {
|
||||
return child;
|
||||
} else if (Folder *subfolder = dynamic_cast<Folder *>(child)) {
|
||||
if (Node *n2 = get_child_with_name_internal(subfolder, s)) {
|
||||
return n2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node *Folder::get_child_with_name(const QString &s) const
|
||||
{
|
||||
return get_child_with_name_internal(this, s);
|
||||
}
|
||||
|
||||
bool Folder::has_child_recursive(Node *child) const
|
||||
{
|
||||
for (Node *i : item_children_) {
|
||||
if (i == child) {
|
||||
return true;
|
||||
} else if (Folder *f = dynamic_cast<Folder *>(i)) {
|
||||
if (f->has_child_recursive(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Folder::index_of_child_in_array(Node *item) const
|
||||
{
|
||||
int index_of_item = item_children_.indexOf(item);
|
||||
|
||||
if (index_of_item == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return item_element_index_.at(index_of_item);
|
||||
}
|
||||
|
||||
void Folder::InputConnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
if (input == k_child_input && element != -1) {
|
||||
Node *item = output;
|
||||
|
||||
// The insert index is always our "count" because we only support appending in our internal
|
||||
// model. For sorting/organizing, a QSortFilterProxyModel is used instead.
|
||||
emit begin_insert_item(item, item_child_count());
|
||||
item_children_.append(item);
|
||||
item_element_index_.append(element);
|
||||
item->set_folder(this);
|
||||
emit end_insert_item();
|
||||
}
|
||||
}
|
||||
|
||||
void Folder::InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
if (input == k_child_input && element != -1) {
|
||||
Node *item = output;
|
||||
|
||||
int child_index = item_children_.indexOf(item);
|
||||
emit begin_remove_item(item, child_index);
|
||||
item_children_.removeAt(child_index);
|
||||
item_element_index_.removeAt(child_index);
|
||||
item->set_folder(nullptr);
|
||||
emit end_remove_item();
|
||||
}
|
||||
}
|
||||
|
||||
FolderAddChild::FolderAddChild(Folder *folder, Node *child)
|
||||
: folder_(folder)
|
||||
, child_(child)
|
||||
{
|
||||
}
|
||||
|
||||
Project *FolderAddChild::get_relevant_project() const
|
||||
{
|
||||
return folder_->project();
|
||||
}
|
||||
|
||||
void FolderAddChild::redo()
|
||||
{
|
||||
int array_index = folder_->input_array_size(Folder::k_child_input);
|
||||
folder_->input_array_append(Folder::k_child_input);
|
||||
Node::connect_edge(child_,
|
||||
NodeInput(folder_, Folder::k_child_input, array_index));
|
||||
}
|
||||
|
||||
void FolderAddChild::undo()
|
||||
{
|
||||
Node::disconnect_edge(
|
||||
child_, NodeInput(folder_, Folder::k_child_input,
|
||||
folder_->input_array_size(Folder::k_child_input) - 1));
|
||||
folder_->input_array_remove_last(Folder::k_child_input);
|
||||
}
|
||||
|
||||
void Folder::RemoveElementCommand::redo()
|
||||
{
|
||||
if (!subcommand_) {
|
||||
remove_index_ = folder_->index_of_child_in_array(child_);
|
||||
if (remove_index_ != -1) {
|
||||
NodeInput connected_input(folder_, Folder::k_child_input,
|
||||
remove_index_);
|
||||
subcommand_ = new MultiUndoCommand();
|
||||
subcommand_->add_child(new NodeEdgeRemoveCommand(
|
||||
folder_->get_connected_output(connected_input), connected_input));
|
||||
subcommand_->add_child(new NodeArrayRemoveCommand(
|
||||
folder_, Folder::k_child_input, remove_index_));
|
||||
}
|
||||
}
|
||||
|
||||
if (subcommand_) {
|
||||
subcommand_->redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_FOLDER_H
|
||||
#define OAK_FOLDER_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The Folder class representing a directory in a project structure
|
||||
*
|
||||
* The Item base class already has support for children, but this functionality is disabled by default
|
||||
* (see CanHaveChildren() override). The Folder is a specific type that enables this functionality.
|
||||
*/
|
||||
class Folder : public Node {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Folder();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(Folder)
|
||||
|
||||
virtual QString name() const override
|
||||
{
|
||||
return tr("Folder");
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.folder");
|
||||
}
|
||||
|
||||
virtual QVector<CategoryID> category() const override
|
||||
{
|
||||
return { k_category_project };
|
||||
}
|
||||
|
||||
virtual QString description() const override
|
||||
{
|
||||
return tr("Organize several items into a single collection.");
|
||||
}
|
||||
|
||||
virtual QVariant data(const DataType &d) const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
Node *get_child_with_name(const QString &s) const;
|
||||
bool child_exists_with_name(const QString &s) const
|
||||
{
|
||||
return get_child_with_name(s);
|
||||
}
|
||||
|
||||
bool has_child_recursive(Node *child) const;
|
||||
|
||||
int item_child_count() const
|
||||
{
|
||||
return item_children_.size();
|
||||
}
|
||||
|
||||
Node *item_child(int i) const
|
||||
{
|
||||
return item_children_.at(i);
|
||||
}
|
||||
|
||||
const QVector<Node *> &children() const
|
||||
{
|
||||
return item_children_;
|
||||
}
|
||||
|
||||
int index_of_child(Node *item) const
|
||||
{
|
||||
return item_children_.indexOf(item);
|
||||
}
|
||||
|
||||
int index_of_child_in_array(Node *item) const;
|
||||
|
||||
template <typename T> QVector<T *> list_children_of_type() const
|
||||
{
|
||||
QVector<T *> list;
|
||||
|
||||
foreach (Node *node, item_children_) {
|
||||
T *cast_test = dynamic_cast<T *>(node);
|
||||
if (cast_test) {
|
||||
list.append(cast_test);
|
||||
}
|
||||
|
||||
Folder *folder_test = dynamic_cast<Folder *>(node);
|
||||
if (folder_test) {
|
||||
list.append(folder_test->list_children_of_type<T>());
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static const QString k_child_input;
|
||||
|
||||
class RemoveElementCommand : public UndoCommand {
|
||||
public:
|
||||
RemoveElementCommand(Folder *folder, Node *child)
|
||||
: folder_(folder)
|
||||
, child_(child)
|
||||
, subcommand_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~RemoveElementCommand() override
|
||||
{
|
||||
delete subcommand_;
|
||||
}
|
||||
|
||||
virtual Project *get_relevant_project() const override
|
||||
{
|
||||
return folder_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
if (subcommand_) {
|
||||
subcommand_->undo_now();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Folder *folder_;
|
||||
|
||||
Node *child_;
|
||||
|
||||
int remove_index_;
|
||||
|
||||
MultiUndoCommand *subcommand_;
|
||||
};
|
||||
|
||||
signals:
|
||||
void begin_insert_item(Node *n, int index);
|
||||
|
||||
void end_insert_item();
|
||||
|
||||
void begin_remove_item(Node *n, int index);
|
||||
|
||||
void end_remove_item();
|
||||
|
||||
protected:
|
||||
virtual void InputConnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static void list_outputs_of_type_internal(const Folder *n, QVector<T *> &list,
|
||||
bool recursive)
|
||||
{
|
||||
foreach (const Node::OutputConnection &c, n->output_connections()) {
|
||||
Node *connected = c.second.node();
|
||||
|
||||
T *cast_test = dynamic_cast<T *>(connected);
|
||||
|
||||
if (cast_test) {
|
||||
// Avoid duplicates
|
||||
if (!list.contains(cast_test)) {
|
||||
list.append(cast_test);
|
||||
}
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
Folder *subfolder = dynamic_cast<Folder *>(connected);
|
||||
|
||||
if (subfolder) {
|
||||
ListOutputsOfTypeInternal(subfolder, list, recursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<Node *> item_children_;
|
||||
QVector<int> item_element_index_;
|
||||
};
|
||||
|
||||
class FolderAddChild : public UndoCommand {
|
||||
public:
|
||||
FolderAddChild(Folder *folder, Node *child);
|
||||
|
||||
virtual Project *get_relevant_project() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Folder *folder_;
|
||||
|
||||
Node *child_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FOLDER_H
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/project/footage/footage.cpp
|
||||
node/project/footage/footage.h
|
||||
node/project/footage/footagedescription.cpp
|
||||
node/project/footage/footagedescription.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,348 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_FOOTAGE_H
|
||||
#define OAK_FOOTAGE_H
|
||||
|
||||
#include <olive/core/core.h>
|
||||
#include <QList>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "codec/decoder.h"
|
||||
#include "codec/proxymanager.h"
|
||||
#include "footagedescription.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/cancelatom.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A reference to an external media file with metadata in a project structure
|
||||
*
|
||||
* Footage objects serve two purposes: storing metadata about external media and storing it as a project item.
|
||||
* Footage objects store a list of Stream objects which store the majority of video/audio metadata. These streams
|
||||
* are identical to the stream data in the files.
|
||||
*/
|
||||
class Footage : public ViewerOutput {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Footage Constructor
|
||||
*/
|
||||
Footage(const QString &filename = QString());
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(Footage)
|
||||
|
||||
virtual QString name() const override
|
||||
{
|
||||
return tr("Media");
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.footage");
|
||||
}
|
||||
|
||||
virtual QVector<CategoryID> category() const override
|
||||
{
|
||||
return { k_category_project };
|
||||
}
|
||||
|
||||
virtual QString description() const override
|
||||
{
|
||||
return tr(
|
||||
"Import video, audio, or still image files into the composition.");
|
||||
}
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
/**
|
||||
* @brief Reset Footage state ready for running through Probe() again
|
||||
*
|
||||
* If a Footage object needs to be re-probed (e.g. source file changes or Footage is linked to a new file), its
|
||||
* state needs to be reset so the Decoder::Probe() function can accurately mirror the source file. Clear() will
|
||||
* reset the Footage object's state to being freshly created (keeping the filename).
|
||||
*
|
||||
* In most cases, you'll be using olive::ProbeMedia() for re-probing which already runs Clear(), so you won't need
|
||||
* to worry about this.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return valid_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets this footage to valid and ready to use
|
||||
*/
|
||||
void set_valid();
|
||||
|
||||
/**
|
||||
* @brief Return the current filename of this Footage object
|
||||
*/
|
||||
QString filename() const;
|
||||
|
||||
/**
|
||||
* @brief Set the filename
|
||||
*
|
||||
* NOTE: This does not automtaically clear the old streams and re-probe for new ones. If the file link has been
|
||||
* changed, this will need to be done manually.
|
||||
*
|
||||
* @param s
|
||||
*
|
||||
* New filename
|
||||
*/
|
||||
void set_filename(const QString &s);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the last modified time/date
|
||||
*
|
||||
* The file's last modified timestamp is stored for potential organization in the ProjectExplorer. It can be
|
||||
* retrieved here.
|
||||
*/
|
||||
const qint64 ×tamp() const;
|
||||
|
||||
/**
|
||||
* @brief Set the last modified time/date
|
||||
*
|
||||
* This should probably only be done on import or replace.
|
||||
*
|
||||
* @param t
|
||||
*
|
||||
* New last modified time/date
|
||||
*/
|
||||
void set_timestamp(const qint64 &t);
|
||||
|
||||
void set_cancel_pointer(CancelAtom *c)
|
||||
{
|
||||
cancelled_ = c;
|
||||
}
|
||||
|
||||
int get_stream_index(Track::Type type, int index) const;
|
||||
int get_stream_index(const Track::Reference &ref) const
|
||||
{
|
||||
return get_stream_index(ref.type(), ref.index());
|
||||
}
|
||||
|
||||
Track::Reference get_reference_from_real_index(int real_index) const;
|
||||
|
||||
/**
|
||||
* @brief Get the Decoder ID set when this Footage was probed
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A decoder ID
|
||||
*/
|
||||
const QString &decoder() const;
|
||||
|
||||
bool has_source_start_time() const
|
||||
{
|
||||
return has_source_start_time_;
|
||||
}
|
||||
|
||||
const Rational &source_start_time() const
|
||||
{
|
||||
return source_start_time_;
|
||||
}
|
||||
|
||||
const QString &source_start_time_source() const
|
||||
{
|
||||
return source_start_time_source_;
|
||||
}
|
||||
|
||||
void set_source_start_time(const Rational &time, const QString &source);
|
||||
|
||||
/**
|
||||
* @brief Removes any source start time (auto-detected or manual)
|
||||
*/
|
||||
void clear_source_start_time();
|
||||
|
||||
bool proxy_enabled() const
|
||||
{
|
||||
return proxy_enabled_;
|
||||
}
|
||||
|
||||
void set_proxy_enabled(bool enabled);
|
||||
|
||||
const QString &proxy_path() const
|
||||
{
|
||||
return proxy_path_;
|
||||
}
|
||||
|
||||
int proxy_video_stream_index() const
|
||||
{
|
||||
return proxy_video_stream_index_;
|
||||
}
|
||||
|
||||
int proxy_preset_version() const
|
||||
{
|
||||
return proxy_preset_version_;
|
||||
}
|
||||
|
||||
ProxyManager::ProxyState proxy_state() const
|
||||
{
|
||||
return proxy_state_;
|
||||
}
|
||||
|
||||
void set_proxy(const QString &path, ProxyManager::ProxyState state,
|
||||
int video_stream_index, int preset_version, bool enabled);
|
||||
|
||||
void clear_proxy();
|
||||
|
||||
/**
|
||||
* @brief Returns true if this footage uses its own proxy parameters
|
||||
* instead of the global proxy settings
|
||||
*/
|
||||
bool has_custom_proxy_params() const
|
||||
{
|
||||
return has_custom_proxy_params_;
|
||||
}
|
||||
|
||||
const ProxyManager::ProxyParams &custom_proxy_params() const
|
||||
{
|
||||
return custom_proxy_params_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets per-footage proxy parameters, overriding the global settings
|
||||
*/
|
||||
void set_custom_proxy_params(const ProxyManager::ProxyParams ¶ms);
|
||||
|
||||
/**
|
||||
* @brief Reverts this footage to using the global proxy settings
|
||||
*/
|
||||
void clear_custom_proxy_params();
|
||||
|
||||
/**
|
||||
* @brief Returns the custom proxy parameters if set, otherwise the
|
||||
* parameters from the global application config
|
||||
*/
|
||||
ProxyManager::ProxyParams get_effective_proxy_params() const;
|
||||
|
||||
static QString describe_video_stream(const VideoParams ¶ms);
|
||||
static QString describe_audio_stream(const AudioParams ¶ms);
|
||||
static QString describe_subtitle_stream(const SubtitleParams ¶ms);
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void generate_frame(FramePtr frame,
|
||||
const GenerateJob &job) const override;
|
||||
|
||||
static QString get_stream_type_name(Track::Type type);
|
||||
|
||||
virtual Node *get_connected_texture_output() override;
|
||||
|
||||
virtual Node *get_connected_sample_output() override;
|
||||
|
||||
static Rational adjust_time_by_loop_mode(Rational time, LoopMode loop_mode,
|
||||
const Rational &length,
|
||||
VideoParams::Type type,
|
||||
const Rational &timebase);
|
||||
|
||||
virtual QVariant data(const DataType &d) const override;
|
||||
|
||||
virtual int get_total_stream_count() const override
|
||||
{
|
||||
return total_stream_count_;
|
||||
}
|
||||
|
||||
virtual bool load_custom(QXmlStreamReader *reader,
|
||||
SerializedData *data) override;
|
||||
virtual void save_custom(QXmlStreamWriter *writer) const override;
|
||||
|
||||
signals:
|
||||
void proxy_settings_changed();
|
||||
|
||||
public:
|
||||
static const QString k_filename_input;
|
||||
|
||||
virtual void AddedToGraphEvent(Project *p) override;
|
||||
virtual void RemovedFromGraphEvent(Project *p) override;
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString &input,
|
||||
int element) override;
|
||||
|
||||
virtual Rational verify_length_internal(Track::Type type) const override;
|
||||
|
||||
private:
|
||||
QString get_colorspace_to_use(const VideoParams ¶ms) const;
|
||||
|
||||
void reprobe();
|
||||
|
||||
VideoParams merge_video_stream(const VideoParams &base,
|
||||
const VideoParams &over);
|
||||
|
||||
/**
|
||||
* @brief Internal timestamp object
|
||||
*/
|
||||
qint64 timestamp_;
|
||||
|
||||
/**
|
||||
* @brief Internal attached decoder ID
|
||||
*/
|
||||
QString decoder_;
|
||||
|
||||
Rational source_start_time_;
|
||||
|
||||
QString source_start_time_source_;
|
||||
|
||||
bool has_source_start_time_;
|
||||
|
||||
bool proxy_enabled_;
|
||||
|
||||
QString proxy_path_;
|
||||
|
||||
ProxyManager::ProxyState proxy_state_;
|
||||
|
||||
int proxy_video_stream_index_;
|
||||
|
||||
int proxy_preset_version_;
|
||||
|
||||
bool has_custom_proxy_params_;
|
||||
|
||||
ProxyManager::ProxyParams custom_proxy_params_;
|
||||
|
||||
bool valid_;
|
||||
|
||||
CancelAtom *cancelled_;
|
||||
|
||||
int total_stream_count_;
|
||||
|
||||
private slots:
|
||||
void check_footage();
|
||||
|
||||
void default_color_space_changed();
|
||||
|
||||
void proxy_ready(const QString &source_filename, int stream_index,
|
||||
const QString &proxy_filename);
|
||||
void proxy_finished(const QString &source_filename, int stream_index,
|
||||
const QString &proxy_filename,
|
||||
ProxyManager::ProxyState state);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FOOTAGE_H
|
||||
@@ -0,0 +1,203 @@
|
||||
/***
|
||||
|
||||
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 "footagedescription.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
#include "node/project/serializer/typeserializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
bool FootageDescription::load(const QString &filename)
|
||||
{
|
||||
// Reset self
|
||||
*this = FootageDescription();
|
||||
|
||||
QFile file(filename);
|
||||
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
QXmlStreamReader reader(&file);
|
||||
|
||||
bool found_streamcache = false;
|
||||
|
||||
while (xml_read_next_start_element(&reader)) {
|
||||
if (reader.name() == QStringLiteral("streamcache")) {
|
||||
found_streamcache = true;
|
||||
// Default to first version of metadata (which wasn't versioned at all)
|
||||
unsigned version = 1;
|
||||
|
||||
{
|
||||
XMLAttributeLoop((&reader), attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("version")) {
|
||||
version = attr.value().toUInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (version != k_footage_meta_version) {
|
||||
// If this is a different version, discard so we can probe new data
|
||||
return false;
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(&reader)) {
|
||||
if (reader.name() == QStringLiteral("decoder")) {
|
||||
decoder_ = reader.readElementText();
|
||||
} else if (reader.name() ==
|
||||
QStringLiteral("sourcestarttime")) {
|
||||
QString source;
|
||||
{
|
||||
XMLAttributeLoop((&reader), attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("source")) {
|
||||
source = attr.value().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QStringList split =
|
||||
reader.readElementText().split('/');
|
||||
if (split.size() == 2) {
|
||||
set_source_start_time(Rational(split.at(0).toInt(),
|
||||
split.at(1).toInt()),
|
||||
source);
|
||||
}
|
||||
} else if (reader.name() == QStringLiteral("streams")) {
|
||||
{
|
||||
XMLAttributeLoop((&reader), attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("count")) {
|
||||
total_stream_count_ = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(&reader)) {
|
||||
if (reader.name() == QStringLiteral("video")) {
|
||||
VideoParams vp;
|
||||
vp.load(&reader);
|
||||
add_video_stream(vp);
|
||||
} else if (reader.name() ==
|
||||
QStringLiteral("audio")) {
|
||||
AudioParams ap =
|
||||
TypeSerializer::load_audio_params(&reader);
|
||||
add_audio_stream(ap);
|
||||
} else if (reader.name() ==
|
||||
QStringLiteral("subtitle")) {
|
||||
SubtitleParams sp;
|
||||
sp.load(&reader);
|
||||
add_subtitle_stream(sp);
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (reader.hasError()) {
|
||||
qWarning() << "Failed to load footage description for" << filename
|
||||
<< reader.errorString();
|
||||
} else {
|
||||
// Only accept files whose root element is the one Save() writes
|
||||
return found_streamcache;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FootageDescription::save(const QString &filename) const
|
||||
{
|
||||
QFile file(filename);
|
||||
|
||||
if (!file.open(QFile::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter writer(&file);
|
||||
|
||||
writer.writeStartDocument();
|
||||
|
||||
writer.writeStartElement(QStringLiteral("streamcache"));
|
||||
|
||||
writer.writeAttribute(QStringLiteral("version"),
|
||||
QString::number(k_footage_meta_version));
|
||||
|
||||
writer.writeTextElement(QStringLiteral("decoder"), decoder_);
|
||||
|
||||
if (has_source_start_time_) {
|
||||
writer.writeStartElement(QStringLiteral("sourcestarttime"));
|
||||
writer.writeAttribute(QStringLiteral("source"),
|
||||
source_start_time_source_);
|
||||
writer.writeCharacters(QStringLiteral("%1/%2").arg(
|
||||
QString::number(source_start_time_.numerator()),
|
||||
QString::number(source_start_time_.denominator())));
|
||||
writer.writeEndElement();
|
||||
}
|
||||
|
||||
writer.writeStartElement(QStringLiteral("streams"));
|
||||
|
||||
writer.writeAttribute(QStringLiteral("count"),
|
||||
QString::number(total_stream_count_));
|
||||
|
||||
foreach (const VideoParams &vp, video_streams_) {
|
||||
writer.writeStartElement(QStringLiteral("video"));
|
||||
vp.save(&writer);
|
||||
writer.writeEndElement(); // video
|
||||
}
|
||||
|
||||
foreach (const AudioParams &ap, audio_streams_) {
|
||||
writer.writeStartElement(QStringLiteral("audio"));
|
||||
TypeSerializer::save_audio_params(&writer, ap);
|
||||
writer.writeEndElement(); // audio
|
||||
}
|
||||
|
||||
foreach (const SubtitleParams &sp, subtitle_streams_) {
|
||||
writer.writeStartElement(QStringLiteral("subtitle"));
|
||||
sp.save(&writer);
|
||||
writer.writeEndElement(); // audio
|
||||
}
|
||||
|
||||
writer.writeEndElement(); // streams
|
||||
|
||||
writer.writeEndElement(); // streamcache
|
||||
|
||||
writer.writeEndDocument();
|
||||
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_FOOTAGEDESCRIPTION_H
|
||||
#define OAK_FOOTAGEDESCRIPTION_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "node/output/track/track.h"
|
||||
#include "render/subtitleparams.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class FootageDescription {
|
||||
public:
|
||||
FootageDescription(const QString &decoder = QString())
|
||||
: decoder_(decoder)
|
||||
, total_stream_count_(0)
|
||||
, has_source_start_time_(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return !decoder_.isEmpty() &&
|
||||
(!video_streams_.isEmpty() || !audio_streams_.isEmpty() ||
|
||||
!subtitle_streams_.isEmpty());
|
||||
}
|
||||
|
||||
const QString &decoder() const
|
||||
{
|
||||
return decoder_;
|
||||
}
|
||||
|
||||
void add_video_stream(const VideoParams &video_params)
|
||||
{
|
||||
Q_ASSERT(!has_stream_index(video_params.stream_index()));
|
||||
|
||||
video_streams_.append(video_params);
|
||||
}
|
||||
|
||||
void add_audio_stream(const AudioParams &audio_params)
|
||||
{
|
||||
Q_ASSERT(!has_stream_index(audio_params.stream_index()));
|
||||
|
||||
audio_streams_.append(audio_params);
|
||||
}
|
||||
|
||||
void add_subtitle_stream(const SubtitleParams &sub_params)
|
||||
{
|
||||
Q_ASSERT(!has_stream_index(sub_params.stream_index()));
|
||||
|
||||
subtitle_streams_.append(sub_params);
|
||||
}
|
||||
|
||||
Track::Type get_type_of_stream(int index)
|
||||
{
|
||||
if (stream_is_video(index)) {
|
||||
return Track::k_video;
|
||||
} else if (stream_is_audio(index)) {
|
||||
return Track::k_audio;
|
||||
} else if (stream_is_subtitle(index)) {
|
||||
return Track::k_subtitle;
|
||||
} else {
|
||||
return Track::k_none;
|
||||
}
|
||||
}
|
||||
|
||||
bool stream_is_video(int index) const
|
||||
{
|
||||
foreach (const VideoParams &vp, video_streams_) {
|
||||
if (vp.stream_index() == index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool stream_is_audio(int index) const
|
||||
{
|
||||
foreach (const AudioParams &ap, audio_streams_) {
|
||||
if (ap.stream_index() == index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool stream_is_subtitle(int index) const
|
||||
{
|
||||
foreach (const SubtitleParams &sp, subtitle_streams_) {
|
||||
if (sp.stream_index() == index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_stream_index(int index) const
|
||||
{
|
||||
return stream_is_video(index) || stream_is_audio(index) ||
|
||||
stream_is_subtitle(index);
|
||||
}
|
||||
|
||||
int get_stream_count() const
|
||||
{
|
||||
return total_stream_count_;
|
||||
}
|
||||
void set_stream_count(int s)
|
||||
{
|
||||
total_stream_count_ = s;
|
||||
}
|
||||
|
||||
void set_source_start_time(const Rational &time, const QString &source)
|
||||
{
|
||||
source_start_time_ = time;
|
||||
source_start_time_source_ = source;
|
||||
has_source_start_time_ = true;
|
||||
}
|
||||
|
||||
bool has_source_start_time() const
|
||||
{
|
||||
return has_source_start_time_;
|
||||
}
|
||||
|
||||
const Rational &source_start_time() const
|
||||
{
|
||||
return source_start_time_;
|
||||
}
|
||||
|
||||
const QString &source_start_time_source() const
|
||||
{
|
||||
return source_start_time_source_;
|
||||
}
|
||||
|
||||
bool load(const QString &filename);
|
||||
|
||||
bool save(const QString &filename) const;
|
||||
|
||||
const QVector<VideoParams> &get_video_streams() const
|
||||
{
|
||||
return video_streams_;
|
||||
}
|
||||
QVector<VideoParams> &get_video_streams()
|
||||
{
|
||||
return video_streams_;
|
||||
}
|
||||
|
||||
const QVector<AudioParams> &get_audio_streams() const
|
||||
{
|
||||
return audio_streams_;
|
||||
}
|
||||
QVector<AudioParams> &get_audio_streams()
|
||||
{
|
||||
return audio_streams_;
|
||||
}
|
||||
|
||||
const QVector<SubtitleParams> &get_subtitle_streams() const
|
||||
{
|
||||
return subtitle_streams_;
|
||||
}
|
||||
QVector<SubtitleParams> &get_subtitle_streams()
|
||||
{
|
||||
return subtitle_streams_;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr unsigned k_footage_meta_version = 7;
|
||||
|
||||
QString decoder_;
|
||||
|
||||
QVector<VideoParams> video_streams_;
|
||||
|
||||
QVector<AudioParams> audio_streams_;
|
||||
|
||||
QVector<SubtitleParams> subtitle_streams_;
|
||||
|
||||
int total_stream_count_;
|
||||
|
||||
Rational source_start_time_;
|
||||
|
||||
QString source_start_time_source_;
|
||||
|
||||
bool has_source_start_time_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FOOTAGEDESCRIPTION_H
|
||||
@@ -0,0 +1,23 @@
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/project/sequence/sequence.h
|
||||
node/project/sequence/sequence.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,200 @@
|
||||
/***
|
||||
|
||||
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 "sequence.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "ui/icons/icons.h"
|
||||
#include "timeline/timelineundogeneral.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QString Sequence::k_track_input_format = QStringLiteral("track_in_%1");
|
||||
|
||||
#define super ViewerOutput
|
||||
|
||||
Sequence::Sequence()
|
||||
{
|
||||
set_flag(k_is_item);
|
||||
|
||||
// Create TrackList instances
|
||||
track_lists_.resize(Track::k_count);
|
||||
|
||||
for (int i = 0; i < Track::k_count; i++) {
|
||||
// Create track input
|
||||
QString track_input_id = k_track_input_format.arg(i);
|
||||
|
||||
add_input(track_input_id, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_array |
|
||||
k_input_flag_hidden | k_input_flag_ignore_invalidations));
|
||||
|
||||
TrackList *list =
|
||||
new TrackList(this, static_cast<Track::Type>(i), track_input_id);
|
||||
track_lists_.replace(i, list);
|
||||
connect(list, &TrackList::track_list_changed, this,
|
||||
&Sequence::update_track_cache);
|
||||
connect(list, &TrackList::length_changed, this, &Sequence::verify_length);
|
||||
connect(list, &TrackList::track_added, this, &Sequence::track_added);
|
||||
connect(list, &TrackList::track_removed, this, &Sequence::track_removed);
|
||||
}
|
||||
}
|
||||
|
||||
void Sequence::add_default_nodes(MultiUndoCommand *command)
|
||||
{
|
||||
// Create tracks and connect them to the viewer
|
||||
UndoCommand *video_track_command =
|
||||
new TimelineAddTrackCommand(track_list(Track::k_video));
|
||||
UndoCommand *audio_track_command =
|
||||
new TimelineAddTrackCommand(track_list(Track::k_audio));
|
||||
|
||||
if (command) {
|
||||
command->add_child(video_track_command);
|
||||
command->add_child(audio_track_command);
|
||||
} else {
|
||||
video_track_command->redo_now();
|
||||
audio_track_command->redo_now();
|
||||
delete video_track_command;
|
||||
delete audio_track_command;
|
||||
}
|
||||
}
|
||||
|
||||
QVariant Sequence::data(const DataType &d) const
|
||||
{
|
||||
if (d == icon) {
|
||||
return icon::sequence;
|
||||
}
|
||||
|
||||
return super::data(d);
|
||||
}
|
||||
|
||||
QVector<Track *> Sequence::get_unlocked_tracks() const
|
||||
{
|
||||
QVector<Track *> tracks = get_tracks();
|
||||
|
||||
for (int i = 0; i < tracks.size(); i++) {
|
||||
if (tracks.at(i)->is_locked()) {
|
||||
tracks.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
void Sequence::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
for (int i = 0; i < Track::k_count; i++) {
|
||||
QString input_name;
|
||||
|
||||
switch (static_cast<Track::Type>(i)) {
|
||||
case Track::k_video:
|
||||
input_name = tr("Video Tracks");
|
||||
break;
|
||||
case Track::k_audio:
|
||||
input_name = tr("Audio Tracks");
|
||||
break;
|
||||
case Track::k_subtitle:
|
||||
input_name = tr("Subtitle Tracks");
|
||||
break;
|
||||
case Track::k_none:
|
||||
case Track::k_count:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!input_name.isEmpty()) {
|
||||
set_input_name(k_track_input_format.arg(i), input_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sequence::invalidate_cache(const TimeRange &range, const QString &from,
|
||||
int element, InvalidateCacheOptions options)
|
||||
{
|
||||
if (from == k_track_input_format.arg(Track::k_subtitle)) {
|
||||
emit subtitles_changed(range);
|
||||
}
|
||||
|
||||
super::invalidate_cache(range, from, element, options);
|
||||
}
|
||||
|
||||
Rational Sequence::verify_length_internal(Track::Type type) const
|
||||
{
|
||||
if (!track_lists_.isEmpty()) {
|
||||
switch (type) {
|
||||
case Track::k_video:
|
||||
return track_lists_.at(Track::k_video)->get_total_length();
|
||||
case Track::k_audio:
|
||||
return track_lists_.at(Track::k_audio)->get_total_length();
|
||||
case Track::k_subtitle:
|
||||
return track_lists_.at(Track::k_subtitle)->get_total_length();
|
||||
case Track::k_none:
|
||||
case Track::k_count:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Sequence::InputConnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
foreach (TrackList *list, track_lists_) {
|
||||
if (list->track_input() == input) {
|
||||
// Return because we found our input
|
||||
list->track_connected(output, element);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super::InputConnectedEvent(input, element, output);
|
||||
}
|
||||
|
||||
void Sequence::InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
foreach (TrackList *list, track_lists_) {
|
||||
if (list->track_input() == input) {
|
||||
// Return because we found our input
|
||||
list->track_disconnected(output, element);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super::InputDisconnectedEvent(input, element, output);
|
||||
}
|
||||
|
||||
void Sequence::update_track_cache()
|
||||
{
|
||||
track_cache_.clear();
|
||||
|
||||
foreach (TrackList *list, track_lists_) {
|
||||
foreach (Track *track, list->get_tracks()) {
|
||||
track_cache_.append(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SEQUENCE_H
|
||||
#define OAK_SEQUENCE_H
|
||||
|
||||
#include "node/output/track/tracklist.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The main timeline object, an graph of edited clips that forms a complete edit
|
||||
*/
|
||||
class Sequence : public ViewerOutput {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Sequence();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(Sequence)
|
||||
|
||||
virtual QString name() const override
|
||||
{
|
||||
return tr("Sequence");
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.sequence");
|
||||
}
|
||||
|
||||
virtual QVector<CategoryID> category() const override
|
||||
{
|
||||
return { k_category_project };
|
||||
}
|
||||
|
||||
virtual QString description() const override
|
||||
{
|
||||
return tr(
|
||||
"A series of cuts that result in an edited video. Also called a timeline.");
|
||||
}
|
||||
|
||||
void add_default_nodes(MultiUndoCommand *command = nullptr);
|
||||
|
||||
virtual QVariant data(const DataType &d) const override;
|
||||
|
||||
const QVector<Track *> &get_tracks() const
|
||||
{
|
||||
return track_cache_;
|
||||
}
|
||||
|
||||
Track *get_track_from_reference(const Track::Reference &track_ref) const
|
||||
{
|
||||
if (track_ref.type() < 0 || track_ref.type() >= track_lists_.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return track_lists_.at(track_ref.type())->get_track_at(track_ref.index());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Same as GetTracks() but omits tracks that are locked.
|
||||
*/
|
||||
QVector<Track *> get_unlocked_tracks() const;
|
||||
|
||||
TrackList *track_list(Track::Type type) const
|
||||
{
|
||||
return track_lists_.at(type);
|
||||
}
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
virtual void invalidate_cache(const TimeRange &range, const QString &from,
|
||||
int element,
|
||||
InvalidateCacheOptions options) override;
|
||||
|
||||
static const QString k_track_input_format;
|
||||
|
||||
protected:
|
||||
virtual void InputConnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual Rational verify_length_internal(Track::Type type) const override;
|
||||
|
||||
signals:
|
||||
void track_added(Track *track);
|
||||
void track_removed(Track *track);
|
||||
|
||||
void subtitles_changed(const TimeRange &range);
|
||||
|
||||
private:
|
||||
QVector<TrackList *> track_lists_;
|
||||
|
||||
QVector<Track *> track_cache_;
|
||||
|
||||
private slots:
|
||||
void update_track_cache();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SEQUENCE_H
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/project/serializer/serializer.cpp
|
||||
node/project/serializer/serializer.h
|
||||
node/project/serializer/serializer190219.cpp
|
||||
node/project/serializer/serializer190219.h
|
||||
node/project/serializer/serializer210528.cpp
|
||||
node/project/serializer/serializer210528.h
|
||||
node/project/serializer/serializer210907.cpp
|
||||
node/project/serializer/serializer210907.h
|
||||
node/project/serializer/serializer211228.cpp
|
||||
node/project/serializer/serializer211228.h
|
||||
node/project/serializer/serializer220403.cpp
|
||||
node/project/serializer/serializer220403.h
|
||||
node/project/serializer/serializer230220.cpp
|
||||
node/project/serializer/serializer230220.h
|
||||
|
||||
|
||||
node/project/serializer/mainwindowlayoutinfo.cpp
|
||||
node/project/serializer/mainwindowlayoutinfo.h
|
||||
|
||||
node/project/serializer/typeserializer.cpp
|
||||
node/project/serializer/typeserializer.h
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mainwindowlayoutinfo.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
void MainWindowLayoutInfo::to_xml(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeAttribute(QStringLiteral("version"),
|
||||
QString::number(k_version));
|
||||
|
||||
writer->writeStartElement(QStringLiteral("folders"));
|
||||
|
||||
foreach (Folder *folder, open_folders_) {
|
||||
writer->writeTextElement(
|
||||
QStringLiteral("folder"),
|
||||
QString::number(reinterpret_cast<quintptr>(folder)));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // folders
|
||||
|
||||
writer->writeStartElement(QStringLiteral("timeline"));
|
||||
|
||||
foreach (Sequence *sequence, open_sequences_) {
|
||||
writer->writeTextElement(
|
||||
QStringLiteral("sequence"),
|
||||
QString::number(reinterpret_cast<quintptr>(sequence)));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // timeline
|
||||
|
||||
writer->writeStartElement(QStringLiteral("viewers"));
|
||||
|
||||
foreach (ViewerOutput *viewer, open_viewers_) {
|
||||
writer->writeTextElement(
|
||||
QStringLiteral("viewer"),
|
||||
QString::number(reinterpret_cast<quintptr>(viewer)));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // viewers
|
||||
|
||||
writer->writeStartElement(QStringLiteral("data"));
|
||||
|
||||
for (auto it = panel_data_.cbegin(); it != panel_data_.cend(); it++) {
|
||||
writer->writeStartElement(QStringLiteral("panel"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("id"), it->first);
|
||||
|
||||
const PanelLayoutInfo &info = it->second;
|
||||
for (auto jt = info.cbegin(); jt != info.cend(); jt++) {
|
||||
writer->writeStartElement(QStringLiteral("option"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("name"), jt->first);
|
||||
|
||||
writer->writeCharacters(jt->second);
|
||||
|
||||
writer->writeEndElement(); // option
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // panel
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // data
|
||||
|
||||
writer->writeTextElement(QStringLiteral("state"),
|
||||
QString(state_.toBase64()));
|
||||
}
|
||||
|
||||
MainWindowLayoutInfo
|
||||
MainWindowLayoutInfo::from_xml(QXmlStreamReader *reader,
|
||||
const QHash<quintptr, Node *> &node_ptrs)
|
||||
{
|
||||
MainWindowLayoutInfo info;
|
||||
|
||||
unsigned int file_version = 0;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("version")) {
|
||||
file_version = attr.value().toUInt();
|
||||
}
|
||||
}
|
||||
|
||||
// Really basic version checking, in the future we may use this to parse multiple versions
|
||||
if (file_version != k_version) {
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("folders")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("folder")) {
|
||||
quintptr item_id = reader->readElementText().toULongLong();
|
||||
|
||||
Folder *open_item =
|
||||
static_cast<Folder *>(node_ptrs.value(item_id));
|
||||
info.open_folders_.push_back(open_item);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("timeline")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("sequence")) {
|
||||
quintptr item_id = reader->readElementText().toULongLong();
|
||||
|
||||
Sequence *open_seq =
|
||||
static_cast<Sequence *>(node_ptrs.value(item_id));
|
||||
info.open_sequences_.push_back(open_seq);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("viewers")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("viewer")) {
|
||||
quintptr item_id = reader->readElementText().toULongLong();
|
||||
|
||||
ViewerOutput *open_viewer =
|
||||
static_cast<ViewerOutput *>(node_ptrs.value(item_id));
|
||||
info.open_viewers_.push_back(open_viewer);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("state")) {
|
||||
info.state_ =
|
||||
QByteArray::fromBase64(reader->readElementText().toLatin1());
|
||||
|
||||
} else if (reader->name() == QStringLiteral("data")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("panel")) {
|
||||
QString id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!id.isEmpty()) {
|
||||
PanelLayoutInfo i;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("option")) {
|
||||
QString name;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("name")) {
|
||||
name = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!name.isEmpty()) {
|
||||
i[name] = reader->readElementText();
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
info.panel_data_[id] = i;
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void MainWindowLayoutInfo::add_folder(olive::Folder *f)
|
||||
{
|
||||
open_folders_.push_back(f);
|
||||
}
|
||||
|
||||
void MainWindowLayoutInfo::add_sequence(Sequence *seq)
|
||||
{
|
||||
open_sequences_.push_back(seq);
|
||||
}
|
||||
|
||||
void MainWindowLayoutInfo::add_viewer(ViewerOutput *viewer)
|
||||
{
|
||||
open_viewers_.push_back(viewer);
|
||||
}
|
||||
|
||||
void MainWindowLayoutInfo::set_panel_data(const QString &id,
|
||||
const PanelLayoutInfo &data)
|
||||
{
|
||||
panel_data_[id] = data;
|
||||
}
|
||||
|
||||
void MainWindowLayoutInfo::move_panel_data(const QString &old,
|
||||
const QString &now)
|
||||
{
|
||||
PanelLayoutInfo tmp = panel_data_.at(old);
|
||||
panel_data_.erase(old);
|
||||
panel_data_[now] = tmp;
|
||||
}
|
||||
|
||||
void MainWindowLayoutInfo::set_state(const QByteArray &layout)
|
||||
{
|
||||
state_ = layout;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OAK_MAINWINDOWLAYOUTINFO_H
|
||||
#define OAK_MAINWINDOWLAYOUTINFO_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "node/project/folder/folder.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Per-panel layout data (key/value pairs)
|
||||
*
|
||||
* Identical to PanelWidget::Info in the UI layer; defined here so the
|
||||
* engine-side layout data structure does not depend on widget headers.
|
||||
*/
|
||||
using PanelLayoutInfo = std::map<QString, QString>;
|
||||
|
||||
class MainWindowLayoutInfo {
|
||||
public:
|
||||
MainWindowLayoutInfo() = default;
|
||||
|
||||
void to_xml(QXmlStreamWriter *writer) const;
|
||||
|
||||
static MainWindowLayoutInfo
|
||||
from_xml(QXmlStreamReader *reader, const QHash<quintptr, Node *> &node_map);
|
||||
|
||||
void add_folder(Folder *f);
|
||||
|
||||
void add_sequence(Sequence *seq);
|
||||
|
||||
void add_viewer(ViewerOutput *viewer);
|
||||
|
||||
void set_panel_data(const QString &id, const PanelLayoutInfo &data);
|
||||
|
||||
void move_panel_data(const QString &old, const QString &now);
|
||||
|
||||
void set_state(const QByteArray &layout);
|
||||
|
||||
const std::vector<Folder *> &open_folders() const
|
||||
{
|
||||
return open_folders_;
|
||||
}
|
||||
|
||||
const std::vector<Sequence *> &open_sequences() const
|
||||
{
|
||||
return open_sequences_;
|
||||
}
|
||||
|
||||
const std::vector<ViewerOutput *> &open_viewers() const
|
||||
{
|
||||
return open_viewers_;
|
||||
}
|
||||
|
||||
const std::map<QString, PanelLayoutInfo> &panel_data() const
|
||||
{
|
||||
return panel_data_;
|
||||
}
|
||||
|
||||
const QByteArray &state() const
|
||||
{
|
||||
return state_;
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray state_;
|
||||
|
||||
std::vector<Folder *> open_folders_;
|
||||
|
||||
std::vector<Sequence *> open_sequences_;
|
||||
|
||||
std::vector<ViewerOutput *> open_viewers_;
|
||||
|
||||
std::map<QString, PanelLayoutInfo> panel_data_;
|
||||
|
||||
static const unsigned int k_version = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::MainWindowLayoutInfo)
|
||||
|
||||
#endif // OAK_MAINWINDOWLAYOUTINFO_H
|
||||
@@ -0,0 +1,338 @@
|
||||
/***
|
||||
|
||||
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 "serializer.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
#include "coreengine.h"
|
||||
#include "node/group/group.h"
|
||||
#include "serializer190219.h"
|
||||
#include "serializer210528.h"
|
||||
#include "serializer210907.h"
|
||||
#include "serializer211228.h"
|
||||
#include "serializer220403.h"
|
||||
#include "serializer230220.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
QVector<ProjectSerializer *> ProjectSerializer::instances;
|
||||
|
||||
void ProjectSerializer::initialize()
|
||||
{
|
||||
// Make sure to order these from oldest to newest
|
||||
|
||||
// FIXME: Implement this - yes it's a 0.1 project loader
|
||||
//instances_.append(new ProjectSerializer190219);
|
||||
|
||||
instances.append(new ProjectSerializer210528);
|
||||
instances.append(new ProjectSerializer210907);
|
||||
instances.append(new ProjectSerializer211228);
|
||||
instances.append(new ProjectSerializer220403);
|
||||
instances.append(new ProjectSerializer230220);
|
||||
}
|
||||
|
||||
void ProjectSerializer::destroy()
|
||||
{
|
||||
qDeleteAll(instances);
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::load(Project *project,
|
||||
const QString &filename,
|
||||
LoadType load_type)
|
||||
{
|
||||
QFile project_file(filename);
|
||||
|
||||
if (project_file.open(QFile::ReadOnly)) {
|
||||
// Some project files are compressed, marked with "OVEC" at the beginning of the file. Check for
|
||||
// that signature now.
|
||||
std::unique_ptr<QXmlStreamReader> reader;
|
||||
if (check_compressed_id(&project_file)) {
|
||||
// File is compressed, decompress into memory
|
||||
QByteArray b;
|
||||
b = qUncompress(project_file.readAll());
|
||||
reader.reset(new QXmlStreamReader(b));
|
||||
} else {
|
||||
project_file.seek(0);
|
||||
reader.reset(new QXmlStreamReader(&project_file));
|
||||
}
|
||||
|
||||
Result inner_result = load(project, reader.get(), load_type);
|
||||
|
||||
project_file.close();
|
||||
|
||||
if (inner_result.code() != k_success) {
|
||||
return inner_result;
|
||||
}
|
||||
|
||||
if (reader->hasError()) {
|
||||
Result r(k_xml_error);
|
||||
r.set_details(reader->errorString());
|
||||
return r;
|
||||
} else {
|
||||
return inner_result;
|
||||
}
|
||||
} else {
|
||||
Result r(k_file_error);
|
||||
r.set_details(QStringLiteral("Unable to open '%1': %2")
|
||||
.arg(filename, project_file.errorString()));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::load(Project *project,
|
||||
QXmlStreamReader *reader,
|
||||
LoadType load_type)
|
||||
{
|
||||
// Determine project version
|
||||
uint version = 0;
|
||||
Result res = k_unknown_version;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("olive") ||
|
||||
reader->name() == QStringLiteral("project")) { // 0.1 projects only
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() ==
|
||||
QStringLiteral("version")) { // 230220+ projects
|
||||
version = attr.value().toUInt();
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("url")) { // 230220+ projects
|
||||
if (project) {
|
||||
project->set_saved_url(attr.value().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("version")) { // projects <= 220403
|
||||
version = reader->readElementText().toUInt();
|
||||
} else if (reader->name() ==
|
||||
QStringLiteral("url")) { // projects <= 220403
|
||||
if (project) {
|
||||
project->set_saved_url(reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
// Handle any other value with the serializer
|
||||
res = load_with_serializer_version(version, project, reader,
|
||||
load_type);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::paste(LoadType load_type,
|
||||
Project *project)
|
||||
{
|
||||
QString clipboard = EngineCore::paste_string_from_clipboard();
|
||||
if (clipboard.isEmpty()) {
|
||||
return k_no_data;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(clipboard);
|
||||
|
||||
return ProjectSerializer::load(project, &reader, load_type);
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::save(const SaveData &data,
|
||||
bool compress)
|
||||
{
|
||||
QString temp_save =
|
||||
FileFunctions::get_safe_temporary_filename(data.get_filename());
|
||||
|
||||
QFile project_file(temp_save);
|
||||
|
||||
if (project_file.open(QFile::WriteOnly)) {
|
||||
QByteArray b;
|
||||
QXmlStreamWriter writer(&b);
|
||||
|
||||
Result inner_result = save(&writer, data);
|
||||
|
||||
if (writer.hasError()) {
|
||||
Result r(k_xml_error);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (compress) {
|
||||
project_file.write("OVEC");
|
||||
project_file.write(qCompress(b));
|
||||
} else {
|
||||
project_file.write(b);
|
||||
}
|
||||
|
||||
project_file.close();
|
||||
|
||||
if (inner_result != k_success) {
|
||||
return inner_result;
|
||||
}
|
||||
|
||||
// Save was successful, we can now rewrite the original file
|
||||
if (FileFunctions::rename_file_allow_overwrite(temp_save,
|
||||
data.get_filename())) {
|
||||
return k_success;
|
||||
} else {
|
||||
Result r(k_overwrite_error);
|
||||
r.set_details(temp_save);
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
Result r(k_file_error);
|
||||
r.set_details(temp_save);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::save(QXmlStreamWriter *writer,
|
||||
const SaveData &data)
|
||||
{
|
||||
writer->setAutoFormatting(true);
|
||||
|
||||
writer->writeStartDocument();
|
||||
|
||||
writer->writeStartElement("olive");
|
||||
|
||||
// By default, save as last serializer which, assuming the instances are ordered correctly,
|
||||
// will be the newest file format. But we may allow saving as older versions later on.
|
||||
ProjectSerializer *serializer = instances.last();
|
||||
|
||||
// Version is stored in YYMMDD from whenever the project format was last changed
|
||||
// Allows easy integer math for checking project versions.
|
||||
writer->writeAttribute(QStringLiteral("version"),
|
||||
QString::number(serializer->version()));
|
||||
|
||||
if (!data.get_filename().isEmpty()) {
|
||||
writer->writeAttribute("url", data.get_filename());
|
||||
}
|
||||
|
||||
serializer->save(writer, data, nullptr);
|
||||
|
||||
writer->writeEndElement(); // olive
|
||||
|
||||
writer->writeEndDocument();
|
||||
|
||||
if (writer->hasError()) {
|
||||
return k_xml_error;
|
||||
}
|
||||
|
||||
return k_success;
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::copy(const SaveData &data)
|
||||
{
|
||||
QString copy_str;
|
||||
QXmlStreamWriter writer(©_str);
|
||||
|
||||
ProjectSerializer::Result res = ProjectSerializer::save(&writer, data);
|
||||
|
||||
if (res == k_success) {
|
||||
EngineCore::copy_string_to_clipboard(copy_str);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ProjectSerializer::check_compressed_id(QFile *file)
|
||||
{
|
||||
QByteArray b = file->read(4);
|
||||
return !memcmp(b.data(), "OVEC", 4);
|
||||
}
|
||||
|
||||
bool ProjectSerializer::is_cancelled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectSerializer::Result
|
||||
ProjectSerializer::load_with_serializer_version(uint version, Project *project,
|
||||
QXmlStreamReader *reader,
|
||||
LoadType load_type)
|
||||
{
|
||||
// Failed to find version in file
|
||||
if (version == 0) {
|
||||
return k_unknown_version;
|
||||
}
|
||||
|
||||
// We should now have the version, if we have a serializer for it, use it to load the project
|
||||
ProjectSerializer *serializer = nullptr;
|
||||
|
||||
foreach (ProjectSerializer *s, instances) {
|
||||
if (version == s->version()) {
|
||||
serializer = s;
|
||||
break;
|
||||
} else if (version < s->version()) {
|
||||
// Assuming the instance list is in order, if the project version is less than any version
|
||||
// we find, we must not support it anymore
|
||||
return k_project_too_old;
|
||||
}
|
||||
}
|
||||
|
||||
if (serializer) {
|
||||
LoadData ld = serializer->load(project, reader, load_type, nullptr);
|
||||
Result r(k_success);
|
||||
if (reader->hasError()) {
|
||||
r = Result(k_xml_error);
|
||||
r.set_details(
|
||||
QCoreApplication::translate("Serializer", "%1 on line %2")
|
||||
.arg(reader->errorString(),
|
||||
QString::number(reader->lineNumber())));
|
||||
}
|
||||
r.set_load_data(ld);
|
||||
return r;
|
||||
} else {
|
||||
// Reached the end of the list with no serializer, assume too new
|
||||
return k_project_too_new;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer::SaveData::set_only_serialize_nodes_and_resolve_groups(
|
||||
QVector<Node *> nodes)
|
||||
{
|
||||
// For any groups, add children
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
// If this is a group, add the child nodes too
|
||||
if (NodeGroup *g = dynamic_cast<NodeGroup *>(nodes.at(i))) {
|
||||
for (auto it = g->get_context_positions().cbegin();
|
||||
it != g->get_context_positions().cend(); it++) {
|
||||
if (!nodes.contains(it.key())) {
|
||||
nodes.append(it.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_only_serialize_nodes(nodes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_PROJECTSERIALIZER_H
|
||||
#define OAK_PROJECTSERIALIZER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "node/project.h"
|
||||
#include "node/project/serializer/mainwindowlayoutinfo.h"
|
||||
#include "typeserializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief An abstract base class for serializing/deserializing project data
|
||||
*
|
||||
* The goal of this is to further abstract serialized project data from their
|
||||
* in-memory representations.
|
||||
*/
|
||||
class ProjectSerializer {
|
||||
public:
|
||||
enum LoadType {
|
||||
k_project,
|
||||
k_only_nodes,
|
||||
k_only_clips,
|
||||
k_only_markers,
|
||||
k_only_keyframes
|
||||
};
|
||||
|
||||
ProjectSerializer() = default;
|
||||
|
||||
virtual ~ProjectSerializer()
|
||||
{
|
||||
}
|
||||
|
||||
DISABLE_COPY_MOVE(ProjectSerializer)
|
||||
|
||||
enum ResultCode {
|
||||
k_success,
|
||||
k_project_too_old,
|
||||
k_project_too_new,
|
||||
k_unknown_version,
|
||||
k_file_error,
|
||||
k_xml_error,
|
||||
k_overwrite_error,
|
||||
k_no_data
|
||||
};
|
||||
|
||||
using SerializedProperties = QHash<Node *, QMap<QString, QString>>;
|
||||
using SerializedKeyframes = QHash<QString, QVector<NodeKeyframe *>>;
|
||||
|
||||
class LoadData {
|
||||
public:
|
||||
LoadData() = default;
|
||||
|
||||
SerializedProperties properties;
|
||||
|
||||
std::vector<TimelineMarker *> markers;
|
||||
|
||||
SerializedKeyframes keyframes;
|
||||
|
||||
MainWindowLayoutInfo layout;
|
||||
|
||||
QVector<Node *> nodes;
|
||||
|
||||
QHash<quintptr, Node *> node_ptrs;
|
||||
|
||||
QHash<Node *, QUuid> node_uuids;
|
||||
|
||||
Node::OutputConnections promised_connections;
|
||||
};
|
||||
|
||||
class Result {
|
||||
public:
|
||||
Result(const ResultCode &code)
|
||||
: code_(code)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const ResultCode &code)
|
||||
{
|
||||
return code_ == code;
|
||||
}
|
||||
bool operator!=(const ResultCode &code)
|
||||
{
|
||||
return code_ != code;
|
||||
}
|
||||
|
||||
const ResultCode &code() const
|
||||
{
|
||||
return code_;
|
||||
}
|
||||
|
||||
const QString &get_details() const
|
||||
{
|
||||
return details_;
|
||||
}
|
||||
|
||||
void set_details(const QString &s)
|
||||
{
|
||||
details_ = s;
|
||||
}
|
||||
|
||||
const LoadData &get_load_data() const
|
||||
{
|
||||
return load_data_;
|
||||
}
|
||||
|
||||
void set_load_data(const LoadData &p)
|
||||
{
|
||||
load_data_ = p;
|
||||
}
|
||||
|
||||
private:
|
||||
ResultCode code_;
|
||||
|
||||
QString details_;
|
||||
|
||||
LoadData load_data_;
|
||||
};
|
||||
|
||||
class SaveData {
|
||||
public:
|
||||
SaveData(LoadType type, Project *project = nullptr,
|
||||
const QString &filename = QString())
|
||||
{
|
||||
type_ = type;
|
||||
project_ = project;
|
||||
filename_ = filename;
|
||||
}
|
||||
|
||||
Project *get_project() const
|
||||
{
|
||||
return project_;
|
||||
}
|
||||
void set_project(Project *p)
|
||||
{
|
||||
project_ = p;
|
||||
}
|
||||
|
||||
const QString &get_filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
void set_filename(const QString &s)
|
||||
{
|
||||
filename_ = s;
|
||||
}
|
||||
|
||||
LoadType type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
const MainWindowLayoutInfo &get_layout() const
|
||||
{
|
||||
return layout_;
|
||||
}
|
||||
void set_layout(const MainWindowLayoutInfo &layout)
|
||||
{
|
||||
layout_ = layout;
|
||||
}
|
||||
|
||||
const QVector<Node *> &get_only_serialize_nodes() const
|
||||
{
|
||||
return only_serialize_nodes_;
|
||||
}
|
||||
void set_only_serialize_nodes(const QVector<Node *> &only)
|
||||
{
|
||||
only_serialize_nodes_ = only;
|
||||
}
|
||||
void set_only_serialize_nodes_and_resolve_groups(QVector<Node *> only);
|
||||
|
||||
const std::vector<TimelineMarker *> &get_only_serialize_markers() const
|
||||
{
|
||||
return only_serialize_markers_;
|
||||
}
|
||||
void set_only_serialize_markers(const std::vector<TimelineMarker *> &only)
|
||||
{
|
||||
only_serialize_markers_ = only;
|
||||
}
|
||||
|
||||
const std::vector<NodeKeyframe *> &get_only_serialize_keyframes() const
|
||||
{
|
||||
return only_serialize_keyframes_;
|
||||
}
|
||||
void set_only_serialize_keyframes(const std::vector<NodeKeyframe *> &only)
|
||||
{
|
||||
only_serialize_keyframes_ = only;
|
||||
}
|
||||
|
||||
const SerializedProperties &get_properties() const
|
||||
{
|
||||
return properties_;
|
||||
}
|
||||
void set_properties(const SerializedProperties &p)
|
||||
{
|
||||
properties_ = p;
|
||||
}
|
||||
|
||||
private:
|
||||
LoadType type_;
|
||||
|
||||
Project *project_;
|
||||
|
||||
QString filename_;
|
||||
|
||||
MainWindowLayoutInfo layout_;
|
||||
|
||||
QVector<Node *> only_serialize_nodes_;
|
||||
|
||||
SerializedProperties properties_;
|
||||
|
||||
std::vector<TimelineMarker *> only_serialize_markers_;
|
||||
|
||||
std::vector<NodeKeyframe *> only_serialize_keyframes_;
|
||||
};
|
||||
|
||||
static void initialize();
|
||||
|
||||
static void destroy();
|
||||
|
||||
static Result load(Project *project, const QString &filename,
|
||||
LoadType load_type);
|
||||
static Result load(Project *project, QXmlStreamReader *read_device,
|
||||
LoadType load_type);
|
||||
static Result paste(LoadType load_type, Project *project = nullptr);
|
||||
|
||||
static Result save(const SaveData &data, bool compress);
|
||||
static Result save(QXmlStreamWriter *write_device, const SaveData &data);
|
||||
static Result copy(const SaveData &data);
|
||||
|
||||
static bool check_compressed_id(QFile *file);
|
||||
|
||||
protected:
|
||||
virtual LoadData load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const = 0;
|
||||
|
||||
virtual void save(QXmlStreamWriter *writer, const SaveData &data,
|
||||
void *reserved) const
|
||||
{
|
||||
}
|
||||
|
||||
virtual uint version() const = 0;
|
||||
|
||||
bool is_cancelled() const;
|
||||
|
||||
private:
|
||||
static Result load_with_serializer_version(uint version, Project *project,
|
||||
QXmlStreamReader *reader,
|
||||
LoadType load_type);
|
||||
|
||||
static QVector<ProjectSerializer *> instances;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_PROJECTSERIALIZER_H
|
||||
@@ -0,0 +1,34 @@
|
||||
/***
|
||||
|
||||
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 "serializer190219.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ProjectSerializer::LoadData
|
||||
ProjectSerializer190219::load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const
|
||||
{
|
||||
return LoadData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_PROJECTSERIALIZER190219_H
|
||||
#define OAK_PROJECTSERIALIZER190219_H
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ProjectSerializer190219 : public ProjectSerializer {
|
||||
public:
|
||||
ProjectSerializer190219() = default;
|
||||
|
||||
protected:
|
||||
virtual LoadData load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const override;
|
||||
|
||||
virtual uint version() const override
|
||||
{
|
||||
return 190219;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SERIALIZER190219_H
|
||||
@@ -0,0 +1,857 @@
|
||||
/***
|
||||
|
||||
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 "serializer210528.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
#include "node/group/group.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ProjectSerializer210528::LoadData
|
||||
ProjectSerializer210528::load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const
|
||||
{
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("uuid")) {
|
||||
project->set_uuid(QUuid::fromString(reader->readElementText()));
|
||||
|
||||
} else if (reader->name() == QStringLiteral("nodes")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
bool is_root = false;
|
||||
bool is_cm = false;
|
||||
bool is_settings = false;
|
||||
QString id;
|
||||
|
||||
{
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("root") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_root = true;
|
||||
} else if (attr.name() == QStringLiteral("cm") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_cm = true;
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("settings") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_settings = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id.isEmpty()) {
|
||||
qWarning() << "Failed to load node with empty ID";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
Node *node;
|
||||
bool handled_elsewhere = false;
|
||||
|
||||
if (is_root) {
|
||||
project->initialize();
|
||||
node = project->root();
|
||||
} else if (is_cm) {
|
||||
load_color_manager(reader, project);
|
||||
handled_elsewhere = true;
|
||||
} else if (is_settings) {
|
||||
load_project_settings(reader, project);
|
||||
handled_elsewhere = true;
|
||||
} else {
|
||||
node = NodeFactory::create_from_id(id);
|
||||
}
|
||||
|
||||
if (!handled_elsewhere) {
|
||||
if (!node) {
|
||||
qWarning()
|
||||
<< "Failed to find node with ID" << id;
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
load_node(node, xml_node_data, reader);
|
||||
node->setParent(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("positions")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("context")) {
|
||||
quintptr context_ptr = 0;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
context_ptr = attr.value().toULongLong();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node *context = xml_node_data.node_ptrs.value(context_ptr);
|
||||
|
||||
if (!context) {
|
||||
qWarning() << "Failed to find pointer for context";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
quintptr node_ptr;
|
||||
Node::Position node_pos;
|
||||
|
||||
if (load_position(reader, &node_ptr,
|
||||
&node_pos)) {
|
||||
Node *node =
|
||||
xml_node_data.node_ptrs.value(node_ptr);
|
||||
|
||||
if (node) {
|
||||
context->set_node_position_in_context(
|
||||
node, node_pos);
|
||||
} else {
|
||||
qWarning()
|
||||
<< "Failed to find pointer for node position";
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Skip this
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Make connections
|
||||
post_connect(xml_node_data);
|
||||
|
||||
// Resolve tracks
|
||||
for (Node *n : project->nodes()) {
|
||||
n->set_caches_enabled(true);
|
||||
|
||||
if (Track *t = dynamic_cast<Track *>(n)) {
|
||||
for (int i = 0; i < t->input_array_size(Track::k_block_input); i++) {
|
||||
Block *b = static_cast<Block *>(
|
||||
t->get_connected_output(Track::k_block_input, i));
|
||||
if (!b->track()) {
|
||||
t->append_block(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LoadData();
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_node(Node *node, XMLNodeData &xml_node_data,
|
||||
QXmlStreamReader *reader) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
load_input(node, reader, xml_node_data);
|
||||
} else if (reader->name() == QStringLiteral("ptr")) {
|
||||
xml_node_data.node_ptrs.insert(
|
||||
reader->readElementText().toULongLong(), node);
|
||||
} else if (reader->name() == QStringLiteral("label")) {
|
||||
node->set_label(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("color")) {
|
||||
node->set_override_color(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("links")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("link")) {
|
||||
xml_node_data.block_links.append(
|
||||
{ node, reader->readElementText().toULongLong() });
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("custom")) {
|
||||
load_node_custom(reader, node, xml_node_data);
|
||||
|
||||
} else if (reader->name() == QStringLiteral("connections")) {
|
||||
// Load connections
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("connection")) {
|
||||
QString param_id;
|
||||
int ele = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("element")) {
|
||||
ele = attr.value().toInt();
|
||||
} else if (attr.name() == QStringLiteral("input")) {
|
||||
param_id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
QString output_node_id;
|
||||
QString output_param_id;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
output_node_id = reader->readElementText();
|
||||
} else if (reader->name() == QStringLiteral("output")) {
|
||||
output_param_id = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
xml_node_data.desired_connections.append(
|
||||
{ NodeInput(node, param_id, ele),
|
||||
output_node_id.toULongLong(), output_param_id });
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("hints")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("hint")) {
|
||||
QString input;
|
||||
int element = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
input = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("element")) {
|
||||
element = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
Node::ValueHint vh;
|
||||
load_value_hint(&vh, reader);
|
||||
node->set_value_hint_for_input(input, vh, element);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
node->LoadFinishedEvent();
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_color_manager(QXmlStreamReader *reader,
|
||||
Project *project) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
QString id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("config") ||
|
||||
id == QStringLiteral("default_input") ||
|
||||
id == QStringLiteral("reference_space")) {
|
||||
QString value;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("track")) {
|
||||
value = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("default_input")) {
|
||||
// Default color space
|
||||
// NOTE: Stupidly, we saved these as integers which means we can't add anything to the OCIO
|
||||
// config. So we must convert back to string here.
|
||||
static const QStringList list = {
|
||||
QStringLiteral("Linear"),
|
||||
QStringLiteral("CIE-XYZ D65"),
|
||||
QStringLiteral("Filmic Log Encoding"),
|
||||
QStringLiteral("sRGB OETF"),
|
||||
QStringLiteral("Apple DCI-P3 D65"),
|
||||
QStringLiteral("AppleP3 sRGB OETF"),
|
||||
QStringLiteral("BT.1886 EOTF"),
|
||||
QStringLiteral("AppleP3 Filmic Log Encoding"),
|
||||
QStringLiteral("BT.1886 Filmic Log Encoding"),
|
||||
QStringLiteral("Fuji F-Log OETF"),
|
||||
QStringLiteral("Fuji F-Log F-Gamut"),
|
||||
QStringLiteral("Panasonic V-Log V-Gamut"),
|
||||
QStringLiteral("Arri Wide Gamut / LogC EI 800"),
|
||||
QStringLiteral("Arri Wide Gamut / LogC EI 400"),
|
||||
QStringLiteral("Blackmagic Film Wide Gamut (Gen 5)"),
|
||||
QStringLiteral("Rec.709 OETF"),
|
||||
QStringLiteral("Non-Colour Data")
|
||||
};
|
||||
int num_value = value.toInt();
|
||||
value = list.at(num_value);
|
||||
project->set_default_input_color_space(value);
|
||||
} else if (id == QStringLiteral("reference_space")) {
|
||||
// Reference space
|
||||
if (value == QStringLiteral("1")) {
|
||||
value = ocio::ROLE_COMPOSITING_LOG;
|
||||
} else {
|
||||
value = ocio::ROLE_SCENE_LINEAR;
|
||||
}
|
||||
project->set_color_reference_space(value);
|
||||
} else {
|
||||
// Config filename
|
||||
project->set_color_config_filename(value);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_project_settings(QXmlStreamReader *reader,
|
||||
Project *project) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
QString id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("cache_setting") ||
|
||||
id == QStringLiteral("cache_path")) {
|
||||
QString value;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("track")) {
|
||||
value = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("cache_setting")) {
|
||||
project->set_cache_location_setting(
|
||||
static_cast<Project::CacheSetting>(value.toInt()));
|
||||
} else {
|
||||
project->set_custom_cache_path(value);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_input(Node *node, QXmlStreamReader *reader,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
QString param_id;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
param_id = attr.value().toString();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param_id.isEmpty()) {
|
||||
qWarning() << "Failed to load parameter with missing ID";
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->has_input_with_id(param_id)) {
|
||||
qWarning() << "Failed to load parameter that didn't exist:" << param_id;
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
// Load primary immediate
|
||||
load_immediate(reader, node, param_id, -1, xml_node_data);
|
||||
} else if (reader->name() == QStringLiteral("subelements")) {
|
||||
// Load subelements
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("count")) {
|
||||
node->input_array_resize(param_id, attr.value().toInt());
|
||||
}
|
||||
}
|
||||
|
||||
int element_counter = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("element")) {
|
||||
load_immediate(reader, node, param_id, element_counter,
|
||||
xml_node_data);
|
||||
|
||||
element_counter++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_immediate(QXmlStreamReader *reader,
|
||||
Node *node, const QString &input,
|
||||
int element,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
Q_UNUSED(xml_node_data)
|
||||
|
||||
NodeValue::Type data_type = node->get_input_data_type(input);
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
// Load standard value
|
||||
int val_index = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
QVariant value_on_track;
|
||||
|
||||
if (data_type == NodeValue::k_video_params) {
|
||||
VideoParams vp;
|
||||
vp.load(reader);
|
||||
value_on_track = QVariant::fromValue(vp);
|
||||
} else if (data_type == NodeValue::k_audio_params) {
|
||||
AudioParams ap =
|
||||
TypeSerializer::load_audio_params(reader);
|
||||
value_on_track = QVariant::fromValue(ap);
|
||||
} else {
|
||||
QString value_text = reader->readElementText();
|
||||
|
||||
if (!value_text.isEmpty()) {
|
||||
value_on_track = NodeValue::string_to_value(
|
||||
data_type, value_text, true);
|
||||
}
|
||||
}
|
||||
|
||||
node->set_split_standard_value_on_track(input, val_index,
|
||||
value_on_track, element);
|
||||
|
||||
val_index++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("keyframing") &&
|
||||
node->is_input_keyframable(input)) {
|
||||
node->set_input_is_keyframing(input, reader->readElementText().toInt(),
|
||||
element);
|
||||
} else if (reader->name() == QStringLiteral("keyframes")) {
|
||||
int track = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("key")) {
|
||||
QString key_input;
|
||||
Rational key_time;
|
||||
NodeKeyframe::Type key_type = NodeKeyframe::k_linear;
|
||||
QVariant key_value;
|
||||
QPointF key_in_handle;
|
||||
QPointF key_out_handle;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
key_input = attr.value().toString();
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("time")) {
|
||||
key_time = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("type")) {
|
||||
key_type = static_cast<NodeKeyframe::Type>(
|
||||
attr.value().toInt());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("inhandlex")) {
|
||||
key_in_handle.setX(attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("inhandley")) {
|
||||
key_in_handle.setY(attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("outhandlex")) {
|
||||
key_out_handle.setX(
|
||||
attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("outhandley")) {
|
||||
key_out_handle.setY(
|
||||
attr.value().toDouble());
|
||||
}
|
||||
}
|
||||
|
||||
key_value = NodeValue::string_to_value(
|
||||
data_type, reader->readElementText(), true);
|
||||
|
||||
NodeKeyframe *key = new NodeKeyframe(
|
||||
key_time, key_value, key_type, track, element,
|
||||
key_input, node);
|
||||
key->set_bezier_control_in(key_in_handle);
|
||||
key->set_bezier_control_out(key_out_handle);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
track++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("csinput")) {
|
||||
node->set_input_property(input, QStringLiteral("col_input"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csdisplay")) {
|
||||
node->set_input_property(input, QStringLiteral("col_display"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csview")) {
|
||||
node->set_input_property(input, QStringLiteral("col_view"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("cslook")) {
|
||||
node->set_input_property(input, QStringLiteral("col_look"),
|
||||
reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectSerializer210528::load_position(QXmlStreamReader *reader,
|
||||
quintptr *node_ptr,
|
||||
Node::Position *pos) const
|
||||
{
|
||||
bool got_node_ptr = false;
|
||||
bool got_pos_x = false;
|
||||
bool got_pos_y = false;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
*node_ptr = attr.value().toULongLong();
|
||||
got_node_ptr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("x")) {
|
||||
pos->position.setX(reader->readElementText().toDouble());
|
||||
got_pos_x = true;
|
||||
} else if (reader->name() == QStringLiteral("y")) {
|
||||
pos->position.setY(reader->readElementText().toDouble());
|
||||
got_pos_y = true;
|
||||
} else if (reader->name() == QStringLiteral("expanded")) {
|
||||
pos->expanded = reader->readElementText().toInt();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return got_node_ptr && got_pos_x && got_pos_y;
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::post_connect(const XMLNodeData &xml_node_data) const
|
||||
{
|
||||
foreach (const XMLNodeData::SerializedConnection &con,
|
||||
xml_node_data.desired_connections) {
|
||||
if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) {
|
||||
// Use output param as hint tag since we grandfathered those in
|
||||
Node::ValueHint hint(con.output_param);
|
||||
|
||||
Node::connect_edge(out, con.input);
|
||||
|
||||
con.input.node()->set_value_hint_for_input(con.input.input(), hint,
|
||||
con.input.element());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const XMLNodeData::BlockLink &l, xml_node_data.block_links) {
|
||||
Node *a = l.block;
|
||||
Node *b = xml_node_data.node_ptrs.value(l.link);
|
||||
|
||||
Node::link(a, b);
|
||||
}
|
||||
|
||||
foreach (const XMLNodeData::GroupLink &l, xml_node_data.group_input_links) {
|
||||
if (Node *input_node = xml_node_data.node_ptrs.value(l.input_node)) {
|
||||
NodeInput resolved(input_node, l.input_id, l.input_element);
|
||||
|
||||
l.group->add_input_passthrough(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = xml_node_data.group_output_links.cbegin();
|
||||
it != xml_node_data.group_output_links.cend(); it++) {
|
||||
if (Node *output_node = xml_node_data.node_ptrs.value(it.value())) {
|
||||
it.key()->set_output_passthrough(output_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_node_custom(QXmlStreamReader *reader,
|
||||
Node *node,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
// Viewer-based nodes
|
||||
if (ViewerOutput *viewer = dynamic_cast<ViewerOutput *>(node)) {
|
||||
Footage *footage = dynamic_cast<Footage *>(node);
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("points")) {
|
||||
load_timeline_points(reader, viewer);
|
||||
} else if (reader->name() == QStringLiteral("timestamp") &&
|
||||
footage) {
|
||||
footage->set_timestamp(reader->readElementText().toLongLong());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (Track *track = dynamic_cast<Track *>(node)) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("height")) {
|
||||
track->set_track_height(reader->readElementText().toDouble());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (NodeGroup *group = dynamic_cast<NodeGroup *>(node)) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("inputpassthroughs")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("inputpassthrough")) {
|
||||
XMLNodeData::GroupLink link;
|
||||
|
||||
link.group = group;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
link.input_node =
|
||||
reader->readElementText().toULongLong();
|
||||
} else if (reader->name() ==
|
||||
QStringLiteral("input")) {
|
||||
link.input_id = reader->readElementText();
|
||||
} else if (reader->name() ==
|
||||
QStringLiteral("element")) {
|
||||
link.input_element =
|
||||
reader->readElementText().toInt();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
xml_node_data.group_input_links.append(link);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("outputpassthrough")) {
|
||||
xml_node_data.group_output_links.insert(
|
||||
group, reader->readElementText().toULongLong());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_timeline_points(QXmlStreamReader *reader,
|
||||
ViewerOutput *points) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("markers")) {
|
||||
load_marker_list(reader, points->get_markers());
|
||||
} else if (reader->name() == QStringLiteral("workarea")) {
|
||||
load_work_area(reader, points->get_work_area());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_work_area(QXmlStreamReader *reader,
|
||||
TimelineWorkArea *workarea) const
|
||||
{
|
||||
Rational range_in = workarea->in();
|
||||
Rational range_out = workarea->out();
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("enabled")) {
|
||||
workarea->set_enabled(attr.value() != QStringLiteral("0"));
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
range_in =
|
||||
Rational::from_string(attr.value().toString().toStdString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
range_out =
|
||||
Rational::from_string(attr.value().toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange loaded_workarea(range_in, range_out);
|
||||
|
||||
if (loaded_workarea != workarea->range()) {
|
||||
workarea->set_range(loaded_workarea);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_marker_list(QXmlStreamReader *reader,
|
||||
TimelineMarkerList *markers) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("marker")) {
|
||||
QString name;
|
||||
Rational in, out;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("name")) {
|
||||
name = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
in = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
out = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
new TimelineMarker(OAK_CONFIG("MarkerColor").toInt(),
|
||||
TimeRange(in, out), name, markers);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::load_value_hint(Node::ValueHint *hint,
|
||||
QXmlStreamReader *reader) const
|
||||
{
|
||||
QVector<NodeValue::Type> types;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("types")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("type")) {
|
||||
types.append(static_cast<NodeValue::Type>(
|
||||
reader->readElementText().toInt()));
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("index")) {
|
||||
hint->set_index(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("tag")) {
|
||||
hint->set_tag(reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
hint->set_type(types);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SERIALIZER210528_H
|
||||
#define OAK_SERIALIZER210528_H
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ProjectSerializer210528 : public ProjectSerializer {
|
||||
public:
|
||||
ProjectSerializer210528() = default;
|
||||
|
||||
protected:
|
||||
virtual LoadData load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const override;
|
||||
|
||||
virtual uint version() const override
|
||||
{
|
||||
return 210528;
|
||||
}
|
||||
|
||||
private:
|
||||
struct XMLNodeData {
|
||||
struct SerializedConnection {
|
||||
NodeInput input;
|
||||
quintptr output_node;
|
||||
QString output_param;
|
||||
};
|
||||
|
||||
struct BlockLink {
|
||||
Node *block;
|
||||
quintptr link;
|
||||
};
|
||||
|
||||
struct GroupLink {
|
||||
NodeGroup *group;
|
||||
quintptr input_node;
|
||||
QString input_id;
|
||||
int input_element;
|
||||
};
|
||||
|
||||
QHash<quintptr, Node *> node_ptrs;
|
||||
QList<SerializedConnection> desired_connections;
|
||||
QList<BlockLink> block_links;
|
||||
QVector<GroupLink> group_input_links;
|
||||
QHash<NodeGroup *, quintptr> group_output_links;
|
||||
};
|
||||
|
||||
void load_node(Node *node, XMLNodeData &xml_node_data,
|
||||
QXmlStreamReader *reader) const;
|
||||
|
||||
void load_color_manager(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_project_settings(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_input(Node *node, QXmlStreamReader *reader,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_immediate(QXmlStreamReader *reader, Node *node,
|
||||
const QString &input, int element,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
bool load_position(QXmlStreamReader *reader, quintptr *node_ptr,
|
||||
Node::Position *pos) const;
|
||||
|
||||
void post_connect(const XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_node_custom(QXmlStreamReader *reader, Node *node,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_timeline_points(QXmlStreamReader *reader,
|
||||
ViewerOutput *points) const;
|
||||
|
||||
void load_work_area(QXmlStreamReader *reader,
|
||||
TimelineWorkArea *workarea) const;
|
||||
|
||||
void load_marker_list(QXmlStreamReader *reader,
|
||||
TimelineMarkerList *markers) const;
|
||||
|
||||
void load_value_hint(Node::ValueHint *hint, QXmlStreamReader *reader) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SERIALIZER211228_H
|
||||
@@ -0,0 +1,848 @@
|
||||
/***
|
||||
|
||||
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 "serializer210907.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
#include "node/group/group.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ProjectSerializer210907::LoadData
|
||||
ProjectSerializer210907::load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const
|
||||
{
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("uuid")) {
|
||||
project->set_uuid(QUuid::fromString(reader->readElementText()));
|
||||
|
||||
} else if (reader->name() == QStringLiteral("nodes")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
bool is_root = false;
|
||||
bool is_cm = false;
|
||||
bool is_settings = false;
|
||||
QString id;
|
||||
|
||||
{
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("root") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_root = true;
|
||||
} else if (attr.name() == QStringLiteral("cm") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_cm = true;
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("settings") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_settings = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id.isEmpty()) {
|
||||
qWarning() << "Failed to load node with empty ID";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
Node *node;
|
||||
bool handled_elsewhere = false;
|
||||
|
||||
if (is_root) {
|
||||
project->initialize();
|
||||
node = project->root();
|
||||
} else if (is_cm) {
|
||||
load_color_manager(reader, project);
|
||||
handled_elsewhere = true;
|
||||
} else if (is_settings) {
|
||||
load_project_settings(reader, project);
|
||||
handled_elsewhere = true;
|
||||
} else {
|
||||
node = NodeFactory::create_from_id(id);
|
||||
}
|
||||
|
||||
if (!handled_elsewhere) {
|
||||
if (!node) {
|
||||
qWarning()
|
||||
<< "Failed to find node with ID" << id;
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
load_node(node, xml_node_data, reader);
|
||||
node->setParent(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("positions")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("context")) {
|
||||
quintptr context_ptr = 0;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
context_ptr = attr.value().toULongLong();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node *context = xml_node_data.node_ptrs.value(context_ptr);
|
||||
|
||||
if (!context) {
|
||||
qWarning() << "Failed to find pointer for context";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
quintptr node_ptr;
|
||||
Node::Position node_pos;
|
||||
|
||||
if (load_position(reader, &node_ptr,
|
||||
&node_pos)) {
|
||||
Node *node =
|
||||
xml_node_data.node_ptrs.value(node_ptr);
|
||||
|
||||
if (node) {
|
||||
context->set_node_position_in_context(
|
||||
node, node_pos);
|
||||
} else {
|
||||
qWarning()
|
||||
<< "Failed to find pointer for node position";
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Skip this
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Make connections
|
||||
post_connect(xml_node_data);
|
||||
|
||||
// Resolve tracks
|
||||
for (Node *n : project->nodes()) {
|
||||
n->set_caches_enabled(true);
|
||||
|
||||
if (Track *t = dynamic_cast<Track *>(n)) {
|
||||
for (int i = 0; i < t->input_array_size(Track::k_block_input); i++) {
|
||||
Block *b = static_cast<Block *>(
|
||||
t->get_connected_output(Track::k_block_input, i));
|
||||
if (!b->track()) {
|
||||
t->append_block(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LoadData();
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_node(Node *node, XMLNodeData &xml_node_data,
|
||||
QXmlStreamReader *reader) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
load_input(node, reader, xml_node_data);
|
||||
} else if (reader->name() == QStringLiteral("ptr")) {
|
||||
xml_node_data.node_ptrs.insert(
|
||||
reader->readElementText().toULongLong(), node);
|
||||
} else if (reader->name() == QStringLiteral("label")) {
|
||||
node->set_label(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("color")) {
|
||||
node->set_override_color(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("links")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("link")) {
|
||||
xml_node_data.block_links.append(
|
||||
{ node, reader->readElementText().toULongLong() });
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("custom")) {
|
||||
load_node_custom(reader, node, xml_node_data);
|
||||
|
||||
} else if (reader->name() == QStringLiteral("connections")) {
|
||||
// Load connections
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("connection")) {
|
||||
QString param_id;
|
||||
int ele = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("element")) {
|
||||
ele = attr.value().toInt();
|
||||
} else if (attr.name() == QStringLiteral("input")) {
|
||||
param_id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
QString output_node_id;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("output")) {
|
||||
output_node_id = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
xml_node_data.desired_connections.append(
|
||||
{ NodeInput(node, param_id, ele),
|
||||
output_node_id.toULongLong() });
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("hints")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("hint")) {
|
||||
QString input;
|
||||
int element = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
input = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("element")) {
|
||||
element = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
Node::ValueHint vh;
|
||||
load_value_hint(&vh, reader);
|
||||
node->set_value_hint_for_input(input, vh, element);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
node->LoadFinishedEvent();
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_color_manager(QXmlStreamReader *reader,
|
||||
Project *project) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
QString id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("config") ||
|
||||
id == QStringLiteral("default_input") ||
|
||||
id == QStringLiteral("reference_space")) {
|
||||
QString value;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("track")) {
|
||||
value = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("default_input")) {
|
||||
// Default color space
|
||||
// NOTE: Stupidly, we saved these as integers which means we can't add anything to the OCIO
|
||||
// config. So we must convert back to string here.
|
||||
static const QStringList list = {
|
||||
QStringLiteral("Linear"),
|
||||
QStringLiteral("CIE-XYZ D65"),
|
||||
QStringLiteral("Filmic Log Encoding"),
|
||||
QStringLiteral("sRGB OETF"),
|
||||
QStringLiteral("Apple DCI-P3 D65"),
|
||||
QStringLiteral("AppleP3 sRGB OETF"),
|
||||
QStringLiteral("BT.1886 EOTF"),
|
||||
QStringLiteral("AppleP3 Filmic Log Encoding"),
|
||||
QStringLiteral("BT.1886 Filmic Log Encoding"),
|
||||
QStringLiteral("Fuji F-Log OETF"),
|
||||
QStringLiteral("Fuji F-Log F-Gamut"),
|
||||
QStringLiteral("Panasonic V-Log V-Gamut"),
|
||||
QStringLiteral("Arri Wide Gamut / LogC EI 800"),
|
||||
QStringLiteral("Arri Wide Gamut / LogC EI 400"),
|
||||
QStringLiteral("Blackmagic Film Wide Gamut (Gen 5)"),
|
||||
QStringLiteral("Rec.709 OETF"),
|
||||
QStringLiteral("Non-Colour Data")
|
||||
};
|
||||
int num_value = value.toInt();
|
||||
value = list.at(num_value);
|
||||
project->set_default_input_color_space(value);
|
||||
} else if (id == QStringLiteral("reference_space")) {
|
||||
// Reference space
|
||||
if (value == QStringLiteral("1")) {
|
||||
value = ocio::ROLE_COMPOSITING_LOG;
|
||||
} else {
|
||||
value = ocio::ROLE_SCENE_LINEAR;
|
||||
}
|
||||
project->set_color_reference_space(value);
|
||||
} else {
|
||||
// Config filename
|
||||
project->set_color_config_filename(value);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_project_settings(QXmlStreamReader *reader,
|
||||
Project *project) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
QString id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("cache_setting") ||
|
||||
id == QStringLiteral("cache_path")) {
|
||||
QString value;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("track")) {
|
||||
value = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("cache_setting")) {
|
||||
project->set_cache_location_setting(
|
||||
static_cast<Project::CacheSetting>(value.toInt()));
|
||||
} else {
|
||||
project->set_custom_cache_path(value);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_input(Node *node, QXmlStreamReader *reader,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
QString param_id;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
param_id = attr.value().toString();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param_id.isEmpty()) {
|
||||
qWarning() << "Failed to load parameter with missing ID";
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->has_input_with_id(param_id)) {
|
||||
qWarning() << "Failed to load parameter that didn't exist:" << param_id;
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
// Load primary immediate
|
||||
load_immediate(reader, node, param_id, -1, xml_node_data);
|
||||
} else if (reader->name() == QStringLiteral("subelements")) {
|
||||
// Load subelements
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("count")) {
|
||||
node->input_array_resize(param_id, attr.value().toInt());
|
||||
}
|
||||
}
|
||||
|
||||
int element_counter = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("element")) {
|
||||
load_immediate(reader, node, param_id, element_counter,
|
||||
xml_node_data);
|
||||
|
||||
element_counter++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_immediate(QXmlStreamReader *reader,
|
||||
Node *node, const QString &input,
|
||||
int element,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
Q_UNUSED(xml_node_data)
|
||||
|
||||
NodeValue::Type data_type = node->get_input_data_type(input);
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
// Load standard value
|
||||
int val_index = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
QVariant value_on_track;
|
||||
|
||||
if (data_type == NodeValue::k_video_params) {
|
||||
VideoParams vp;
|
||||
vp.load(reader);
|
||||
value_on_track = QVariant::fromValue(vp);
|
||||
} else if (data_type == NodeValue::k_audio_params) {
|
||||
AudioParams ap =
|
||||
TypeSerializer::load_audio_params(reader);
|
||||
value_on_track = QVariant::fromValue(ap);
|
||||
} else {
|
||||
QString value_text = reader->readElementText();
|
||||
|
||||
if (!value_text.isEmpty()) {
|
||||
value_on_track = NodeValue::string_to_value(
|
||||
data_type, value_text, true);
|
||||
}
|
||||
}
|
||||
|
||||
node->set_split_standard_value_on_track(input, val_index,
|
||||
value_on_track, element);
|
||||
|
||||
val_index++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("keyframing") &&
|
||||
node->is_input_keyframable(input)) {
|
||||
node->set_input_is_keyframing(input, reader->readElementText().toInt(),
|
||||
element);
|
||||
} else if (reader->name() == QStringLiteral("keyframes")) {
|
||||
int track = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("key")) {
|
||||
QString key_input;
|
||||
Rational key_time;
|
||||
NodeKeyframe::Type key_type = NodeKeyframe::k_linear;
|
||||
QVariant key_value;
|
||||
QPointF key_in_handle;
|
||||
QPointF key_out_handle;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
key_input = attr.value().toString();
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("time")) {
|
||||
key_time = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("type")) {
|
||||
key_type = static_cast<NodeKeyframe::Type>(
|
||||
attr.value().toInt());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("inhandlex")) {
|
||||
key_in_handle.setX(attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("inhandley")) {
|
||||
key_in_handle.setY(attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("outhandlex")) {
|
||||
key_out_handle.setX(
|
||||
attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("outhandley")) {
|
||||
key_out_handle.setY(
|
||||
attr.value().toDouble());
|
||||
}
|
||||
}
|
||||
|
||||
key_value = NodeValue::string_to_value(
|
||||
data_type, reader->readElementText(), true);
|
||||
|
||||
NodeKeyframe *key = new NodeKeyframe(
|
||||
key_time, key_value, key_type, track, element,
|
||||
key_input, node);
|
||||
key->set_bezier_control_in(key_in_handle);
|
||||
key->set_bezier_control_out(key_out_handle);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
track++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("csinput")) {
|
||||
node->set_input_property(input, QStringLiteral("col_input"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csdisplay")) {
|
||||
node->set_input_property(input, QStringLiteral("col_display"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csview")) {
|
||||
node->set_input_property(input, QStringLiteral("col_view"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("cslook")) {
|
||||
node->set_input_property(input, QStringLiteral("col_look"),
|
||||
reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectSerializer210907::load_position(QXmlStreamReader *reader,
|
||||
quintptr *node_ptr,
|
||||
Node::Position *pos) const
|
||||
{
|
||||
bool got_node_ptr = false;
|
||||
bool got_pos_x = false;
|
||||
bool got_pos_y = false;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
*node_ptr = attr.value().toULongLong();
|
||||
got_node_ptr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("x")) {
|
||||
pos->position.setX(reader->readElementText().toDouble());
|
||||
got_pos_x = true;
|
||||
} else if (reader->name() == QStringLiteral("y")) {
|
||||
pos->position.setY(reader->readElementText().toDouble());
|
||||
got_pos_y = true;
|
||||
} else if (reader->name() == QStringLiteral("expanded")) {
|
||||
pos->expanded = reader->readElementText().toInt();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return got_node_ptr && got_pos_x && got_pos_y;
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::post_connect(const XMLNodeData &xml_node_data) const
|
||||
{
|
||||
foreach (const XMLNodeData::SerializedConnection &con,
|
||||
xml_node_data.desired_connections) {
|
||||
if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) {
|
||||
Node::connect_edge(out, con.input);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const XMLNodeData::BlockLink &l, xml_node_data.block_links) {
|
||||
Node *a = l.block;
|
||||
Node *b = xml_node_data.node_ptrs.value(l.link);
|
||||
|
||||
Node::link(a, b);
|
||||
}
|
||||
|
||||
foreach (const XMLNodeData::GroupLink &l, xml_node_data.group_input_links) {
|
||||
if (Node *input_node = xml_node_data.node_ptrs.value(l.input_node)) {
|
||||
NodeInput resolved(input_node, l.input_id, l.input_element);
|
||||
|
||||
l.group->add_input_passthrough(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = xml_node_data.group_output_links.cbegin();
|
||||
it != xml_node_data.group_output_links.cend(); it++) {
|
||||
if (Node *output_node = xml_node_data.node_ptrs.value(it.value())) {
|
||||
it.key()->set_output_passthrough(output_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_node_custom(QXmlStreamReader *reader,
|
||||
Node *node,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
// Viewer-based nodes
|
||||
if (ViewerOutput *viewer = dynamic_cast<ViewerOutput *>(node)) {
|
||||
Footage *footage = dynamic_cast<Footage *>(node);
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("points")) {
|
||||
load_timeline_points(reader, viewer);
|
||||
} else if (reader->name() == QStringLiteral("timestamp") &&
|
||||
footage) {
|
||||
footage->set_timestamp(reader->readElementText().toLongLong());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (Track *track = dynamic_cast<Track *>(node)) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("height")) {
|
||||
track->set_track_height(reader->readElementText().toDouble());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (NodeGroup *group = dynamic_cast<NodeGroup *>(node)) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("inputpassthroughs")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("inputpassthrough")) {
|
||||
XMLNodeData::GroupLink link;
|
||||
|
||||
link.group = group;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
link.input_node =
|
||||
reader->readElementText().toULongLong();
|
||||
} else if (reader->name() ==
|
||||
QStringLiteral("input")) {
|
||||
link.input_id = reader->readElementText();
|
||||
} else if (reader->name() ==
|
||||
QStringLiteral("element")) {
|
||||
link.input_element =
|
||||
reader->readElementText().toInt();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
xml_node_data.group_input_links.append(link);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("outputpassthrough")) {
|
||||
xml_node_data.group_output_links.insert(
|
||||
group, reader->readElementText().toULongLong());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_timeline_points(QXmlStreamReader *reader,
|
||||
ViewerOutput *points) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("markers")) {
|
||||
load_marker_list(reader, points->get_markers());
|
||||
} else if (reader->name() == QStringLiteral("workarea")) {
|
||||
load_work_area(reader, points->get_work_area());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_work_area(QXmlStreamReader *reader,
|
||||
TimelineWorkArea *workarea) const
|
||||
{
|
||||
Rational range_in = workarea->in();
|
||||
Rational range_out = workarea->out();
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("enabled")) {
|
||||
workarea->set_enabled(attr.value() != QStringLiteral("0"));
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
range_in =
|
||||
Rational::from_string(attr.value().toString().toStdString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
range_out =
|
||||
Rational::from_string(attr.value().toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange loaded_workarea(range_in, range_out);
|
||||
|
||||
if (loaded_workarea != workarea->range()) {
|
||||
workarea->set_range(loaded_workarea);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_marker_list(QXmlStreamReader *reader,
|
||||
TimelineMarkerList *markers) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("marker")) {
|
||||
QString name;
|
||||
Rational in, out;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("name")) {
|
||||
name = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
in = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
out = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
new TimelineMarker(OAK_CONFIG("MarkerColor").toInt(),
|
||||
TimeRange(in, out), name, markers);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::load_value_hint(Node::ValueHint *hint,
|
||||
QXmlStreamReader *reader) const
|
||||
{
|
||||
QVector<NodeValue::Type> types;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("types")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("type")) {
|
||||
types.append(static_cast<NodeValue::Type>(
|
||||
reader->readElementText().toInt()));
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("index")) {
|
||||
hint->set_index(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("tag")) {
|
||||
hint->set_tag(reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
hint->set_type(types);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SERIALIZER210907_H
|
||||
#define OAK_SERIALIZER210907_H
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ProjectSerializer210907 : public ProjectSerializer {
|
||||
public:
|
||||
ProjectSerializer210907() = default;
|
||||
|
||||
protected:
|
||||
virtual LoadData load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const override;
|
||||
|
||||
virtual uint version() const override
|
||||
{
|
||||
return 210907;
|
||||
}
|
||||
|
||||
private:
|
||||
struct XMLNodeData {
|
||||
struct SerializedConnection {
|
||||
NodeInput input;
|
||||
quintptr output_node;
|
||||
};
|
||||
|
||||
struct BlockLink {
|
||||
Node *block;
|
||||
quintptr link;
|
||||
};
|
||||
|
||||
struct GroupLink {
|
||||
NodeGroup *group;
|
||||
quintptr input_node;
|
||||
QString input_id;
|
||||
int input_element;
|
||||
};
|
||||
|
||||
QHash<quintptr, Node *> node_ptrs;
|
||||
QList<SerializedConnection> desired_connections;
|
||||
QList<BlockLink> block_links;
|
||||
QVector<GroupLink> group_input_links;
|
||||
QHash<NodeGroup *, quintptr> group_output_links;
|
||||
};
|
||||
|
||||
void load_node(Node *node, XMLNodeData &xml_node_data,
|
||||
QXmlStreamReader *reader) const;
|
||||
|
||||
void load_color_manager(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_project_settings(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_input(Node *node, QXmlStreamReader *reader,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_immediate(QXmlStreamReader *reader, Node *node,
|
||||
const QString &input, int element,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
bool load_position(QXmlStreamReader *reader, quintptr *node_ptr,
|
||||
Node::Position *pos) const;
|
||||
|
||||
void post_connect(const XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_node_custom(QXmlStreamReader *reader, Node *node,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_timeline_points(QXmlStreamReader *reader,
|
||||
ViewerOutput *points) const;
|
||||
|
||||
void load_work_area(QXmlStreamReader *reader,
|
||||
TimelineWorkArea *workarea) const;
|
||||
|
||||
void load_marker_list(QXmlStreamReader *reader,
|
||||
TimelineMarkerList *markers) const;
|
||||
|
||||
void load_value_hint(Node::ValueHint *hint, QXmlStreamReader *reader) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SERIALIZER211228_H
|
||||
@@ -0,0 +1,902 @@
|
||||
/***
|
||||
|
||||
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 "serializer211228.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
#include "node/group/group.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ProjectSerializer211228::LoadData
|
||||
ProjectSerializer211228::load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const
|
||||
{
|
||||
QMap<quintptr, QMap<QString, QString>> properties;
|
||||
QMap<quintptr, QMap<quintptr, Node::Position>> positions;
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("uuid")) {
|
||||
project->set_uuid(QUuid::fromString(reader->readElementText()));
|
||||
|
||||
} else if (reader->name() == QStringLiteral("nodes")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
bool is_root = false;
|
||||
bool is_cm = false;
|
||||
bool is_settings = false;
|
||||
QString id;
|
||||
|
||||
{
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("root") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_root = true;
|
||||
} else if (attr.name() == QStringLiteral("cm") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_cm = true;
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("settings") &&
|
||||
attr.value() == QStringLiteral("1")) {
|
||||
is_settings = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id.isEmpty()) {
|
||||
qWarning() << "Failed to load node with empty ID";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
Node *node;
|
||||
bool handled_elsewhere = false;
|
||||
|
||||
if (is_root) {
|
||||
project->initialize();
|
||||
node = project->root();
|
||||
} else if (is_cm) {
|
||||
load_color_manager(reader, project);
|
||||
handled_elsewhere = true;
|
||||
} else if (is_settings) {
|
||||
load_project_settings(reader, project);
|
||||
handled_elsewhere = true;
|
||||
} else {
|
||||
node = NodeFactory::create_from_id(id);
|
||||
}
|
||||
|
||||
if (!handled_elsewhere) {
|
||||
if (!node) {
|
||||
qWarning()
|
||||
<< "Failed to find node with ID" << id;
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
load_node(node, xml_node_data, reader);
|
||||
node->setParent(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("positions")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("context")) {
|
||||
quintptr context_ptr = 0;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
context_ptr = attr.value().toULongLong();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (context_ptr) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
quintptr node_ptr;
|
||||
Node::Position node_pos;
|
||||
|
||||
if (load_position(reader, &node_ptr,
|
||||
&node_pos)) {
|
||||
if (node_ptr) {
|
||||
positions[context_ptr].insert(node_ptr,
|
||||
node_pos);
|
||||
} else {
|
||||
qWarning()
|
||||
<< "Failed to find pointer for node position";
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning()
|
||||
<< "Attempted to load context with no pointer";
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("properties")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
quintptr ptr = 0;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
ptr = attr.value().toULongLong();
|
||||
|
||||
// Only attribute we're looking for right now
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr) {
|
||||
QMap<QString, QString> properties_for_node;
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
properties_for_node.insert(
|
||||
reader->name().toString(),
|
||||
reader->readElementText());
|
||||
}
|
||||
properties.insert(ptr, properties_for_node);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Skip this
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve positions
|
||||
for (auto it = positions.cbegin(); it != positions.cend(); it++) {
|
||||
Node *ctx = xml_node_data.node_ptrs.value(it.key());
|
||||
if (ctx) {
|
||||
for (auto jt = it.value().cbegin(); jt != it.value().cend(); jt++) {
|
||||
Node *n = xml_node_data.node_ptrs.value(jt.key());
|
||||
if (n) {
|
||||
ctx->set_node_position_in_context(n, jt.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make connections
|
||||
post_connect(xml_node_data);
|
||||
|
||||
LoadData load_data;
|
||||
load_data.node_ptrs = xml_node_data.node_ptrs;
|
||||
load_data.node_uuids = xml_node_data.node_uuids;
|
||||
|
||||
// Resolve serialized properties (if any)
|
||||
for (auto it = properties.cbegin(); it != properties.cend(); it++) {
|
||||
Node *node = xml_node_data.node_ptrs.value(it.key());
|
||||
if (node) {
|
||||
load_data.properties.insert(node, it.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve tracks
|
||||
for (Node *n : project->nodes()) {
|
||||
n->set_caches_enabled(true);
|
||||
|
||||
if (Track *t = dynamic_cast<Track *>(n)) {
|
||||
for (int i = 0; i < t->input_array_size(Track::k_block_input); i++) {
|
||||
Block *b = static_cast<Block *>(
|
||||
t->get_connected_output(Track::k_block_input, i));
|
||||
if (!b->track()) {
|
||||
t->append_block(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return load_data;
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_node(Node *node, XMLNodeData &xml_node_data,
|
||||
QXmlStreamReader *reader) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
load_input(node, reader, xml_node_data);
|
||||
} else if (reader->name() == QStringLiteral("ptr")) {
|
||||
quintptr ptr = reader->readElementText().toULongLong();
|
||||
xml_node_data.node_ptrs.insert(ptr, node);
|
||||
} else if (reader->name() == QStringLiteral("label")) {
|
||||
node->set_label(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("uuid")) {
|
||||
xml_node_data.node_uuids.insert(
|
||||
node, QUuid::fromString(reader->readElementText()));
|
||||
} else if (reader->name() == QStringLiteral("color")) {
|
||||
node->set_override_color(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("links")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("link")) {
|
||||
xml_node_data.block_links.append(
|
||||
{ node, reader->readElementText().toULongLong() });
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("custom")) {
|
||||
load_node_custom(reader, node, xml_node_data);
|
||||
} else if (reader->name() == QStringLiteral("connections")) {
|
||||
// Load connections
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("connection")) {
|
||||
QString param_id;
|
||||
int ele = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("element")) {
|
||||
ele = attr.value().toInt();
|
||||
} else if (attr.name() == QStringLiteral("input")) {
|
||||
param_id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
QString output_node_id;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("output")) {
|
||||
output_node_id = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
xml_node_data.desired_connections.append(
|
||||
{ NodeInput(node, param_id, ele),
|
||||
output_node_id.toULongLong() });
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("hints")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("hint")) {
|
||||
QString input;
|
||||
int element = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
input = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("element")) {
|
||||
element = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
Node::ValueHint vh;
|
||||
load_value_hint(&vh, reader);
|
||||
node->set_value_hint_for_input(input, vh, element);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
node->LoadFinishedEvent();
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_color_manager(QXmlStreamReader *reader,
|
||||
Project *project) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
QString id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("config") ||
|
||||
id == QStringLiteral("default_input") ||
|
||||
id == QStringLiteral("reference_space")) {
|
||||
QString value;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("track")) {
|
||||
value = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("default_input")) {
|
||||
// Default color space
|
||||
// NOTE: Stupidly, we saved these as integers which means we can't add anything to the OCIO
|
||||
// config. So we must convert back to string here.
|
||||
static const QStringList list = {
|
||||
QStringLiteral("Linear"),
|
||||
QStringLiteral("CIE-XYZ D65"),
|
||||
QStringLiteral("Filmic Log Encoding"),
|
||||
QStringLiteral("sRGB OETF"),
|
||||
QStringLiteral("Apple DCI-P3 D65"),
|
||||
QStringLiteral("AppleP3 sRGB OETF"),
|
||||
QStringLiteral("BT.1886 EOTF"),
|
||||
QStringLiteral("AppleP3 Filmic Log Encoding"),
|
||||
QStringLiteral("BT.1886 Filmic Log Encoding"),
|
||||
QStringLiteral("Fuji F-Log OETF"),
|
||||
QStringLiteral("Fuji F-Log F-Gamut"),
|
||||
QStringLiteral("Panasonic V-Log V-Gamut"),
|
||||
QStringLiteral("Arri Wide Gamut / LogC EI 800"),
|
||||
QStringLiteral("Arri Wide Gamut / LogC EI 400"),
|
||||
QStringLiteral("Blackmagic Film Wide Gamut (Gen 5)"),
|
||||
QStringLiteral("Rec.709 OETF"),
|
||||
QStringLiteral("Non-Colour Data")
|
||||
};
|
||||
int num_value = value.toInt();
|
||||
value = list.at(num_value);
|
||||
project->set_default_input_color_space(value);
|
||||
} else if (id == QStringLiteral("reference_space")) {
|
||||
// Reference space
|
||||
if (value == QStringLiteral("1")) {
|
||||
value = ocio::ROLE_COMPOSITING_LOG;
|
||||
} else {
|
||||
value = ocio::ROLE_SCENE_LINEAR;
|
||||
}
|
||||
project->set_color_reference_space(value);
|
||||
} else {
|
||||
// Config filename
|
||||
project->set_color_config_filename(value);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_project_settings(QXmlStreamReader *reader,
|
||||
Project *project) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
QString id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("cache_setting") ||
|
||||
id == QStringLiteral("cache_path")) {
|
||||
QString value;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("track")) {
|
||||
value = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (id == QStringLiteral("cache_setting")) {
|
||||
project->set_cache_location_setting(
|
||||
static_cast<Project::CacheSetting>(value.toInt()));
|
||||
} else {
|
||||
project->set_custom_cache_path(value);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_input(Node *node, QXmlStreamReader *reader,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
QString param_id;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
param_id = attr.value().toString();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param_id.isEmpty()) {
|
||||
qWarning() << "Failed to load parameter with missing ID";
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->has_input_with_id(param_id)) {
|
||||
qWarning() << "Failed to load parameter that didn't exist:" << param_id;
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
// Load primary immediate
|
||||
load_immediate(reader, node, param_id, -1, xml_node_data);
|
||||
} else if (reader->name() == QStringLiteral("subelements")) {
|
||||
// Load subelements
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("count")) {
|
||||
node->input_array_resize(param_id, attr.value().toInt());
|
||||
}
|
||||
}
|
||||
|
||||
int element_counter = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("element")) {
|
||||
load_immediate(reader, node, param_id, element_counter,
|
||||
xml_node_data);
|
||||
|
||||
element_counter++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_immediate(QXmlStreamReader *reader,
|
||||
Node *node, const QString &input,
|
||||
int element,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
Q_UNUSED(xml_node_data)
|
||||
|
||||
NodeValue::Type data_type = node->get_input_data_type(input);
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
// Load standard value
|
||||
int val_index = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
QVariant value_on_track;
|
||||
|
||||
if (data_type == NodeValue::k_video_params) {
|
||||
VideoParams vp;
|
||||
vp.load(reader);
|
||||
value_on_track = QVariant::fromValue(vp);
|
||||
} else if (data_type == NodeValue::k_audio_params) {
|
||||
AudioParams ap =
|
||||
TypeSerializer::load_audio_params(reader);
|
||||
value_on_track = QVariant::fromValue(ap);
|
||||
} else {
|
||||
QString value_text = reader->readElementText();
|
||||
|
||||
if (!value_text.isEmpty()) {
|
||||
value_on_track = NodeValue::string_to_value(
|
||||
data_type, value_text, true);
|
||||
}
|
||||
}
|
||||
|
||||
node->set_split_standard_value_on_track(input, val_index,
|
||||
value_on_track, element);
|
||||
|
||||
val_index++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("keyframing") &&
|
||||
node->is_input_keyframable(input)) {
|
||||
node->set_input_is_keyframing(input, reader->readElementText().toInt(),
|
||||
element);
|
||||
} else if (reader->name() == QStringLiteral("keyframes")) {
|
||||
int track = 0;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("key")) {
|
||||
QString key_input;
|
||||
Rational key_time;
|
||||
NodeKeyframe::Type key_type = NodeKeyframe::k_linear;
|
||||
QVariant key_value;
|
||||
QPointF key_in_handle;
|
||||
QPointF key_out_handle;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (is_cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
key_input = attr.value().toString();
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("time")) {
|
||||
key_time = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("type")) {
|
||||
key_type = static_cast<NodeKeyframe::Type>(
|
||||
attr.value().toInt());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("inhandlex")) {
|
||||
key_in_handle.setX(attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("inhandley")) {
|
||||
key_in_handle.setY(attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("outhandlex")) {
|
||||
key_out_handle.setX(
|
||||
attr.value().toDouble());
|
||||
} else if (attr.name() ==
|
||||
QStringLiteral("outhandley")) {
|
||||
key_out_handle.setY(
|
||||
attr.value().toDouble());
|
||||
}
|
||||
}
|
||||
|
||||
key_value = NodeValue::string_to_value(
|
||||
data_type, reader->readElementText(), true);
|
||||
|
||||
NodeKeyframe *key = new NodeKeyframe(
|
||||
key_time, key_value, key_type, track, element,
|
||||
key_input, node);
|
||||
key->set_bezier_control_in(key_in_handle);
|
||||
key->set_bezier_control_out(key_out_handle);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
track++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("csinput")) {
|
||||
node->set_input_property(input, QStringLiteral("col_input"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csdisplay")) {
|
||||
node->set_input_property(input, QStringLiteral("col_display"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csview")) {
|
||||
node->set_input_property(input, QStringLiteral("col_view"),
|
||||
reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("cslook")) {
|
||||
node->set_input_property(input, QStringLiteral("col_look"),
|
||||
reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectSerializer211228::load_position(QXmlStreamReader *reader,
|
||||
quintptr *node_ptr,
|
||||
Node::Position *pos) const
|
||||
{
|
||||
bool got_node_ptr = false;
|
||||
bool got_pos_x = false;
|
||||
bool got_pos_y = false;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
*node_ptr = attr.value().toULongLong();
|
||||
got_node_ptr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("x")) {
|
||||
pos->position.setX(reader->readElementText().toDouble());
|
||||
got_pos_x = true;
|
||||
} else if (reader->name() == QStringLiteral("y")) {
|
||||
pos->position.setY(reader->readElementText().toDouble());
|
||||
got_pos_y = true;
|
||||
} else if (reader->name() == QStringLiteral("expanded")) {
|
||||
pos->expanded = reader->readElementText().toInt();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return got_node_ptr && got_pos_x && got_pos_y;
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::post_connect(const XMLNodeData &xml_node_data) const
|
||||
{
|
||||
foreach (const XMLNodeData::SerializedConnection &con,
|
||||
xml_node_data.desired_connections) {
|
||||
if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) {
|
||||
Node::connect_edge(out, con.input);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const XMLNodeData::BlockLink &l, xml_node_data.block_links) {
|
||||
Node *a = l.block;
|
||||
Node *b = xml_node_data.node_ptrs.value(l.link);
|
||||
|
||||
Node::link(a, b);
|
||||
}
|
||||
|
||||
foreach (const XMLNodeData::GroupLink &l, xml_node_data.group_input_links) {
|
||||
if (Node *input_node = xml_node_data.node_ptrs.value(l.input_node)) {
|
||||
NodeInput resolved(input_node, l.input_id, l.input_element);
|
||||
|
||||
l.group->add_input_passthrough(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = xml_node_data.group_output_links.cbegin();
|
||||
it != xml_node_data.group_output_links.cend(); it++) {
|
||||
if (Node *output_node = xml_node_data.node_ptrs.value(it.value())) {
|
||||
it.key()->set_output_passthrough(output_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_node_custom(QXmlStreamReader *reader,
|
||||
Node *node,
|
||||
XMLNodeData &xml_node_data) const
|
||||
{
|
||||
// Viewer-based nodes
|
||||
if (ViewerOutput *viewer = dynamic_cast<ViewerOutput *>(node)) {
|
||||
Footage *footage = dynamic_cast<Footage *>(node);
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("points")) {
|
||||
load_timeline_points(reader, viewer);
|
||||
} else if (reader->name() == QStringLiteral("timestamp") &&
|
||||
footage) {
|
||||
footage->set_timestamp(reader->readElementText().toLongLong());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (Track *track = dynamic_cast<Track *>(node)) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("height")) {
|
||||
track->set_track_height(reader->readElementText().toDouble());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (NodeGroup *group = dynamic_cast<NodeGroup *>(node)) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("inputpassthroughs")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("inputpassthrough")) {
|
||||
XMLNodeData::GroupLink link;
|
||||
|
||||
link.group = group;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
link.input_node =
|
||||
reader->readElementText().toULongLong();
|
||||
} else if (reader->name() ==
|
||||
QStringLiteral("input")) {
|
||||
link.input_id = reader->readElementText();
|
||||
} else if (reader->name() ==
|
||||
QStringLiteral("element")) {
|
||||
link.input_element =
|
||||
reader->readElementText().toInt();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
xml_node_data.group_input_links.append(link);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("outputpassthrough")) {
|
||||
xml_node_data.group_output_links.insert(
|
||||
group, reader->readElementText().toULongLong());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_timeline_points(QXmlStreamReader *reader,
|
||||
ViewerOutput *points) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("markers")) {
|
||||
load_marker_list(reader, points->get_markers());
|
||||
} else if (reader->name() == QStringLiteral("workarea")) {
|
||||
load_work_area(reader, points->get_work_area());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_work_area(QXmlStreamReader *reader,
|
||||
TimelineWorkArea *workarea) const
|
||||
{
|
||||
Rational range_in = workarea->in();
|
||||
Rational range_out = workarea->out();
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("enabled")) {
|
||||
workarea->set_enabled(attr.value() != QStringLiteral("0"));
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
range_in =
|
||||
Rational::from_string(attr.value().toString().toStdString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
range_out =
|
||||
Rational::from_string(attr.value().toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange loaded_workarea(range_in, range_out);
|
||||
|
||||
if (loaded_workarea != workarea->range()) {
|
||||
workarea->set_range(loaded_workarea);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_marker_list(QXmlStreamReader *reader,
|
||||
TimelineMarkerList *markers) const
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("marker")) {
|
||||
QString name;
|
||||
Rational in, out;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("name")) {
|
||||
name = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
in = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
out = Rational::from_string(
|
||||
attr.value().toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
new TimelineMarker(OAK_CONFIG("MarkerColor").toInt(),
|
||||
TimeRange(in, out), name, markers);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::load_value_hint(Node::ValueHint *hint,
|
||||
QXmlStreamReader *reader) const
|
||||
{
|
||||
QVector<NodeValue::Type> types;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("types")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("type")) {
|
||||
types.append(static_cast<NodeValue::Type>(
|
||||
reader->readElementText().toInt()));
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("index")) {
|
||||
hint->set_index(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("tag")) {
|
||||
hint->set_tag(reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
hint->set_type(types);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SERIALIZER211228_H
|
||||
#define OAK_SERIALIZER211228_H
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ProjectSerializer211228 : public ProjectSerializer {
|
||||
public:
|
||||
ProjectSerializer211228() = default;
|
||||
|
||||
protected:
|
||||
virtual LoadData load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const override;
|
||||
|
||||
virtual uint version() const override
|
||||
{
|
||||
return 211228;
|
||||
}
|
||||
|
||||
private:
|
||||
struct XMLNodeData {
|
||||
struct SerializedConnection {
|
||||
NodeInput input;
|
||||
quintptr output_node;
|
||||
};
|
||||
|
||||
struct BlockLink {
|
||||
Node *block;
|
||||
quintptr link;
|
||||
};
|
||||
|
||||
struct GroupLink {
|
||||
NodeGroup *group;
|
||||
quintptr input_node;
|
||||
QString input_id;
|
||||
int input_element;
|
||||
};
|
||||
|
||||
QHash<quintptr, Node *> node_ptrs;
|
||||
QList<SerializedConnection> desired_connections;
|
||||
QList<BlockLink> block_links;
|
||||
QVector<GroupLink> group_input_links;
|
||||
QHash<NodeGroup *, quintptr> group_output_links;
|
||||
QHash<Node *, QUuid> node_uuids;
|
||||
};
|
||||
|
||||
void load_node(Node *node, XMLNodeData &xml_node_data,
|
||||
QXmlStreamReader *reader) const;
|
||||
|
||||
void load_color_manager(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_project_settings(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_input(Node *node, QXmlStreamReader *reader,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_immediate(QXmlStreamReader *reader, Node *node,
|
||||
const QString &input, int element,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
bool load_position(QXmlStreamReader *reader, quintptr *node_ptr,
|
||||
Node::Position *pos) const;
|
||||
|
||||
void post_connect(const XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_node_custom(QXmlStreamReader *reader, Node *node,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_timeline_points(QXmlStreamReader *reader,
|
||||
ViewerOutput *points) const;
|
||||
|
||||
void load_work_area(QXmlStreamReader *reader,
|
||||
TimelineWorkArea *workarea) const;
|
||||
|
||||
void load_marker_list(QXmlStreamReader *reader,
|
||||
TimelineMarkerList *markers) const;
|
||||
|
||||
void load_value_hint(Node::ValueHint *hint, QXmlStreamReader *reader) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SERIALIZER211228_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SERIALIZER220403_H
|
||||
#define OAK_SERIALIZER220403_H
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ProjectSerializer220403 : public ProjectSerializer {
|
||||
public:
|
||||
ProjectSerializer220403() = default;
|
||||
|
||||
protected:
|
||||
virtual LoadData load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const override;
|
||||
|
||||
virtual uint version() const override
|
||||
{
|
||||
return 220403;
|
||||
}
|
||||
|
||||
private:
|
||||
struct XMLNodeData {
|
||||
struct SerializedConnection {
|
||||
NodeInput input;
|
||||
quintptr output_node;
|
||||
};
|
||||
|
||||
struct BlockLink {
|
||||
Node *block;
|
||||
quintptr link;
|
||||
};
|
||||
|
||||
struct GroupLink {
|
||||
NodeGroup *group;
|
||||
QString passthrough_id;
|
||||
quintptr input_node;
|
||||
QString input_id;
|
||||
int input_element;
|
||||
QString custom_name;
|
||||
InputFlags custom_flags;
|
||||
NodeValue::Type data_type;
|
||||
QVariant default_val;
|
||||
QHash<QString, QVariant> custom_properties;
|
||||
};
|
||||
|
||||
QHash<quintptr, Node *> node_ptrs;
|
||||
QList<SerializedConnection> desired_connections;
|
||||
QList<BlockLink> block_links;
|
||||
QVector<GroupLink> group_input_links;
|
||||
QHash<NodeGroup *, quintptr> group_output_links;
|
||||
QHash<Node *, QUuid> node_uuids;
|
||||
};
|
||||
|
||||
void load_node(Node *node, XMLNodeData &xml_node_data,
|
||||
QXmlStreamReader *reader) const;
|
||||
|
||||
void load_color_manager(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_project_settings(QXmlStreamReader *reader, Project *project) const;
|
||||
|
||||
void load_input(Node *node, QXmlStreamReader *reader,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_immediate(QXmlStreamReader *reader, Node *node,
|
||||
const QString &input, int element,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_keyframe(QXmlStreamReader *reader, NodeKeyframe *key,
|
||||
NodeValue::Type data_type) const;
|
||||
|
||||
bool load_position(QXmlStreamReader *reader, quintptr *node_ptr,
|
||||
Node::Position *pos) const;
|
||||
|
||||
void post_connect(const XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_node_custom(QXmlStreamReader *reader, Node *node,
|
||||
XMLNodeData &xml_node_data) const;
|
||||
|
||||
void load_timeline_points(QXmlStreamReader *reader,
|
||||
ViewerOutput *viewer) const;
|
||||
|
||||
void load_marker(QXmlStreamReader *reader, TimelineMarker *marker) const;
|
||||
|
||||
void load_work_area(QXmlStreamReader *reader,
|
||||
TimelineWorkArea *workarea) const;
|
||||
|
||||
void load_marker_list(QXmlStreamReader *reader,
|
||||
TimelineMarkerList *markers) const;
|
||||
|
||||
void load_value_hint(Node::ValueHint *hint, QXmlStreamReader *reader) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SERIALIZER220403_H
|
||||
@@ -0,0 +1,558 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
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 <QString>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "serializer230220.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
#include "node/group/group.h"
|
||||
#include "node/serializeddata.h"
|
||||
#include <QtCore>
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ProjectSerializer230220::LoadData
|
||||
ProjectSerializer230220::load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const
|
||||
{
|
||||
QMap<quintptr, QMap<QString, QString>> properties;
|
||||
QMap<quintptr, QMap<quintptr, Node::Position>> positions;
|
||||
LoadData load_data;
|
||||
SerializedData project_data;
|
||||
|
||||
switch (load_type) {
|
||||
case k_project: {
|
||||
if (reader->name() == QStringLiteral("project")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("project")) {
|
||||
project_data = project->load(reader);
|
||||
load_data.node_ptrs = project_data.node_ptrs;
|
||||
} else if (reader->name() == QStringLiteral("layout")) {
|
||||
load_data.layout = MainWindowLayoutInfo::from_xml(
|
||||
reader, project_data.node_ptrs);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
post_connect(project->nodes(), &project_data);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case k_only_markers: {
|
||||
if (reader->name() == QStringLiteral("markers")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("marker")) {
|
||||
TimelineMarker *marker = new TimelineMarker();
|
||||
marker->load(reader);
|
||||
load_data.markers.push_back(marker);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case k_only_keyframes: {
|
||||
if (reader->name() == QStringLiteral("keyframes")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
QString node_id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
node_id = attr.value().toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node *n = nullptr;
|
||||
if (!node_id.isEmpty()) {
|
||||
n = NodeFactory::create_from_id(node_id);
|
||||
}
|
||||
|
||||
if (!n) {
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
QString input_id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
input_id = attr.value().toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (input_id.isEmpty()) {
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral("element")) {
|
||||
QString element_id;
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() ==
|
||||
QStringLiteral("id")) {
|
||||
element_id =
|
||||
attr.value().toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (element_id.isEmpty()) {
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
while (xml_read_next_start_element(
|
||||
reader)) {
|
||||
if (reader->name() ==
|
||||
QStringLiteral(
|
||||
"track")) {
|
||||
QString track_id;
|
||||
XMLAttributeLoop(reader,
|
||||
attr)
|
||||
{
|
||||
if (attr.name() ==
|
||||
QStringLiteral(
|
||||
"id")) {
|
||||
track_id =
|
||||
attr.value()
|
||||
.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (track_id.isEmpty()) {
|
||||
reader
|
||||
->skipCurrentElement();
|
||||
} else {
|
||||
while (
|
||||
xml_read_next_start_element(
|
||||
reader)) {
|
||||
if (reader
|
||||
->name() ==
|
||||
QStringLiteral(
|
||||
"key")) {
|
||||
NodeKeyframe *key =
|
||||
new NodeKeyframe();
|
||||
key->set_input(
|
||||
input_id);
|
||||
key->set_element(
|
||||
element_id
|
||||
.toInt());
|
||||
key->set_track(
|
||||
track_id
|
||||
.toInt());
|
||||
|
||||
key->load(
|
||||
reader,
|
||||
n->get_input_data_type(
|
||||
input_id));
|
||||
|
||||
load_data
|
||||
.keyframes
|
||||
[node_id]
|
||||
.append(
|
||||
key);
|
||||
} else {
|
||||
reader
|
||||
->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader
|
||||
->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete n;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case k_only_clips:
|
||||
case k_only_nodes: {
|
||||
if ((load_type == k_only_nodes &&
|
||||
reader->name() == QStringLiteral("nodes")) ||
|
||||
(load_type == k_only_clips &&
|
||||
reader->name() == QStringLiteral("timeline"))) {
|
||||
QMap<quintptr, Node *> skipped_items;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
QString id;
|
||||
quintptr ptr = 0;
|
||||
QVector<quintptr> items;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
id = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("ptr")) {
|
||||
ptr = attr.value().toULongLong();
|
||||
} else if (attr.name() == QStringLiteral("items")) {
|
||||
QVector<QStringView> l = attr.value().split(',');
|
||||
items.reserve(l.size());
|
||||
for (const QStringView &s : l) {
|
||||
items.append(s.toULongLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id.isEmpty()) {
|
||||
qWarning() << "Failed to load node with empty ID";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
bool dependency_of_item = false;
|
||||
|
||||
if (project && !items.empty()) {
|
||||
for (quintptr p : items) {
|
||||
if (project->nodes().contains(
|
||||
reinterpret_cast<Node *>(p))) {
|
||||
dependency_of_item = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dependency_of_item) {
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
Node *node = NodeFactory::create_from_id(id);
|
||||
if (!node) {
|
||||
qWarning()
|
||||
<< "Failed to find node with ID" << id;
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
if (project && node->is_item() && ptr) {
|
||||
// If we're pasting an object into the same project, we should re-use the item
|
||||
// rather than duplicate.
|
||||
Node *existing =
|
||||
reinterpret_cast<Node *>(ptr);
|
||||
if (project->nodes().contains(existing)) {
|
||||
// Connect this
|
||||
skipped_items.insert(ptr, existing);
|
||||
|
||||
// Don't continue loading this
|
||||
delete node;
|
||||
node = nullptr;
|
||||
|
||||
// Skip element
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (node) {
|
||||
// Disable cache while node is being loaded (we'll re-enable it later)
|
||||
node->set_caches_enabled(false);
|
||||
node->load(reader, &project_data);
|
||||
load_data.nodes.append(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
load_data.node_ptrs = project_data.node_ptrs;
|
||||
} else if (reader->name() == QStringLiteral("properties")) {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
quintptr ptr = 0;
|
||||
|
||||
XMLAttributeLoop(reader, attr)
|
||||
{
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
ptr = attr.value().toULongLong();
|
||||
|
||||
// Only attribute we're looking for right now
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr) {
|
||||
QMap<QString, QString> properties_for_node;
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
properties_for_node.insert(
|
||||
reader->name().toString(),
|
||||
reader->readElementText());
|
||||
}
|
||||
properties.insert(ptr, properties_for_node);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (!skipped_items.empty()) {
|
||||
for (auto it = project_data.desired_connections.begin();
|
||||
it != project_data.desired_connections.end();) {
|
||||
const SerializedData::SerializedConnection &sc = *it;
|
||||
|
||||
if (Node *si = skipped_items.value(sc.output_node)) {
|
||||
// Convert this to a promised connection
|
||||
Node::OutputConnection oc = { si, sc.input };
|
||||
load_data.promised_connections.push_back(oc);
|
||||
it = project_data.desired_connections.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve serialized properties (if any)
|
||||
for (auto it = properties.cbegin(); it != properties.cend(); it++) {
|
||||
Node *node = project_data.node_ptrs.value(it.key());
|
||||
if (node) {
|
||||
load_data.properties.insert(node, it.value());
|
||||
}
|
||||
}
|
||||
|
||||
post_connect(load_data.nodes, &project_data);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return load_data;
|
||||
}
|
||||
|
||||
void write_node_map(QXmlStreamWriter *writer, Node *node,
|
||||
const QVector<Node *> &nodes)
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("node"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"),
|
||||
QString::number(reinterpret_cast<quintptr>(node)));
|
||||
|
||||
for (auto oc : node->output_connections()) {
|
||||
if (nodes.contains(oc.second.node())) {
|
||||
write_node_map(writer, oc.second.node(), nodes);
|
||||
}
|
||||
}
|
||||
|
||||
writer->writeEndElement();
|
||||
}
|
||||
|
||||
void ProjectSerializer230220::save(QXmlStreamWriter *writer,
|
||||
const SaveData &data, void *reserved) const
|
||||
{
|
||||
if (!data.get_only_serialize_markers().empty()) {
|
||||
writer->writeStartElement(QStringLiteral("markers"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("version"), QString::number(1));
|
||||
|
||||
for (auto it = data.get_only_serialize_markers().cbegin();
|
||||
it != data.get_only_serialize_markers().cend(); it++) {
|
||||
TimelineMarker *marker = *it;
|
||||
writer->writeStartElement(QStringLiteral("marker"));
|
||||
marker->save(writer);
|
||||
writer->writeEndElement(); // marker
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // markers
|
||||
} else if (!data.get_only_serialize_keyframes().empty()) {
|
||||
writer->writeStartElement(QStringLiteral("keyframes"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("version"), QString::number(1));
|
||||
|
||||
// Organize keyframes into node+input
|
||||
QHash<QString,
|
||||
QHash<QString, QMap<int, QMap<int, QVector<NodeKeyframe *>>>>>
|
||||
organized;
|
||||
|
||||
for (auto it = data.get_only_serialize_keyframes().cbegin();
|
||||
it != data.get_only_serialize_keyframes().cend(); it++) {
|
||||
NodeKeyframe *key = *it;
|
||||
organized[key->parent()->id()][key->input()][key->element()]
|
||||
[key->track()]
|
||||
.append(key);
|
||||
}
|
||||
|
||||
for (auto it = organized.cbegin(); it != organized.cend(); it++) {
|
||||
writer->writeStartElement(QStringLiteral("node"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("id"), it.key());
|
||||
|
||||
for (auto jt = it.value().cbegin(); jt != it.value().cend(); jt++) {
|
||||
writer->writeStartElement(QStringLiteral("input"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("id"), jt.key());
|
||||
|
||||
for (auto kt = jt.value().cbegin(); kt != jt.value().cend();
|
||||
kt++) {
|
||||
writer->writeStartElement(QStringLiteral("element"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("id"),
|
||||
QString::number(kt.key()));
|
||||
|
||||
for (auto lt = kt.value().cbegin(); lt != kt.value().cend();
|
||||
lt++) {
|
||||
const QVector<NodeKeyframe *> &keys = lt.value();
|
||||
|
||||
writer->writeStartElement(QStringLiteral("track"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("id"),
|
||||
QString::number(lt.key()));
|
||||
|
||||
for (NodeKeyframe *key : keys) {
|
||||
writer->writeStartElement(QStringLiteral("key"));
|
||||
key->save(writer, key->parent()->get_input_data_type(
|
||||
key->input()));
|
||||
writer->writeEndElement(); // key
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // track
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // element
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // input
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // node;
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // keyframes
|
||||
} else if (!data.get_only_serialize_nodes().empty()) {
|
||||
if (data.type() == k_only_clips) {
|
||||
writer->writeStartElement(QStringLiteral("timeline"));
|
||||
} else {
|
||||
writer->writeStartElement(QStringLiteral("nodes"));
|
||||
}
|
||||
|
||||
writer->writeAttribute(QStringLiteral("version"), QString::number(1));
|
||||
|
||||
for (Node *n : data.get_only_serialize_nodes()) {
|
||||
writer->writeStartElement(QStringLiteral("node"));
|
||||
|
||||
QStringList item_list;
|
||||
for (Node *i : data.get_only_serialize_nodes()) {
|
||||
if (i->is_item() && i->inputs_from(n, true)) {
|
||||
item_list.append(
|
||||
QString::number(reinterpret_cast<quintptr>(i)));
|
||||
}
|
||||
}
|
||||
if (!item_list.empty()) {
|
||||
writer->writeAttribute(QStringLiteral("items"),
|
||||
item_list.join(','));
|
||||
}
|
||||
|
||||
n->save(writer);
|
||||
writer->writeEndElement(); // node
|
||||
}
|
||||
|
||||
if (!data.get_properties().empty()) {
|
||||
writer->writeStartElement(QStringLiteral("properties"));
|
||||
for (auto it = data.get_properties().cbegin();
|
||||
it != data.get_properties().cend(); it++) {
|
||||
writer->writeStartElement(QStringLiteral("node"));
|
||||
|
||||
writer->writeAttribute(
|
||||
QStringLiteral("ptr"),
|
||||
QString::number(reinterpret_cast<quintptr>(it.key())));
|
||||
|
||||
for (auto jt = it.value().cbegin(); jt != it.value().cend();
|
||||
jt++) {
|
||||
writer->writeTextElement(jt.key(), jt.value());
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // node
|
||||
}
|
||||
writer->writeEndElement(); // properties
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // nodes
|
||||
} else if (Project *project = data.get_project()) {
|
||||
writer->writeStartElement(QStringLiteral("project"));
|
||||
|
||||
writer->writeStartElement(QStringLiteral("project"));
|
||||
project->save(writer);
|
||||
writer->writeEndElement(); // project
|
||||
|
||||
writer->writeStartElement(QStringLiteral("layout"));
|
||||
data.get_layout().to_xml(writer);
|
||||
writer->writeEndElement(); // layout
|
||||
|
||||
writer->writeEndElement(); // project
|
||||
} else {
|
||||
qCritical() << "ProjectSerializer provided nothing to save";
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer230220::post_connect(const QVector<Node *> &nodes,
|
||||
SerializedData *project_data) const
|
||||
{
|
||||
foreach (const SerializedData::SerializedConnection &con,
|
||||
project_data->desired_connections) {
|
||||
if (Node *out = project_data->node_ptrs.value(con.output_node)) {
|
||||
Node::connect_edge(out, con.input);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const SerializedData::BlockLink &l, project_data->block_links) {
|
||||
Node *a = l.block;
|
||||
Node *b = project_data->node_ptrs.value(l.link);
|
||||
|
||||
Node::link(a, b);
|
||||
}
|
||||
|
||||
for (auto it = nodes.cbegin(); it != nodes.cend(); it++) {
|
||||
Node *n = *it;
|
||||
|
||||
n->PostLoadEvent(project_data);
|
||||
|
||||
n->set_caches_enabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_PROJECTSERIALIZER230220_H
|
||||
#define OAK_PROJECTSERIALIZER230220_H
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ProjectSerializer230220 : public ProjectSerializer {
|
||||
public:
|
||||
ProjectSerializer230220() = default;
|
||||
|
||||
protected:
|
||||
virtual LoadData load(Project *project, QXmlStreamReader *reader,
|
||||
LoadType load_type, void *reserved) const override;
|
||||
|
||||
virtual void save(QXmlStreamWriter *writer, const SaveData &data,
|
||||
void *reserved) const override;
|
||||
|
||||
virtual uint version() const override
|
||||
{
|
||||
return 230220;
|
||||
}
|
||||
|
||||
private:
|
||||
void post_connect(const QVector<Node *> &nodes,
|
||||
SerializedData *project_data) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_PROJECTSERIALIZER230220_H
|
||||
@@ -0,0 +1,75 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 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 "typeserializer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
AudioParams TypeSerializer::load_audio_params(QXmlStreamReader *reader)
|
||||
{
|
||||
AudioParams a;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == QStringLiteral("samplerate")) {
|
||||
a.set_sample_rate(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("channellayout")) {
|
||||
a.set_channel_layout(reader->readElementText().toULongLong());
|
||||
} else if (reader->name() == QStringLiteral("format")) {
|
||||
a.set_format(SampleFormat::from_string(
|
||||
reader->readElementText().toStdString()));
|
||||
} else if (reader->name() == QStringLiteral("enabled")) {
|
||||
a.set_enabled(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("streamindex")) {
|
||||
a.set_stream_index(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("duration")) {
|
||||
a.set_duration(reader->readElementText().toLongLong());
|
||||
} else if (reader->name() == QStringLiteral("timebase")) {
|
||||
a.set_time_base(
|
||||
Rational::from_string(reader->readElementText().toStdString()));
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
void TypeSerializer::save_audio_params(QXmlStreamWriter *writer,
|
||||
const AudioParams &a)
|
||||
{
|
||||
writer->writeTextElement(QStringLiteral("samplerate"),
|
||||
QString::number(a.sample_rate()));
|
||||
writer->writeTextElement(QStringLiteral("channellayout"),
|
||||
QString::number(a.channel_layout()));
|
||||
writer->writeTextElement(QStringLiteral("format"),
|
||||
QString::fromStdString(a.format().to_string()));
|
||||
writer->writeTextElement(QStringLiteral("enabled"),
|
||||
QString::number(a.enabled()));
|
||||
writer->writeTextElement(QStringLiteral("streamindex"),
|
||||
QString::number(a.stream_index()));
|
||||
writer->writeTextElement(QStringLiteral("duration"),
|
||||
QString::number(a.duration()));
|
||||
writer->writeTextElement(QStringLiteral("timebase"),
|
||||
QString::fromStdString(a.time_base().to_string()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_TYPESERIALIZER_H
|
||||
#define OAK_TYPESERIALIZER_H
|
||||
|
||||
#include <olive/core/core.h>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using namespace core;
|
||||
|
||||
class TypeSerializer {
|
||||
public:
|
||||
TypeSerializer() = default;
|
||||
|
||||
static AudioParams load_audio_params(QXmlStreamReader *reader);
|
||||
static void save_audio_params(QXmlStreamWriter *writer, const AudioParams &a);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_TYPESERIALIZER_H
|
||||
Reference in New Issue
Block a user