refactor(node): de-Qt oaknode and wrap it in a pure C ABI

- copy engine/node (188 files) to src/node/src, de-Qt in waves:
  core infra (Node/Param/Value/Variant/mathtypes), project/serializer,
  block/output, color, effect leaves, generator, gizmo/plugins
- strip QObject/signals/slots: notifications move to the facade's
  oakengine_event channel, ownership becomes explicit (unique_ptr,
  add_keyframe/add_gizmo), sender() replaced by current_gizmo
- QVariant replaced by olive::Variant, Qt math types by POD mathtypes,
  QXmlStreamReader/Writer by oakcommon's expat-based classes
- sink VideoParams/SubtitleParams/LoopMode/ColorTransform to oakcommon
  (M3.5); polygon/text rasterization behind backend hooks
- pure C ABI in include/node + src/node/c_api (oaknode_ prefix,
  OAKNODE_E_* codes, undoable variants take OakUndoCommand out-params)
- fix Project::clear() root_ reset + disconnect assert, Sequence
  TrackList leak
- 96 gtest cases green in standalone build (build-oaknode)
- docs: signal/slot handling strategy + M3 implementation status
This commit is contained in:
2026-08-05 23:55:54 +08:00
parent c50017127b
commit d77348ad9f
375 changed files with 59682 additions and 1 deletions
+15
View File
@@ -0,0 +1,15 @@
target_sources(oaknode PRIVATE
block.cpp
colormanager.cpp
factory.cpp
folder.cpp
footage.cpp
group.cpp
keyframe.cpp
node.cpp
project.cpp
sequence.cpp
serializer.cpp
track.cpp
traverser.cpp
)
+39
View File
@@ -0,0 +1,39 @@
/***
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/>.
***/
#ifndef OAK_EDITOR_NODE_ALIVECOUNT_H
#define OAK_EDITOR_NODE_ALIVECOUNT_H
/**
* @brief Shared live-object counter hooks (internal, not installed).
*
* The counter itself and the public oaknode_debug_alive_count() live in
* the node family (src/node/c_api/node.cpp); these hooks have external
* linkage so the other families' create/free functions can participate.
*/
namespace oaknode_c_api
{
void alive_inc();
void alive_dec();
}
#endif //OAK_EDITOR_NODE_ALIVECOUNT_H
+477
View File
@@ -0,0 +1,477 @@
/***
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/block.h"
#include "alivecount.h"
#include "block/block.h"
#include "block/clip/clip.h"
#include "block/gap/gap.h"
#include "block/transition/crossdissolve/crossdissolvetransition.h"
#include "block/transition/diptocolor/diptocolortransition.h"
#include "block/transition/transition.h"
#include "output/track/track.h"
namespace
{
olive::Block *impl(OakNodeBlock *h)
{
return reinterpret_cast<olive::Block *>(h);
}
olive::ClipBlock *clip_impl(OakNodeBlock *h)
{
return h ? dynamic_cast<olive::ClipBlock *>(impl(h)) : nullptr;
}
olive::TransitionBlock *transition_impl(OakNodeBlock *h)
{
return h ? dynamic_cast<olive::TransitionBlock *>(impl(h)) : nullptr;
}
OakNodeBlock *wrap(olive::Block *b)
{
return reinterpret_cast<OakNodeBlock *>(b);
}
int get_rational(const olive::core::Rational &r, int *numerator,
int *denominator)
{
if (!numerator || !denominator) {
return OAKNODE_E_INVALID;
}
*numerator = r.numerator();
*denominator = r.denominator();
return OAKNODE_OK;
}
template <typename T, typename... Args>
OakNodeBlock *create_block(Args &&...args)
{
try {
T *b = new T(std::forward<Args>(args)...);
oaknode_c_api::alive_inc();
return wrap(b);
} catch (...) {
return nullptr;
}
}
} // namespace
OakNodeBlock *oaknode_block_clip_create(void)
{
return create_block<olive::ClipBlock>();
}
OakNodeBlock *oaknode_block_gap_create(void)
{
return create_block<olive::GapBlock>();
}
OakNodeBlock *oaknode_block_transition_create(int kind)
{
switch (kind) {
case OAKNODE_TRANSITION_CROSS_DISSOLVE:
return create_block<olive::CrossDissolveTransition>();
case OAKNODE_TRANSITION_DIP_TO_COLOR:
return create_block<olive::DipToColorTransition>();
default:
return nullptr;
}
}
void oaknode_block_free(OakNodeBlock *block)
{
if (!block) {
return;
}
delete impl(block);
oaknode_c_api::alive_dec();
}
int oaknode_block_get_in(OakNodeBlock *block, int *numerator, int *denominator)
{
if (!block) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(block)->in(), numerator, denominator);
}
int oaknode_block_set_in(OakNodeBlock *block, int numerator, int denominator)
{
if (!block) {
return OAKNODE_E_INVALID;
}
impl(block)->set_in(olive::core::Rational(numerator, denominator));
return OAKNODE_OK;
}
int oaknode_block_get_out(OakNodeBlock *block, int *numerator, int *denominator)
{
if (!block) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(block)->out(), numerator, denominator);
}
int oaknode_block_set_out(OakNodeBlock *block, int numerator, int denominator)
{
if (!block) {
return OAKNODE_E_INVALID;
}
impl(block)->set_out(olive::core::Rational(numerator, denominator));
return OAKNODE_OK;
}
int oaknode_block_get_length(OakNodeBlock *block, int *numerator,
int *denominator)
{
if (!block) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(block)->length(), numerator, denominator);
}
int oaknode_block_set_length_and_media_out(OakNodeBlock *block, int numerator,
int denominator)
{
if (!block) {
return OAKNODE_E_INVALID;
}
try {
impl(block)->set_length_and_media_out(
olive::core::Rational(numerator, denominator));
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_block_set_length_and_media_in(OakNodeBlock *block, int numerator,
int denominator)
{
if (!block) {
return OAKNODE_E_INVALID;
}
try {
impl(block)->set_length_and_media_in(
olive::core::Rational(numerator, denominator));
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_block_get_enabled(OakNodeBlock *block, int *enabled)
{
if (!block || !enabled) {
return OAKNODE_E_INVALID;
}
*enabled = impl(block)->is_enabled() ? 1 : 0;
return OAKNODE_OK;
}
int oaknode_block_set_enabled(OakNodeBlock *block, int enabled)
{
if (!block) {
return OAKNODE_E_INVALID;
}
impl(block)->set_enabled(enabled != 0);
return OAKNODE_OK;
}
int oaknode_block_get_previous(OakNodeBlock *block, OakNodeBlock **out)
{
if (!block || !out) {
return OAKNODE_E_INVALID;
}
*out = wrap(impl(block)->previous());
return OAKNODE_OK;
}
int oaknode_block_get_next(OakNodeBlock *block, OakNodeBlock **out)
{
if (!block || !out) {
return OAKNODE_E_INVALID;
}
*out = wrap(impl(block)->next());
return OAKNODE_OK;
}
int oaknode_block_get_track(OakNodeBlock *block, OakNodeTrack **out)
{
if (!block || !out) {
return OAKNODE_E_INVALID;
}
*out = reinterpret_cast<OakNodeTrack *>(impl(block)->track());
return OAKNODE_OK;
}
int oaknode_block_link(OakNodeBlock *a, OakNodeBlock *b)
{
if (!a || !b) {
return OAKNODE_E_INVALID;
}
return olive::Node::link(impl(a), impl(b)) ? OAKNODE_OK : OAKNODE_E_FAILED;
}
int oaknode_block_unlink(OakNodeBlock *a, OakNodeBlock *b)
{
if (!a || !b) {
return OAKNODE_E_INVALID;
}
return olive::Node::unlink(impl(a), impl(b)) ? OAKNODE_OK : OAKNODE_E_FAILED;
}
int oaknode_block_are_linked(OakNodeBlock *a, OakNodeBlock *b, int *linked)
{
if (!a || !b || !linked) {
return OAKNODE_E_INVALID;
}
*linked = olive::Node::are_linked(impl(a), impl(b)) ? 1 : 0;
return OAKNODE_OK;
}
int oaknode_block_get_link_count(OakNodeBlock *block, int *count)
{
if (!block || !count) {
return OAKNODE_E_INVALID;
}
*count = int(impl(block)->links().size());
return OAKNODE_OK;
}
int oaknode_block_get_link_at(OakNodeBlock *block, int index,
OakNodeBlock **out)
{
if (!block || !out || index < 0) {
return OAKNODE_E_INVALID;
}
const auto &links = impl(block)->links();
if (index >= int(links.size())) {
return OAKNODE_E_NOT_FOUND;
}
*out = wrap(static_cast<olive::Block *>(links.at(index)));
return OAKNODE_OK;
}
/* ---------------------------------------------------------------- Clip */
int oaknode_clip_get_media_in(OakNodeBlock *clip, int *numerator,
int *denominator)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c) {
return OAKNODE_E_INVALID;
}
return get_rational(c->media_in(), numerator, denominator);
}
int oaknode_clip_set_media_in(OakNodeBlock *clip, int numerator,
int denominator)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c) {
return OAKNODE_E_INVALID;
}
c->set_media_in(olive::core::Rational(numerator, denominator));
return OAKNODE_OK;
}
int oaknode_clip_get_speed(OakNodeBlock *clip, double *speed)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c || !speed) {
return OAKNODE_E_INVALID;
}
*speed = c->speed();
return OAKNODE_OK;
}
int oaknode_clip_set_speed(OakNodeBlock *clip, double speed)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c) {
return OAKNODE_E_INVALID;
}
c->set_standard_value(olive::ClipBlock::k_speed_input, speed);
return OAKNODE_OK;
}
int oaknode_clip_get_reverse(OakNodeBlock *clip, int *reverse)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c || !reverse) {
return OAKNODE_E_INVALID;
}
*reverse = c->reverse() ? 1 : 0;
return OAKNODE_OK;
}
int oaknode_clip_set_reverse(OakNodeBlock *clip, int reverse)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c) {
return OAKNODE_E_INVALID;
}
c->set_reverse(reverse != 0);
return OAKNODE_OK;
}
int oaknode_clip_get_maintain_audio_pitch(OakNodeBlock *clip, int *maintain)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c || !maintain) {
return OAKNODE_E_INVALID;
}
*maintain = c->maintain_audio_pitch() ? 1 : 0;
return OAKNODE_OK;
}
int oaknode_clip_set_maintain_audio_pitch(OakNodeBlock *clip, int maintain)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c) {
return OAKNODE_E_INVALID;
}
c->set_maintain_audio_pitch(maintain != 0);
return OAKNODE_OK;
}
int oaknode_clip_get_loop_mode(OakNodeBlock *clip, int *loop_mode)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c || !loop_mode) {
return OAKNODE_E_INVALID;
}
*loop_mode = int(c->loop_mode());
return OAKNODE_OK;
}
int oaknode_clip_set_loop_mode(OakNodeBlock *clip, int loop_mode)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c) {
return OAKNODE_E_INVALID;
}
c->set_loop_mode(static_cast<olive::LoopMode>(loop_mode));
return OAKNODE_OK;
}
int oaknode_clip_get_track_type(OakNodeBlock *clip, int *type)
{
olive::ClipBlock *c = clip_impl(clip);
if (!c || !type) {
return OAKNODE_E_INVALID;
}
*type = int(c->get_track_type());
return OAKNODE_OK;
}
/* ----------------------------------------------------------- Transition */
int oaknode_transition_get_in_offset(OakNodeBlock *transition, int *numerator,
int *denominator)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t) {
return OAKNODE_E_INVALID;
}
return get_rational(t->in_offset(), numerator, denominator);
}
int oaknode_transition_get_out_offset(OakNodeBlock *transition, int *numerator,
int *denominator)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t) {
return OAKNODE_E_INVALID;
}
return get_rational(t->out_offset(), numerator, denominator);
}
int oaknode_transition_get_offset_center(OakNodeBlock *transition,
int *numerator, int *denominator)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t) {
return OAKNODE_E_INVALID;
}
return get_rational(t->offset_center(), numerator, denominator);
}
int oaknode_transition_set_offset_center(OakNodeBlock *transition,
int numerator, int denominator)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t) {
return OAKNODE_E_INVALID;
}
t->set_offset_center(olive::core::Rational(numerator, denominator));
return OAKNODE_OK;
}
int oaknode_transition_set_offsets_and_length(OakNodeBlock *transition,
int in_num, int in_den,
int out_num, int out_den)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t) {
return OAKNODE_E_INVALID;
}
t->set_offsets_and_length(olive::core::Rational(in_num, in_den),
olive::core::Rational(out_num, out_den));
return OAKNODE_OK;
}
int oaknode_transition_is_dual(OakNodeBlock *transition, int *dual)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t || !dual) {
return OAKNODE_E_INVALID;
}
*dual = t->is_dual_transition() ? 1 : 0;
return OAKNODE_OK;
}
int oaknode_transition_get_connected_out_block(OakNodeBlock *transition,
OakNodeBlock **out)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t || !out) {
return OAKNODE_E_INVALID;
}
*out = wrap(t->connected_out_block());
return OAKNODE_OK;
}
int oaknode_transition_get_connected_in_block(OakNodeBlock *transition,
OakNodeBlock **out)
{
olive::TransitionBlock *t = transition_impl(transition);
if (!t || !out) {
return OAKNODE_E_INVALID;
}
*out = wrap(t->connected_in_block());
return OAKNODE_OK;
}
+369
View File
@@ -0,0 +1,369 @@
/***
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/colormanager.h"
#include <cstring>
#include "alivecount.h"
#include "color/colormanager/colormanager.h"
#include "colortransform.h"
#include "project.h"
// Same handle-echo pattern as sequence.cpp: oakcommon defines
// `struct OakCommonColorTransform { olive::ColorTransform impl; }`
// (src/common/c_api/colortransform.cpp) without exporting the definition.
struct OakCommonColorTransform {
olive::ColorTransform impl;
};
struct OakNodeColorManager {
olive::ColorManager impl;
};
namespace
{
int copy_string(const std::string &value, char *buf, int buf_size)
{
int needed = int(value.size()) + 1;
if (buf && buf_size >= needed) {
memcpy(buf, value.c_str(), needed);
}
return needed;
}
bool has_config(olive::ColorManager *cm)
{
return cm && cm->get_config() != nullptr;
}
int list_at(const olive::StringList &list, int index, char *buf, int buf_size)
{
if (index < 0 || index >= int(list.size())) {
return OAKNODE_E_NOT_FOUND;
}
return copy_string(list.at(index), buf, buf_size);
}
} // namespace
OakNodeColorManager *oaknode_colormanager_init(OakNodeProject *project)
{
if (!project) {
return nullptr;
}
try {
auto *m = new OakNodeColorManager{
olive::ColorManager(reinterpret_cast<olive::Project *>(project))};
oaknode_c_api::alive_inc();
return m;
} catch (...) {
return nullptr;
}
}
void oaknode_colormanager_free(OakNodeColorManager *manager)
{
if (!manager) {
return;
}
delete manager;
oaknode_c_api::alive_dec();
}
int oaknode_colormanager_initialize(OakNodeColorManager *manager)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
try {
manager->impl.init();
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_colormanager_set_up_default_config(void)
{
try {
olive::ColorManager::set_up_default_config();
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_colormanager_get_config_filename(OakNodeColorManager *manager,
char *buf, int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
return copy_string(manager->impl.get_config_filename(), buf, buf_size);
}
int oaknode_colormanager_set_config_filename(OakNodeColorManager *manager,
const char *filename)
{
if (!manager || !filename) {
return OAKNODE_E_INVALID;
}
manager->impl.set_config_filename(filename);
return OAKNODE_OK;
}
int oaknode_colormanager_update_config_from_filename(
OakNodeColorManager *manager)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
try {
manager->impl.update_config_from_filename();
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_colormanager_get_default_input_color_space(
OakNodeColorManager *manager, char *buf, int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
return copy_string(manager->impl.get_default_input_color_space(), buf,
buf_size);
}
int oaknode_colormanager_set_default_input_color_space(
OakNodeColorManager *manager, const char *colorspace)
{
if (!manager || !colorspace) {
return OAKNODE_E_INVALID;
}
manager->impl.set_default_input_color_space(colorspace);
return OAKNODE_OK;
}
int oaknode_colormanager_get_reference_color_space(
OakNodeColorManager *manager, char *buf, int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
return copy_string(manager->impl.get_reference_color_space(), buf,
buf_size);
}
int oaknode_colormanager_get_compliant_color_space(
OakNodeColorManager *manager, const char *colorspace, char *buf,
int buf_size)
{
if (!manager || !colorspace) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return copy_string(manager->impl.get_compliant_color_space(colorspace), buf,
buf_size);
}
int oaknode_colormanager_get_colorspace_for_ffmpeg_tags(
OakNodeColorManager *manager, int primaries, int trc, char *buf,
int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return copy_string(
manager->impl.get_colorspace_for_ffmpeg_tags(primaries, trc), buf,
buf_size);
}
int oaknode_colormanager_get_display_count(OakNodeColorManager *manager,
int *count)
{
if (!manager || !count) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
*count = int(manager->impl.list_available_displays().size());
return OAKNODE_OK;
}
int oaknode_colormanager_get_display_at(OakNodeColorManager *manager,
int index, char *buf, int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return list_at(manager->impl.list_available_displays(), index, buf,
buf_size);
}
int oaknode_colormanager_get_default_display(OakNodeColorManager *manager,
char *buf, int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return copy_string(manager->impl.get_default_display(), buf, buf_size);
}
int oaknode_colormanager_get_view_count(OakNodeColorManager *manager,
const char *display, int *count)
{
if (!manager || !display || !count) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
*count = int(manager->impl.list_available_views(display).size());
return OAKNODE_OK;
}
int oaknode_colormanager_get_view_at(OakNodeColorManager *manager,
const char *display, int index, char *buf,
int buf_size)
{
if (!manager || !display) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return list_at(manager->impl.list_available_views(display), index, buf,
buf_size);
}
int oaknode_colormanager_get_default_view(OakNodeColorManager *manager,
const char *display, char *buf,
int buf_size)
{
if (!manager || !display) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return copy_string(manager->impl.get_default_view(display), buf, buf_size);
}
int oaknode_colormanager_get_look_count(OakNodeColorManager *manager,
int *count)
{
if (!manager || !count) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
*count = int(manager->impl.list_available_looks().size());
return OAKNODE_OK;
}
int oaknode_colormanager_get_look_at(OakNodeColorManager *manager, int index,
char *buf, int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return list_at(manager->impl.list_available_looks(), index, buf, buf_size);
}
int oaknode_colormanager_get_colorspace_count(OakNodeColorManager *manager,
int *count)
{
if (!manager || !count) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
*count = int(manager->impl.list_available_colorspaces().size());
return OAKNODE_OK;
}
int oaknode_colormanager_get_colorspace_at(OakNodeColorManager *manager,
int index, char *buf,
int buf_size)
{
if (!manager) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
return list_at(manager->impl.list_available_colorspaces(), index, buf,
buf_size);
}
int oaknode_colormanager_get_default_luma_coefs(OakNodeColorManager *manager,
double rgb[3])
{
if (!manager || !rgb) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
manager->impl.get_default_luma_coefs(rgb);
return OAKNODE_OK;
}
int oaknode_colormanager_get_compliant_color_transform(
OakNodeColorManager *manager, const OakCommonColorTransform *transform,
int force_display, OakCommonColorTransform **out)
{
if (!manager || !transform || !out) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
try {
*out = new OakCommonColorTransform{
manager->impl.get_compliant_color_space(transform->impl,
force_display != 0)};
} catch (...) {
return OAKNODE_E_NOMEM;
}
return OAKNODE_OK;
}
+144
View File
@@ -0,0 +1,144 @@
/***
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/factory.h"
#include "factory.h"
#include "valueconvert.h"
namespace
{
inline OakNodeNode *from_node(olive::Node *node)
{
return reinterpret_cast<OakNodeNode *>(node);
}
}
int oaknode_factory_initialize(void)
{
try {
if (olive::NodeFactory::get_library().empty()) {
olive::NodeFactory::initialize();
}
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
void oaknode_factory_destroy(void)
{
try {
olive::NodeFactory::destroy();
} catch (...) {
}
}
int oaknode_factory_id_count(int *out_count)
{
if (!out_count) {
return OAKNODE_E_INVALID;
}
try {
if (olive::NodeFactory::get_library().empty()) {
return OAKNODE_E_STATE;
}
*out_count = static_cast<int>(olive::NodeFactory::get_library().size());
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_factory_id_at(int index, char *buf, int buf_size)
{
try {
const std::vector<olive::Node *> &library =
olive::NodeFactory::get_library();
if (library.empty()) {
return OAKNODE_E_STATE;
}
if (index < 0 || index >= static_cast<int>(library.size())) {
return OAKNODE_E_NOT_FOUND;
}
return oaknode_c_api::copy_string(library[size_t(index)]->id(), buf,
buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_factory_name_from_id(const char *type_id, char *buf,
int buf_size)
{
if (!type_id) {
return OAKNODE_E_INVALID;
}
try {
return oaknode_c_api::copy_string(
olive::NodeFactory::get_name_from_id(type_id), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
OakNodeNode *oaknode_factory_create_from_id(const char *type_id)
{
if (!type_id) {
return NULL;
}
try {
olive::Node *node = olive::NodeFactory::create_from_id(type_id);
if (node) {
oaknode_c_api::alive_inc();
}
return from_node(node);
} catch (...) {
return NULL;
}
}
int oaknode_factory_node_at(int index, OakNodeNode **out_node)
{
if (!out_node) {
return OAKNODE_E_INVALID;
}
try {
const std::vector<olive::Node *> &library =
olive::NodeFactory::get_library();
if (library.empty()) {
return OAKNODE_E_STATE;
}
if (index < 0 || index >= static_cast<int>(library.size())) {
return OAKNODE_E_NOT_FOUND;
}
*out_node = from_node(library[size_t(index)]);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
+232
View File
@@ -0,0 +1,232 @@
/***
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/folder.h"
#include <new>
#include "../src/project.h"
#include "../src/project/folder/folder.h"
namespace
{
olive::Folder *to_cpp(OakNodeFolder *folder)
{
return reinterpret_cast<olive::Folder *>(folder);
}
const olive::Folder *to_cpp(const OakNodeFolder *folder)
{
return reinterpret_cast<const olive::Folder *>(folder);
}
olive::Node *to_cpp(OakNodeNode *node)
{
return reinterpret_cast<olive::Node *>(node);
}
const olive::Node *to_cpp(const OakNodeNode *node)
{
return reinterpret_cast<const olive::Node *>(node);
}
OakNodeNode *to_c(olive::Node *node)
{
return reinterpret_cast<OakNodeNode *>(node);
}
olive::Project *to_cpp(OakNodeProject *project)
{
return reinterpret_cast<olive::Project *>(project);
}
} // namespace
OakNodeFolder *oaknode_folder_create(OakNodeProject *project)
{
if (!project) {
return NULL;
}
try {
auto *folder = new (std::nothrow) olive::Folder();
if (!folder) {
return NULL;
}
to_cpp(project)->add_node(folder);
return reinterpret_cast<OakNodeFolder *>(folder);
} catch (...) {
return NULL;
}
}
int oaknode_folder_child_count(const OakNodeFolder *folder)
{
if (!folder) {
return OAKNODE_E_INVALID;
}
try {
return to_cpp(folder)->item_child_count();
} catch (...) {
return OAKNODE_E_FAILED;
}
}
OakNodeNode *oaknode_folder_child_at(const OakNodeFolder *folder, int index)
{
if (!folder || index < 0 || index >= to_cpp(folder)->item_child_count()) {
return NULL;
}
try {
return to_c(to_cpp(folder)->item_child(index));
} catch (...) {
return NULL;
}
}
int oaknode_folder_add_child(OakNodeFolder *folder, OakNodeNode *child)
{
if (!folder || !child) {
return OAKNODE_E_INVALID;
}
try {
olive::Folder *f = to_cpp(folder);
olive::Node *c = to_cpp(child);
if (c->folder()) {
return OAKNODE_E_STATE;
}
olive::FolderAddChild cmd(f, c);
cmd.redo_now();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_folder_remove_child(OakNodeFolder *folder, OakNodeNode *child)
{
if (!folder || !child) {
return OAKNODE_E_INVALID;
}
try {
olive::Folder *f = to_cpp(folder);
olive::Node *c = to_cpp(child);
if (f->index_of_child(c) == -1) {
return OAKNODE_E_NOT_FOUND;
}
olive::Folder::RemoveElementCommand cmd(f, c);
cmd.redo_now();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_folder_move_children(OakNodeNode *const *nodes, int count,
OakNodeFolder *dest_folder)
{
if (!nodes || count < 0 || !dest_folder) {
return OAKNODE_E_INVALID;
}
try {
olive::Folder *dest = to_cpp(dest_folder);
for (int i = 0; i < count; i++) {
if (!nodes[i]) {
return OAKNODE_E_INVALID;
}
olive::Node *node = to_cpp(nodes[i]);
olive::Folder *old_folder = node->folder();
if (old_folder == dest) {
continue;
}
if (old_folder) {
olive::Folder::RemoveElementCommand remove_cmd(old_folder, node);
remove_cmd.redo_now();
}
olive::FolderAddChild add_cmd(dest, node);
add_cmd.redo_now();
}
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_folder_has_child_recursive(const OakNodeFolder *folder,
const OakNodeNode *child)
{
if (!folder || !child) {
return OAKNODE_E_INVALID;
}
try {
return to_cpp(folder)->has_child_recursive(
const_cast<olive::Node *>(to_cpp(child)))
? 1
: 0;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_folder_index_of_child(const OakNodeFolder *folder,
const OakNodeNode *child)
{
if (!folder || !child) {
return OAKNODE_E_INVALID;
}
try {
int index = to_cpp(folder)->index_of_child(
const_cast<olive::Node *>(to_cpp(child)));
return index == -1 ? OAKNODE_E_NOT_FOUND : index;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
OakNodeFolder *oaknode_folder_parent_of(const OakNodeNode *node)
{
if (!node) {
return NULL;
}
try {
return reinterpret_cast<OakNodeFolder *>(to_cpp(node)->folder());
} catch (...) {
return NULL;
}
}
+322
View File
@@ -0,0 +1,322 @@
/***
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/footage.h"
#include <cstring>
#include <new>
#include <string>
#include "../src/project.h"
#include "../src/project/footage/footage.h"
namespace
{
olive::Footage *to_cpp(OakNodeFootage *footage)
{
return reinterpret_cast<olive::Footage *>(footage);
}
const olive::Footage *to_cpp(const OakNodeFootage *footage)
{
return reinterpret_cast<const olive::Footage *>(footage);
}
olive::Project *to_cpp(OakNodeProject *project)
{
return reinterpret_cast<olive::Project *>(project);
}
/**
* @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
OakNodeFootage *oaknode_footage_create(OakNodeProject *project,
const char *filename)
{
if (!project) {
return NULL;
}
try {
auto *footage = new (std::nothrow)
olive::Footage(filename ? filename : "");
if (!footage) {
return NULL;
}
to_cpp(project)->add_node(footage);
return reinterpret_cast<OakNodeFootage *>(footage);
} catch (...) {
return NULL;
}
}
int oaknode_footage_filename(const OakNodeFootage *footage, char *buf,
int buf_size)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(footage)->filename(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_set_filename(OakNodeFootage *footage, const char *filename)
{
if (!footage || !filename) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(footage)->set_filename(filename);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_is_valid(const OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
return to_cpp(footage)->is_valid() ? 1 : 0;
}
int oaknode_footage_timestamp(const OakNodeFootage *footage,
int64_t *out_timestamp)
{
if (!footage || !out_timestamp) {
return OAKNODE_E_INVALID;
}
try {
*out_timestamp = to_cpp(footage)->timestamp();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_set_timestamp(OakNodeFootage *footage, int64_t timestamp)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(footage)->set_timestamp(timestamp);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_decoder(const OakNodeFootage *footage, char *buf,
int buf_size)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(footage)->decoder(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_total_stream_count(const OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return to_cpp(footage)->get_total_stream_count();
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_video_stream_count(const OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return to_cpp(footage)->get_video_stream_count();
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_audio_stream_count(const OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return to_cpp(footage)->get_audio_stream_count();
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_subtitle_stream_count(const OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return to_cpp(footage)->get_subtitle_stream_count();
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_duration(const OakNodeFootage *footage, int *out_numerator,
int *out_denominator)
{
if (!footage || !out_numerator || !out_denominator) {
return OAKNODE_E_INVALID;
}
try {
const olive::Rational &length = to_cpp(footage)->get_length();
*out_numerator = length.numerator();
*out_denominator = length.denominator();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_proxy_enabled(const OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
return to_cpp(footage)->proxy_enabled() ? 1 : 0;
}
int oaknode_footage_set_proxy_enabled(OakNodeFootage *footage, int enabled)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(footage)->set_proxy_enabled(enabled != 0);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_proxy_path(const OakNodeFootage *footage, char *buf,
int buf_size)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(footage)->proxy_path(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_proxy_state(const OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
return static_cast<int>(to_cpp(footage)->proxy_state());
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_set_proxy(OakNodeFootage *footage, const char *path,
int state, int video_stream_index,
int preset_version, int enabled)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(footage)->set_proxy(
path ? path : "",
static_cast<olive::ProxyManager::ProxyState>(state),
video_stream_index, preset_version, enabled != 0);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_footage_clear_proxy(OakNodeFootage *footage)
{
if (!footage) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(footage)->clear_proxy();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
+294
View File
@@ -0,0 +1,294 @@
/***
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/group.h"
#include "group/group.h"
#include "valueconvert.h"
namespace
{
inline olive::NodeGroup *to_group(OakNodeGroup *group)
{
return reinterpret_cast<olive::NodeGroup *>(group);
}
inline const olive::NodeGroup *to_group(const OakNodeGroup *group)
{
return reinterpret_cast<const olive::NodeGroup *>(group);
}
inline olive::Node *to_node(OakNodeNode *node)
{
return reinterpret_cast<olive::Node *>(node);
}
inline OakNodeNode *from_node(olive::Node *node)
{
return reinterpret_cast<OakNodeNode *>(node);
}
}
OakNodeGroup *oaknode_group_create(void)
{
try {
olive::NodeGroup *group = new (std::nothrow) olive::NodeGroup();
if (group) {
oaknode_c_api::alive_inc();
}
return reinterpret_cast<OakNodeGroup *>(group);
} catch (...) {
return NULL;
}
}
OakNodeGroup *oaknode_group_cast(OakNodeNode *node)
{
if (!node) {
return NULL;
}
try {
return reinterpret_cast<OakNodeGroup *>(
dynamic_cast<olive::NodeGroup *>(to_node(node)));
} catch (...) {
return NULL;
}
}
void oaknode_group_free(OakNodeGroup *group)
{
if (!group) {
return;
}
try {
delete to_group(group);
oaknode_c_api::alive_dec();
} catch (...) {
}
}
int oaknode_group_add_input_passthrough(OakNodeGroup *group,
OakNodeNode *node,
const char *input_id, int element,
char *buf, int buf_size)
{
if (!group || !node || !input_id) {
return OAKNODE_E_INVALID;
}
try {
std::string id = to_group(group)->add_input_passthrough(
olive::NodeInput(to_node(node), input_id, element));
return oaknode_c_api::copy_string(id, buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_add_input_passthrough_undoable(OakNodeGroup *group,
OakNodeNode *node,
const char *input_id,
int element,
OakUndoCommand **out_command)
{
if (!group || !node || !input_id || !out_command) {
return OAKNODE_E_INVALID;
}
try {
OakUndoCommand *handle = oaknode_c_api::wrap_command(
new olive::NodeGroupAddInputPassthrough(
to_group(group),
olive::NodeInput(to_node(node), input_id, element)));
if (!handle) {
return OAKNODE_E_NOMEM;
}
*out_command = handle;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_remove_input_passthrough(OakNodeGroup *group,
OakNodeNode *node,
const char *input_id, int element)
{
if (!group || !node || !input_id) {
return OAKNODE_E_INVALID;
}
try {
olive::NodeInput input(to_node(node), input_id, element);
if (!to_group(group)->contains_input_passthrough(input)) {
return OAKNODE_E_NOT_FOUND;
}
to_group(group)->remove_input_passthrough(input);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_passthrough_count(const OakNodeGroup *group, int *out_count)
{
if (!group || !out_count) {
return OAKNODE_E_INVALID;
}
try {
*out_count =
static_cast<int>(to_group(group)->get_input_passthroughs().size());
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_passthrough_id_at(const OakNodeGroup *group, int index,
char *buf, int buf_size)
{
if (!group) {
return OAKNODE_E_INVALID;
}
try {
const olive::NodeGroup::InputPassthroughs &passthroughs =
to_group(group)->get_input_passthroughs();
if (index < 0 || index >= static_cast<int>(passthroughs.size())) {
return OAKNODE_E_NOT_FOUND;
}
return oaknode_c_api::copy_string(passthroughs[size_t(index)].first, buf,
buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_passthrough_input_at(const OakNodeGroup *group, int index,
OakNodeNode **out_node, char *buf,
int buf_size, int *out_element)
{
if (!group) {
return OAKNODE_E_INVALID;
}
try {
const olive::NodeGroup::InputPassthroughs &passthroughs =
to_group(group)->get_input_passthroughs();
if (index < 0 || index >= static_cast<int>(passthroughs.size())) {
return OAKNODE_E_NOT_FOUND;
}
const olive::NodeInput &input = passthroughs[size_t(index)].second;
if (out_node) {
*out_node = from_node(input.node());
}
if (out_element) {
*out_element = input.element();
}
return oaknode_c_api::copy_string(input.input(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_get_output_passthrough(const OakNodeGroup *group,
OakNodeNode **out_node)
{
if (!group || !out_node) {
return OAKNODE_E_INVALID;
}
try {
*out_node = from_node(to_group(group)->get_output_passthrough());
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_set_output_passthrough(OakNodeGroup *group,
OakNodeNode *node)
{
if (!group) {
return OAKNODE_E_INVALID;
}
try {
to_group(group)->set_output_passthrough(to_node(node));
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_set_output_passthrough_undoable(
OakNodeGroup *group, OakNodeNode *node, OakUndoCommand **out_command)
{
if (!group || !out_command) {
return OAKNODE_E_INVALID;
}
try {
OakUndoCommand *handle = oaknode_c_api::wrap_command(
new olive::NodeGroupSetOutputPassthrough(to_group(group),
to_node(node)));
if (!handle) {
return OAKNODE_E_NOMEM;
}
*out_command = handle;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_group_resolve_input(OakNodeNode *node, const char *input_id,
int element, OakNodeNode **out_node,
char *buf, int buf_size, int *out_element)
{
if (!node || !input_id) {
return OAKNODE_E_INVALID;
}
try {
olive::NodeInput resolved = olive::NodeGroup::resolve_input(
olive::NodeInput(to_node(node), input_id, element));
if (!resolved.is_valid()) {
return OAKNODE_E_NOT_FOUND;
}
if (out_node) {
*out_node = from_node(resolved.node());
}
if (out_element) {
*out_element = resolved.element();
}
return oaknode_c_api::copy_string(resolved.input(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
+588
View File
@@ -0,0 +1,588 @@
/***
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/keyframe.h"
#include "keyframe.h"
#include "node.h"
#include "nodeundo.h"
#include "valueconvert.h"
namespace
{
inline olive::NodeKeyframe *to_key(OakNodeKeyframe *keyframe)
{
return reinterpret_cast<olive::NodeKeyframe *>(keyframe);
}
inline const olive::NodeKeyframe *to_key(const OakNodeKeyframe *keyframe)
{
return reinterpret_cast<const olive::NodeKeyframe *>(keyframe);
}
inline olive::Node *to_node(OakNodeNode *node)
{
return reinterpret_cast<olive::Node *>(node);
}
inline OakNodeNode *from_node(olive::Node *node)
{
return reinterpret_cast<OakNodeNode *>(node);
}
/**
* @brief Convert an oaknode_keyframe_type to olive::NodeKeyframe::Type.
* The oaknode enum mirrors the olive ordinals exactly (invalid = -1,
* linear = 0, hold = 1, bezier = 2).
*/
bool keyframe_type_from_oak(int type, olive::NodeKeyframe::Type *out)
{
if (type < OAKNODE_KEYFRAME_LINEAR || type > OAKNODE_KEYFRAME_BEZIER) {
return false;
}
*out = static_cast<olive::NodeKeyframe::Type>(type);
return true;
}
/**
* @brief Undoable set-type command (no olive command class exists for
* this; defined locally, mirroring NodeOverrideColorCommand's
* capture-on-redo pattern).
*/
class KeyframeSetTypeCommand : public olive::UndoCommand {
public:
KeyframeSetTypeCommand(olive::NodeKeyframe *key,
olive::NodeKeyframe::Type type)
: key_(key)
, new_type_(type)
, old_type_(olive::NodeKeyframe::k_invalid)
{
}
protected:
virtual void redo() override
{
old_type_ = key_->type();
key_->set_type(new_type_);
}
virtual void undo() override
{
key_->set_type(old_type_);
}
private:
olive::NodeKeyframe *key_;
olive::NodeKeyframe::Type new_type_;
olive::NodeKeyframe::Type old_type_;
};
/**
* @brief Undoable set-bezier-control command (no olive command class
* exists for this; defined locally).
*/
class KeyframeSetBezierControlCommand : public olive::UndoCommand {
public:
KeyframeSetBezierControlCommand(olive::NodeKeyframe *key,
olive::NodeKeyframe::BezierType handle,
const olive::PointF &point)
: key_(key)
, handle_(handle)
, new_point_(point)
{
}
protected:
virtual void redo() override
{
old_point_ = key_->bezier_control(handle_);
key_->set_bezier_control(handle_, new_point_);
}
virtual void undo() override
{
key_->set_bezier_control(handle_, old_point_);
}
private:
olive::NodeKeyframe *key_;
olive::NodeKeyframe::BezierType handle_;
olive::PointF new_point_;
olive::PointF old_point_;
};
}
OakNodeKeyframe *oaknode_keyframe_create(int64_t time_num, int64_t time_den,
const oaknode_value *value, int type,
int track, int element,
const char *input_id,
OakNodeNode *parent_or_null)
{
olive::NodeKeyframe::Type keyframe_type;
if (!keyframe_type_from_oak(type, &keyframe_type)) {
return NULL;
}
try {
olive::Variant variant;
if (value) {
if (!oaknode_c_api::variant_from_value(value, &variant)) {
return NULL;
}
}
olive::core::Rational time(static_cast<int>(time_num),
static_cast<int>(time_den));
olive::NodeKeyframe *key = new (std::nothrow) olive::NodeKeyframe(
time, variant, keyframe_type, track, element,
input_id ? input_id : "", to_node(parent_or_null));
if (key) {
oaknode_c_api::alive_inc();
}
return reinterpret_cast<OakNodeKeyframe *>(key);
} catch (...) {
return NULL;
}
}
void oaknode_keyframe_free(OakNodeKeyframe *keyframe)
{
if (!keyframe) {
return;
}
try {
delete to_key(keyframe);
oaknode_c_api::alive_dec();
} catch (...) {
}
}
int oaknode_keyframe_get_time(const OakNodeKeyframe *keyframe,
int64_t *out_num, int64_t *out_den)
{
if (!keyframe || !out_num || !out_den) {
return OAKNODE_E_INVALID;
}
try {
const olive::core::Rational &time = to_key(keyframe)->time();
*out_num = time.numerator();
*out_den = time.denominator();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_time(OakNodeKeyframe *keyframe, int64_t time_num,
int64_t time_den)
{
if (!keyframe) {
return OAKNODE_E_INVALID;
}
try {
to_key(keyframe)->set_time(olive::core::Rational(
static_cast<int>(time_num), static_cast<int>(time_den)));
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_time_undoable(OakNodeKeyframe *keyframe,
int64_t time_num, int64_t time_den,
OakUndoCommand **out_command)
{
if (!keyframe || !out_command) {
return OAKNODE_E_INVALID;
}
try {
OakUndoCommand *handle = oaknode_c_api::wrap_command(
new olive::NodeParamSetKeyframeTimeCommand(
to_key(keyframe),
olive::core::Rational(static_cast<int>(time_num),
static_cast<int>(time_den))));
if (!handle) {
return OAKNODE_E_NOMEM;
}
*out_command = handle;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_value(const OakNodeKeyframe *keyframe,
oaknode_value *out)
{
if (!keyframe || !out) {
return OAKNODE_E_INVALID;
}
try {
const olive::NodeKeyframe *key = to_key(keyframe);
const olive::Variant &variant = key->value();
// Preferred path: the parent node's declared input type pins the
// mapping.
olive::Node *parent = key->parent();
if (parent && !key->input().empty() &&
parent->has_input_with_id(key->input())) {
return oaknode_c_api::value_from_variant(
parent->get_input_data_type(key->input()), variant, out);
}
// Orphan fallback: infer the POD type from the stored variant
// content. Numeric kinds are reported as FLOAT (the Variant kind is
// not recoverable across the POD).
if (variant.can_convert<olive::core::Rational>()) {
olive::core::Rational r = variant.value<olive::core::Rational>();
*out = oaknode_value();
out->type = OAKNODE_VALUE_RATIONAL;
out->num = r.numerator();
out->den = r.denominator();
return OAKNODE_OK;
}
if (variant.can_convert<olive::core::Color>()) {
return oaknode_c_api::value_from_variant(olive::NodeValue::k_color,
variant, out);
}
if (variant.can_convert<olive::Vector2D>()) {
return oaknode_c_api::value_from_variant(olive::NodeValue::k_vec2,
variant, out);
}
if (variant.can_convert<olive::Vector3D>()) {
return oaknode_c_api::value_from_variant(olive::NodeValue::k_vec3,
variant, out);
}
if (variant.can_convert<olive::Vector4D>()) {
return oaknode_c_api::value_from_variant(olive::NodeValue::k_vec4,
variant, out);
}
if (variant.can_convert<double>()) {
*out = oaknode_value();
out->type = OAKNODE_VALUE_FLOAT;
out->f[0] = variant.to_double();
return OAKNODE_OK;
}
return OAKNODE_E_FAILED;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_value(OakNodeKeyframe *keyframe,
const oaknode_value *v)
{
if (!keyframe || !v) {
return OAKNODE_E_INVALID;
}
try {
olive::Variant variant;
if (!oaknode_c_api::variant_from_value(v, &variant)) {
return OAKNODE_E_INVALID;
}
to_key(keyframe)->set_value(variant);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_value_undoable(OakNodeKeyframe *keyframe,
const oaknode_value *v,
OakUndoCommand **out_command)
{
if (!keyframe || !v || !out_command) {
return OAKNODE_E_INVALID;
}
try {
olive::Variant variant;
if (!oaknode_c_api::variant_from_value(v, &variant)) {
return OAKNODE_E_INVALID;
}
OakUndoCommand *handle = oaknode_c_api::wrap_command(
new olive::NodeParamSetKeyframeValueCommand(to_key(keyframe),
variant));
if (!handle) {
return OAKNODE_E_NOMEM;
}
*out_command = handle;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_value_string(const OakNodeKeyframe *keyframe,
char *buf, int buf_size)
{
if (!keyframe) {
return OAKNODE_E_INVALID;
}
try {
return oaknode_c_api::copy_string(to_key(keyframe)->value().to_string(),
buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_value_string(OakNodeKeyframe *keyframe,
const char *value)
{
if (!keyframe || !value) {
return OAKNODE_E_INVALID;
}
try {
to_key(keyframe)->set_value(olive::Variant(value));
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_value_string_undoable(OakNodeKeyframe *keyframe,
const char *value,
OakUndoCommand **out_command)
{
if (!keyframe || !value || !out_command) {
return OAKNODE_E_INVALID;
}
try {
OakUndoCommand *handle = oaknode_c_api::wrap_command(
new olive::NodeParamSetKeyframeValueCommand(
to_key(keyframe), olive::Variant(value)));
if (!handle) {
return OAKNODE_E_NOMEM;
}
*out_command = handle;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_type(const OakNodeKeyframe *keyframe, int *out_type)
{
if (!keyframe || !out_type) {
return OAKNODE_E_INVALID;
}
try {
*out_type = static_cast<int>(to_key(keyframe)->type());
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_type(OakNodeKeyframe *keyframe, int type)
{
if (!keyframe) {
return OAKNODE_E_INVALID;
}
try {
olive::NodeKeyframe::Type keyframe_type;
if (!keyframe_type_from_oak(type, &keyframe_type)) {
return OAKNODE_E_INVALID;
}
to_key(keyframe)->set_type(keyframe_type);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_type_undoable(OakNodeKeyframe *keyframe, int type,
OakUndoCommand **out_command)
{
if (!keyframe || !out_command) {
return OAKNODE_E_INVALID;
}
try {
olive::NodeKeyframe::Type keyframe_type;
if (!keyframe_type_from_oak(type, &keyframe_type)) {
return OAKNODE_E_INVALID;
}
OakUndoCommand *handle = oaknode_c_api::wrap_command(
new KeyframeSetTypeCommand(to_key(keyframe), keyframe_type));
if (!handle) {
return OAKNODE_E_NOMEM;
}
*out_command = handle;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_bezier_control(const OakNodeKeyframe *keyframe,
int handle, double *out_x,
double *out_y)
{
if (!keyframe || !out_x || !out_y) {
return OAKNODE_E_INVALID;
}
try {
olive::PointF point;
if (handle == OAKNODE_KEYFRAME_IN_HANDLE) {
point = to_key(keyframe)->bezier_control_in();
} else if (handle == OAKNODE_KEYFRAME_OUT_HANDLE) {
point = to_key(keyframe)->bezier_control_out();
} else {
return OAKNODE_E_INVALID;
}
*out_x = point.x();
*out_y = point.y();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_bezier_control(OakNodeKeyframe *keyframe, int handle,
double x, double y)
{
if (!keyframe) {
return OAKNODE_E_INVALID;
}
try {
if (handle == OAKNODE_KEYFRAME_IN_HANDLE) {
to_key(keyframe)->set_bezier_control_in(olive::PointF(x, y));
} else if (handle == OAKNODE_KEYFRAME_OUT_HANDLE) {
to_key(keyframe)->set_bezier_control_out(olive::PointF(x, y));
} else {
return OAKNODE_E_INVALID;
}
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_set_bezier_control_undoable(OakNodeKeyframe *keyframe,
int handle, double x, double y,
OakUndoCommand **out_command)
{
if (!keyframe || !out_command) {
return OAKNODE_E_INVALID;
}
try {
olive::NodeKeyframe::BezierType bezier_handle;
if (handle == OAKNODE_KEYFRAME_IN_HANDLE) {
bezier_handle = olive::NodeKeyframe::k_in_handle;
} else if (handle == OAKNODE_KEYFRAME_OUT_HANDLE) {
bezier_handle = olive::NodeKeyframe::k_out_handle;
} else {
return OAKNODE_E_INVALID;
}
OakUndoCommand *handle_ptr = oaknode_c_api::wrap_command(
new KeyframeSetBezierControlCommand(to_key(keyframe), bezier_handle,
olive::PointF(x, y)));
if (!handle_ptr) {
return OAKNODE_E_NOMEM;
}
*out_command = handle_ptr;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_track(const OakNodeKeyframe *keyframe,
int *out_track)
{
if (!keyframe || !out_track) {
return OAKNODE_E_INVALID;
}
try {
*out_track = to_key(keyframe)->track();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_element(const OakNodeKeyframe *keyframe,
int *out_element)
{
if (!keyframe || !out_element) {
return OAKNODE_E_INVALID;
}
try {
*out_element = to_key(keyframe)->element();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_input(const OakNodeKeyframe *keyframe, char *buf,
int buf_size)
{
if (!keyframe) {
return OAKNODE_E_INVALID;
}
try {
return oaknode_c_api::copy_string(to_key(keyframe)->input(), buf,
buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_keyframe_get_parent(const OakNodeKeyframe *keyframe,
OakNodeNode **out_node)
{
if (!keyframe || !out_node) {
return OAKNODE_E_INVALID;
}
try {
*out_node = from_node(to_key(keyframe)->parent());
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
File diff suppressed because it is too large Load Diff
+373
View File
@@ -0,0 +1,373 @@
/***
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/project.h"
#include <algorithm>
#include <cstring>
#include <new>
#include <string>
#include "../src/project.h"
namespace
{
olive::Project *to_cpp(OakNodeProject *project)
{
return reinterpret_cast<olive::Project *>(project);
}
const olive::Project *to_cpp(const OakNodeProject *project)
{
return reinterpret_cast<const 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);
}
/**
* @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
OakNodeProject *oaknode_project_init(void)
{
try {
return reinterpret_cast<OakNodeProject *>(
new (std::nothrow) olive::Project());
} catch (...) {
return NULL;
}
}
void oaknode_project_free(OakNodeProject *project)
{
delete to_cpp(project);
}
int oaknode_project_initialize(OakNodeProject *project)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
if (to_cpp(project)->root()) {
return OAKNODE_E_STATE;
}
to_cpp(project)->initialize();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_clear(OakNodeProject *project)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(project)->clear();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
OakNodeFolder *oaknode_project_root(OakNodeProject *project)
{
if (!project) {
return NULL;
}
try {
return reinterpret_cast<OakNodeFolder *>(to_cpp(project)->root());
} catch (...) {
return NULL;
}
}
int oaknode_project_name(const OakNodeProject *project, char *buf, int buf_size)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(project)->name(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_filename(const OakNodeProject *project, char *buf,
int buf_size)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(project)->filename(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_pretty_filename(const OakNodeProject *project, char *buf,
int buf_size)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(project)->pretty_filename(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_set_filename(OakNodeProject *project, const char *filename)
{
if (!project || !filename) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(project)->set_filename(filename);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_is_modified(const OakNodeProject *project)
{
if (!project) {
return OAKNODE_E_INVALID;
}
return to_cpp(project)->is_modified() ? 1 : 0;
}
int oaknode_project_set_modified(OakNodeProject *project, int modified)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(project)->set_modified(modified != 0);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_is_new(const OakNodeProject *project)
{
if (!project) {
return OAKNODE_E_INVALID;
}
return to_cpp(project)->is_new() ? 1 : 0;
}
int oaknode_project_cache_path(const OakNodeProject *project, char *buf,
int buf_size)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(project)->cache_path(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_get_cache_location_setting(const OakNodeProject *project)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return static_cast<int>(to_cpp(project)->get_cache_location_setting());
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_set_cache_location_setting(OakNodeProject *project,
int setting)
{
if (!project || setting < 0 ||
setting > static_cast<int>(olive::Project::k_cache_custom_path)) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(project)->set_cache_location_setting(
static_cast<olive::Project::CacheSetting>(setting));
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_get_custom_cache_path(const OakNodeProject *project,
char *buf, int buf_size)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(project)->get_custom_cache_path(), buf,
buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_set_custom_cache_path(OakNodeProject *project,
const char *path)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(project)->set_custom_cache_path(path ? path : "");
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_get_uuid(const OakNodeProject *project, char *buf,
int buf_size)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return copy_string(to_cpp(project)->get_uuid(), buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_add_node(OakNodeProject *project, OakNodeNode *node)
{
if (!project || !node) {
return OAKNODE_E_INVALID;
}
try {
to_cpp(project)->add_node(to_cpp(node));
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_remove_node(OakNodeProject *project, OakNodeNode *node)
{
if (!project || !node) {
return OAKNODE_E_INVALID;
}
try {
olive::Project *p = to_cpp(project);
olive::Node *n = to_cpp(node);
const auto &nodes = p->nodes();
if (std::find(nodes.begin(), nodes.end(), n) == nodes.end()) {
return OAKNODE_E_NOT_FOUND;
}
p->remove_node(n);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_project_node_count(const OakNodeProject *project)
{
if (!project) {
return OAKNODE_E_INVALID;
}
try {
return static_cast<int>(to_cpp(project)->nodes().size());
} catch (...) {
return OAKNODE_E_FAILED;
}
}
OakNodeNode *oaknode_project_node_at(const OakNodeProject *project, int index)
{
if (!project || index < 0) {
return NULL;
}
try {
const auto &nodes = to_cpp(project)->nodes();
if (static_cast<size_t>(index) >= nodes.size()) {
return NULL;
}
return to_c(nodes[static_cast<size_t>(index)]);
} catch (...) {
return NULL;
}
}
+319
View File
@@ -0,0 +1,319 @@
/***
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/sequence.h"
#include "alivecount.h"
#include "node/track.h"
#include "globals.h"
#include "output/track/tracklist.h"
#include "project/sequence/sequence.h"
#include "videoparams.h"
// oakcommon defines its handle as `struct OakCommonVideoParams {
// olive::VideoParams impl; }` (src/common/c_api/videoparams.cpp) without
// exporting the definition. Echoing the identical layout here is the only
// way to hand native VideoParams values across without a field-by-field
// copy; keep in sync with oakcommon (flagged in the family-C report).
struct OakCommonVideoParams {
olive::VideoParams impl;
};
namespace
{
olive::Sequence *impl(OakNodeSequence *h)
{
return reinterpret_cast<olive::Sequence *>(h);
}
int get_rational(const olive::core::Rational &r, int *numerator,
int *denominator)
{
if (!numerator || !denominator) {
return OAKNODE_E_INVALID;
}
*numerator = r.numerator();
*denominator = r.denominator();
return OAKNODE_OK;
}
bool valid_track_type(int type)
{
return type >= OAKNODE_TRACK_TYPE_VIDEO && type < OAKNODE_TRACK_TYPE_COUNT;
}
} // namespace
OakNodeSequence *oaknode_sequence_create(void)
{
try {
olive::Sequence *s = new olive::Sequence();
oaknode_c_api::alive_inc();
return reinterpret_cast<OakNodeSequence *>(s);
} catch (...) {
return nullptr;
}
}
void oaknode_sequence_free(OakNodeSequence *sequence)
{
if (!sequence) {
return;
}
olive::Sequence *s = impl(sequence);
// ~Sequence() deletes the owned TrackLists
delete s;
oaknode_c_api::alive_dec();
}
int oaknode_sequence_get_track_list(OakNodeSequence *sequence, int type,
OakNodeTrackList **out)
{
if (!sequence || !out) {
return OAKNODE_E_INVALID;
}
if (!valid_track_type(type)) {
return OAKNODE_E_NOT_FOUND;
}
*out = reinterpret_cast<OakNodeTrackList *>(
impl(sequence)->track_list(static_cast<olive::Track::Type>(type)));
return OAKNODE_OK;
}
int oaknode_sequence_get_track_count(OakNodeSequence *sequence, int type,
int *count)
{
if (!sequence || !count) {
return OAKNODE_E_INVALID;
}
if (!valid_track_type(type)) {
return OAKNODE_E_NOT_FOUND;
}
*count = impl(sequence)
->track_list(static_cast<olive::Track::Type>(type))
->get_track_count();
return OAKNODE_OK;
}
int oaknode_sequence_get_track_at(OakNodeSequence *sequence, int type,
int index, OakNodeTrack **out)
{
if (!sequence || !out || index < 0) {
return OAKNODE_E_INVALID;
}
if (!valid_track_type(type)) {
return OAKNODE_E_NOT_FOUND;
}
olive::TrackList *list =
impl(sequence)->track_list(static_cast<olive::Track::Type>(type));
if (index >= list->get_track_count()) {
return OAKNODE_E_NOT_FOUND;
}
*out = reinterpret_cast<OakNodeTrack *>(list->get_track_at(index));
return OAKNODE_OK;
}
int oaknode_sequence_get_all_track_count(OakNodeSequence *sequence, int *count)
{
if (!sequence || !count) {
return OAKNODE_E_INVALID;
}
*count = int(impl(sequence)->get_tracks().size());
return OAKNODE_OK;
}
int oaknode_sequence_get_all_track_at(OakNodeSequence *sequence, int index,
OakNodeTrack **out)
{
if (!sequence || !out || index < 0) {
return OAKNODE_E_INVALID;
}
const auto &tracks = impl(sequence)->get_tracks();
if (index >= int(tracks.size())) {
return OAKNODE_E_NOT_FOUND;
}
*out = reinterpret_cast<OakNodeTrack *>(tracks.at(index));
return OAKNODE_OK;
}
int oaknode_sequence_get_playhead(OakNodeSequence *sequence, int *numerator,
int *denominator)
{
if (!sequence) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(sequence)->get_playhead(), numerator, denominator);
}
int oaknode_sequence_set_playhead(OakNodeSequence *sequence, int numerator,
int denominator)
{
if (!sequence) {
return OAKNODE_E_INVALID;
}
try {
impl(sequence)->set_playhead(olive::core::Rational(numerator,
denominator));
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_sequence_get_length(OakNodeSequence *sequence, int *numerator,
int *denominator)
{
if (!sequence) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(sequence)->get_length(), numerator, denominator);
}
int oaknode_sequence_get_video_length(OakNodeSequence *sequence, int *numerator,
int *denominator)
{
if (!sequence) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(sequence)->get_video_length(), numerator,
denominator);
}
int oaknode_sequence_get_audio_length(OakNodeSequence *sequence, int *numerator,
int *denominator)
{
if (!sequence) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(sequence)->get_audio_length(), numerator,
denominator);
}
int oaknode_sequence_verify_length(OakNodeSequence *sequence)
{
if (!sequence) {
return OAKNODE_E_INVALID;
}
try {
impl(sequence)->verify_length();
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
/* --------------------------------------------------- Video/audio params */
int oaknode_sequence_get_video_stream_count(OakNodeSequence *sequence,
int *count)
{
if (!sequence || !count) {
return OAKNODE_E_INVALID;
}
*count = impl(sequence)->get_video_stream_count();
return OAKNODE_OK;
}
int oaknode_sequence_get_audio_stream_count(OakNodeSequence *sequence,
int *count)
{
if (!sequence || !count) {
return OAKNODE_E_INVALID;
}
*count = impl(sequence)->get_audio_stream_count();
return OAKNODE_OK;
}
int oaknode_sequence_get_video_params(OakNodeSequence *sequence, int index,
OakCommonVideoParams **out)
{
if (!sequence || !out || index < 0) {
return OAKNODE_E_INVALID;
}
if (index >= impl(sequence)->get_video_stream_count()) {
return OAKNODE_E_NOT_FOUND;
}
try {
*out = new OakCommonVideoParams{impl(sequence)->get_video_params(index)};
} catch (...) {
return OAKNODE_E_NOMEM;
}
return OAKNODE_OK;
}
int oaknode_sequence_set_video_params(OakNodeSequence *sequence, int index,
const OakCommonVideoParams *params)
{
if (!sequence || !params || index < 0) {
return OAKNODE_E_INVALID;
}
if (index >= impl(sequence)->get_video_stream_count()) {
return OAKNODE_E_NOT_FOUND;
}
try {
impl(sequence)->set_video_params(params->impl, index);
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_sequence_get_audio_params(OakNodeSequence *sequence, int index,
OakAudioParams **out)
{
if (!sequence || !out || index < 0) {
return OAKNODE_E_INVALID;
}
if (index >= impl(sequence)->get_audio_stream_count()) {
return OAKNODE_E_NOT_FOUND;
}
OakAudioParams *copy =
oakcore_audioparams_copy(impl(sequence)->get_audio_params(index).handle());
if (!copy) {
return OAKNODE_E_NOMEM;
}
*out = copy;
return OAKNODE_OK;
}
int oaknode_sequence_set_audio_params(OakNodeSequence *sequence, int index,
const OakAudioParams *params)
{
if (!sequence || !params || index < 0) {
return OAKNODE_E_INVALID;
}
if (index >= impl(sequence)->get_audio_stream_count()) {
return OAKNODE_E_NOT_FOUND;
}
OakAudioParams *copy = oakcore_audioparams_copy(params);
if (!copy) {
return OAKNODE_E_NOMEM;
}
try {
impl(sequence)->set_audio_params(
olive::core::AudioParams::from_handle(copy), index);
} catch (...) {
oakcore_audioparams_free(copy);
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
+357
View File
@@ -0,0 +1,357 @@
/***
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;
}
}
+574
View File
@@ -0,0 +1,574 @@
/***
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/track.h"
#include <algorithm>
#include "alivecount.h"
#include "block/block.h"
#include "output/track/track.h"
#include "output/track/tracklist.h"
#include "project/sequence/sequence.h"
namespace
{
olive::Track *impl(OakNodeTrack *h)
{
return reinterpret_cast<olive::Track *>(h);
}
olive::TrackList *list_impl(OakNodeTrackList *h)
{
return reinterpret_cast<olive::TrackList *>(h);
}
olive::Block *block_impl(OakNodeBlock *h)
{
return reinterpret_cast<olive::Block *>(h);
}
OakNodeTrack *wrap(olive::Track *t)
{
return reinterpret_cast<OakNodeTrack *>(t);
}
OakNodeBlock *wrap_block(olive::Block *b)
{
return reinterpret_cast<OakNodeBlock *>(b);
}
bool valid_type(int type)
{
return type >= OAKNODE_TRACK_TYPE_VIDEO && type < OAKNODE_TRACK_TYPE_COUNT;
}
/**
* @brief Refresh the cached lengths after a block mutation
*
* De-Qt wave: TrackList::update_total_length / Sequence::verify_length
* were signal-driven; the C API performs the refresh synchronously so
* that state read back right after a mutation is consistent.
*/
void refresh_lengths(olive::Track *t)
{
olive::Sequence *s = t->sequence();
if (s && valid_type(int(t->type()))) {
olive::TrackList *l = s->track_list(t->type());
if (l) {
l->update_total_length();
}
s->verify_length();
}
}
int get_rational(const olive::core::Rational &r, int *numerator,
int *denominator)
{
if (!numerator || !denominator) {
return OAKNODE_E_INVALID;
}
*numerator = r.numerator();
*denominator = r.denominator();
return OAKNODE_OK;
}
} // namespace
/* ---------------------------------------------------------------- Track */
OakNodeTrack *oaknode_track_create(int type)
{
if (!valid_type(type)) {
return nullptr;
}
try {
olive::Track *t = new olive::Track();
t->set_type(static_cast<olive::Track::Type>(type));
oaknode_c_api::alive_inc();
return wrap(t);
} catch (...) {
return nullptr;
}
}
void oaknode_track_free(OakNodeTrack *track)
{
if (!track) {
return;
}
delete impl(track);
oaknode_c_api::alive_dec();
}
int oaknode_track_get_type(OakNodeTrack *track, int *type)
{
if (!track || !type) {
return OAKNODE_E_INVALID;
}
*type = int(impl(track)->type());
return OAKNODE_OK;
}
int oaknode_track_set_type(OakNodeTrack *track, int type)
{
if (!track || !valid_type(type)) {
return OAKNODE_E_INVALID;
}
impl(track)->set_type(static_cast<olive::Track::Type>(type));
return OAKNODE_OK;
}
int oaknode_track_get_height(OakNodeTrack *track, double *height)
{
if (!track || !height) {
return OAKNODE_E_INVALID;
}
*height = impl(track)->get_track_height();
return OAKNODE_OK;
}
int oaknode_track_set_height(OakNodeTrack *track, double height)
{
if (!track) {
return OAKNODE_E_INVALID;
}
impl(track)->set_track_height(height);
return OAKNODE_OK;
}
int oaknode_track_get_height_in_pixels(OakNodeTrack *track, int *height)
{
if (!track || !height) {
return OAKNODE_E_INVALID;
}
*height = impl(track)->get_track_height_in_pixels();
return OAKNODE_OK;
}
int oaknode_track_set_height_in_pixels(OakNodeTrack *track, int height)
{
if (!track) {
return OAKNODE_E_INVALID;
}
impl(track)->set_track_height_in_pixels(height);
return OAKNODE_OK;
}
int oaknode_track_get_default_height_in_pixels(void)
{
return olive::Track::get_default_track_height_in_pixels();
}
int oaknode_track_get_minimum_height_in_pixels(void)
{
return olive::Track::get_minimum_track_height_in_pixels();
}
int oaknode_track_get_index(OakNodeTrack *track, int *index)
{
if (!track || !index) {
return OAKNODE_E_INVALID;
}
*index = impl(track)->index();
return OAKNODE_OK;
}
int oaknode_track_set_index(OakNodeTrack *track, int index)
{
if (!track) {
return OAKNODE_E_INVALID;
}
impl(track)->set_index(index);
return OAKNODE_OK;
}
int oaknode_track_get_muted(OakNodeTrack *track, int *muted)
{
if (!track || !muted) {
return OAKNODE_E_INVALID;
}
*muted = impl(track)->is_muted() ? 1 : 0;
return OAKNODE_OK;
}
int oaknode_track_set_muted(OakNodeTrack *track, int muted)
{
if (!track) {
return OAKNODE_E_INVALID;
}
impl(track)->set_muted(muted != 0);
return OAKNODE_OK;
}
int oaknode_track_get_locked(OakNodeTrack *track, int *locked)
{
if (!track || !locked) {
return OAKNODE_E_INVALID;
}
*locked = impl(track)->is_locked() ? 1 : 0;
return OAKNODE_OK;
}
int oaknode_track_set_locked(OakNodeTrack *track, int locked)
{
if (!track) {
return OAKNODE_E_INVALID;
}
impl(track)->set_locked(locked != 0);
return OAKNODE_OK;
}
int oaknode_track_get_reference(OakNodeTrack *track, int *type, int *index)
{
if (!track || !type || !index) {
return OAKNODE_E_INVALID;
}
olive::Track::Reference ref = impl(track)->to_reference();
*type = int(ref.type());
*index = ref.index();
return OAKNODE_OK;
}
int oaknode_track_get_length(OakNodeTrack *track, int *numerator,
int *denominator)
{
if (!track) {
return OAKNODE_E_INVALID;
}
return get_rational(impl(track)->track_length(), numerator, denominator);
}
int oaknode_track_get_sequence(OakNodeTrack *track, OakNodeSequence **out)
{
if (!track || !out) {
return OAKNODE_E_INVALID;
}
*out = reinterpret_cast<OakNodeSequence *>(impl(track)->sequence());
return OAKNODE_OK;
}
/* ------------------------------------------------------- Track blocks */
int oaknode_track_get_block_count(OakNodeTrack *track, int *count)
{
if (!track || !count) {
return OAKNODE_E_INVALID;
}
*count = int(impl(track)->blocks().size());
return OAKNODE_OK;
}
int oaknode_track_get_block_at(OakNodeTrack *track, int index,
OakNodeBlock **out)
{
if (!track || !out || index < 0) {
return OAKNODE_E_INVALID;
}
const auto &blocks = impl(track)->blocks();
if (index >= int(blocks.size())) {
return OAKNODE_E_NOT_FOUND;
}
*out = wrap_block(blocks.at(index));
return OAKNODE_OK;
}
int oaknode_track_append_block(OakNodeTrack *track, OakNodeBlock *block)
{
if (!track || !block) {
return OAKNODE_E_INVALID;
}
try {
impl(track)->append_block(block_impl(block));
} catch (...) {
return OAKNODE_E_FAILED;
}
refresh_lengths(impl(track));
return OAKNODE_OK;
}
int oaknode_track_prepend_block(OakNodeTrack *track, OakNodeBlock *block)
{
if (!track || !block) {
return OAKNODE_E_INVALID;
}
try {
impl(track)->prepend_block(block_impl(block));
} catch (...) {
return OAKNODE_E_FAILED;
}
refresh_lengths(impl(track));
return OAKNODE_OK;
}
int oaknode_track_insert_block_at_index(OakNodeTrack *track,
OakNodeBlock *block, int index)
{
if (!track || !block) {
return OAKNODE_E_INVALID;
}
try {
impl(track)->insert_block_at_index(block_impl(block), index);
} catch (...) {
return OAKNODE_E_FAILED;
}
refresh_lengths(impl(track));
return OAKNODE_OK;
}
int oaknode_track_insert_block_after(OakNodeTrack *track, OakNodeBlock *block,
OakNodeBlock *before)
{
if (!track || !block || !before) {
return OAKNODE_E_INVALID;
}
try {
impl(track)->insert_block_after(block_impl(block), block_impl(before));
} catch (...) {
return OAKNODE_E_FAILED;
}
refresh_lengths(impl(track));
return OAKNODE_OK;
}
int oaknode_track_insert_block_before(OakNodeTrack *track, OakNodeBlock *block,
OakNodeBlock *after)
{
if (!track || !block || !after) {
return OAKNODE_E_INVALID;
}
try {
impl(track)->insert_block_before(block_impl(block), block_impl(after));
} catch (...) {
return OAKNODE_E_FAILED;
}
refresh_lengths(impl(track));
return OAKNODE_OK;
}
int oaknode_track_ripple_remove_block(OakNodeTrack *track, OakNodeBlock *block)
{
if (!track || !block) {
return OAKNODE_E_INVALID;
}
try {
impl(track)->ripple_remove_block(block_impl(block));
} catch (...) {
return OAKNODE_E_FAILED;
}
refresh_lengths(impl(track));
return OAKNODE_OK;
}
int oaknode_track_replace_block(OakNodeTrack *track, OakNodeBlock *old_block,
OakNodeBlock *new_block)
{
if (!track || !old_block || !new_block) {
return OAKNODE_E_INVALID;
}
try {
impl(track)->replace_block(block_impl(old_block), block_impl(new_block));
} catch (...) {
return OAKNODE_E_FAILED;
}
refresh_lengths(impl(track));
return OAKNODE_OK;
}
int oaknode_track_get_block_index(OakNodeTrack *track, OakNodeBlock *block,
int *index)
{
if (!track || !block || !index) {
return OAKNODE_E_INVALID;
}
int i = impl(track)->get_array_index_from_block(block_impl(block));
if (i < 0) {
return OAKNODE_E_NOT_FOUND;
}
*index = i;
return OAKNODE_OK;
}
int oaknode_track_get_block_containing_time(OakNodeTrack *track, int numerator,
int denominator,
OakNodeBlock **out)
{
if (!track || !out) {
return OAKNODE_E_INVALID;
}
olive::Block *b = impl(track)->block_containing_time(
olive::core::Rational(numerator, denominator));
if (!b) {
return OAKNODE_E_NOT_FOUND;
}
*out = wrap_block(b);
return OAKNODE_OK;
}
int oaknode_track_get_visible_block_at_time(OakNodeTrack *track, int numerator,
int denominator,
OakNodeBlock **out)
{
if (!track || !out) {
return OAKNODE_E_INVALID;
}
olive::Block *b = impl(track)->visible_block_at_time(
olive::core::Rational(numerator, denominator));
if (!b) {
return OAKNODE_E_NOT_FOUND;
}
*out = wrap_block(b);
return OAKNODE_OK;
}
int oaknode_track_is_range_free(OakNodeTrack *track, int in_num, int in_den,
int out_num, int out_den, int *is_free)
{
if (!track || !is_free) {
return OAKNODE_E_INVALID;
}
*is_free = impl(track)->is_range_free(
olive::core::TimeRange(
olive::core::Rational(in_num, in_den),
olive::core::Rational(out_num, out_den))) ?
1 :
0;
return OAKNODE_OK;
}
/* ------------------------------------------------------------ TrackList */
int oaknode_tracklist_get_type(OakNodeTrackList *list, int *type)
{
if (!list || !type) {
return OAKNODE_E_INVALID;
}
*type = int(list_impl(list)->type());
return OAKNODE_OK;
}
int oaknode_tracklist_get_track_count(OakNodeTrackList *list, int *count)
{
if (!list || !count) {
return OAKNODE_E_INVALID;
}
*count = list_impl(list)->get_track_count();
return OAKNODE_OK;
}
int oaknode_tracklist_get_track_at(OakNodeTrackList *list, int index,
OakNodeTrack **out)
{
if (!list || !out || index < 0) {
return OAKNODE_E_INVALID;
}
if (index >= list_impl(list)->get_track_count()) {
return OAKNODE_E_NOT_FOUND;
}
*out = wrap(list_impl(list)->get_track_at(index));
return OAKNODE_OK;
}
int oaknode_tracklist_get_total_length(OakNodeTrackList *list, int *numerator,
int *denominator)
{
if (!list) {
return OAKNODE_E_INVALID;
}
return get_rational(list_impl(list)->get_total_length(), numerator,
denominator);
}
int oaknode_tracklist_get_array_size(OakNodeTrackList *list, int *size)
{
if (!list || !size) {
return OAKNODE_E_INVALID;
}
*size = list_impl(list)->array_size();
return OAKNODE_OK;
}
int oaknode_tracklist_add_track(OakNodeTrackList *list, OakNodeTrack *track)
{
if (!list || !track) {
return OAKNODE_E_INVALID;
}
olive::TrackList *l = list_impl(list);
olive::Track *t = impl(track);
olive::Sequence *sequence = l->parent();
if (!sequence) {
return OAKNODE_E_STATE;
}
try {
// Graph steps of TimelineAddTrackCommand::redo() minus auto-merge
t->set_parent(l->get_parent_graph());
if (l->get_track_count() > 0) {
t->set_track_height(
l->get_track_at(l->get_track_count() - 1)->get_track_height());
}
l->array_append();
olive::Node::connect_edge(t, l->track_input(l->array_size() - 1));
// De-Qt wave: the former signal emissions are the caller's job
sequence->update_track_cache();
l->update_total_length();
sequence->verify_length();
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
int oaknode_tracklist_remove_track(OakNodeTrackList *list, OakNodeTrack *track)
{
if (!list || !track) {
return OAKNODE_E_INVALID;
}
olive::TrackList *l = list_impl(list);
olive::Track *t = impl(track);
olive::Sequence *sequence = l->parent();
if (!sequence) {
return OAKNODE_E_STATE;
}
const auto &tracks = l->get_tracks();
auto it = std::find(tracks.begin(), tracks.end(), t);
if (it == tracks.end()) {
return OAKNODE_E_NOT_FOUND;
}
try {
int cache_index = int(it - tracks.begin());
int array_index = l->get_array_index_from_cache_index(cache_index);
olive::Node::disconnect_edge(t, l->track_input(array_index));
sequence->input_array_remove(l->track_input(), array_index);
// De-Qt wave: the former signal emissions are the caller's job
sequence->update_track_cache();
l->update_total_length();
sequence->verify_length();
} catch (...) {
return OAKNODE_E_FAILED;
}
return OAKNODE_OK;
}
+236
View File
@@ -0,0 +1,236 @@
/***
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/traverser.h"
#include <new>
#include "traverser.h"
#include "valuedatabase.h"
#include "valueconvert.h"
struct OakNodeValueDatabase {
olive::NodeValueDatabase impl;
};
namespace
{
inline olive::NodeTraverser *to_traverser(OakNodeTraverser *traverser)
{
return reinterpret_cast<olive::NodeTraverser *>(traverser);
}
inline olive::Node *to_node(OakNodeNode *node)
{
return reinterpret_cast<olive::Node *>(node);
}
/**
* @brief Find the table named `key`, or NULL when absent.
*/
const olive::NodeValueTable *find_table(const OakNodeValueDatabase *db,
const char *key)
{
for (auto it = db->impl.cbegin(); it != db->impl.cend(); ++it) {
if (it->first == key) {
return &it->second;
}
}
return nullptr;
}
}
OakNodeTraverser *oaknode_traverser_init(void)
{
try {
olive::NodeTraverser *traverser = new (std::nothrow) olive::NodeTraverser();
if (traverser) {
oaknode_c_api::alive_inc();
}
return reinterpret_cast<OakNodeTraverser *>(traverser);
} catch (...) {
return NULL;
}
}
void oaknode_traverser_free(OakNodeTraverser *traverser)
{
if (!traverser) {
return;
}
try {
delete to_traverser(traverser);
oaknode_c_api::alive_dec();
} catch (...) {
}
}
int oaknode_traverser_generate_database(OakNodeTraverser *traverser,
OakNodeNode *node, int64_t in_num,
int64_t in_den, int64_t out_num,
int64_t out_den,
OakNodeValueDatabase **out_db)
{
if (!traverser || !node || !out_db) {
return OAKNODE_E_INVALID;
}
try {
olive::core::Rational in(static_cast<int>(in_num),
static_cast<int>(in_den));
olive::core::Rational out(static_cast<int>(out_num),
static_cast<int>(out_den));
OakNodeValueDatabase *db = new (std::nothrow) OakNodeValueDatabase();
if (!db) {
return OAKNODE_E_NOMEM;
}
db->impl = to_traverser(traverser)->generate_database(
to_node(node), olive::core::TimeRange(in, out));
*out_db = db;
oaknode_c_api::alive_inc();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
void oaknode_traverser_database_free(OakNodeValueDatabase *db)
{
if (!db) {
return;
}
delete db;
oaknode_c_api::alive_dec();
}
int oaknode_traverser_database_row_count(const OakNodeValueDatabase *db,
int *out_count)
{
if (!db || !out_count) {
return OAKNODE_E_INVALID;
}
try {
int count = 0;
for (auto it = db->impl.cbegin(); it != db->impl.cend(); ++it) {
count++;
}
*out_count = count;
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_traverser_database_row_key_at(const OakNodeValueDatabase *db,
int index, char *buf, int buf_size)
{
if (!db) {
return OAKNODE_E_INVALID;
}
try {
if (index < 0) {
return OAKNODE_E_NOT_FOUND;
}
auto it = db->impl.cbegin();
for (int i = 0; i < index && it != db->impl.cend(); i++, ++it) {
}
if (it == db->impl.cend()) {
return OAKNODE_E_NOT_FOUND;
}
return oaknode_c_api::copy_string(it->first, buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_traverser_database_row_value_count(const OakNodeValueDatabase *db,
const char *key,
int *out_count)
{
if (!db || !key || !out_count) {
return OAKNODE_E_INVALID;
}
try {
const olive::NodeValueTable *table = find_table(db, key);
if (!table) {
return OAKNODE_E_NOT_FOUND;
}
*out_count = table->count();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_traverser_database_value_at(const OakNodeValueDatabase *db,
const char *key, int index,
oaknode_value *out)
{
if (!db || !key || !out) {
return OAKNODE_E_INVALID;
}
try {
const olive::NodeValueTable *table = find_table(db, key);
if (!table || index < 0 || index >= table->count()) {
return OAKNODE_E_NOT_FOUND;
}
const olive::NodeValue &value = table->at(index);
return oaknode_c_api::value_from_variant(value.type(), value.data(),
out);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_traverser_database_value_string_at(const OakNodeValueDatabase *db,
const char *key, int index,
char *buf, int buf_size)
{
if (!db || !key) {
return OAKNODE_E_INVALID;
}
try {
const olive::NodeValueTable *table = find_table(db, key);
if (!table || index < 0 || index >= table->count()) {
return OAKNODE_E_NOT_FOUND;
}
const olive::NodeValue &value = table->at(index);
return oaknode_c_api::copy_string(
olive::NodeValue::value_to_string(value.type(), value.data(), false),
buf, buf_size);
} catch (...) {
return OAKNODE_E_FAILED;
}
}
+273
View File
@@ -0,0 +1,273 @@
/***
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/>.
***/
#ifndef OAK_NODE_C_API_VALUECONVERT_H
#define OAK_NODE_C_API_VALUECONVERT_H
// Internal helpers shared by the oaknode c_api translation units:
// oaknode_value <-> olive::Variant mapping, the pinned
// oaknode_value_type <-> olive::NodeValue::Type mapping, two-stage string
// copy, OakUndoCommand wrapping and the debug alive counter.
#include "node/node.h"
#include <cstring>
#include <new>
#include <string>
#include "value.h"
// Internal layout of the OakUndoCommand handle, shared with the oakundo
// module (src/undo/c_api/commandhandle.h). Included so undoable variants
// can hand out owned handles wrapping freshly created olive commands.
#include "../../undo/c_api/commandhandle.h"
namespace oaknode_c_api
{
/**
* @brief Bump/release the debug alive counter (defined in node.cpp).
*/
void alive_inc();
void alive_dec();
/**
* @brief Shared two-stage string getter.
*
* Returns the required buffer size in bytes (including the terminating
* NUL) as a non-negative value.
*/
inline 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;
}
/**
* @brief Pinned mapping olive::NodeValue::Type -> oaknode_value_type
* (see the table on oaknode_value_type in node/node.h). Types without a
* POD representation map to OAKNODE_VALUE_NONE.
*/
inline int value_type_to_oak(olive::NodeValue::Type type)
{
switch (type) {
case olive::NodeValue::k_int:
return OAKNODE_VALUE_INT;
case olive::NodeValue::k_float:
return OAKNODE_VALUE_FLOAT;
case olive::NodeValue::k_boolean:
return OAKNODE_VALUE_BOOL;
case olive::NodeValue::k_rational:
return OAKNODE_VALUE_RATIONAL;
case olive::NodeValue::k_color:
return OAKNODE_VALUE_COLOR;
case olive::NodeValue::k_vec2:
return OAKNODE_VALUE_VEC2;
case olive::NodeValue::k_vec3:
return OAKNODE_VALUE_VEC3;
case olive::NodeValue::k_vec4:
return OAKNODE_VALUE_VEC4;
case olive::NodeValue::k_combo:
return OAKNODE_VALUE_COMBO;
case olive::NodeValue::k_file:
case olive::NodeValue::k_text:
case olive::NodeValue::k_font:
case olive::NodeValue::k_str_combo:
return OAKNODE_VALUE_STRING;
default:
return OAKNODE_VALUE_NONE;
}
}
/**
* @brief 1 if the olive type is string-carried (no POD representation,
* handled by the dedicated string functions).
*/
inline bool value_type_is_string(olive::NodeValue::Type type)
{
return type == olive::NodeValue::k_file || type == olive::NodeValue::k_text ||
type == olive::NodeValue::k_font || type == olive::NodeValue::k_str_combo;
}
/**
* @brief Build an olive::Variant from an oaknode_value POD.
*
* `value->type` must be one of the POD-carrying oaknode_value_type
* values (STRING is rejected: no string data fits the POD).
*/
inline bool variant_from_value(const oaknode_value *value, olive::Variant *out)
{
using olive::core::Color;
using olive::core::Rational;
using olive::Vector2D;
using olive::Vector3D;
using olive::Vector4D;
switch (value->type) {
case OAKNODE_VALUE_INT:
case OAKNODE_VALUE_COMBO:
*out = olive::Variant(value->num);
return true;
case OAKNODE_VALUE_FLOAT:
*out = olive::Variant(value->f[0]);
return true;
case OAKNODE_VALUE_BOOL:
*out = olive::Variant(value->num != 0);
return true;
case OAKNODE_VALUE_RATIONAL:
*out = olive::Variant::from_value(
Rational(static_cast<int>(value->num), static_cast<int>(value->den)));
return true;
case OAKNODE_VALUE_COLOR:
*out = olive::Variant::from_value(
Color(static_cast<float>(value->f[0]), static_cast<float>(value->f[1]),
static_cast<float>(value->f[2]), static_cast<float>(value->f[3])));
return true;
case OAKNODE_VALUE_VEC2:
*out = olive::Variant::from_value(Vector2D(static_cast<float>(value->f[0]),
static_cast<float>(value->f[1])));
return true;
case OAKNODE_VALUE_VEC3:
*out = olive::Variant::from_value(Vector3D(static_cast<float>(value->f[0]),
static_cast<float>(value->f[1]),
static_cast<float>(value->f[2])));
return true;
case OAKNODE_VALUE_VEC4:
*out = olive::Variant::from_value(Vector4D(static_cast<float>(value->f[0]),
static_cast<float>(value->f[1]),
static_cast<float>(value->f[2]),
static_cast<float>(value->f[3])));
return true;
default:
return false;
}
}
/**
* @brief Map an olive::Variant of declared type `type` into an
* oaknode_value POD.
*
* Returns OAKNODE_OK, OAKNODE_E_INVALID for string-family types (use the
* string getters), or OAKNODE_E_FAILED for types without a POD
* representation.
*/
inline int value_from_variant(olive::NodeValue::Type type, const olive::Variant &v,
oaknode_value *out)
{
using olive::core::Color;
using olive::core::Rational;
using olive::Vector2D;
using olive::Vector3D;
using olive::Vector4D;
if (value_type_is_string(type)) {
return OAKNODE_E_INVALID;
}
*out = oaknode_value();
out->type = value_type_to_oak(type);
switch (type) {
case olive::NodeValue::k_none:
return OAKNODE_OK;
case olive::NodeValue::k_int:
case olive::NodeValue::k_combo:
out->num = v.to_long_long();
return OAKNODE_OK;
case olive::NodeValue::k_float:
out->f[0] = v.to_double();
return OAKNODE_OK;
case olive::NodeValue::k_boolean:
out->num = v.to_bool() ? 1 : 0;
return OAKNODE_OK;
case olive::NodeValue::k_rational: {
Rational r = v.value<Rational>();
out->num = r.numerator();
out->den = r.denominator();
return OAKNODE_OK;
}
case olive::NodeValue::k_color: {
Color c = v.value<Color>();
out->f[0] = c.red();
out->f[1] = c.green();
out->f[2] = c.blue();
out->f[3] = c.alpha();
return OAKNODE_OK;
}
case olive::NodeValue::k_vec2: {
Vector2D vec = v.value<Vector2D>();
out->f[0] = vec.x();
out->f[1] = vec.y();
return OAKNODE_OK;
}
case olive::NodeValue::k_vec3: {
Vector3D vec = v.value<Vector3D>();
out->f[0] = vec.x();
out->f[1] = vec.y();
out->f[2] = vec.z();
return OAKNODE_OK;
}
case olive::NodeValue::k_vec4: {
Vector4D vec = v.value<Vector4D>();
out->f[0] = vec.x();
out->f[1] = vec.y();
out->f[2] = vec.z();
out->f[3] = vec.w();
return OAKNODE_OK;
}
default:
out->type = OAKNODE_VALUE_NONE;
return OAKNODE_E_FAILED;
}
}
/**
* @brief Wrap a freshly created olive::UndoCommand in an owned
* OakUndoCommand handle. Returns NULL on allocation failure.
*/
inline OakUndoCommand *wrap_command(olive::UndoCommand *command)
{
if (!command) {
return NULL;
}
OakUndoCommand *handle = new (std::nothrow) OakUndoCommand();
if (!handle) {
delete command;
return NULL;
}
handle->command = command;
handle->owned = true;
return handle;
}
}
#endif // OAK_NODE_C_API_VALUECONVERT_H