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:
2026-07-20 03:23:28 +08:00
parent 026ff94b5e
commit 28c4426236
604 changed files with 243 additions and 172 deletions
@@ -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(&copy_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);
}
}
+279
View File
@@ -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