/***
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 .
***/
#include
#include
#include "serializer230220.h"
#include "config/config.h"
#include "node/factory.h"
#include "node/group/group.h"
#include "node/serializeddata.h"
#include
namespace olive
{
ProjectSerializer230220::LoadData
ProjectSerializer230220::load(Project *project, QXmlStreamReader *reader,
LoadType load_type, void *reserved) const
{
QMap> properties;
QMap> 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 = SerializedLayoutInfo::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 skipped_items;
while (xml_read_next_start_element(reader)) {
if (reader->name() == QStringLiteral("node")) {
QString id;
quintptr ptr = 0;
QVector 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 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(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(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 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 &nodes)
{
writer->writeStartElement(QStringLiteral("node"));
writer->writeAttribute(QStringLiteral("ptr"),
QString::number(reinterpret_cast(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>>>>
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 &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(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(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 &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);
}
}
}