/*** Oak Video Editor - Non-Linear Video Editor Copyright (C) 2026 Oak Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "node/serializer.h" #include #include #include #include #include "../src/factory.h" #include "../src/project.h" #include "../src/project/serializer/serializer.h" #include "xmlutils.h" struct OakNodeSerializerSaveData { olive::ProjectSerializer::SaveData impl; OakNodeSerializerSaveData(olive::ProjectSerializer::LoadType type, olive::Project *project) : impl(type, project) { } }; struct OakNodeSerializerLoadData { olive::ProjectSerializer::LoadData impl; }; namespace { bool g_initialized = false; olive::Project *to_cpp(OakNodeProject *project) { return reinterpret_cast(project); } olive::Node *to_cpp(OakNodeNode *node) { return reinterpret_cast(node); } OakNodeNode *to_c(olive::Node *node) { return reinterpret_cast(node); } bool is_valid_load_type(int load_type) { return load_type >= static_cast(olive::ProjectSerializer::k_project) && load_type <= static_cast(olive::ProjectSerializer::k_only_keyframes); } /** * @brief Shared two-stage string getter. * * Returns the required buffer size in bytes (including the terminating * NUL) as a non-negative value. */ int copy_string(const std::string &value, char *buf, int buf_size) { int required = static_cast(value.size()) + 1; if (buf && buf_size > 0) { size_t copy_len = value.size(); if (copy_len > static_cast(buf_size) - 1) { copy_len = static_cast(buf_size) - 1; } memcpy(buf, value.data(), copy_len); buf[copy_len] = '\0'; } return required; } } // namespace int oaknode_serializer_initialize(void) { if (g_initialized) { return OAKNODE_OK; } try { // The loaders instantiate nodes by id through the factory. olive::NodeFactory::initialize(); olive::ProjectSerializer::initialize(); g_initialized = true; return OAKNODE_OK; } catch (...) { return OAKNODE_E_FAILED; } } void oaknode_serializer_shutdown(void) { if (!g_initialized) { return; } try { olive::ProjectSerializer::destroy(); olive::NodeFactory::destroy(); } catch (...) { } g_initialized = false; } OakNodeSerializerSaveData *oaknode_serializer_savedata_create( int load_type, OakNodeProject *project) { if (!is_valid_load_type(load_type)) { return NULL; } try { return new (std::nothrow) OakNodeSerializerSaveData( static_cast(load_type), to_cpp(project)); } catch (...) { return NULL; } } void oaknode_serializer_savedata_free(OakNodeSerializerSaveData *save_data) { delete save_data; } int oaknode_serializer_savedata_set_nodes( OakNodeSerializerSaveData *save_data, OakNodeNode *const *nodes, int count) { if (!save_data || !nodes || count < 0) { return OAKNODE_E_INVALID; } try { std::vector cpp_nodes; cpp_nodes.reserve(static_cast(count)); for (int i = 0; i < count; i++) { if (!nodes[i]) { return OAKNODE_E_INVALID; } cpp_nodes.push_back(to_cpp(nodes[i])); } save_data->impl.set_only_serialize_nodes(cpp_nodes); return OAKNODE_OK; } catch (...) { return OAKNODE_E_FAILED; } } int oaknode_serializer_savedata_set_property( OakNodeSerializerSaveData *save_data, OakNodeNode *node, const char *key, const char *value) { if (!save_data || !node || !key || !value) { return OAKNODE_E_INVALID; } try { olive::ProjectSerializer::SerializedProperties properties = save_data->impl.get_properties(); properties[to_cpp(node)][key] = value; save_data->impl.set_properties(properties); return OAKNODE_OK; } catch (...) { return OAKNODE_E_FAILED; } } int oaknode_serializer_save_to_xml(OakNodeSerializerSaveData *save_data, char *buf, int buf_size) { if (!save_data) { return OAKNODE_E_INVALID; } if (!g_initialized) { return OAKNODE_E_STATE; } try { olive::XmlStreamWriter writer; olive::ProjectSerializer::Result result = olive::ProjectSerializer::save(&writer, save_data->impl); if (result != olive::ProjectSerializer::k_success) { return OAKNODE_E_FAILED; } return copy_string(writer.output(), buf, buf_size); } catch (...) { return OAKNODE_E_FAILED; } } int oaknode_serializer_load_from_xml(OakNodeProject *project, const char *xml, int load_type, int *out_result, OakNodeSerializerLoadData **out_load_data, char *details_buf, int details_buf_size) { if (!xml || !out_result || !is_valid_load_type(load_type)) { return OAKNODE_E_INVALID; } if (!g_initialized) { return OAKNODE_E_STATE; } if (out_load_data) { *out_load_data = NULL; } try { olive::XmlStreamReader reader(xml); olive::ProjectSerializer::Result result = olive::ProjectSerializer::load( to_cpp(project), &reader, static_cast(load_type)); *out_result = static_cast(result.code()); if (details_buf && details_buf_size > 0) { copy_string(result.get_details(), details_buf, details_buf_size); } if (result == olive::ProjectSerializer::k_success && out_load_data) { auto *load_data = new (std::nothrow) OakNodeSerializerLoadData(); if (!load_data) { return OAKNODE_E_NOMEM; } load_data->impl = result.get_load_data(); *out_load_data = load_data; } return OAKNODE_OK; } catch (...) { return OAKNODE_E_FAILED; } } void oaknode_serializer_loaddata_free(OakNodeSerializerLoadData *load_data) { delete load_data; } int oaknode_serializer_loaddata_node_count( const OakNodeSerializerLoadData *load_data) { if (!load_data) { return OAKNODE_E_INVALID; } try { return static_cast(load_data->impl.nodes.size()); } catch (...) { return OAKNODE_E_FAILED; } } OakNodeNode *oaknode_serializer_loaddata_node_at( const OakNodeSerializerLoadData *load_data, int index) { if (!load_data || index < 0 || static_cast(index) >= load_data->impl.nodes.size()) { return NULL; } try { return to_c(load_data->impl.nodes[static_cast(index)]); } catch (...) { return NULL; } } int oaknode_serializer_loaddata_get_property( const OakNodeSerializerLoadData *load_data, OakNodeNode *node, const char *key, char *buf, int buf_size) { if (!load_data || !node || !key) { return OAKNODE_E_INVALID; } try { auto node_it = load_data->impl.properties.find(to_cpp(node)); if (node_it == load_data->impl.properties.end()) { return OAKNODE_E_NOT_FOUND; } auto key_it = node_it->second.find(key); if (key_it == node_it->second.end()) { return OAKNODE_E_NOT_FOUND; } return copy_string(key_it->second, buf, buf_size); } catch (...) { return OAKNODE_E_FAILED; } } int oaknode_serializer_loaddata_connection_count( const OakNodeSerializerLoadData *load_data) { if (!load_data) { return OAKNODE_E_INVALID; } try { return static_cast(load_data->impl.promised_connections.size()); } catch (...) { return OAKNODE_E_FAILED; } } int oaknode_serializer_loaddata_connection_at( const OakNodeSerializerLoadData *load_data, int index, OakNodeNode **out_output_node, OakNodeNode **out_input_node, char *input_id_buf, int input_id_buf_size, int *out_element) { if (!load_data || !out_output_node || !out_input_node || !out_element) { return OAKNODE_E_INVALID; } if (index < 0 || static_cast(index) >= load_data->impl.promised_connections.size()) { return OAKNODE_E_NOT_FOUND; } try { const olive::Node::OutputConnection &connection = load_data->impl.promised_connections[static_cast(index)]; *out_output_node = to_c(connection.first); *out_input_node = to_c(connection.second.node()); if (input_id_buf && input_id_buf_size > 0) { copy_string(connection.second.input(), input_id_buf, input_id_buf_size); } *out_element = connection.second.element(); return OAKNODE_OK; } catch (...) { return OAKNODE_E_FAILED; } } namespace { int report_serializer_result(const olive::ProjectSerializer::Result &result, int *out_code, char *details, int details_size) { if (out_code) { *out_code = int(result.code()); } int needed = 0; if (details || details_size > 0) { std::string d = result.get_details(); needed = int(d.size()) + 1; if (details && details_size >= needed) { memcpy(details, d.c_str(), needed); } } if (result.code() == olive::ProjectSerializer::k_success) { return OAKNODE_OK; } return (details && details_size > 0) ? needed : OAKNODE_E_FAILED; } } // namespace int oaknode_serializer_save_to_file(OakNodeProject *project, const char *filename, int use_compression, int *out_code, char *details, int details_size) { if (!project || !filename) { return OAKNODE_E_INVALID; } try { oaknode_serializer_initialize(); olive::ProjectSerializer::SaveData data( olive::ProjectSerializer::k_project, reinterpret_cast(project), filename); olive::ProjectSerializer::Result result = olive::ProjectSerializer::save(data, use_compression != 0); return report_serializer_result(result, out_code, details, details_size); } catch (...) { return OAKNODE_E_FAILED; } } int oaknode_serializer_load_from_file(OakNodeProject *project, const char *filename, int *out_code, char *details, int details_size) { if (!project || !filename) { return OAKNODE_E_INVALID; } try { oaknode_serializer_initialize(); olive::ProjectSerializer::Result result = olive::ProjectSerializer::load( reinterpret_cast(project), filename, olive::ProjectSerializer::k_project); return report_serializer_result(result, out_code, details, details_size); } catch (...) { return OAKNODE_E_FAILED; } }