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:
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user