Files
oak-editor/src/node/c_api/serializer.cpp
T
Mike-Solar 6ded1d2d63 refactor(task): de-Qt oaktask base + project tasks, wire codec task submitter
- Task base: Qt signals become lifecycle listeners (the async-command
  callback exception), cancellation uses oakrender's OakCancelAtom C
  handle; TaskManager runs std::thread workers, no signals
- ConformTask/ProxyTask implement oakcodec's submit-callback contract
  (synchronous interim); register_codec_task_submitter() closes the
  conform/proxy loop - conform of demo.mp4 produces pcm caches in tests
- ProjectImportTask/ProjectLoadTask/ProjectSaveTask over the oaknode
  C ABI; image-sequence confirmation becomes a facade callback
  (default: not a sequence)
- C API additions needed by oaktask: oaknode serializer file-level
  save/load (auto-initializing), footage video-params/cancel-atom/
  as-node, folder add-child command factory; oakcodec decoder
  conform_audio + image-sequence helpers; oakrender cancelatom
  get_native
- fix double-ownership: submitting a task to the manager transfers
  ownership, freeing its handle only releases the wrapper
- C ABI in include/task (task/manager/project, OakTaskTask opaque
  handle + lifecycle subscribe), every function tested (103 cases in
  build-oaktask incl. regressions)
- RenderTask family and OTIO tasks follow in separate commits
2026-08-07 14:01:23 +08:00

433 lines
10 KiB
C++

/***
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 <http://www.gnu.org/licenses/>.
***/
#include "node/serializer.h"
#include <cstring>
#include <new>
#include <string>
#include <vector>
#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<olive::Project *>(project);
}
olive::Node *to_cpp(OakNodeNode *node)
{
return reinterpret_cast<olive::Node *>(node);
}
OakNodeNode *to_c(olive::Node *node)
{
return reinterpret_cast<OakNodeNode *>(node);
}
bool is_valid_load_type(int load_type)
{
return load_type >= static_cast<int>(olive::ProjectSerializer::k_project) &&
load_type <=
static_cast<int>(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<int>(value.size()) + 1;
if (buf && buf_size > 0) {
size_t copy_len = value.size();
if (copy_len > static_cast<size_t>(buf_size) - 1) {
copy_len = static_cast<size_t>(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<olive::ProjectSerializer::LoadType>(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<olive::Node *> cpp_nodes;
cpp_nodes.reserve(static_cast<size_t>(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<olive::ProjectSerializer::LoadType>(load_type));
*out_result = static_cast<int>(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<int>(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<size_t>(index) >= load_data->impl.nodes.size()) {
return NULL;
}
try {
return to_c(load_data->impl.nodes[static_cast<size_t>(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<int>(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<size_t>(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<size_t>(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<olive::Project *>(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<olive::Project *>(project), filename,
olive::ProjectSerializer::k_project);
return report_serializer_result(result, out_code, details,
details_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}