From ffec819eef204e65463bfdc84c27f104a10d4450 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:56:04 -0800 Subject: [PATCH] successfully transmit block map Requires a binary data type, which requires a new project serializer --- app/node/output/track/track.cpp | 85 +- app/node/output/track/track.h | 8 +- app/node/project/serializer/CMakeLists.txt | 3 + app/node/project/serializer/serializer.cpp | 12 +- app/node/project/serializer/serializer.h | 2 + .../project/serializer/serializer210528.cpp | 14 + .../project/serializer/serializer210907.cpp | 14 + .../project/serializer/serializer211228.cpp | 14 + .../project/serializer/serializer220403.cpp | 453 +----- .../project/serializer/serializer220403.h | 24 - .../project/serializer/serializer230220.cpp | 1336 +++++++++++++++++ .../project/serializer/serializer230220.h | 127 ++ app/node/value.cpp | 8 + app/render/opengl/openglrenderer.cpp | 1 + app/render/projectcopier.cpp | 8 + .../nodeparamviewwidgetbridge.cpp | 3 + 16 files changed, 1611 insertions(+), 501 deletions(-) create mode 100644 app/node/project/serializer/serializer230220.cpp create mode 100644 app/node/project/serializer/serializer230220.h diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 4c82c06a9..1249e3c13 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -46,7 +46,8 @@ Track::Track() : index_(-1), locked_(false), sequence_(nullptr), - ignore_arraymap_(0) + ignore_arraymap_(0), + arraymap_invalid_(false) { AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable | kInputFlagHidden)); @@ -205,33 +206,11 @@ void Track::InputValueChangedEvent(const QString &input, int element) if (input == kMutedInput) { emit MutedChanged(IsMuted()); - } else if (false && input == kArrayMapInput) { + } else if (input == kArrayMapInput) { if (ignore_arraymap_ > 0) { - qDebug() << "ignored our own array map update"; ignore_arraymap_--; } else { - qDebug() << "received REAL array map update"; - - block_array_indexes_ = GetStandardValue(kArrayMapInput).value< QVector >(); - blocks_.reserve(block_array_indexes_.size()); - Block *prev = nullptr; - for (int i = 0; i < block_array_indexes_.size(); i++) { - Block *b = static_cast(GetConnectedOutput(kBlockInput, block_array_indexes_.at(i))); - - Block::set_previous_next(prev, b); - - if (b) { - blocks_.append(b); - prev = b; - } else { - block_array_indexes_.removeAt(i); - i--; - } - } - if (prev) { - prev->set_next(nullptr); - } - UpdateInOutFrom(0); + RefreshBlockCacheFromArrayMap(); } } } @@ -427,8 +406,7 @@ void Track::InsertBlockAtIndex(Block *block, int index) emit BlockAdded(block); - ignore_arraymap_++; - SetStandardValue(kArrayMapInput, QVariant::fromValue(block_array_indexes_)); + UpdateArrayMap(); } void Track::AppendBlock(Block *block) @@ -472,8 +450,7 @@ void Track::RippleRemoveBlock(Block *block) Node::InvalidateCache(TimeRange(remove_in, qMax(track_length(), remove_out)), kBlockInput); - ignore_arraymap_++; - SetStandardValue(kArrayMapInput, QVariant::fromValue(block_array_indexes_)); + UpdateArrayMap(); } void Track::ReplaceBlock(Block *old, Block *replace) @@ -520,8 +497,7 @@ void Track::ReplaceBlock(Block *old, Block *replace) emit BlockAdded(replace); - ignore_arraymap_++; - SetStandardValue(kArrayMapInput, QVariant::fromValue(block_array_indexes_)); + UpdateArrayMap(); } rational Track::track_length() const @@ -553,6 +529,13 @@ void Track::SetLocked(bool e) locked_ = e; } +void Track::InputConnectedEvent(const QString &input, int element, Node *node) +{ + if (arraymap_invalid_ && input == kBlockInput && element >= 0) { + RefreshBlockCacheFromArrayMap(); + } +} + void Track::UpdateInOutFrom(int index) { // Find block just before this one to find the last out point @@ -727,6 +710,46 @@ int Track::ConnectBlock(Block *b) } } +void Track::UpdateArrayMap() +{ + ignore_arraymap_++; + SetStandardValue(kArrayMapInput, QByteArray(reinterpret_cast(block_array_indexes_.data()), block_array_indexes_.size() * sizeof(uint32_t))); +} + +void Track::RefreshBlockCacheFromArrayMap() +{ + QByteArray bytes = GetStandardValue(kArrayMapInput).toByteArray(); + block_array_indexes_.resize(bytes.size() / sizeof(uint32_t)); + memcpy(block_array_indexes_.data(), bytes.data(), bytes.size()); + blocks_.clear(); + blocks_.reserve(block_array_indexes_.size()); + + Block *prev = nullptr; + arraymap_invalid_ = false; + + for (int i = 0; i < block_array_indexes_.size(); i++) { + Block *b = static_cast(GetConnectedOutput(kBlockInput, block_array_indexes_.at(i))); + + Block::set_previous_next(prev, b); + + if (b) { + b->set_track(this); + blocks_.append(b); + prev = b; + } else { + block_array_indexes_.resize(i); + arraymap_invalid_ = true; + break; + } + } + + if (prev) { + prev->set_next(nullptr); + } + + UpdateInOutFrom(0); +} + void Track::BlockLengthChanged() { // Assumes sender is a Block diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 8f0414ab8..a23f75f50 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -444,6 +444,7 @@ signals: void BlocksRefreshed(); protected: + virtual void InputConnectedEvent(const QString& input, int element, Node *node) override; virtual void InputValueChangedEvent(const QString& input, int element) override; private: @@ -459,10 +460,12 @@ private: int ConnectBlock(Block *b); + void UpdateArrayMap(); + TimeRangeList block_length_pending_invalidations_; QVector blocks_; - QVector block_array_indexes_; + QVector block_array_indexes_; std::list empty_inputs_; @@ -477,10 +480,13 @@ private: Sequence *sequence_; int ignore_arraymap_; + bool arraymap_invalid_; private slots: void BlockLengthChanged(); + void RefreshBlockCacheFromArrayMap(); + }; uint qHash(const Track::Reference& r, uint seed = 0); diff --git a/app/node/project/serializer/CMakeLists.txt b/app/node/project/serializer/CMakeLists.txt index 4eca1cb5f..df529037c 100644 --- a/app/node/project/serializer/CMakeLists.txt +++ b/app/node/project/serializer/CMakeLists.txt @@ -29,6 +29,9 @@ set(OLIVE_SOURCES 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/typeserializer.cpp node/project/serializer/typeserializer.h diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index 14cba1098..f6a72b475 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -32,6 +32,7 @@ #include "serializer210907.h" #include "serializer211228.h" #include "serializer220403.h" +#include "serializer230220.h" namespace olive { @@ -48,6 +49,7 @@ void ProjectSerializer::Initialize() instances_.append(new ProjectSerializer210907); instances_.append(new ProjectSerializer211228); instances_.append(new ProjectSerializer220403); + instances_.append(new ProjectSerializer230220); } void ProjectSerializer::Destroy() @@ -64,9 +66,9 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QStrin // Some project files are compressed, marked with "OVEC" at the beginning of the file. Check for // that signature now. std::unique_ptr reader; - QByteArray b = project_file.read(4); - if (!memcmp(b.data(), "OVEC", 4)) { + if (CheckCompressedID(&project_file)) { // File is compressed, decompress into memory + QByteArray b; b = qUncompress(project_file.readAll()); reader.reset(new QXmlStreamReader(b)); } else { @@ -256,6 +258,12 @@ ProjectSerializer::Result ProjectSerializer::Copy(const SaveData &data, const QS return res; } +bool ProjectSerializer::CheckCompressedID(QFile *file) +{ + QByteArray b = file->read(4); + return !memcmp(b.data(), "OVEC", 4); +} + bool ProjectSerializer::IsCancelled() const { return false; diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index a7f86dfcc..c44d8654a 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -165,6 +165,8 @@ public: static Result Save(QXmlStreamWriter *write_device, const SaveData &data, const QString &type); static Result Copy(const SaveData &data, const QString &type); + static bool CheckCompressedID(QFile *file); + protected: virtual LoadData Load(Project *project, QXmlStreamReader *reader, void *reserved) const = 0; diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index f4b735c70..563d63a86 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -156,6 +156,20 @@ ProjectSerializer210528::LoadData ProjectSerializer210528::Load(Project *project // Make connections PostConnect(xml_node_data); + // Resolve tracks + for (Node *n : project->nodes()) { + n->SetCachesEnabled(true); + + if (Track *t = dynamic_cast(n)) { + for (int i = 0; i < t->InputArraySize(Track::kBlockInput); i++) { + Block *b = static_cast(t->GetConnectedOutput(Track::kBlockInput, i)); + if (!b->track()) { + t->AppendBlock(b); + } + } + } + } + return LoadData(); } diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index 8633dc55b..db926f496 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -156,6 +156,20 @@ ProjectSerializer210907::LoadData ProjectSerializer210907::Load(Project *project // Make connections PostConnect(xml_node_data); + // Resolve tracks + for (Node *n : project->nodes()) { + n->SetCachesEnabled(true); + + if (Track *t = dynamic_cast(n)) { + for (int i = 0; i < t->InputArraySize(Track::kBlockInput); i++) { + Block *b = static_cast(t->GetConnectedOutput(Track::kBlockInput, i)); + if (!b->track()) { + t->AppendBlock(b); + } + } + } + } + return LoadData(); } diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index 921ee4108..a87c225c5 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -205,6 +205,20 @@ ProjectSerializer211228::LoadData ProjectSerializer211228::Load(Project *project } } + // Resolve tracks + for (Node *n : project->nodes()) { + n->SetCachesEnabled(true); + + if (Track *t = dynamic_cast(n)) { + for (int i = 0; i < t->InputArraySize(Track::kBlockInput); i++) { + Block *b = static_cast(t->GetConnectedOutput(Track::kBlockInput, i)); + if (!b->track()) { + t->AppendBlock(b); + } + } + } + } + return load_data; } diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 32ba82207..226790570 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -26,20 +26,6 @@ namespace olive { -// These wrappers may appear to do nothing, but they seem to address a bug where sometimes -// QXmlStreamWriter would insert nonsense null characters into a project. This is probably some -// real edge case like compiler optimization or some bullshit like that. I don't even know if it -// affects all platforms, but it definitely affected me on Linux. -void WriteStartElement(QXmlStreamWriter *writer, const QString &s) -{ - writer->writeStartElement(s); -} - -void WriteEndElement(QXmlStreamWriter *writer) -{ - writer->writeEndElement(); -} - ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project, QXmlStreamReader *reader, void *reserved) const { QMap > properties; @@ -331,164 +317,23 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project } } - // Re-enable caches + // Re-enable caches and resolve tracks for (Node *n : project->nodes()) { n->SetCachesEnabled(true); + + if (Track *t = dynamic_cast(n)) { + for (int i = 0; i < t->InputArraySize(Track::kBlockInput); i++) { + Block *b = static_cast(t->GetConnectedOutput(Track::kBlockInput, i)); + if (!b->track()) { + t->AppendBlock(b); + } + } + } } return load_data; } -void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const -{ - if (!data.GetOnlySerializeMarkers().empty()) { - - WriteStartElement(writer, QStringLiteral("markers")); - - for (auto it=data.GetOnlySerializeMarkers().cbegin(); it!=data.GetOnlySerializeMarkers().cend(); it++) { - TimelineMarker *marker = *it; - - WriteStartElement(writer, QStringLiteral("marker")); - - SaveMarker(writer, marker); - - WriteEndElement(writer); // marker - } - - WriteEndElement(writer); // markers - - } else if (!data.GetOnlySerializeKeyframes().empty()) { - - WriteStartElement(writer, QStringLiteral("keyframes")); - - // Organize keyframes into node+input - QHash > > > > organized; - - for (auto it=data.GetOnlySerializeKeyframes().cbegin(); it!=data.GetOnlySerializeKeyframes().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++) { - WriteStartElement(writer, QStringLiteral("node")); - - writer->writeAttribute(QStringLiteral("id"), it.key()); - - for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { - WriteStartElement(writer, QStringLiteral("input")); - - writer->writeAttribute(QStringLiteral("id"), jt.key()); - - for (auto kt=jt.value().cbegin(); kt!=jt.value().cend(); kt++) { - WriteStartElement(writer, 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(); - - WriteStartElement(writer, QStringLiteral("track")); - - writer->writeAttribute(QStringLiteral("id"), QString::number(lt.key())); - - for (NodeKeyframe *key : keys) { - WriteStartElement(writer, QStringLiteral("key")); - SaveKeyframe(writer, key, key->parent()->GetInputDataType(key->input())); - WriteEndElement(writer); // key - } - - WriteEndElement(writer); // track - } - - WriteEndElement(writer); // element - } - - WriteEndElement(writer); // input - } - - WriteEndElement(writer); // node; - } - - WriteEndElement(writer); // keyframes - - } else if (Project *project = data.GetProject()) { - - writer->writeTextElement(QStringLiteral("uuid"), project->GetUuid().toString()); - - WriteStartElement(writer, QStringLiteral("nodes")); - - const QVector &using_node_list = (data.GetOnlySerializeNodes().isEmpty()) ? project->nodes() : data.GetOnlySerializeNodes(); - - foreach (Node* node, using_node_list) { - WriteStartElement(writer, QStringLiteral("node")); - - if (node == project->root()) { - writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); - } else if (node == project->color_manager()) { - writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1")); - } else if (node == project->settings()) { - writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1")); - } - - writer->writeAttribute(QStringLiteral("id"), node->id()); - - SaveNode(node, writer); - - WriteEndElement(writer); // node - } - - WriteEndElement(writer); // nodes - - WriteStartElement(writer, QStringLiteral("positions")); - - foreach (Node* context, using_node_list) { - const Node::PositionMap &map = context->GetContextPositions(); - - if (!map.isEmpty()) { - WriteStartElement(writer, QStringLiteral("context")); - - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); - - for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { - if (data.GetOnlySerializeNodes().isEmpty() || data.GetOnlySerializeNodes().contains(jt.key())) { - WriteStartElement(writer, QStringLiteral("node")); - SavePosition(writer, jt.key(), jt.value()); - WriteEndElement(writer); // node - } - } - - WriteEndElement(writer); // context - } - } - - WriteEndElement(writer); // positions - - WriteStartElement(writer, QStringLiteral("properties")); - - for (auto it=data.GetProperties().cbegin(); it!=data.GetProperties().cend(); it++) { - WriteStartElement(writer, 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()); - } - - WriteEndElement(writer); // node - } - - WriteEndElement(writer); // properties - - // Save main window project layout - project->GetLayoutInfo().toXml(writer); - - } else { - - qCritical() << "Cannot save nodes without project object"; - - } -} - void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const { while (XMLReadNextStartElement(reader)) { @@ -590,69 +435,6 @@ void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, Q node->LoadFinishedEvent(); } -void ProjectSerializer220403::SaveNode(Node *node, QXmlStreamWriter *writer) const -{ - writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); - - writer->writeTextElement(QStringLiteral("label"), node->GetLabel()); - writer->writeTextElement(QStringLiteral("color"), QString::number(node->GetOverrideColor())); - - foreach (const QString& input, node->inputs()) { - WriteStartElement(writer, QStringLiteral("input")); - - SaveInput(node, writer, input); - - WriteEndElement(writer); // input - } - - WriteStartElement(writer, QStringLiteral("links")); - foreach (Node* link, node->links()) { - writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); - } - WriteEndElement(writer); // links - - WriteStartElement(writer, QStringLiteral("connections")); - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - WriteStartElement(writer, QStringLiteral("connection")); - - writer->writeAttribute(QStringLiteral("input"), it->first.input()); - writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element())); - - writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast(it->second))); - - WriteEndElement(writer); // connection - } - WriteEndElement(writer); // connections - - WriteStartElement(writer, QStringLiteral("hints")); - for (auto it=node->GetValueHints().cbegin(); it!=node->GetValueHints().cend(); it++) { - WriteStartElement(writer, QStringLiteral("hint")); - - writer->writeAttribute(QStringLiteral("input"), it.key().input); - writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element)); - - SaveValueHint(&it.value(), writer); - - WriteEndElement(writer); // hint - } - WriteEndElement(writer); // hints - - WriteStartElement(writer, QStringLiteral("caches")); - - writer->writeTextElement(QStringLiteral("audio"), node->audio_playback_cache()->GetUuid().toString()); - writer->writeTextElement(QStringLiteral("video"), node->video_frame_cache()->GetUuid().toString()); - writer->writeTextElement(QStringLiteral("thumb"), node->thumbnail_cache()->GetUuid().toString()); - writer->writeTextElement(QStringLiteral("waveform"), node->waveform_cache()->GetUuid().toString()); - - WriteEndElement(writer); // caches - - WriteStartElement(writer, QStringLiteral("custom")); - - SaveNodeCustom(writer, node); - - WriteEndElement(writer); // custom -} - void ProjectSerializer220403::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const { if (dynamic_cast(node)) { @@ -716,33 +498,6 @@ void ProjectSerializer220403::LoadInput(Node *node, QXmlStreamReader *reader, XM } } -void ProjectSerializer220403::SaveInput(Node *node, QXmlStreamWriter *writer, const QString &id) const -{ - writer->writeAttribute(QStringLiteral("id"), id); - - WriteStartElement(writer, QStringLiteral("primary")); - - SaveImmediate(writer, node, id, -1); - - WriteEndElement(writer); // primary - - WriteStartElement(writer, QStringLiteral("subelements")); - - int arr_sz = node->InputArraySize(id); - - writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz)); - - for (int i=0; iIsInputKeyframable(input)) { - writer->writeTextElement(QStringLiteral("keyframing"), QString::number(node->IsInputKeyframing(input, element))); - } - - NodeValue::Type data_type = node->GetInputDataType(input); - - // Write standard value - WriteStartElement(writer, QStringLiteral("standard")); - - foreach (const QVariant& v, node->GetSplitStandardValue(input, element)) { - WriteStartElement(writer, QStringLiteral("track")); - - if (data_type == NodeValue::kVideoParams) { - v.value().Save(writer); - } else if (data_type == NodeValue::kAudioParams) { - TypeSerializer::SaveAudioParams(writer, v.value()); - } else { - writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); - } - - WriteEndElement(writer); // track - } - - WriteEndElement(writer); // standard - - // Write keyframes - WriteStartElement(writer, QStringLiteral("keyframes")); - - for (const NodeKeyframeTrack& track : node->GetKeyframeTracks(input, element)) { - WriteStartElement(writer, QStringLiteral("track")); - - for (NodeKeyframe* key : track) { - WriteStartElement(writer, QStringLiteral("key")); - - SaveKeyframe(writer, key, data_type); - - WriteEndElement(writer); // key - } - - WriteEndElement(writer); // track - } - - WriteEndElement(writer); // keyframes - - if (data_type == NodeValue::kColor) { - // Save color management information - writer->writeTextElement(QStringLiteral("csinput"), node->GetInputProperty(input, QStringLiteral("col_input")).toString()); - writer->writeTextElement(QStringLiteral("csdisplay"), node->GetInputProperty(input, QStringLiteral("col_display")).toString()); - writer->writeTextElement(QStringLiteral("csview"), node->GetInputProperty(input, QStringLiteral("col_view")).toString()); - writer->writeTextElement(QStringLiteral("cslook"), node->GetInputProperty(input, QStringLiteral("col_look")).toString()); - } -} - void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const { QString key_input; @@ -929,19 +629,6 @@ void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyfram key->set_bezier_control_out(key_out_handle); } -void ProjectSerializer220403::SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const -{ - writer->writeAttribute(QStringLiteral("input"), key->input()); - writer->writeAttribute(QStringLiteral("time"), QString::fromStdString(key->time().toString())); - writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); - writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); - writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); - writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); - writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); - - writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); -} - bool ProjectSerializer220403::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const { bool got_node_ptr = false; @@ -973,15 +660,6 @@ bool ProjectSerializer220403::LoadPosition(QXmlStreamReader *reader, quintptr *n return got_node_ptr && got_pos_x && got_pos_y; } -void ProjectSerializer220403::SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) const -{ - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); - - writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); - writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); - writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); -} - void ProjectSerializer220403::PostConnect(const XMLNodeData &xml_node_data) const { foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { @@ -1125,63 +803,6 @@ void ProjectSerializer220403::LoadNodeCustom(QXmlStreamReader *reader, Node *nod } } -void ProjectSerializer220403::SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const -{ - if (ViewerOutput *viewer = dynamic_cast(node)) { - // Write TimelinePoints - WriteStartElement(writer, QStringLiteral("points")); - SaveTimelinePoints(writer, viewer); - WriteEndElement(writer); // points - - if (Footage *footage = dynamic_cast(node)) { - writer->writeTextElement(QStringLiteral("timestamp"), QString::number(footage->timestamp())); - } - } else if (Track *track = dynamic_cast(node)) { - writer->writeTextElement(QStringLiteral("height"), QString::number(track->GetTrackHeight())); - } else if (NodeGroup *group = dynamic_cast(node)) { - WriteStartElement(writer, QStringLiteral("inputpassthroughs")); - - foreach (const NodeGroup::InputPassthrough &ip, group->GetInputPassthroughs()) { - WriteStartElement(writer, QStringLiteral("inputpassthrough")); - - // Reference to inner input - writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.second.node()))); - writer->writeTextElement(QStringLiteral("input"), ip.second.input()); - writer->writeTextElement(QStringLiteral("element"), QString::number(ip.second.element())); - - // ID of passthrough - writer->writeTextElement(QStringLiteral("id"), ip.first); - - // Passthrough-specific details - NodeInput group_input(group, ip.first, ip.second.element()); - writer->writeTextElement(QStringLiteral("name"), group->Node::GetInputName(ip.first)); - - writer->writeTextElement(QStringLiteral("flags"), QString::number((group_input.GetFlags() & ~ip.second.GetFlags()).value())); - - NodeValue::Type data_type = group_input.GetDataType(); - writer->writeTextElement(QStringLiteral("type"), NodeValue::GetDataTypeName(data_type)); - - writer->writeTextElement(QStringLiteral("default"), NodeValue::ValueToString(data_type, group_input.GetDefaultValue(), false)); - - WriteStartElement(writer, QStringLiteral("properties")); - auto p = group_input.GetProperties(); - for (auto it=p.cbegin(); it!=p.cend(); it++) { - WriteStartElement(writer, QStringLiteral("property")); - writer->writeTextElement(QStringLiteral("key"), it.key()); - writer->writeTextElement(QStringLiteral("value"), it.value().toString()); - WriteEndElement(writer); // property - } - WriteEndElement(writer); // properties - - WriteEndElement(writer); // input - } - - WriteEndElement(writer); // inputpassthroughs - - writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(group->GetOutputPassthrough()))); - } -} - void ProjectSerializer220403::LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *viewer) const { while (XMLReadNextStartElement(reader)) { @@ -1195,17 +816,6 @@ void ProjectSerializer220403::LoadTimelinePoints(QXmlStreamReader *reader, Viewe } } -void ProjectSerializer220403::SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const -{ - WriteStartElement(writer, QStringLiteral("workarea")); - SaveWorkArea(writer, viewer->GetWorkArea()); - WriteEndElement(writer); // workarea - - WriteStartElement(writer, QStringLiteral("markers")); - SaveMarkerList(writer, viewer->GetMarkers()); - WriteEndElement(writer); // markers -} - void ProjectSerializer220403::LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const { rational in, out; @@ -1228,14 +838,6 @@ void ProjectSerializer220403::LoadMarker(QXmlStreamReader *reader, TimelineMarke reader->skipCurrentElement(); } -void ProjectSerializer220403::SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const -{ - writer->writeAttribute(QStringLiteral("name"), marker->name()); - writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(marker->time().in().toString())); - writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(marker->time().out().toString())); - writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); -} - void ProjectSerializer220403::LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const { rational range_in = workarea->in(); @@ -1260,13 +862,6 @@ void ProjectSerializer220403::LoadWorkArea(QXmlStreamReader *reader, TimelineWor reader->skipCurrentElement(); } -void ProjectSerializer220403::SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const -{ - writer->writeAttribute(QStringLiteral("enabled"), QString::number(workarea->enabled())); - writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(workarea->in().toString())); - writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(workarea->out().toString())); -} - void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const { while (XMLReadNextStartElement(reader)) { @@ -1279,19 +874,6 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } -void ProjectSerializer220403::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const -{ - for (auto it=markers->cbegin(); it!=markers->cend(); it++) { - TimelineMarker* marker = *it; - - WriteStartElement(writer, QStringLiteral("marker")); - - SaveMarker(writer, marker); - - WriteEndElement(writer); // marker - } -} - void ProjectSerializer220403::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const { QVector types; @@ -1317,19 +899,4 @@ void ProjectSerializer220403::LoadValueHint(Node::ValueHint *hint, QXmlStreamRea hint->set_type(types); } -void ProjectSerializer220403::SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const -{ - WriteStartElement(writer, QStringLiteral("types")); - - for (auto it=hint->types().cbegin(); it!=hint->types().cend(); it++) { - writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); - } - - WriteEndElement(writer); // types - - writer->writeTextElement(QStringLiteral("index"), QString::number(hint->index())); - - writer->writeTextElement(QStringLiteral("tag"), hint->tag()); -} - } diff --git a/app/node/project/serializer/serializer220403.h b/app/node/project/serializer/serializer220403.h index fbeb4bfc2..5b4b8f7aa 100644 --- a/app/node/project/serializer/serializer220403.h +++ b/app/node/project/serializer/serializer220403.h @@ -33,8 +33,6 @@ public: protected: virtual LoadData Load(Project *project, QXmlStreamReader *reader, void *reserved) const override; - virtual void Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const override; - virtual uint Version() const override { return 220403; @@ -76,50 +74,28 @@ private: void LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const; - void SaveNode(Node *node, QXmlStreamWriter *writer) const; - void LoadInput(Node *node, QXmlStreamReader* reader, XMLNodeData &xml_node_data) const; - void SaveInput(Node *node, QXmlStreamWriter* writer, const QString& id) const; - void LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData& xml_node_data) const; - void SaveImmediate(QXmlStreamWriter *writer, Node *node, const QString &input, int element) const; - void LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const; - void SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const; - bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const; - void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) const; - void PostConnect(const XMLNodeData &xml_node_data) const; void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; - void SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const; - void LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *viewer) const; - void SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const; - void LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const; - void SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const; - void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; - void SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const; - void LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const; - void SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const; - void LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const; - void SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const; - }; } diff --git a/app/node/project/serializer/serializer230220.cpp b/app/node/project/serializer/serializer230220.cpp new file mode 100644 index 000000000..ed644219a --- /dev/null +++ b/app/node/project/serializer/serializer230220.cpp @@ -0,0 +1,1336 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "serializer230220.h" + +#include "config/config.h" +#include "node/factory.h" +#include "node/group/group.h" + +namespace olive { + +// These wrappers may appear to do nothing, but they seem to address a bug where sometimes +// QXmlStreamWriter would insert nonsense null characters into a project. This is probably some +// real edge case like compiler optimization or some bullshit like that. I don't even know if it +// affects all platforms, but it definitely affected me on Linux. +void WriteStartElement(QXmlStreamWriter *writer, const QString &s) +{ + writer->writeStartElement(s); +} + +void WriteEndElement(QXmlStreamWriter *writer) +{ + writer->writeEndElement(); +} + +ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project, QXmlStreamReader *reader, void *reserved) const +{ + QMap > properties; + QMap > positions; + XMLNodeData xml_node_data; + + LoadData load_data; + + while (XMLReadNextStartElement(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. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + + while (XMLReadNextStartElement(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; + + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } + + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + // Disable cache while node is being loaded (we'll re-enable it later) + node->SetCachesEnabled(false); + + LoadNode(node, xml_node_data, reader); + + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("keyframes")) { + + while (XMLReadNextStartElement(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::CreateFromID(node_id); + } + + if (!n) { + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(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 (XMLReadNextStartElement(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 (XMLReadNextStartElement(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 (XMLReadNextStartElement(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()); + + LoadKeyframe(reader, key, n->GetInputDataType(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 (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("marker")) { + TimelineMarker *marker = new TimelineMarker(); + LoadMarker(reader, marker); + load_data.markers.push_back(marker); + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("positions")) { + + while (XMLReadNextStartElement(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 (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(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 (XMLReadNextStartElement(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 (XMLReadNextStartElement(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->SetNodePositionInContext(n, jt.value()); + } + } + } + } + + // Make connections + PostConnect(xml_node_data); + + // 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 + for (Node *n : project->nodes()) { + n->SetCachesEnabled(true); + } + + return load_data; +} + +void ProjectSerializer230220::Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const +{ + if (!data.GetOnlySerializeMarkers().empty()) { + + WriteStartElement(writer, QStringLiteral("markers")); + + for (auto it=data.GetOnlySerializeMarkers().cbegin(); it!=data.GetOnlySerializeMarkers().cend(); it++) { + TimelineMarker *marker = *it; + + WriteStartElement(writer, QStringLiteral("marker")); + + SaveMarker(writer, marker); + + WriteEndElement(writer); // marker + } + + WriteEndElement(writer); // markers + + } else if (!data.GetOnlySerializeKeyframes().empty()) { + + WriteStartElement(writer, QStringLiteral("keyframes")); + + // Organize keyframes into node+input + QHash > > > > organized; + + for (auto it=data.GetOnlySerializeKeyframes().cbegin(); it!=data.GetOnlySerializeKeyframes().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++) { + WriteStartElement(writer, QStringLiteral("node")); + + writer->writeAttribute(QStringLiteral("id"), it.key()); + + for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { + WriteStartElement(writer, QStringLiteral("input")); + + writer->writeAttribute(QStringLiteral("id"), jt.key()); + + for (auto kt=jt.value().cbegin(); kt!=jt.value().cend(); kt++) { + WriteStartElement(writer, 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(); + + WriteStartElement(writer, QStringLiteral("track")); + + writer->writeAttribute(QStringLiteral("id"), QString::number(lt.key())); + + for (NodeKeyframe *key : keys) { + WriteStartElement(writer, QStringLiteral("key")); + SaveKeyframe(writer, key, key->parent()->GetInputDataType(key->input())); + WriteEndElement(writer); // key + } + + WriteEndElement(writer); // track + } + + WriteEndElement(writer); // element + } + + WriteEndElement(writer); // input + } + + WriteEndElement(writer); // node; + } + + WriteEndElement(writer); // keyframes + + } else if (Project *project = data.GetProject()) { + + writer->writeTextElement(QStringLiteral("uuid"), project->GetUuid().toString()); + + WriteStartElement(writer, QStringLiteral("nodes")); + + const QVector &using_node_list = (data.GetOnlySerializeNodes().isEmpty()) ? project->nodes() : data.GetOnlySerializeNodes(); + + foreach (Node* node, using_node_list) { + WriteStartElement(writer, QStringLiteral("node")); + + if (node == project->root()) { + writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); + } else if (node == project->color_manager()) { + writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1")); + } else if (node == project->settings()) { + writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1")); + } + + writer->writeAttribute(QStringLiteral("id"), node->id()); + + SaveNode(node, writer); + + WriteEndElement(writer); // node + } + + WriteEndElement(writer); // nodes + + WriteStartElement(writer, QStringLiteral("positions")); + + foreach (Node* context, using_node_list) { + const Node::PositionMap &map = context->GetContextPositions(); + + if (!map.isEmpty()) { + WriteStartElement(writer, QStringLiteral("context")); + + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); + + for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { + if (data.GetOnlySerializeNodes().isEmpty() || data.GetOnlySerializeNodes().contains(jt.key())) { + WriteStartElement(writer, QStringLiteral("node")); + SavePosition(writer, jt.key(), jt.value()); + WriteEndElement(writer); // node + } + } + + WriteEndElement(writer); // context + } + } + + WriteEndElement(writer); // positions + + WriteStartElement(writer, QStringLiteral("properties")); + + for (auto it=data.GetProperties().cbegin(); it!=data.GetProperties().cend(); it++) { + WriteStartElement(writer, 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()); + } + + WriteEndElement(writer); // node + } + + WriteEndElement(writer); // properties + + // Save main window project layout + project->GetLayoutInfo().toXml(writer); + + } else { + + qCritical() << "Cannot save nodes without project object"; + + } +} + +void ProjectSerializer230220::LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const +{ + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("input")) { + LoadInput(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->SetLabel(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->SetOverrideColor(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("links")) { + while (XMLReadNextStartElement(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")) { + LoadNodeCustom(reader, node, xml_node_data); + } else if (reader->name() == QStringLiteral("connections")) { + // Load connections + while (XMLReadNextStartElement(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 (XMLReadNextStartElement(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 (XMLReadNextStartElement(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; + LoadValueHint(&vh, reader); + node->SetValueHintForInput(input, vh, element); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("caches")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("audio")) { + node->audio_playback_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("video")) { + node->video_frame_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("thumb")) { + node->thumbnail_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("waveform")) { + node->waveform_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + + node->LoadFinishedEvent(); +} + +void ProjectSerializer230220::SaveNode(Node *node, QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); + + writer->writeTextElement(QStringLiteral("label"), node->GetLabel()); + writer->writeTextElement(QStringLiteral("color"), QString::number(node->GetOverrideColor())); + + foreach (const QString& input, node->inputs()) { + WriteStartElement(writer, QStringLiteral("input")); + + SaveInput(node, writer, input); + + WriteEndElement(writer); // input + } + + WriteStartElement(writer, QStringLiteral("links")); + foreach (Node* link, node->links()) { + writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); + } + WriteEndElement(writer); // links + + WriteStartElement(writer, QStringLiteral("connections")); + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + WriteStartElement(writer, QStringLiteral("connection")); + + writer->writeAttribute(QStringLiteral("input"), it->first.input()); + writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element())); + + writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast(it->second))); + + WriteEndElement(writer); // connection + } + WriteEndElement(writer); // connections + + WriteStartElement(writer, QStringLiteral("hints")); + for (auto it=node->GetValueHints().cbegin(); it!=node->GetValueHints().cend(); it++) { + WriteStartElement(writer, QStringLiteral("hint")); + + writer->writeAttribute(QStringLiteral("input"), it.key().input); + writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element)); + + SaveValueHint(&it.value(), writer); + + WriteEndElement(writer); // hint + } + WriteEndElement(writer); // hints + + WriteStartElement(writer, QStringLiteral("caches")); + + writer->writeTextElement(QStringLiteral("audio"), node->audio_playback_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("video"), node->video_frame_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("thumb"), node->thumbnail_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("waveform"), node->waveform_cache()->GetUuid().toString()); + + WriteEndElement(writer); // caches + + WriteStartElement(writer, QStringLiteral("custom")); + + SaveNodeCustom(writer, node); + + WriteEndElement(writer); // custom +} + +void ProjectSerializer230220::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const +{ + if (dynamic_cast(node)) { + // Ignore input of group + reader->skipCurrentElement(); + return; + } + + 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->HasInputWithID(param_id)) { + qWarning() << "Failed to load parameter that didn't exist:" << param_id; + reader->skipCurrentElement(); + return; + } + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("primary")) { + // Load primary immediate + LoadImmediate(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->InputArrayResize(param_id, attr.value().toInt()); + } + } + + int element_counter = 0; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("element")) { + LoadImmediate(reader, node, param_id, element_counter, xml_node_data); + + element_counter++; + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer230220::SaveInput(Node *node, QXmlStreamWriter *writer, const QString &id) const +{ + writer->writeAttribute(QStringLiteral("id"), id); + + WriteStartElement(writer, QStringLiteral("primary")); + + SaveImmediate(writer, node, id, -1); + + WriteEndElement(writer); // primary + + WriteStartElement(writer, QStringLiteral("subelements")); + + int arr_sz = node->InputArraySize(id); + + writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz)); + + for (int i=0; iGetInputDataType(input); + + // HACK: SubtitleParams contain the actual subtitle data, so loading/replacing it will overwrite + // the valid subtitles. We hack around it by simply skipping loading subtitles, we'll see + // if this ends up being an issue in the future. + if (data_type == NodeValue::kSubtitleParams) { + reader->skipCurrentElement(); + return; + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("standard")) { + // Load standard value + int val_index = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + QVariant value_on_track; + + if (data_type == NodeValue::kVideoParams) { + VideoParams vp; + vp.Load(reader); + value_on_track = QVariant::fromValue(vp); + } else if (data_type == NodeValue::kAudioParams) { + AudioParams ap = TypeSerializer::LoadAudioParams(reader); + value_on_track = QVariant::fromValue(ap); + } else { + QString value_text = reader->readElementText(); + + if (!value_text.isEmpty()) { + value_on_track = NodeValue::StringToValue(data_type, value_text, true); + } + } + + node->SetSplitStandardValueOnTrack(input, val_index, value_on_track, element); + + val_index++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("keyframing") && node->IsInputKeyframable(input)) { + node->SetInputIsKeyframing(input, reader->readElementText().toInt(), element); + } else if (reader->name() == QStringLiteral("keyframes")) { + int track = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("key")) { + NodeKeyframe* key = new NodeKeyframe(); + key->set_input(input); + key->set_element(element); + key->set_track(track); + + LoadKeyframe(reader, key, data_type); + key->setParent(node); + } else { + reader->skipCurrentElement(); + } + } + + track++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("csinput")) { + node->SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csdisplay")) { + node->SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csview")) { + node->SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("cslook")) { + node->SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer230220::SaveImmediate(QXmlStreamWriter *writer, Node *node, const QString& input, int element) const +{ + if (node->IsInputKeyframable(input)) { + writer->writeTextElement(QStringLiteral("keyframing"), QString::number(node->IsInputKeyframing(input, element))); + } + + NodeValue::Type data_type = node->GetInputDataType(input); + + // Write standard value + WriteStartElement(writer, QStringLiteral("standard")); + + foreach (const QVariant& v, node->GetSplitStandardValue(input, element)) { + WriteStartElement(writer, QStringLiteral("track")); + + if (data_type == NodeValue::kVideoParams) { + v.value().Save(writer); + } else if (data_type == NodeValue::kAudioParams) { + TypeSerializer::SaveAudioParams(writer, v.value()); + } else { + writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); + } + + WriteEndElement(writer); // track + } + + WriteEndElement(writer); // standard + + // Write keyframes + WriteStartElement(writer, QStringLiteral("keyframes")); + + for (const NodeKeyframeTrack& track : node->GetKeyframeTracks(input, element)) { + WriteStartElement(writer, QStringLiteral("track")); + + for (NodeKeyframe* key : track) { + WriteStartElement(writer, QStringLiteral("key")); + + SaveKeyframe(writer, key, data_type); + + WriteEndElement(writer); // key + } + + WriteEndElement(writer); // track + } + + WriteEndElement(writer); // keyframes + + if (data_type == NodeValue::kColor) { + // Save color management information + writer->writeTextElement(QStringLiteral("csinput"), node->GetInputProperty(input, QStringLiteral("col_input")).toString()); + writer->writeTextElement(QStringLiteral("csdisplay"), node->GetInputProperty(input, QStringLiteral("col_display")).toString()); + writer->writeTextElement(QStringLiteral("csview"), node->GetInputProperty(input, QStringLiteral("col_view")).toString()); + writer->writeTextElement(QStringLiteral("cslook"), node->GetInputProperty(input, QStringLiteral("col_look")).toString()); + } +} + +void ProjectSerializer230220::LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const +{ + QString key_input; + QPointF key_in_handle; + QPointF key_out_handle; + + XMLAttributeLoop(reader, attr) { + if (IsCancelled()) { + return; + } + + if (attr.name() == QStringLiteral("input")) { + key_input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("time")) { + key->set_time(rational::fromString(attr.value().toString().toStdString())); + } else if (attr.name() == QStringLiteral("type")) { + key->set_type_no_bezier_adj(static_cast(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->set_value(NodeValue::StringToValue(data_type, reader->readElementText(), true)); + + key->set_bezier_control_in(key_in_handle); + key->set_bezier_control_out(key_out_handle); +} + +void ProjectSerializer230220::SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const +{ + writer->writeAttribute(QStringLiteral("input"), key->input()); + writer->writeAttribute(QStringLiteral("time"), QString::fromStdString(key->time().toString())); + writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); + writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); + writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); + writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); + writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); + + writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); +} + +bool ProjectSerializer230220::LoadPosition(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 (XMLReadNextStartElement(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 ProjectSerializer230220::SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) const +{ + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); + + writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); + writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); + writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); +} + +void ProjectSerializer230220::PostConnect(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::ConnectEdge(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->AddInputPassthrough(resolved, l.passthrough_id); + + l.group->SetInputFlags(l.passthrough_id, resolved.GetFlags() | l.custom_flags); + + if (!l.custom_name.isEmpty()) { + l.group->SetInputName(l.passthrough_id, l.custom_name); + } + + l.group->SetInputDataType(l.passthrough_id, l.data_type); + + l.group->SetDefaultValue(l.passthrough_id, l.default_val); + + for (auto it=l.custom_properties.cbegin(); it!=l.custom_properties.cend(); it++) { + l.group->SetInputProperty(l.passthrough_id, it.key(), it.value()); + } + } + } + + 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()->SetOutputPassthrough(output_node); + } + } +} + +void ProjectSerializer230220::LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const +{ + // Viewer-based nodes + if (ViewerOutput *viewer = dynamic_cast(node)) { + + Footage *footage = dynamic_cast(node); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("points")) { + LoadTimelinePoints(reader, viewer); + } else if (reader->name() == QStringLiteral("timestamp") && footage) { + footage->set_timestamp(reader->readElementText().toLongLong()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (Track *track = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("height")) { + track->SetTrackHeight(reader->readElementText().toDouble()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (NodeGroup *group = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + XMLNodeData::GroupLink link; + + link.group = group; + + while (XMLReadNextStartElement(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 if (reader->name() == QStringLiteral("id")) { + link.passthrough_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("name")) { + link.custom_name = reader->readElementText(); + } else if (reader->name() == QStringLiteral("flags")) { + link.custom_flags = InputFlags(reader->readElementText().toULongLong()); + } else if (reader->name() == QStringLiteral("type")) { + link.data_type = NodeValue::GetDataTypeFromName(reader->readElementText()); + } else if (reader->name() == QStringLiteral("default")) { + link.default_val = NodeValue::StringToValue(link.data_type, reader->readElementText(), false); + } else if (reader->name() == QStringLiteral("properties")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("property")) { + QString key; + QString value; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("key")) { + key = reader->readElementText(); + } else if (reader->name() == QStringLiteral("value")) { + value = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + + if (!key.isEmpty()) { + link.custom_properties.insert(key, value); + } + } else { + reader->skipCurrentElement(); + } + } + } 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 ProjectSerializer230220::SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const +{ + if (ViewerOutput *viewer = dynamic_cast(node)) { + // Write TimelinePoints + WriteStartElement(writer, QStringLiteral("points")); + SaveTimelinePoints(writer, viewer); + WriteEndElement(writer); // points + + if (Footage *footage = dynamic_cast(node)) { + writer->writeTextElement(QStringLiteral("timestamp"), QString::number(footage->timestamp())); + } + } else if (Track *track = dynamic_cast(node)) { + writer->writeTextElement(QStringLiteral("height"), QString::number(track->GetTrackHeight())); + } else if (NodeGroup *group = dynamic_cast(node)) { + WriteStartElement(writer, QStringLiteral("inputpassthroughs")); + + foreach (const NodeGroup::InputPassthrough &ip, group->GetInputPassthroughs()) { + WriteStartElement(writer, QStringLiteral("inputpassthrough")); + + // Reference to inner input + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.second.node()))); + writer->writeTextElement(QStringLiteral("input"), ip.second.input()); + writer->writeTextElement(QStringLiteral("element"), QString::number(ip.second.element())); + + // ID of passthrough + writer->writeTextElement(QStringLiteral("id"), ip.first); + + // Passthrough-specific details + NodeInput group_input(group, ip.first, ip.second.element()); + writer->writeTextElement(QStringLiteral("name"), group->Node::GetInputName(ip.first)); + + writer->writeTextElement(QStringLiteral("flags"), QString::number((group_input.GetFlags() & ~ip.second.GetFlags()).value())); + + NodeValue::Type data_type = group_input.GetDataType(); + writer->writeTextElement(QStringLiteral("type"), NodeValue::GetDataTypeName(data_type)); + + writer->writeTextElement(QStringLiteral("default"), NodeValue::ValueToString(data_type, group_input.GetDefaultValue(), false)); + + WriteStartElement(writer, QStringLiteral("properties")); + auto p = group_input.GetProperties(); + for (auto it=p.cbegin(); it!=p.cend(); it++) { + WriteStartElement(writer, QStringLiteral("property")); + writer->writeTextElement(QStringLiteral("key"), it.key()); + writer->writeTextElement(QStringLiteral("value"), it.value().toString()); + WriteEndElement(writer); // property + } + WriteEndElement(writer); // properties + + WriteEndElement(writer); // input + } + + WriteEndElement(writer); // inputpassthroughs + + writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(group->GetOutputPassthrough()))); + } +} + +void ProjectSerializer230220::LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *viewer) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("markers")) { + LoadMarkerList(reader, viewer->GetMarkers()); + } else if (reader->name() == QStringLiteral("workarea")) { + LoadWorkArea(reader, viewer->GetWorkArea()); + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer230220::SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const +{ + WriteStartElement(writer, QStringLiteral("workarea")); + SaveWorkArea(writer, viewer->GetWorkArea()); + WriteEndElement(writer); // workarea + + WriteStartElement(writer, QStringLiteral("markers")); + SaveMarkerList(writer, viewer->GetMarkers()); + WriteEndElement(writer); // markers +} + +void ProjectSerializer230220::LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const +{ + rational in, out; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("name")) { + marker->set_name(attr.value().toString()); + } else if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString().toStdString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString().toStdString()); + } else if (attr.name() == QStringLiteral("color")) { + marker->set_color(attr.value().toInt()); + } + } + + marker->set_time(TimeRange(in, out)); + + // This element has no inner text, so just skip it + reader->skipCurrentElement(); +} + +void ProjectSerializer230220::SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const +{ + writer->writeAttribute(QStringLiteral("name"), marker->name()); + writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(marker->time().in().toString())); + writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(marker->time().out().toString())); + writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); +} + +void ProjectSerializer230220::LoadWorkArea(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::fromString(attr.value().toString().toStdString()); + } else if (attr.name() == QStringLiteral("out")) { + range_out = rational::fromString(attr.value().toString().toStdString()); + } + } + + TimeRange loaded_workarea(range_in, range_out); + + if (loaded_workarea != workarea->range()) { + workarea->set_range(loaded_workarea); + } + + reader->skipCurrentElement(); +} + +void ProjectSerializer230220::SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const +{ + writer->writeAttribute(QStringLiteral("enabled"), QString::number(workarea->enabled())); + writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(workarea->in().toString())); + writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(workarea->out().toString())); +} + +void ProjectSerializer230220::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("marker")) { + TimelineMarker *marker = new TimelineMarker(markers); + LoadMarker(reader, marker); + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer230220::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const +{ + for (auto it=markers->cbegin(); it!=markers->cend(); it++) { + TimelineMarker* marker = *it; + + WriteStartElement(writer, QStringLiteral("marker")); + + SaveMarker(writer, marker); + + WriteEndElement(writer); // marker + } +} + +void ProjectSerializer230220::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const +{ + QVector types; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("types")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("type")) { + types.append(static_cast(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); +} + +void ProjectSerializer230220::SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const +{ + WriteStartElement(writer, QStringLiteral("types")); + + for (auto it=hint->types().cbegin(); it!=hint->types().cend(); it++) { + writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); + } + + WriteEndElement(writer); // types + + writer->writeTextElement(QStringLiteral("index"), QString::number(hint->index())); + + writer->writeTextElement(QStringLiteral("tag"), hint->tag()); +} + +} + diff --git a/app/node/project/serializer/serializer230220.h b/app/node/project/serializer/serializer230220.h new file mode 100644 index 000000000..76b9c66f3 --- /dev/null +++ b/app/node/project/serializer/serializer230220.h @@ -0,0 +1,127 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 . + +***/ + +#ifndef PROJECTSERIALIZER230220_H +#define PROJECTSERIALIZER230220_H + +#include "serializer.h" + +namespace olive { + +class ProjectSerializer230220 : public ProjectSerializer +{ +public: + ProjectSerializer230220() = default; + +protected: + virtual LoadData Load(Project *project, QXmlStreamReader *reader, void *reserved) const override; + + virtual void Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const override; + + virtual uint Version() const override + { + return 230220; + } + +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 custom_properties; + }; + + QHash node_ptrs; + QList desired_connections; + QList block_links; + QVector group_input_links; + QHash group_output_links; + QHash node_uuids; + + }; + + void LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const; + + void SaveNode(Node *node, QXmlStreamWriter *writer) const; + + void LoadInput(Node *node, QXmlStreamReader* reader, XMLNodeData &xml_node_data) const; + + void SaveInput(Node *node, QXmlStreamWriter* writer, const QString& id) const; + + void LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData& xml_node_data) const; + + void SaveImmediate(QXmlStreamWriter *writer, Node *node, const QString &input, int element) const; + + void LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const; + + void SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const; + + bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const; + + void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) const; + + void PostConnect(const XMLNodeData &xml_node_data) const; + + void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; + + void SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const; + + void LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *viewer) const; + + void SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const; + + void LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const; + + void SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const; + + void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; + + void SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const; + + void LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const; + + void SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const; + + void LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const; + + void SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const; + +}; + +} + +#endif // PROJECTSERIALIZER230220_H diff --git a/app/node/value.cpp b/app/node/value.cpp index 763bd96e5..dab17809e 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -77,6 +77,8 @@ QString NodeValue::ValueToString(Type data_type, const QVariant &value, bool val return QString(); } else if (data_type == kInt) { return QString::number(value.value()); + } else if (data_type == kBinary) { + return value.toByteArray().toBase64(); } else { if (value.canConvert()) { return value.toString(); @@ -243,6 +245,8 @@ QVariant NodeValue::StringToValue(Type data_type, const QString &string, bool va return QVariant::fromValue(string.toLongLong()); } else if (data_type == kRational) { return QVariant::fromValue(rational::fromString(string.toStdString())); + } else if (data_type == kBinary) { + return QByteArray::fromBase64(string.toLatin1()); } else { return string; } @@ -297,6 +301,8 @@ QString NodeValue::GetPrettyDataTypeName(Type type) return QCoreApplication::translate("NodeValue", "Audio Parameters"); case kSubtitleParams: return QCoreApplication::translate("NodeValue", "Subtitle Parameters"); + case kBinary: + return QCoreApplication::translate("NodeValue", "Binary"); case kDataTypeCount: break; @@ -348,6 +354,8 @@ QString NodeValue::GetDataTypeName(Type type) return QStringLiteral("aparam"); case kSubtitleParams: return QStringLiteral("sparam"); + case kBinary: + return QStringLiteral("binary"); case kDataTypeCount: break; } diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index decdfe709..b7451395f 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -497,6 +497,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video case NodeValue::kAudioParams: case NodeValue::kSubtitleParams: case NodeValue::kBezier: + case NodeValue::kBinary: case NodeValue::kNone: case NodeValue::kDataTypeCount: break; diff --git a/app/render/projectcopier.cpp b/app/render/projectcopier.cpp index 2ff55dae2..91f574b63 100644 --- a/app/render/projectcopier.cpp +++ b/app/render/projectcopier.cpp @@ -238,6 +238,14 @@ void ProjectCopier::QueueEdgeRemove(Node *output, const NodeInput &input) void ProjectCopier::QueueValueChange(const NodeInput &input) { + for (auto it = graph_update_queue_.begin(); it != graph_update_queue_.end(); ) { + if (it->type == QueuedJob::kValueChanged && it->input == input) { + it = graph_update_queue_.erase(it); + } else { + it++; + } + } + graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr}); UpdateGraphChangeValue(); } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index c43192927..23b9d59f0 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -86,6 +86,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeValue::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kSubtitleParams: + case NodeValue::kBinary: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: @@ -238,6 +239,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kSubtitleParams: + case NodeValue::kBinary: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: @@ -416,6 +418,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeValue::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kSubtitleParams: + case NodeValue::kBinary: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: