/***
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 .
***/
#include "serializer220403.h"
#include "config/config.h"
#include "node/factory.h"
#include "node/group/group.h"
namespace olive
{
ProjectSerializer220403::LoadData
ProjectSerializer220403::load(Project *project, QXmlStreamReader *reader,
LoadType load_type, void *reserved) const
{
QMap> properties;
QMap> positions;
XMLNodeData xml_node_data;
LoadData load_data;
if ((load_type == k_project &&
reader->name() == QStringLiteral("project")) ||
((load_type == k_only_nodes &&
reader->name() == QStringLiteral("nodes")) ||
(load_type == k_only_clips &&
reader->name() == QStringLiteral("timeline"))) ||
(load_type == k_only_keyframes &&
reader->name() == QStringLiteral("keyframes")) ||
(load_type == k_only_markers &&
reader->name() == QStringLiteral("markers"))) {
while (xml_read_next_start_element(reader)) {
if (reader->name() == QStringLiteral("layout")) {
// Since the main window's functions have to occur in the GUI thread (and we're likely
// loading in a secondary thread), we load all necessary data into a separate struct so we
// can continue loading and queue it with the main window so it can handle the data
// appropriately in its own thread.
load_data.layout = SerializedLayoutInfo::from_xml(
reader, xml_node_data.node_ptrs);
} else if (reader->name() == QStringLiteral("uuid")) {
if (project) {
project->set_uuid(
QUuid::fromString(reader->readElementText()));
} else {
reader->skipCurrentElement();
}
} 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);
if (project) {
node->setParent(project);
} else {
load_data.nodes.append(node);
}
}
}
}
} else {
reader->skipCurrentElement();
}
}
} else 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());
load_keyframe(
reader,
key,
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 if (reader->name() == QStringLiteral("markers")) {
while (xml_read_next_start_element(reader)) {
if (reader->name() == QStringLiteral("marker")) {
TimelineMarker *marker = new TimelineMarker();
load_marker(reader, marker);
load_data.markers.push_back(marker);
} 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 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);
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());
}
}
// Re-enable caches and resolve tracks
const QVector &nodes = project ? project->nodes() : load_data.nodes;
for (Node *n : nodes) {
n->set_caches_enabled(true);
if (Track *t = dynamic_cast