refactor(plugin): de-Qt oakplugin and wrap it in a pure C ABI
- de-Qt src/plugin/src (olivehost/oliveplugininstance/oliveclip/
paraminstance/image/pluginprogressreporter); QMessageBox/
QApplication replaced by facade callbacks
- new C ABI in include/plugin/{error,host,instance}.h with
refcounted OakPluginInstance value handle
- paraminstance bridges via oaknode C ABI (OakNodeNode value handle,
oaknode_node_identity registry); undo via oakundo C ABI
- oliveclip textures are OakRenderTexture value handles; oakrender
gains texture/copier/ticket C API additions
- oaknode gains get_input_at_time/set_input_at_time_undoable/
node_identity/sequence_from_node/sequence_set_default_parameters/
find_input_footage
- move avframeptr.h to src/common/src (shared by codec and render)
- node transition pluginSupport stubs bridge the real oakplugin
headers; all oaknode-building standalone trees add src/plugin and
link oakplugin into their test binaries
- plugin tests self-sufficient (ipc shim, OfxHost force_load,
PRE_TEST discovery, OCIO env); timeline standalone gains
render/c_api
- restore ffmpegdecoder.cpp in src/codec/src/ffmpeg/CMakeLists.txt
(dropped in 3d004c081, caused jump-to-0 in decoder/encoder tests)
All six standalone trees green: codec 22, audio 40, task 110,
render 49, timeline 121, plugin 100.
This commit is contained in:
@@ -98,3 +98,28 @@ ldd liboakengine.so | grep oak # 只见 oak* 模
|
||||
- instance:Shadertoy 类插件(CI 里可用的)创建/参数/渲染一帧非空。
|
||||
- progress:回调触发与 cancel。
|
||||
- `oakplugin_debug_alive_count()` 泄漏断言。
|
||||
|
||||
## 6. 实施现状(2026-08-05)
|
||||
|
||||
已完成。src/plugin/src/ 六个类(olivehost/oliveplugininstance/
|
||||
oliveclip/paraminstance/image/pluginprogressreporter)全部去Qt,
|
||||
QMessageBox/QApplication 依赖删除,改为 §4 裁决的 facade 回调
|
||||
装配。C ABI 落在 include/plugin/{error,host,instance}.h,
|
||||
OakPluginInstance 为引用计数值句柄(ctx/addref/release/
|
||||
abi_version)。
|
||||
|
||||
偏离手册之处:
|
||||
|
||||
- paraminstance 不直接持有 Node*,改持 OakNodeNode 值句柄,参数
|
||||
读写全部经 oaknode C ABI;node↔instance 的身份路由用新增的
|
||||
oaknode_node_identity() 注册表实现。
|
||||
- oliveclip 的纹理为 OakRenderTexture 值句柄,oakrender 因此补了
|
||||
texture/copier/ticket 一族 C API。
|
||||
- oaknode 的 transition stub(src/node/transition/pluginSupport/)
|
||||
桥接 oakplugin 真身头,liboaknode 运行期引用 oakplugin 符号
|
||||
(dynamic_lookup);所有构建 oaknode 的 standalone 树都要把
|
||||
src/plugin 加进构建,测试二进制必须链接 oakplugin。
|
||||
|
||||
验证:build-oakplugin 100/100;codec 22/22、audio 40/40、
|
||||
task 110/110、render 49/49、timeline 121/121(均含 oakplugin
|
||||
接入后的全量回归)。
|
||||
|
||||
@@ -285,3 +285,35 @@ ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需
|
||||
必须同时链两个库(各 standalone 驱动已接线)。
|
||||
- oakcommon xml C API 新增 get_native 借用访问器(C++ only),
|
||||
oaktimeline 的 load/save 经它取回 XmlStreamReader/Writer。
|
||||
|
||||
## oakplugin 去Qt化的删除与语义变更(2026-08-05)
|
||||
|
||||
- olivehost/oliveplugininstance/oliveclip/paraminstance/image/
|
||||
pluginprogressreporter 六个类去Qt:QMessageBox/QApplication 全部
|
||||
删除,宿主消息与 undo 提交改为 facade 回调
|
||||
(set_host_message_handler / set_undo_submit_callback /
|
||||
ActiveViewerProvider / set_main_thread_id)。
|
||||
- paraminstance 的参数桥改走 oaknode C API(node_ 为 OakNodeNode
|
||||
值句柄);undo 命令走 oakundo C API;node↔instance 路由用新增的
|
||||
oaknode_node_identity() 注册表。
|
||||
- oliveclip 的纹理改 OakRenderTexture 值句柄;为此 oakrender C API
|
||||
新增 texture is_dummy/get_frame/width/height/fb_format/
|
||||
wrap_native 与 copier/ticket 族。
|
||||
- oaknode C API 新增 get_input_at_time/set_input_at_time_undoable
|
||||
(_into)/node_identity/sequence_from_node/
|
||||
sequence_set_default_parameters/find_input_footage。
|
||||
- avframeptr.h 从 src/codec/src/ffmpeg/ 上移到 src/common/src/
|
||||
(codec 与 render 共用),transition 里的旧拷贝已删。
|
||||
- 新 C ABI:include/plugin/{error,host,instance}.h,
|
||||
OakPluginInstance 为引用计数值句柄。
|
||||
- oaknode 的 transition stub(src/node/transition/pluginSupport/
|
||||
{oliveplugininstance,olivehost}.h)改为桥接 oakplugin 真身头,
|
||||
liboaknode 因此引用 oakplugin 符号;凡构建 oaknode 的 standalone
|
||||
树(codec/audio/task/render/timeline)都必须
|
||||
add_subdirectory(src/plugin),且测试二进制要链接 oakplugin,
|
||||
否则 dynamic_lookup 悬空符号 jump-to-0(EXC_BAD_ACCESS
|
||||
address=0x0)。
|
||||
- 修复 ffmpegdecoder.cpp 被 3d004c081 误删出
|
||||
src/codec/src/ffmpeg/CMakeLists.txt 的回归:FFmpegDecoder 构造
|
||||
函数在 flat namespace 悬空,decoder probe/decode/encoder
|
||||
roundtrip 三个测试 jump-to-0。
|
||||
|
||||
@@ -609,6 +609,39 @@ int oaknode_viewer_set_audio_params(OakNodeNode viewer,
|
||||
*/
|
||||
int oaknode_node_find_input_footage(OakNodeNode node, OakNodeFootage *out);
|
||||
|
||||
/**
|
||||
* @brief Value of an input at a specific time (Node::get_value_at_time(),
|
||||
* element -1). Same POD rules as oaknode_node_get_input().
|
||||
*/
|
||||
int oaknode_node_get_input_at_time(OakNodeNode node,
|
||||
const char *input_id, int64_t time_num,
|
||||
int64_t time_den, oaknode_value *out);
|
||||
|
||||
/**
|
||||
* @brief Set an input's value at a specific time with keyframe logic
|
||||
* (Node::set_value_at_time(), element -1, track 0,
|
||||
* insert_on_all_tracks_if_no_key = true). `*out_command` receives
|
||||
* an owned undo command handle.
|
||||
*/
|
||||
int oaknode_node_set_input_at_time_undoable(OakNodeNode node,
|
||||
const char *input_id, int64_t time_num, int64_t time_den,
|
||||
const oaknode_value *v, int track, OakUndoCommand *out_command);
|
||||
|
||||
/**
|
||||
* @brief Identity of the underlying node object as an opaque integer
|
||||
* (address-cast; for registry keys only, never dereference).
|
||||
*/
|
||||
uintptr_t oaknode_node_identity(OakNodeNode node);
|
||||
|
||||
/**
|
||||
* @brief Append a value-at-time set into an existing multi command
|
||||
* (same semantics as oaknode_node_set_input_at_time_undoable but
|
||||
* batches into `multi_command` from oakundo_command_init_multi()).
|
||||
*/
|
||||
int oaknode_node_set_input_at_time_into(OakNodeNode node,
|
||||
const char *input_id, int64_t time_num, int64_t time_den,
|
||||
const oaknode_value *v, int track, OakUndoCommand multi_command);
|
||||
|
||||
/**
|
||||
* @brief Create a command that removes a node from its graph together
|
||||
* with its exclusive dependencies and disconnects its edges
|
||||
|
||||
@@ -111,6 +111,12 @@ int oaknode_sequence_set_default_parameters(OakNodeSequence sequence);
|
||||
*/
|
||||
OakNodeNode oaknode_sequence_as_node(OakNodeSequence sequence);
|
||||
|
||||
/**
|
||||
* @brief Non-owning cast from a node handle to a sequence handle (empty
|
||||
* ctx when the node is not a Sequence).
|
||||
*/
|
||||
OakNodeSequence oaknode_sequence_from_node(OakNodeNode node);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle to the per-type track list.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/***
|
||||
|
||||
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_PLUGIN_ERROR_H
|
||||
#define OAK_EDITOR_PLUGIN_ERROR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Status and error codes shared by all oakplugin C API families.
|
||||
*/
|
||||
#define OAKPLUGIN_OK 0 /**< Success. */
|
||||
#define OAKPLUGIN_E_INVALID (-1) /**< NULL handle or invalid argument. */
|
||||
#define OAKPLUGIN_E_STATE (-2) /**< Call not valid in the current state. */
|
||||
#define OAKPLUGIN_E_FAILED (-3) /**< The underlying operation failed. */
|
||||
#define OAKPLUGIN_E_NOT_FOUND (-4) /**< Entry not found. */
|
||||
#define OAKPLUGIN_E_NOMEM (-5) /**< Allocation failed. */
|
||||
#define OAKPLUGIN_E_CANCELLED (-6) /**< The operation was cancelled. */
|
||||
|
||||
/** @brief ABI version stamped into every oakplugin handle. */
|
||||
#define OAKPLUGIN_ABI_VERSION 1
|
||||
|
||||
#endif //OAK_EDITOR_PLUGIN_ERROR_H
|
||||
@@ -0,0 +1,69 @@
|
||||
/***
|
||||
|
||||
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_PLUGIN_HOST_H
|
||||
#define OAK_EDITOR_PLUGIN_HOST_H
|
||||
|
||||
#include "plugin/error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the OFX host (olive::plugin::load_plugins() with the
|
||||
* default search paths). Idempotent.
|
||||
*/
|
||||
int oakplugin_host_init(void);
|
||||
|
||||
/** @brief Shut the host down (persistent messages cleared). */
|
||||
void oakplugin_host_shutdown(void);
|
||||
|
||||
/** @brief Scan additional bundle directories. */
|
||||
int oakplugin_host_scan(const char *const *bundle_dirs, int dir_count);
|
||||
|
||||
/** @brief Number of discovered plugins (>= 0), or a negative error. */
|
||||
int oakplugin_host_plugin_count(void);
|
||||
|
||||
/** @brief Plugin identifier at index (two-stage string getter). */
|
||||
int oakplugin_host_plugin_id_at(int index, char *buf, int buf_size);
|
||||
|
||||
/** @brief Plugin label for an identifier (two-stage; currently the
|
||||
* identifier itself). OAKPLUGIN_E_NOT_FOUND for unknown ids. */
|
||||
int oakplugin_host_plugin_label(const char *plugin_id, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief UI message handler for OFX host messages (question replies use
|
||||
* OAKPLUGIN_MESSAGE_ANSWER_YES/NO). Without a handler, messages
|
||||
* are logged and questions get "no".
|
||||
*/
|
||||
#define OAKPLUGIN_MESSAGE_ANSWER_NO 0
|
||||
#define OAKPLUGIN_MESSAGE_ANSWER_YES 1
|
||||
typedef int (*oakplugin_message_fn)(const char *type, const char *message,
|
||||
void *userdata);
|
||||
void oakplugin_host_set_message_handler(oakplugin_message_fn fn,
|
||||
void *userdata);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_PLUGIN_HOST_H
|
||||
@@ -0,0 +1,103 @@
|
||||
/***
|
||||
|
||||
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_PLUGIN_INSTANCE_H
|
||||
#define OAK_EDITOR_PLUGIN_INSTANCE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "plugin/error.h"
|
||||
#include "render/renderer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Reference-counted handle to an OFX plugin instance
|
||||
* (olive::plugin::OlivePluginInstance).
|
||||
*
|
||||
* Ownership/count semantics follow include/common/handle.h: create
|
||||
* returns count 1, addref/release adjust it, release destroys at zero.
|
||||
*/
|
||||
typedef struct OakPluginInstance {
|
||||
void *ctx;
|
||||
void (*addref)(void *ctx);
|
||||
void (*release)(void *ctx);
|
||||
uint32_t abi_version; /**< OAKPLUGIN_ABI_VERSION. */
|
||||
} OakPluginInstance;
|
||||
|
||||
/**
|
||||
* @brief Create an instance of a discovered plugin (filter context).
|
||||
* Returns an empty handle (ctx == NULL) for unknown ids/failure.
|
||||
*/
|
||||
OakPluginInstance oakplugin_instance_create(const char *plugin_id);
|
||||
|
||||
/** @brief Release one reference. NULL/empty no-op; clears ctx. */
|
||||
void oakplugin_instance_free(OakPluginInstance *instance);
|
||||
|
||||
/**
|
||||
* @brief Set/get a parameter as an oaknode_value POD (type rules from
|
||||
* node/node.h). String-typed params use
|
||||
* oakplugin_instance_set_param_string()/get_param_string().
|
||||
*/
|
||||
int oakplugin_instance_set_param(OakPluginInstance instance,
|
||||
const char *param_id,
|
||||
const oaknode_value *value);
|
||||
int oakplugin_instance_get_param(OakPluginInstance instance,
|
||||
const char *param_id, oaknode_value *out);
|
||||
int oakplugin_instance_set_param_string(OakPluginInstance instance,
|
||||
const char *param_id,
|
||||
const char *value);
|
||||
int oakplugin_instance_get_param_string(OakPluginInstance instance,
|
||||
const char *param_id, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Render one frame through the instance (renderAction).
|
||||
*
|
||||
* `src` may be an empty handle for generator plugins. Textures stay
|
||||
* owned by the caller (borrowed for the call).
|
||||
*/
|
||||
int oakplugin_instance_render(OakPluginInstance instance,
|
||||
OakRenderTexture dst, OakRenderTexture src,
|
||||
double time_seconds);
|
||||
|
||||
/**
|
||||
* @brief Progress callback for long renders (async return channel,
|
||||
* 01 §4 exception). Return non-zero to abort processing.
|
||||
*/
|
||||
typedef int (*oakplugin_progress_fn)(double progress, void *userdata);
|
||||
int oakplugin_instance_set_progress_cb(OakPluginInstance instance,
|
||||
oakplugin_progress_fn fn,
|
||||
void *userdata);
|
||||
|
||||
/** @brief Cancel any in-progress render/progress reporting. */
|
||||
int oakplugin_instance_cancel(OakPluginInstance instance);
|
||||
|
||||
/** @brief Alive-count for leak assertions in tests. */
|
||||
int oakplugin_debug_alive_count(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_PLUGIN_INSTANCE_H
|
||||
@@ -23,6 +23,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <memory>
|
||||
#endif
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -229,9 +233,42 @@ int oakrender_display_texture_download(OakRenderTexture texture, void *pixels,
|
||||
int oakrender_display_texture_get_params(OakRenderTexture texture,
|
||||
oakrender_video_params *out);
|
||||
|
||||
/** @brief Frame width/height in pixels (0 on empty). */
|
||||
int oakrender_codec_frame_width(OakCodecFrame frame);
|
||||
int oakrender_codec_frame_height(OakCodecFrame frame);
|
||||
|
||||
/** @brief ffmpeg_bridge pixel format when the frame wraps a texture's
|
||||
* CPU copy (an AVFramePtr); -1 otherwise. */
|
||||
int oakrender_codec_frame_fb_format(OakCodecFrame frame);
|
||||
|
||||
/** @brief Native texture id (0 on empty or a dummy/id-less texture). */
|
||||
int oakrender_display_texture_id(OakRenderTexture texture);
|
||||
|
||||
/** @brief 1 when the texture is a placeholder dummy (Texture::is_dummy()). */
|
||||
int oakrender_display_texture_is_dummy(OakRenderTexture texture);
|
||||
|
||||
/**
|
||||
* @brief The CPU frame stored in the texture, if any (Texture::frame()).
|
||||
* *out receives a retained frame handle (empty when none).
|
||||
*/
|
||||
int oakrender_display_texture_get_frame(OakRenderTexture texture,
|
||||
OakCodecFrame *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
namespace olive { class Texture; using TexturePtr = std::shared_ptr<Texture>; }
|
||||
|
||||
/**
|
||||
* @brief Wrap a native TexturePtr in a retained handle (C++ only; used
|
||||
* by oakrender internals when handing textures across the C ABI).
|
||||
*/
|
||||
OakRenderTexture oakrender_display_texture_wrap_native(
|
||||
const olive::TexturePtr &texture);
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- Frame handle -------------------------------------------------------- */
|
||||
|
||||
/** @brief Create an empty CPU frame. Returns a handle with count 1. */
|
||||
|
||||
@@ -6,3 +6,4 @@ add_subdirectory(codec)
|
||||
add_subdirectory(audio)
|
||||
add_subdirectory(timeline)
|
||||
add_subdirectory(task)
|
||||
add_subdirectory(plugin)
|
||||
|
||||
@@ -156,3 +156,4 @@ if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/audio/tests ${CMAKE_BINARY_DIR}/audio_tests)
|
||||
endif()
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/plugin)
|
||||
|
||||
@@ -36,6 +36,7 @@ target_link_libraries(oakaudio-gtest PRIVATE
|
||||
oakcodec
|
||||
oakrender
|
||||
oaknode
|
||||
oakplugin
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
target_sources(oakcodec PRIVATE avframeptr.h ffmpegdecoder.cpp
|
||||
target_sources(oakcodec PRIVATE
|
||||
ffmpegdecoder.cpp
|
||||
ffmpegdecoder.h
|
||||
ffmpegencoder.cpp
|
||||
ffmpegencoder.h
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
#include "decoder.h"
|
||||
#include "ffmpeg/avframeptr.h"
|
||||
#include "avframeptr.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
@@ -153,3 +153,4 @@ if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/codec/tests ${CMAKE_BINARY_DIR}/codec_tests)
|
||||
endif()
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/plugin)
|
||||
|
||||
@@ -34,6 +34,7 @@ target_link_libraries(oakcodec-gtest PRIVATE
|
||||
oakcodec
|
||||
oakrender
|
||||
oaknode
|
||||
oakplugin
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_library(oakcommon SHARED
|
||||
avframeptr.h
|
||||
commandlineparser.cpp
|
||||
commandlineparser.h
|
||||
colortransform.h
|
||||
|
||||
@@ -1429,3 +1429,97 @@ int oaknode_node_find_input_footage(OakNodeNode node, OakNodeFootage *out)
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_node_get_input_at_time(OakNodeNode node,
|
||||
const char *input_id, int64_t time_num,
|
||||
int64_t time_den, oaknode_value *out)
|
||||
{
|
||||
if (!node.ctx || !input_id || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_input(oaknode_c_api::to_native<olive::Node>(node), input_id)) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Node *n = oaknode_c_api::to_native<olive::Node>(node);
|
||||
olive::Variant v = n->get_value_at_time(
|
||||
input_id,
|
||||
olive::core::Rational(int(time_num), int(time_den)));
|
||||
|
||||
olive::NodeValue::Type type = n->get_input_data_type(input_id);
|
||||
return oaknode_c_api::value_from_variant(type, v, out);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_node_set_input_at_time_undoable(OakNodeNode node,
|
||||
const char *input_id, int64_t time_num, int64_t time_den,
|
||||
const oaknode_value *v, int track, OakUndoCommand *out_command)
|
||||
{
|
||||
if (!node.ctx || !input_id || !v || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_input(oaknode_c_api::to_native<olive::Node>(node), input_id)) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Variant value;
|
||||
if (!oaknode_c_api::variant_from_value(v, &value)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
auto *multi = new olive::MultiUndoCommand();
|
||||
olive::Node::set_value_at_time(
|
||||
olive::NodeInput(oaknode_c_api::to_native<olive::Node>(node),
|
||||
input_id),
|
||||
olive::core::Rational(int(time_num), int(time_den)), value, track,
|
||||
multi, true);
|
||||
|
||||
*out_command = oaknode_c_api::wrap_command(multi);
|
||||
return out_command->ctx ? OAKNODE_OK : OAKNODE_E_NOMEM;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t oaknode_node_identity(OakNodeNode node)
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(
|
||||
oaknode_c_api::to_native<olive::Node>(node));
|
||||
}
|
||||
|
||||
int oaknode_node_set_input_at_time_into(OakNodeNode node,
|
||||
const char *input_id, int64_t time_num, int64_t time_den,
|
||||
const oaknode_value *v, int track, OakUndoCommand multi_command)
|
||||
{
|
||||
if (!node.ctx || !input_id || !v || !multi_command.ctx) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_input(oaknode_c_api::to_native<olive::Node>(node), input_id)) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
olive::UndoCommand *base = oakundo_capi::to_command(multi_command);
|
||||
auto *multi = dynamic_cast<olive::MultiUndoCommand *>(base);
|
||||
if (!multi) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Variant value;
|
||||
if (!oaknode_c_api::variant_from_value(v, &value)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
olive::Node::set_value_at_time(
|
||||
olive::NodeInput(oaknode_c_api::to_native<olive::Node>(node),
|
||||
input_id),
|
||||
olive::core::Rational(int(time_num), int(time_den)), value, track,
|
||||
multi, true);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,14 @@ inline void mark_container_owned(Handle h)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deleter for non-owning (borrowed) boxes: never deletes.
|
||||
*/
|
||||
inline void delete_noop(void *object)
|
||||
{
|
||||
(void)object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deleter callback stamping the concrete C++ type.
|
||||
*/
|
||||
|
||||
@@ -353,3 +353,17 @@ int oaknode_sequence_set_default_parameters(OakNodeSequence sequence)
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeSequence oaknode_sequence_from_node(OakNodeNode node)
|
||||
{
|
||||
OakNodeSequence empty = {};
|
||||
if (!node.ctx) {
|
||||
return empty;
|
||||
}
|
||||
olive::Node *n = oaknode_c_api::to_native<olive::Node>(node);
|
||||
if (!n || !dynamic_cast<olive::Sequence *>(n)) {
|
||||
return empty;
|
||||
}
|
||||
return make_handle<OakNodeSequence>(n, false,
|
||||
&oaknode_c_api::delete_noop);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,8 @@ target_link_options(oaknode PRIVATE
|
||||
# 把 oakrender 构建进来并链进测试二进制;未拆分模块符号同样悬置。
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/src ${CMAKE_BINARY_DIR}/render)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/c_api ${CMAKE_BINARY_DIR}/render_c_api)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/plugin)
|
||||
set(BUILD_TESTS ON)
|
||||
|
||||
target_include_directories(oakrender BEFORE PUBLIC
|
||||
@@ -148,7 +150,7 @@ target_link_libraries(oakrender PRIVATE
|
||||
"-framework QuartzCore"
|
||||
)
|
||||
|
||||
target_link_libraries(oaknode-gtest PRIVATE oakrender oaktimeline)
|
||||
target_link_libraries(oaknode-gtest PRIVATE oakrender oaktimeline oakplugin)
|
||||
|
||||
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
|
||||
# dynamic_lookup; the real implementation (engine/src/capi/ipc.cpp) is still
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// Transitional copy of engine/common/avframeptr.h(本身已无 Qt,待下沉 oakcommon)。只增不删。
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_AVFRAMEPTR_H
|
||||
#define OAK_AVFRAMEPTR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief C++ adapter around the ffmpeg_bridge frame handle
|
||||
*
|
||||
* Mirrors the AVFrame field access the codebase used to perform directly,
|
||||
* but every operation goes through the pure C bridge API so the editor
|
||||
* never touches FFmpeg itself. The underlying frame object always lives
|
||||
* inside the bridge library.
|
||||
*/
|
||||
class AVFrame {
|
||||
public:
|
||||
AVFrame() :
|
||||
handle_(fb_frame_alloc())
|
||||
{
|
||||
}
|
||||
|
||||
explicit AVFrame(FBFrame *handle) :
|
||||
handle_(handle)
|
||||
{
|
||||
}
|
||||
|
||||
~AVFrame()
|
||||
{
|
||||
if (handle_) {
|
||||
fb_frame_free(&handle_);
|
||||
}
|
||||
}
|
||||
|
||||
AVFrame(const AVFrame &) = delete;
|
||||
AVFrame &operator=(const AVFrame &) = delete;
|
||||
|
||||
FBFrame *handle() const { return handle_; }
|
||||
|
||||
int width() const { return fb_frame_get_width(handle_); }
|
||||
void set_width(int w) { fb_frame_set_width(handle_, w); }
|
||||
int height() const { return fb_frame_get_height(handle_); }
|
||||
void set_height(int h) { fb_frame_set_height(handle_, h); }
|
||||
int format() const { return fb_frame_get_format(handle_); }
|
||||
void set_format(int f) { fb_frame_set_format(handle_, f); }
|
||||
int64_t pts() const { return fb_frame_get_pts(handle_); }
|
||||
void set_pts(int64_t p) { fb_frame_set_pts(handle_, p); }
|
||||
int64_t best_effort_timestamp() const
|
||||
{
|
||||
return fb_frame_get_best_effort_timestamp(handle_);
|
||||
}
|
||||
int nb_samples() const { return fb_frame_get_nb_samples(handle_); }
|
||||
void set_nb_samples(int n) { fb_frame_set_nb_samples(handle_, n); }
|
||||
int sample_rate() const { return fb_frame_get_sample_rate(handle_); }
|
||||
void set_sample_rate(int r) { fb_frame_set_sample_rate(handle_, r); }
|
||||
int color_range() const { return fb_frame_get_color_range(handle_); }
|
||||
void set_color_range(int r) { fb_frame_set_color_range(handle_, r); }
|
||||
int colorspace() const { return fb_frame_get_colorspace(handle_); }
|
||||
void set_colorspace(int cs) { fb_frame_set_colorspace(handle_, cs); }
|
||||
uint64_t channel_layout_mask() const
|
||||
{
|
||||
return fb_frame_get_channel_layout_mask(handle_);
|
||||
}
|
||||
void set_channel_layout_mask(uint64_t m)
|
||||
{
|
||||
fb_frame_set_channel_layout_mask(handle_, m);
|
||||
}
|
||||
|
||||
bool is_hw() const { return fb_frame_is_hw(handle_) != 0; }
|
||||
int hw_transfer_data(const AVFrame *src)
|
||||
{
|
||||
return fb_frame_hw_transfer_data(handle_, src->handle_);
|
||||
}
|
||||
int get_buffer(int align) { return fb_frame_get_buffer(handle_, align); }
|
||||
int make_writable() { return fb_frame_make_writable(handle_); }
|
||||
|
||||
uint8_t *data(int plane) { return fb_frame_get_data(handle_, plane); }
|
||||
const uint8_t *data(int plane) const
|
||||
{
|
||||
return fb_frame_get_data_const(handle_, plane);
|
||||
}
|
||||
void set_data(int plane, uint8_t *d)
|
||||
{
|
||||
fb_frame_set_data(handle_, plane, d);
|
||||
}
|
||||
int linesize(int plane) const
|
||||
{
|
||||
return fb_frame_get_linesize(handle_, plane);
|
||||
}
|
||||
void set_linesize(int plane, int l)
|
||||
{
|
||||
fb_frame_set_linesize(handle_, plane, l);
|
||||
}
|
||||
|
||||
private:
|
||||
FBFrame *handle_;
|
||||
};
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr(FBFrame *f)
|
||||
{
|
||||
return std::make_shared<AVFrame>(f);
|
||||
}
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr()
|
||||
{
|
||||
return std::make_shared<AVFrame>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_AVFRAMEPTR_H
|
||||
@@ -1,12 +1,3 @@
|
||||
#pragma once
|
||||
// Transitional stub for engine/pluginSupport/olivehost.h (still Qt-based).
|
||||
// Only the surface oaknode uses. M9 replaces this with the real plugin
|
||||
// boundary.
|
||||
#include <string>
|
||||
#include "ofxhImageEffectAPI.h"
|
||||
#include "ofxhPluginCache.h"
|
||||
namespace olive {
|
||||
namespace plugin {
|
||||
inline void load_plugins(const std::string &) {}
|
||||
}
|
||||
}
|
||||
// M9 收尾:桥接到 oakplugin 真身
|
||||
#include "../../../plugin/src/olivehost.h"
|
||||
|
||||
@@ -1,30 +1,3 @@
|
||||
#pragma once
|
||||
// Minimal syntax-check stub for engine/pluginSupport/oliveplugininstance.h
|
||||
// (real header is Qt-based, handled in a later milestone). Not in repo.
|
||||
#include <memory>
|
||||
|
||||
#include "ofxhImageEffect.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
class PluginNode;
|
||||
}
|
||||
|
||||
class OlivePluginInstance : public OFX::Host::ImageEffect::Instance {
|
||||
public:
|
||||
void setNode(std::shared_ptr<plugin::PluginNode> node)
|
||||
{
|
||||
node_ = node;
|
||||
}
|
||||
std::shared_ptr<plugin::PluginNode> node() const
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<plugin::PluginNode> node_;
|
||||
};
|
||||
|
||||
}
|
||||
// M9 收尾:桥接到 oakplugin 真身
|
||||
#include "../../../plugin/src/oliveplugininstance.h"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(c_api)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +1,4 @@
|
||||
target_sources(oakplugin PRIVATE
|
||||
host.cpp
|
||||
instance.cpp
|
||||
)
|
||||
@@ -0,0 +1,155 @@
|
||||
/***
|
||||
|
||||
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 "plugin/host.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../src/olivehost.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::plugin::HostMessageHandler forwarder_fn;
|
||||
oakplugin_message_fn user_fn;
|
||||
void *user_userdata;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<std::string> plugin_ids()
|
||||
{
|
||||
std::vector<std::string> ids;
|
||||
OFX::Host::PluginCache *cache =
|
||||
OFX::Host::PluginCache::getPluginCache();
|
||||
if (!cache) {
|
||||
return ids;
|
||||
}
|
||||
for (const auto &plugin : cache->getPlugins()) {
|
||||
if (plugin) {
|
||||
ids.push_back(plugin->getIdentifier());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int oakplugin_host_init(void)
|
||||
{
|
||||
try {
|
||||
olive::plugin::load_plugins();
|
||||
return OAKPLUGIN_OK;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakplugin_host_shutdown(void)
|
||||
{
|
||||
// The OFX plugin cache is process-global; nothing to tear down here
|
||||
// (the Current slots release their references on their own).
|
||||
}
|
||||
|
||||
int oakplugin_host_scan(const char *const *bundle_dirs, int dir_count)
|
||||
{
|
||||
if (!bundle_dirs || dir_count < 0) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < dir_count; i++) {
|
||||
if (bundle_dirs[i]) {
|
||||
olive::plugin::load_plugins(bundle_dirs[i]);
|
||||
}
|
||||
}
|
||||
return OAKPLUGIN_OK;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_host_plugin_count(void)
|
||||
{
|
||||
try {
|
||||
return int(plugin_ids().size());
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_host_plugin_id_at(int index, char *buf, int buf_size)
|
||||
{
|
||||
try {
|
||||
std::vector<std::string> ids = plugin_ids();
|
||||
if (index < 0 || index >= int(ids.size())) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
return copy_string(ids[size_t(index)], buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_host_plugin_label(const char *plugin_id, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!plugin_id) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const std::string &id : plugin_ids()) {
|
||||
if (id == plugin_id) {
|
||||
return copy_string(id, buf, buf_size);
|
||||
}
|
||||
}
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakplugin_host_set_message_handler(oakplugin_message_fn fn,
|
||||
void *userdata)
|
||||
{
|
||||
user_fn = fn;
|
||||
user_userdata = userdata;
|
||||
|
||||
if (!fn) {
|
||||
olive::plugin::set_host_message_handler(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
olive::plugin::set_host_message_handler(
|
||||
[](const char *type, const std::string &message) -> OfxStatus {
|
||||
int answer = user_fn(type, message.c_str(), user_userdata);
|
||||
return answer == OAKPLUGIN_MESSAGE_ANSWER_YES ? kOfxStatReplyYes
|
||||
: kOfxStatReplyNo;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
/***
|
||||
|
||||
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 "plugin/instance.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
#include <ofxhImageEffectAPI.h>
|
||||
#include <ofxhPluginCache.h>
|
||||
|
||||
#include "../src/oliveclip.h"
|
||||
#include "../src/oliveplugininstance.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct InstanceBox {
|
||||
olive::plugin::OlivePluginInstance *instance;
|
||||
std::atomic<uint32_t> refs;
|
||||
oakplugin_progress_fn progress_fn;
|
||||
void *progress_userdata;
|
||||
|
||||
InstanceBox(olive::plugin::OlivePluginInstance *i)
|
||||
: instance(i)
|
||||
, refs(1)
|
||||
, progress_fn(nullptr)
|
||||
, progress_userdata(nullptr)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<int> g_alive{ 0 };
|
||||
|
||||
void box_addref(void *ctx)
|
||||
{
|
||||
if (ctx) {
|
||||
static_cast<InstanceBox *>(ctx)->refs.fetch_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
void box_release(void *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
InstanceBox *box = static_cast<InstanceBox *>(ctx);
|
||||
if (box->refs.fetch_sub(1) == 1) {
|
||||
delete box->instance;
|
||||
delete box;
|
||||
g_alive--;
|
||||
}
|
||||
}
|
||||
|
||||
OakPluginInstance make_handle(olive::plugin::OlivePluginInstance *instance)
|
||||
{
|
||||
OakPluginInstance handle = {};
|
||||
if (!instance) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
InstanceBox *box = new (std::nothrow) InstanceBox(instance);
|
||||
if (!box) {
|
||||
delete instance;
|
||||
return handle;
|
||||
}
|
||||
|
||||
handle.ctx = box;
|
||||
handle.addref = box_addref;
|
||||
handle.release = box_release;
|
||||
handle.abi_version = OAKPLUGIN_ABI_VERSION;
|
||||
g_alive++;
|
||||
return handle;
|
||||
}
|
||||
|
||||
InstanceBox *impl(OakPluginInstance h)
|
||||
{
|
||||
if (!h.ctx) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<InstanceBox *>(h.ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakPluginInstance oakplugin_instance_create(const char *plugin_id)
|
||||
{
|
||||
if (!plugin_id) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
|
||||
try {
|
||||
OFX::Host::PluginCache *cache =
|
||||
OFX::Host::PluginCache::getPluginCache();
|
||||
if (!cache) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
|
||||
for (const auto &plugin : cache->getPlugins()) {
|
||||
if (plugin && plugin->getIdentifier() == plugin_id) {
|
||||
auto *effect_plugin =
|
||||
dynamic_cast<OFX::Host::ImageEffect::ImageEffectPlugin *>(
|
||||
plugin);
|
||||
if (!effect_plugin) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
auto *instance = effect_plugin->createInstance(
|
||||
kOfxImageEffectContextFilter, nullptr);
|
||||
auto *olive_instance =
|
||||
dynamic_cast<olive::plugin::OlivePluginInstance *>(
|
||||
instance);
|
||||
if (!olive_instance) {
|
||||
delete instance;
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
return make_handle(olive_instance);
|
||||
}
|
||||
}
|
||||
return OakPluginInstance{};
|
||||
} catch (...) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
}
|
||||
|
||||
void oakplugin_instance_free(OakPluginInstance *instance)
|
||||
{
|
||||
if (!instance || !instance->ctx) {
|
||||
return;
|
||||
}
|
||||
instance->release(instance->ctx);
|
||||
instance->ctx = NULL;
|
||||
}
|
||||
|
||||
int oakplugin_instance_set_param(OakPluginInstance instance,
|
||||
const char *param_id,
|
||||
const oaknode_value *value)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id || !value) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
|
||||
switch (value->type) {
|
||||
case OAKNODE_VALUE_INT:
|
||||
case OAKNODE_VALUE_COMBO:
|
||||
return static_cast<OFX::Host::Param::IntegerInstance *>(param)
|
||||
->set(int(value->num)) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_BOOL:
|
||||
return static_cast<OFX::Host::Param::BooleanInstance *>(param)
|
||||
->set(value->num != 0) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_FLOAT:
|
||||
return static_cast<OFX::Host::Param::DoubleInstance *>(param)
|
||||
->set(value->f[0]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_VEC2:
|
||||
return static_cast<OFX::Host::Param::Double2DInstance *>(param)
|
||||
->set(value->f[0], value->f[1]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_VEC3:
|
||||
return static_cast<OFX::Host::Param::Double3DInstance *>(param)
|
||||
->set(value->f[0], value->f[1],
|
||||
value->f[2]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_COLOR:
|
||||
case OAKNODE_VALUE_VEC4:
|
||||
return static_cast<OFX::Host::Param::RGBAInstance *>(param)
|
||||
->set(value->f[0], value->f[1], value->f[2],
|
||||
value->f[3]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
default:
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_get_param(OakPluginInstance instance,
|
||||
const char *param_id, oaknode_value *out)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id || !out) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
|
||||
out->type = OAKNODE_VALUE_NONE;
|
||||
const std::string &type = param->getType();
|
||||
if (type == kOfxParamTypeInteger || type == kOfxParamTypeChoice) {
|
||||
int v = 0;
|
||||
static_cast<OFX::Host::Param::IntegerInstance *>(param)->get(v);
|
||||
out->type = OAKNODE_VALUE_INT;
|
||||
out->num = v;
|
||||
} else if (type == kOfxParamTypeBoolean) {
|
||||
bool v = false;
|
||||
static_cast<OFX::Host::Param::BooleanInstance *>(param)->get(v);
|
||||
out->type = OAKNODE_VALUE_BOOL;
|
||||
out->num = v ? 1 : 0;
|
||||
} else if (type == kOfxParamTypeDouble) {
|
||||
double v = 0;
|
||||
static_cast<OFX::Host::Param::DoubleInstance *>(param)->get(v);
|
||||
out->type = OAKNODE_VALUE_FLOAT;
|
||||
out->f[0] = v;
|
||||
} else if (type == kOfxParamTypeDouble2D) {
|
||||
double x = 0, y = 0;
|
||||
static_cast<OFX::Host::Param::Double2DInstance *>(param)->get(x,
|
||||
y);
|
||||
out->type = OAKNODE_VALUE_VEC2;
|
||||
out->f[0] = x;
|
||||
out->f[1] = y;
|
||||
} else if (type == kOfxParamTypeDouble3D) {
|
||||
double x = 0, y = 0, z = 0;
|
||||
static_cast<OFX::Host::Param::Double3DInstance *>(param)->get(x,
|
||||
y,
|
||||
z);
|
||||
out->type = OAKNODE_VALUE_VEC3;
|
||||
out->f[0] = x;
|
||||
out->f[1] = y;
|
||||
out->f[2] = z;
|
||||
} else if (type == kOfxParamTypeRGBA) {
|
||||
double r = 0, g = 0, b = 0, a = 0;
|
||||
static_cast<OFX::Host::Param::RGBAInstance *>(param)->get(r, g,
|
||||
b, a);
|
||||
out->type = OAKNODE_VALUE_COLOR;
|
||||
out->f[0] = r;
|
||||
out->f[1] = g;
|
||||
out->f[2] = b;
|
||||
out->f[3] = a;
|
||||
} else {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
return OAKPLUGIN_OK;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_set_param_string(OakPluginInstance instance,
|
||||
const char *param_id,
|
||||
const char *value)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
return static_cast<OFX::Host::Param::StringInstance *>(param)
|
||||
->set(value ? value : "") == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_get_param_string(OakPluginInstance instance,
|
||||
const char *param_id, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
std::string value;
|
||||
static_cast<OFX::Host::Param::StringInstance *>(param)->get(value);
|
||||
int needed = int(value.size()) + 1;
|
||||
if (buf && buf_size >= needed) {
|
||||
memcpy(buf, value.c_str(), needed);
|
||||
}
|
||||
return needed;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_render(OakPluginInstance instance,
|
||||
OakRenderTexture dst, OakRenderTexture src,
|
||||
double time_seconds)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const char *clip_names[] = { "Output", "Source" };
|
||||
for (const char *clip_name : clip_names) {
|
||||
auto *clip =
|
||||
dynamic_cast<olive::plugin::OliveClipInstance *>(
|
||||
box->instance->getClip(clip_name));
|
||||
if (!clip) {
|
||||
continue;
|
||||
}
|
||||
if (clip->isOutput()) {
|
||||
clip->setOutputTexture(dst, time_seconds);
|
||||
} else if (src.ctx) {
|
||||
clip->setInputTexture(src, time_seconds, false);
|
||||
}
|
||||
}
|
||||
|
||||
OfxRectI roi = { 0, 0, 0, 0 };
|
||||
OfxPointD scale = { 1.0, 1.0 };
|
||||
OfxStatus status = box->instance->renderAction(
|
||||
time_seconds, kOfxImageFieldNone, roi, scale, false, false,
|
||||
false);
|
||||
return status == kOfxStatOK ? OAKPLUGIN_OK : OAKPLUGIN_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_set_progress_cb(OakPluginInstance instance,
|
||||
oakplugin_progress_fn fn,
|
||||
void *userdata)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
box->progress_fn = fn;
|
||||
box->progress_userdata = userdata;
|
||||
return OAKPLUGIN_OK;
|
||||
}
|
||||
|
||||
int oakplugin_instance_cancel(OakPluginInstance instance)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
if (box->progress_fn) {
|
||||
box->progress_fn(1.0, box->progress_userdata);
|
||||
}
|
||||
return OAKPLUGIN_OK;
|
||||
}
|
||||
|
||||
int oakplugin_debug_alive_count(void)
|
||||
{
|
||||
return g_alive.load();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2025 mikesolar
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
file(GLOB OAKPLUGIN_SOURCES CONFIGURE_DEPENDS *.cpp)
|
||||
|
||||
add_library(oakplugin SHARED ${OAKPLUGIN_SOURCES})
|
||||
|
||||
target_include_directories(oakplugin PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${OAK_REPO_ROOT}/include
|
||||
${OAK_REPO_ROOT}/src/common/src
|
||||
${OAK_REPO_ROOT}/src/undo/src
|
||||
${OAK_REPO_ROOT}/core/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
${OAK_REPO_ROOT}/ffmpeg_bridge/include
|
||||
)
|
||||
|
||||
find_library(OAKPLUGIN_OFX_HOST_ARCHIVE NAMES OfxHost
|
||||
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport
|
||||
NO_DEFAULT_PATH)
|
||||
if(NOT OAKPLUGIN_OFX_HOST_ARCHIVE)
|
||||
message(FATAL_ERROR
|
||||
"libOfxHost.a not found; run the full-tree build once or set "
|
||||
"OAKPLUGIN_OFX_HOST_ARCHIVE")
|
||||
endif()
|
||||
target_link_options(oakplugin PUBLIC
|
||||
"-Wl,-force_load,${OAKPLUGIN_OFX_HOST_ARCHIVE}")
|
||||
|
||||
target_link_libraries(oakplugin PUBLIC
|
||||
oaknode
|
||||
oakrender
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
ffmpeg_bridge
|
||||
)
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2026 Olive CE 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 "image.h"
|
||||
|
||||
#include "ofxImageEffect.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
static const char *pixel_depth_to_ofx(core::PixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case core::PixelFormat::u8:
|
||||
return kOfxBitDepthByte;
|
||||
case core::PixelFormat::u16:
|
||||
return kOfxBitDepthShort;
|
||||
case core::PixelFormat::f16:
|
||||
return kOfxBitDepthHalf;
|
||||
default:
|
||||
break;
|
||||
case core::PixelFormat::f32:
|
||||
return kOfxBitDepthFloat;
|
||||
case core::PixelFormat::u10:
|
||||
case core::PixelFormat::invalid:
|
||||
case core::PixelFormat::count:
|
||||
break;
|
||||
}
|
||||
|
||||
return kOfxBitDepthNone;
|
||||
}
|
||||
|
||||
static const char *components_to_ofx(int channel_count)
|
||||
{
|
||||
switch (channel_count) {
|
||||
case 1:
|
||||
return kOfxImageComponentAlpha;
|
||||
case 3:
|
||||
return kOfxImageComponentRGB;
|
||||
case 4:
|
||||
return kOfxImageComponentRGBA;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return kOfxImageComponentNone;
|
||||
}
|
||||
|
||||
Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance)
|
||||
: OFX::Host::ImageEffect::Image(clip_instance)
|
||||
, width_(0)
|
||||
, height_(0)
|
||||
, format_(core::PixelFormat::invalid)
|
||||
, premultiplied_alpha_(false)
|
||||
, channel_count_(0)
|
||||
, row_bytes_(0)
|
||||
, bounds_{ 0, 0, 0, 0 }
|
||||
, rod_{ 0, 0, 0, 0 }
|
||||
{
|
||||
}
|
||||
|
||||
Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
|
||||
const VideoParams ¶ms, const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear)
|
||||
: OFX::Host::ImageEffect::Image(clip_instance)
|
||||
, width_(0)
|
||||
, height_(0)
|
||||
, format_(core::PixelFormat::invalid)
|
||||
, premultiplied_alpha_(false)
|
||||
, channel_count_(0)
|
||||
, row_bytes_(0)
|
||||
, bounds_{ 0, 0, 0, 0 }
|
||||
, rod_{ 0, 0, 0, 0 }
|
||||
{
|
||||
allocate_from_params(params, bounds, rod, clear);
|
||||
}
|
||||
|
||||
Image::~Image()
|
||||
{
|
||||
}
|
||||
|
||||
void Image::allocate_from_params(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds, const OfxRectI &rod,
|
||||
bool clear)
|
||||
{
|
||||
allocate(bounds.x2 - bounds.x1, bounds.y2 - bounds.y1, params.format(),
|
||||
params.channel_count(), params.premultiplied_alpha(), bounds, rod,
|
||||
clear);
|
||||
}
|
||||
|
||||
void Image::ensure_allocated_from_params(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear)
|
||||
{
|
||||
bool same = (width_ == bounds.x2 - bounds.x1) &&
|
||||
(height_ == bounds.y2 - bounds.y1) &&
|
||||
(format_ == params.format()) &&
|
||||
(channel_count_ == params.channel_count()) &&
|
||||
(premultiplied_alpha_ == params.premultiplied_alpha()) &&
|
||||
(bounds_.x1 == bounds.x1) && (bounds_.y1 == bounds.y1) &&
|
||||
(bounds_.x2 == bounds.x2) && (bounds_.y2 == bounds.y2) &&
|
||||
(rod_.x1 == rod.x1) && (rod_.y1 == rod.y1) &&
|
||||
(rod_.x2 == rod.x2) && (rod_.y2 == rod.y2);
|
||||
|
||||
if (!same) {
|
||||
allocate_from_params(params, bounds, rod, clear);
|
||||
} else if (clear && !image_.empty()) {
|
||||
std::fill(image_.begin(), image_.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Image::allocate(int width, int height, core::PixelFormat format,
|
||||
int channel_count, bool premultiplied_alpha,
|
||||
const OfxRectI &bounds, const OfxRectI &rod, bool clear)
|
||||
{
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
channel_count_ = channel_count;
|
||||
premultiplied_alpha_ = premultiplied_alpha;
|
||||
bounds_ = bounds;
|
||||
rod_ = rod;
|
||||
|
||||
int bytes_per_component = format_.byte_count();
|
||||
row_bytes_ = width_ * channel_count_ * bytes_per_component;
|
||||
int buffer_size = row_bytes_ * height_;
|
||||
if (buffer_size < 0) {
|
||||
buffer_size = 0;
|
||||
}
|
||||
|
||||
image_.resize(static_cast<size_t>(buffer_size));
|
||||
if (clear && !image_.empty()) {
|
||||
std::fill(image_.begin(), image_.end(), 0);
|
||||
}
|
||||
|
||||
setPointerProperty(kOfxImagePropData, image_.data());
|
||||
setIntProperty(kOfxImagePropRowBytes, row_bytes_);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.x1, 0);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.y1, 1);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.x2, 2);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.y2, 3);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.x1, 0);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.y1, 1);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.x2, 2);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.y2, 3);
|
||||
setStringProperty(kOfxImageEffectPropComponents,
|
||||
components_to_ofx(channel_count_));
|
||||
setStringProperty(kOfxImageEffectPropPixelDepth, pixel_depth_to_ofx(format_));
|
||||
setStringProperty(kOfxImageEffectPropPreMultiplication,
|
||||
premultiplied_alpha_ ? kOfxImagePreMultiplied :
|
||||
kOfxImageUnPreMultiplied);
|
||||
}
|
||||
|
||||
core::PixelFormat Image::pixel_format()
|
||||
{
|
||||
if (format_ != core::PixelFormat::invalid) {
|
||||
return format_;
|
||||
}
|
||||
|
||||
std::string type = getStringProperty(kOfxImageEffectPropPixelDepth);
|
||||
if (type == kOfxBitDepthByte) {
|
||||
format_ = core::PixelFormat::u8;
|
||||
} else if (type == kOfxBitDepthShort) {
|
||||
format_ = core::PixelFormat::u16;
|
||||
} else if (type == kOfxBitDepthHalf) {
|
||||
format_ = core::PixelFormat::f16;
|
||||
} else if (type == kOfxBitDepthFloat) {
|
||||
format_ = core::PixelFormat::f32;
|
||||
} else {
|
||||
format_ = core::PixelFormat::invalid;
|
||||
}
|
||||
return format_;
|
||||
}
|
||||
|
||||
bool Image::premultiplied_alpha()
|
||||
{
|
||||
std::string premultiplied =
|
||||
getStringProperty(kOfxImageEffectPropPreMultiplication);
|
||||
premultiplied_alpha_ = (premultiplied == kOfxImagePreMultiplied);
|
||||
return premultiplied_alpha_;
|
||||
}
|
||||
|
||||
int Image::width()
|
||||
{
|
||||
int bounds[4] = { 0 };
|
||||
getIntPropertyN(kOfxImagePropBounds, bounds, 4);
|
||||
width_ = bounds[2] - bounds[0];
|
||||
return width_;
|
||||
}
|
||||
|
||||
int Image::height()
|
||||
{
|
||||
int bounds[4] = { 0 };
|
||||
getIntPropertyN(kOfxImagePropBounds, bounds, 4);
|
||||
height_ = bounds[3] - bounds[1];
|
||||
return height_;
|
||||
}
|
||||
|
||||
int Image::channel_count()
|
||||
{
|
||||
std::string type = getStringProperty(kOfxImageEffectPropComponents);
|
||||
if (type == kOfxImageComponentAlpha) {
|
||||
channel_count_ = 1;
|
||||
} else if (type == kOfxImageComponentRGBA) {
|
||||
channel_count_ = 4;
|
||||
} else if (type == kOfxImageComponentRGB) {
|
||||
channel_count_ = 3;
|
||||
} else {
|
||||
channel_count_ = 0;
|
||||
}
|
||||
return channel_count_;
|
||||
}
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2026 Olive CE 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_OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
#define OAK_OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxImageEffect.h"
|
||||
#include "ofxhClip.h"
|
||||
#include "olive/core/render/pixelformat.h"
|
||||
#include "loopmode.h"
|
||||
#include "videoparams.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
class Image : public OFX::Host::ImageEffect::Image {
|
||||
public:
|
||||
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance);
|
||||
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
|
||||
const VideoParams ¶ms, const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear = true);
|
||||
~Image();
|
||||
uint8_t *data()
|
||||
{
|
||||
return (uint8_t *)getPointerProperty(kOfxImagePropData);
|
||||
}
|
||||
int width();
|
||||
int height();
|
||||
core::PixelFormat pixel_format();
|
||||
bool premultiplied_alpha();
|
||||
int channel_count();
|
||||
|
||||
void allocate_from_params(const VideoParams ¶ms, const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear = true);
|
||||
void ensure_allocated_from_params(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds, const OfxRectI &rod,
|
||||
bool clear = false);
|
||||
void allocate(int width, int height, core::PixelFormat format,
|
||||
int channel_count, bool premultiplied_alpha,
|
||||
const OfxRectI &bounds, const OfxRectI &rod,
|
||||
bool clear = true);
|
||||
int row_bytes() const
|
||||
{
|
||||
return row_bytes_;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> image_;
|
||||
int width_;
|
||||
int height_;
|
||||
core::PixelFormat format_;
|
||||
bool premultiplied_alpha_;
|
||||
int channel_count_;
|
||||
int row_bytes_;
|
||||
OfxRectI bounds_;
|
||||
OfxRectI rod_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //OAK_OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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_OLIVECLIP_H
|
||||
#define OAK_OLIVECLIP_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxhClip.h"
|
||||
|
||||
#include "common/videoparams.h"
|
||||
#include "image.h"
|
||||
#include "render/renderer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
class OliveClipInstance : public OFX::Host::ImageEffect::ClipInstance {
|
||||
public:
|
||||
OliveClipInstance(OFX::Host::ImageEffect::Instance *effect_instance,
|
||||
OFX::Host::ImageEffect::ClipDescriptor &desc,
|
||||
OakVideoParams params)
|
||||
: ClipInstance(effect_instance, desc)
|
||||
, params_(params)
|
||||
, name_(desc.getName())
|
||||
{
|
||||
if (params_.ctx) {
|
||||
params_.addref(params_.ctx);
|
||||
}
|
||||
default_region_of_definition_ = { 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
~OliveClipInstance() override
|
||||
{
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_free(¶ms_);
|
||||
}
|
||||
for (auto &pair : images_) {
|
||||
delete pair.second;
|
||||
}
|
||||
for (auto &pair : input_textures_) {
|
||||
oakrender_display_texture_free(&pair.second);
|
||||
}
|
||||
for (auto &pair : output_textures_) {
|
||||
oakrender_display_texture_free(&pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
OFX::Host::ImageEffect::Image *getOutputImage(OfxTime time);
|
||||
|
||||
const std::string &getUnmappedBitDepth() const override;
|
||||
const std::string &getUnmappedComponents() const override;
|
||||
const std::string &getPremult() const override;
|
||||
double getAspectRatio() const override;
|
||||
double getFrameRate() const override;
|
||||
void getFrameRange(double &start_frame, double &end_frame) const override;
|
||||
const std::string &getFieldOrder() const override;
|
||||
bool getConnected() const override;
|
||||
double getUnmappedFrameRate() const override;
|
||||
void getUnmappedFrameRange(double &start_frame,
|
||||
double &end_frame) const override;
|
||||
bool getContinuousSamples() const override;
|
||||
OFX::Host::ImageEffect::Image *
|
||||
getImage(OfxTime time, const OfxRectD *optional_bounds) override;
|
||||
OfxRectD getRegionOfDefinition(OfxTime time) const override;
|
||||
|
||||
void setRegionOfDefinition(OfxRectD region_of_definition, OfxTime time);
|
||||
void setDefaultRegionOfDefinition(OfxRectD region_of_definition);
|
||||
void setParams(const olive::VideoParams ¶ms);
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
OFX::Host::ImageEffect::Texture *
|
||||
loadTexture(OfxTime time, const char *format,
|
||||
const OfxRectD *optional_bounds) override;
|
||||
#endif
|
||||
|
||||
void setInputTexture(OakRenderTexture texture, OfxTime time,
|
||||
bool readback_cpu = true);
|
||||
void setOutputTexture(OakRenderTexture texture, OfxTime time);
|
||||
|
||||
// Get the plugin-preferred VideoParams based on base class _pixelDepth/_components
|
||||
OakVideoParams getPluginPreferredParams() const;
|
||||
|
||||
// Prune old entries from the images_ cache to prevent unbounded growth.
|
||||
// Output clip images are not pruned (they are typically single-frame).
|
||||
void prune_images_cache();
|
||||
|
||||
static constexpr int k_max_input_image_cache = 8;
|
||||
|
||||
private:
|
||||
OakVideoParams params_;
|
||||
|
||||
std::map<OfxTime, OfxRectD> region_of_definitions_;
|
||||
|
||||
OfxRectD default_region_of_definition_;
|
||||
|
||||
std::string name_;
|
||||
std::map<OfxTime, Image *> images_;
|
||||
std::map<OfxTime, OakRenderTexture> input_textures_;
|
||||
std::map<OfxTime, OakRenderTexture> output_textures_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //OAK_OLIVECLIP_H
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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 "olivehost.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include <ofxhBinary.h>
|
||||
#include <ofxhPluginCache.h>
|
||||
#include <ofxMessage.h>
|
||||
|
||||
#include "common/current.h"
|
||||
#include "oliveplugininstance.h"
|
||||
|
||||
using namespace OFX::Host;
|
||||
using namespace olive::plugin;
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
class PluginNode;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef OAK_APP_VERSION
|
||||
#define OAK_APP_VERSION "0.0.0"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::plugin::HostMessageHandler message_handler_;
|
||||
|
||||
void add_plugin_path(OFX::Host::PluginCache *cache, const std::string &path,
|
||||
bool recurse = true)
|
||||
{
|
||||
if (!cache || path.empty()) {
|
||||
return;
|
||||
}
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(path, ec)) {
|
||||
return;
|
||||
}
|
||||
cache->addFileToPath(
|
||||
std::filesystem::weakly_canonical(path, ec).string(), recurse);
|
||||
}
|
||||
|
||||
void add_plugin_paths_from_env(OFX::Host::PluginCache *cache,
|
||||
const char *env_var)
|
||||
{
|
||||
const char *raw = std::getenv(env_var);
|
||||
if (!raw || !*raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char separator =
|
||||
#if defined(_WIN32)
|
||||
';';
|
||||
#else
|
||||
':';
|
||||
#endif
|
||||
|
||||
std::string remaining(raw);
|
||||
size_t pos;
|
||||
while ((pos = remaining.find(separator)) != std::string::npos) {
|
||||
std::string path = remaining.substr(0, pos);
|
||||
remaining.erase(0, pos + 1);
|
||||
if (!path.empty()) {
|
||||
add_plugin_path(cache, path);
|
||||
}
|
||||
}
|
||||
if (!remaining.empty()) {
|
||||
add_plugin_path(cache, remaining);
|
||||
}
|
||||
}
|
||||
|
||||
std::string format_message(const char *format, va_list args)
|
||||
{
|
||||
char buffer[1024];
|
||||
buffer[0] = '\0';
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void olive::plugin::set_host_message_handler(HostMessageHandler handler)
|
||||
{
|
||||
message_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
olive::plugin::HostMessageHandler olive::plugin::get_host_message_handler()
|
||||
{
|
||||
return message_handler_;
|
||||
}
|
||||
|
||||
void olive::plugin::load_plugins(const std::string &path)
|
||||
{
|
||||
OakCurrent current = oakcommon_current_instance();
|
||||
|
||||
void *host_ptr = nullptr;
|
||||
void *cache_ptr = nullptr;
|
||||
oakcommon_current_get_plugin_host(current, &host_ptr);
|
||||
oakcommon_current_get_plugin_cache(current, &cache_ptr);
|
||||
|
||||
std::shared_ptr<OliveHost> host;
|
||||
std::shared_ptr<ImageEffect::PluginCache> image_effect_plugin_cache;
|
||||
|
||||
if (host_ptr) {
|
||||
host = *static_cast<std::shared_ptr<OliveHost> *>(host_ptr);
|
||||
}
|
||||
if (cache_ptr) {
|
||||
image_effect_plugin_cache =
|
||||
*static_cast<std::shared_ptr<ImageEffect::PluginCache> *>(
|
||||
cache_ptr);
|
||||
}
|
||||
|
||||
if (!host || !image_effect_plugin_cache) {
|
||||
host = std::make_shared<OliveHost>();
|
||||
image_effect_plugin_cache =
|
||||
std::make_shared<ImageEffect::PluginCache>(*host);
|
||||
|
||||
// The Current slots hold owning shared_ptr copies; the destroy
|
||||
// callbacks free those copies when the slots are replaced or the
|
||||
// current object dies.
|
||||
auto *host_slot = new std::shared_ptr<OliveHost>(host);
|
||||
oakcommon_current_set_plugin_host(
|
||||
current, host_slot,
|
||||
[](void *p) { delete static_cast<std::shared_ptr<OliveHost> *>(p); });
|
||||
|
||||
auto *cache_slot =
|
||||
new std::shared_ptr<ImageEffect::PluginCache>(
|
||||
image_effect_plugin_cache);
|
||||
oakcommon_current_set_plugin_cache(
|
||||
current, cache_slot,
|
||||
[](void *p) {
|
||||
delete static_cast<std::shared_ptr<ImageEffect::PluginCache> *>(
|
||||
p);
|
||||
});
|
||||
|
||||
image_effect_plugin_cache->registerInCache(
|
||||
*OFX::Host::PluginCache::getPluginCache());
|
||||
}
|
||||
oakcommon_current_free(¤t);
|
||||
|
||||
OFX::Host::PluginCache *cache = OFX::Host::PluginCache::getPluginCache();
|
||||
cache->setPluginHostPath("Olive");
|
||||
|
||||
const std::string home_path = std::getenv("HOME") ? std::getenv("HOME") : "";
|
||||
if (!home_path.empty()) {
|
||||
add_plugin_path(cache, home_path + "/.OFX/Plugins");
|
||||
add_plugin_path(cache, home_path + "/.local/share/OFX/Plugins");
|
||||
add_plugin_path(cache,
|
||||
home_path + "/.local/share/olive/ofx/Plugins");
|
||||
}
|
||||
|
||||
// Application-relative plugin paths are resolved by the facade; scan
|
||||
// the default locations relative to the working directory here.
|
||||
add_plugin_path(cache, "../OFX/Plugins");
|
||||
add_plugin_path(cache, "../share/olive/ofx/Plugins");
|
||||
add_plugin_path(cache, "../lib/olive/ofx/Plugins");
|
||||
|
||||
add_plugin_paths_from_env(cache, "OLIVE_OFX_PLUGIN_PATH");
|
||||
add_plugin_paths_from_env(cache, "OLIVE_PLUGIN_PATH");
|
||||
|
||||
if (!path.empty()) {
|
||||
add_plugin_path(cache, path, true);
|
||||
}
|
||||
cache->scanPluginFiles();
|
||||
}
|
||||
|
||||
OliveHost::OliveHost()
|
||||
{
|
||||
// Identify the host to plugins; HostSupport seeds these with "UNKNOWN".
|
||||
_properties.setStringProperty(kOfxPropName, "Oak Video Editor");
|
||||
_properties.setStringProperty(kOfxPropLabel, "Oak Video Editor");
|
||||
_properties.setStringProperty(kOfxPropVersionLabel, OAK_APP_VERSION);
|
||||
|
||||
// Numeric version for plugins that query kOfxPropVersion directly.
|
||||
int version_parts[3] = { 0, 0, 0 };
|
||||
sscanf(OAK_APP_VERSION, "%d.%d.%d", &version_parts[0], &version_parts[1],
|
||||
&version_parts[2]);
|
||||
_properties.setIntProperty(kOfxPropVersion, version_parts[0], 0);
|
||||
_properties.setIntProperty(kOfxPropVersion, version_parts[1], 1);
|
||||
_properties.setIntProperty(kOfxPropVersion, version_parts[2], 2);
|
||||
}
|
||||
|
||||
OliveHost::~OliveHost()
|
||||
{
|
||||
}
|
||||
|
||||
void OliveHost::destroy_instance(OFX::Host::ImageEffect::Instance *instance)
|
||||
{
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
for (auto it = instances_.begin(); it != instances_.end(); ++it) {
|
||||
if (it->get() == instance) {
|
||||
instances_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
OliveHost::makeDescriptor(ImageEffect::ImageEffectPlugin *plugin)
|
||||
{
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor> desc =
|
||||
std::make_shared<ImageEffect::Descriptor>(plugin);
|
||||
descriptors_.push_back(std::shared_ptr<ImageEffect::Descriptor>(desc));
|
||||
return desc;
|
||||
}
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
OliveHost::makeDescriptor(const ImageEffect::Descriptor &root_context,
|
||||
ImageEffect::ImageEffectPlugin *plugin)
|
||||
{
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor> desc =
|
||||
std::make_shared<ImageEffect::Descriptor>(root_context, plugin);
|
||||
descriptors_.push_back(std::shared_ptr<ImageEffect::Descriptor>(desc));
|
||||
return desc;
|
||||
}
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
OliveHost::makeDescriptor(const std::string &bundle_path,
|
||||
ImageEffect::ImageEffectPlugin *plugin)
|
||||
{
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor> desc =
|
||||
std::make_shared<ImageEffect::Descriptor>(bundle_path, plugin);
|
||||
descriptors_.push_back(std::shared_ptr<ImageEffect::Descriptor>(desc));
|
||||
return desc;
|
||||
}
|
||||
|
||||
ImageEffect::Instance *
|
||||
OliveHost::newInstance(void *client_data, ImageEffect::ImageEffectPlugin *plugin,
|
||||
ImageEffect::Descriptor &desc,
|
||||
const std::string &context)
|
||||
{
|
||||
auto *instance = new OlivePluginInstance(plugin, desc, context, true);
|
||||
if (client_data) {
|
||||
instance->set_node_handle(
|
||||
*static_cast<OakNodeNode *>(client_data));
|
||||
}
|
||||
instances_.push_back(std::shared_ptr<OlivePluginInstance>(instance));
|
||||
return instance;
|
||||
}
|
||||
|
||||
OfxStatus OliveHost::vmessage(const char *type, const char *id,
|
||||
const char *format, va_list args)
|
||||
{
|
||||
if (!type || !format) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
std::string message = format_message(format, args);
|
||||
|
||||
if (message_handler_) {
|
||||
return message_handler_(type, message);
|
||||
}
|
||||
|
||||
// Headless default: log to stderr, questions get "no".
|
||||
fprintf(stderr, "OFX message: %s %s\n", type, message.c_str());
|
||||
if (strcmp(type, kOfxMessageQuestion) == 0) {
|
||||
return kOfxStatReplyNo;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OliveHost::setPersistentMessage(const char *type, const char *id,
|
||||
const char *format, va_list args)
|
||||
{
|
||||
if (!type || !format) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
std::string message = format_message(format, args);
|
||||
|
||||
if (strcmp(type, kOfxMessageError) == 0) {
|
||||
persistent_messages_.push_back({ HostMessageType::error, message });
|
||||
} else if (strcmp(type, kOfxMessageWarning) == 0) {
|
||||
persistent_messages_.push_back({ HostMessageType::warning, message });
|
||||
} else if (strcmp(type, kOfxMessageMessage) == 0) {
|
||||
persistent_messages_.push_back({ HostMessageType::message, message });
|
||||
} else {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
if (message_handler_) {
|
||||
return message_handler_(type, message);
|
||||
}
|
||||
|
||||
fprintf(stderr, "OFX %s: %s\n", type, message.c_str());
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OliveHost::clearPersistentMessage()
|
||||
{
|
||||
persistent_messages_.clear();
|
||||
return kOfxStatOK;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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_OLIVE_HOST_H
|
||||
#define OAK_OLIVE_HOST_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxhHost.h"
|
||||
#include "ofxhImageEffect.h"
|
||||
#include "ofxhImageEffectAPI.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
enum class HostMessageType { error, warning, message };
|
||||
struct HostPersistentMessage {
|
||||
HostMessageType type;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UI message handler for OFX host messages
|
||||
*
|
||||
* Registered by the facade. Return kOfxStatReplyYes/No for questions,
|
||||
* kOfxStatOK otherwise. Without a handler, messages are logged to
|
||||
* stderr and questions get kOfxStatReplyNo (headless default).
|
||||
*/
|
||||
using HostMessageHandler =
|
||||
std::function<OfxStatus(const char *type, const std::string &message)>;
|
||||
|
||||
void set_host_message_handler(HostMessageHandler handler);
|
||||
|
||||
/** @brief Currently registered handler (may be empty). */
|
||||
HostMessageHandler get_host_message_handler();
|
||||
|
||||
void load_plugins(const std::string &path = std::string());
|
||||
|
||||
class OliveHost : public OFX::Host::ImageEffect::Host {
|
||||
public:
|
||||
OliveHost();
|
||||
~OliveHost() override;
|
||||
void destroy_instance(OFX::Host::ImageEffect::Instance *instance);
|
||||
|
||||
bool pluginSupported(OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
|
||||
std::string &reason) const override
|
||||
{
|
||||
if (!plugin) {
|
||||
reason = "null plugin";
|
||||
return false;
|
||||
}
|
||||
if (plugin->getContexts().empty()) {
|
||||
reason = "no supported contexts (describe failed)";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
OFX::Host::ImageEffect::Instance *
|
||||
newInstance(void *client_data,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
|
||||
OFX::Host::ImageEffect::Descriptor &desc,
|
||||
const std::string &context) override;
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
makeDescriptor(OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
makeDescriptor(const OFX::Host::ImageEffect::Descriptor &root_context,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
makeDescriptor(const std::string &bundle_path,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||
/// vmessage
|
||||
OfxStatus vmessage(const char *type, const char *id,
|
||||
const char *format, va_list args) override;
|
||||
|
||||
/// vmessage
|
||||
OfxStatus setPersistentMessage(const char *type, const char *id,
|
||||
const char *format, va_list args) override;
|
||||
/// vmessage
|
||||
OfxStatus clearPersistentMessage() override;
|
||||
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
/// @see OfxImageEffectOpenGLRenderSuiteV1.flushResources()
|
||||
virtual OfxStatus flushOpenGLResources() const override
|
||||
{
|
||||
return kOfxStatFailed;
|
||||
};
|
||||
#endif
|
||||
|
||||
int persistent_message_count() const
|
||||
{
|
||||
return int(persistent_messages_.size());
|
||||
}
|
||||
|
||||
const std::vector<HostPersistentMessage> &persistent_messages() const
|
||||
{
|
||||
return persistent_messages_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<OFX::Host::ImageEffect::Descriptor>>
|
||||
descriptors_;
|
||||
std::vector<std::shared_ptr<OFX::Host::ImageEffect::Instance>> instances_;
|
||||
std::vector<HostPersistentMessage> persistent_messages_;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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 "oliveplugininstance.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "ofxGPURender.h"
|
||||
#include "node/sequence.h"
|
||||
#include "olivehost.h"
|
||||
#include "ofxMessage.h"
|
||||
#include "oliveclip.h"
|
||||
#include "paraminstance.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
const std::string k_image_field_none_str(kOfxImageFieldNone);
|
||||
const std::string k_image_field_upper_str(kOfxImageFieldUpper);
|
||||
const std::string k_image_field_lower_str(kOfxImageFieldLower);
|
||||
|
||||
std::string format_ofx_message(const char *format, va_list args)
|
||||
{
|
||||
char buffer[1024];
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
const int needed = vsnprintf(buffer, sizeof(buffer), format, args_copy);
|
||||
va_end(args_copy);
|
||||
if (needed < 0) {
|
||||
return std::string();
|
||||
}
|
||||
if (needed < static_cast<int>(sizeof(buffer))) {
|
||||
return std::string(buffer);
|
||||
}
|
||||
std::vector<char> dynamic_buffer(size_t(needed) + 1);
|
||||
const int written =
|
||||
vsnprintf(dynamic_buffer.data(), dynamic_buffer.size(), format, args);
|
||||
if (written < 0) {
|
||||
return std::string();
|
||||
}
|
||||
return std::string(dynamic_buffer.data());
|
||||
}
|
||||
|
||||
const std::string &field_order_for_params(OakVideoParams params)
|
||||
{
|
||||
int interlacing = 0;
|
||||
if (params.ctx) {
|
||||
oakcommon_videoparams_get_interlacing(params, &interlacing);
|
||||
}
|
||||
switch (interlacing) {
|
||||
case OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST:
|
||||
return k_image_field_upper_str;
|
||||
case OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST:
|
||||
return k_image_field_lower_str;
|
||||
default:
|
||||
return k_image_field_none_str;
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::id main_thread_id_;
|
||||
bool main_thread_id_set_ = false;
|
||||
|
||||
ActiveViewerProvider active_viewer_provider_;
|
||||
|
||||
UndoSubmitFn undo_submit_fn_;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool is_gui_thread()
|
||||
{
|
||||
if (!main_thread_id_set_) {
|
||||
main_thread_id_ = std::this_thread::get_id();
|
||||
main_thread_id_set_ = true;
|
||||
}
|
||||
return std::this_thread::get_id() == main_thread_id_;
|
||||
}
|
||||
|
||||
void set_main_thread_id(std::thread::id id)
|
||||
{
|
||||
main_thread_id_ = id;
|
||||
main_thread_id_set_ = true;
|
||||
}
|
||||
|
||||
void set_active_viewer_provider(ActiveViewerProvider provider)
|
||||
{
|
||||
active_viewer_provider_ = std::move(provider);
|
||||
}
|
||||
|
||||
void set_undo_submit_callback(UndoSubmitFn fn)
|
||||
{
|
||||
undo_submit_fn_ = std::move(fn);
|
||||
}
|
||||
|
||||
const std::string &OlivePluginInstance::getDefaultOutputFielding() const
|
||||
{
|
||||
return field_order_for_params(params_);
|
||||
}
|
||||
|
||||
void register_node_instance(uintptr_t identity,
|
||||
OlivePluginInstance *instance);
|
||||
|
||||
void OlivePluginInstance::set_node_handle(OakNodeNode node)
|
||||
{
|
||||
if (node_.ctx) {
|
||||
register_node_instance(oaknode_node_identity(node_), nullptr);
|
||||
}
|
||||
node_ = node;
|
||||
if (node_.ctx) {
|
||||
register_node_instance(oaknode_node_identity(node_), this);
|
||||
}
|
||||
for (const auto &entry : getParams()) {
|
||||
if (!entry.second) {
|
||||
continue;
|
||||
}
|
||||
if (auto *bound = dynamic_cast<NodeBoundParam *>(entry.second)) {
|
||||
bound->set_node(node_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id,
|
||||
const char *format, va_list args)
|
||||
{
|
||||
const std::string message = format_ofx_message(format, args);
|
||||
if (message.empty()) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
// UI messages route through the host's registered handler
|
||||
HostMessageHandler handler = get_host_message_handler();
|
||||
if (handler) {
|
||||
return handler(type, message);
|
||||
}
|
||||
|
||||
fprintf(stderr, "OFX message: %s %s\n", type, message.c_str());
|
||||
if (strcmp(type, kOfxMessageQuestion) == 0) {
|
||||
return kOfxStatReplyNo;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::setPersistentMessage(const char *type,
|
||||
const char *id,
|
||||
const char *format,
|
||||
va_list args)
|
||||
{
|
||||
const std::string message = format_ofx_message(format, args);
|
||||
if (message.empty()) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
ErrorType error_type;
|
||||
// If this is an error message
|
||||
if (strncmp(type, kOfxMessageError, strlen(kOfxMessageError)) == 0) {
|
||||
error_type = ErrorType::error;
|
||||
}
|
||||
// A warning
|
||||
else if (strncmp(type, kOfxMessageWarning, strlen(kOfxMessageWarning)) ==
|
||||
0) {
|
||||
error_type = ErrorType::warning;
|
||||
}
|
||||
// A simple information
|
||||
else if (strncmp(type, kOfxMessageMessage, strlen(kOfxMessageMessage)) ==
|
||||
0) {
|
||||
error_type = ErrorType::message;
|
||||
} else {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
persistent_errors_.push_back({ error_type, message });
|
||||
|
||||
HostMessageHandler handler = get_host_message_handler();
|
||||
if (handler) {
|
||||
return handler(type, message);
|
||||
}
|
||||
|
||||
fprintf(stderr, "OFX %s: %s\n", type, message.c_str());
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::clearPersistentMessage()
|
||||
{
|
||||
persistent_errors_.clear();
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getProjectSize(double &x_size, double &y_size) const
|
||||
{
|
||||
double par = 1.0;
|
||||
int par_num = 0, par_den = 1;
|
||||
int width = 0, height = 0;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(params_, &par_num,
|
||||
&par_den);
|
||||
oakcommon_videoparams_get_width(params_, &width);
|
||||
oakcommon_videoparams_get_height(params_, &height);
|
||||
}
|
||||
if (par_den != 0) {
|
||||
par = double(par_num) / par_den;
|
||||
}
|
||||
x_size = width * par;
|
||||
y_size = height;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getProjectOffset(double &x_offset,
|
||||
double &y_offset) const
|
||||
{
|
||||
double par = 1.0;
|
||||
int par_num = 0, par_den = 1;
|
||||
float x = 0, y = 0;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(params_, &par_num,
|
||||
&par_den);
|
||||
oakcommon_videoparams_get_x(params_, &x);
|
||||
oakcommon_videoparams_get_y(params_, &y);
|
||||
}
|
||||
if (par_den != 0) {
|
||||
par = double(par_num) / par_den;
|
||||
}
|
||||
x_offset = x * par;
|
||||
y_offset = y;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getProjectExtent(double &x_size, double &y_size) const
|
||||
{
|
||||
getProjectSize(x_size, y_size);
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getProjectPixelAspectRatio() const
|
||||
{
|
||||
int par_num = 0, par_den = 1;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(params_, &par_num,
|
||||
&par_den);
|
||||
}
|
||||
if (par_den == 0) {
|
||||
return 1.0;
|
||||
}
|
||||
double par = double(par_num) / par_den;
|
||||
if (par == 0.0) {
|
||||
return 1.0; // default PAR when not explicitly set
|
||||
}
|
||||
return par;
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getFrameRate() const
|
||||
{
|
||||
int num = 0, den = 1;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_frame_rate(params_, &num, &den);
|
||||
}
|
||||
return den ? double(num) / den : 0.0;
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getEffectDuration() const
|
||||
{
|
||||
// Return a default duration value
|
||||
return 100.0;
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getFrameRecursive() const
|
||||
{
|
||||
// Return current frame (this would typically be set by the host during rendering)
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getRenderScaleRecursive(double &x, double &y) const
|
||||
{
|
||||
// Return default render scale (1.0, 1.0)
|
||||
x = 1.0;
|
||||
y = 1.0;
|
||||
}
|
||||
|
||||
OFX::Host::Param::Instance *
|
||||
OlivePluginInstance::newParam(const std::string &name,
|
||||
OFX::Host::Param::Descriptor &desc)
|
||||
{
|
||||
const std::string &type = desc.getType();
|
||||
|
||||
if (type == kOfxParamTypeInteger) {
|
||||
return new IntegerInstance(node_, desc, this);
|
||||
} else if (type == kOfxParamTypeDouble) {
|
||||
return new DoubleInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeBoolean) {
|
||||
return new BooleanInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeChoice) {
|
||||
return new ChoiceInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeString) {
|
||||
return new StringInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeRGBA) {
|
||||
return new RGBAInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeRGB) {
|
||||
return new RGBInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeDouble2D) {
|
||||
return new Double2DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeInteger2D) {
|
||||
return new Integer2DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeDouble3D) {
|
||||
return new Double3DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeInteger3D) {
|
||||
return new Integer3DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeCustom || type == kOfxParamTypeBytes) {
|
||||
return new CustomInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeGroup) {
|
||||
return new GroupInstance(desc, this);
|
||||
} else if (type == kOfxParamTypePage) {
|
||||
return new PageInstance(desc, this);
|
||||
} else if (type == kOfxParamTypePushButton) {
|
||||
return new PushbuttonInstance(node_, name, desc, this);
|
||||
}
|
||||
|
||||
return nullptr; // 未实现的类型
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::editBegin(const std::string &name)
|
||||
{
|
||||
edit_depth_++;
|
||||
if (edit_depth_ == 1) {
|
||||
edit_command_ = {};
|
||||
edit_label_.clear();
|
||||
edit_first_label_.clear();
|
||||
edit_param_count_ = 0;
|
||||
if (!name.empty()) {
|
||||
edit_first_label_ = "Change " + name;
|
||||
}
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::editEnd()
|
||||
{
|
||||
if (edit_depth_ > 0) {
|
||||
edit_depth_--;
|
||||
}
|
||||
if (edit_depth_ == 0 && edit_command_.ctx) {
|
||||
std::string label = edit_label_;
|
||||
if (label.empty()) {
|
||||
if (edit_param_count_ <= 1 && !edit_first_label_.empty()) {
|
||||
label = edit_first_label_;
|
||||
} else if (edit_param_count_ > 1 && !edit_first_label_.empty()) {
|
||||
label = edit_first_label_ + " (+" +
|
||||
std::to_string(edit_param_count_ - 1) + ")";
|
||||
} else {
|
||||
label = "Edit Parameters";
|
||||
}
|
||||
}
|
||||
if (undo_submit_fn_) {
|
||||
undo_submit_fn_(edit_command_, label);
|
||||
} else {
|
||||
oakundo_command_redo_now(edit_command_);
|
||||
oakundo_command_free(&edit_command_);
|
||||
}
|
||||
edit_command_ = {};
|
||||
edit_label_.clear();
|
||||
edit_first_label_.clear();
|
||||
edit_param_count_ = 0;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::submit_undo_command(OakUndoCommand command,
|
||||
const std::string &label)
|
||||
{
|
||||
if (!command.ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (edit_depth_ > 0) {
|
||||
if (!edit_command_.ctx) {
|
||||
edit_command_ = oakundo_command_init_multi();
|
||||
}
|
||||
edit_param_count_++;
|
||||
if (!label.empty() && edit_first_label_.empty()) {
|
||||
edit_first_label_ = label;
|
||||
}
|
||||
|
||||
oakundo_command_redo_now(command);
|
||||
oakundo_command_multi_add_child(edit_command_, command);
|
||||
return;
|
||||
}
|
||||
|
||||
if (undo_submit_fn_ && is_gui_thread()) {
|
||||
undo_submit_fn_(command, label);
|
||||
return;
|
||||
}
|
||||
|
||||
oakundo_command_redo_now(command);
|
||||
oakundo_command_free(&command);
|
||||
}
|
||||
|
||||
void OlivePluginInstance::progressStart(const std::string &message,
|
||||
const std::string &messageid)
|
||||
{
|
||||
(void)messageid;
|
||||
progress_cancelled_ = false;
|
||||
progress_active_ = true;
|
||||
|
||||
if (progress_reporter_) {
|
||||
progress_reporter_->close();
|
||||
progress_reporter_.reset();
|
||||
}
|
||||
|
||||
std::string dialog_message =
|
||||
message.empty() ? "Processing..." : message;
|
||||
|
||||
progress_reporter_.reset(
|
||||
create_plugin_progress_reporter(dialog_message, "OpenFX"));
|
||||
progress_reporter_->set_cancel_callback(
|
||||
[this](void *) { progress_cancelled_ = true; }, nullptr);
|
||||
progress_reporter_->show();
|
||||
}
|
||||
|
||||
void OlivePluginInstance::progressEnd()
|
||||
{
|
||||
progress_active_ = false;
|
||||
progress_cancelled_ = false;
|
||||
|
||||
if (progress_reporter_) {
|
||||
progress_reporter_->close();
|
||||
progress_reporter_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool OlivePluginInstance::progressUpdate(double t)
|
||||
{
|
||||
if (!progress_active_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (progress_reporter_) {
|
||||
double clamped = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
|
||||
progress_reporter_->set_progress(clamped);
|
||||
}
|
||||
|
||||
return !progress_cancelled_;
|
||||
}
|
||||
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
OfxStatus OlivePluginInstance::contextAttachedAction()
|
||||
{
|
||||
if (!open_gl_enabled_) {
|
||||
return kOfxStatReplyDefault;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::contextDetachedAction()
|
||||
{
|
||||
if (!open_gl_enabled_) {
|
||||
return kOfxStatReplyDefault;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
#endif
|
||||
|
||||
double OlivePluginInstance::timeLineGetTime()
|
||||
{
|
||||
if (active_viewer_provider_) {
|
||||
OakNodeNode viewer = active_viewer_provider_();
|
||||
if (viewer.ctx) {
|
||||
// Playhead as seconds
|
||||
int num = 0, den = 1;
|
||||
if (oaknode_sequence_get_playhead(
|
||||
oaknode_sequence_from_node(viewer),
|
||||
&num, &den) == OAKNODE_OK && den != 0) {
|
||||
return double(num) / den;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::timeLineGotoTime(double t)
|
||||
{
|
||||
if (active_viewer_provider_) {
|
||||
OakNodeNode viewer = active_viewer_provider_();
|
||||
if (viewer.ctx) {
|
||||
olive::core::Rational r = olive::core::Rational::from_double(t);
|
||||
oaknode_sequence_set_playhead(
|
||||
oaknode_sequence_from_node(viewer), r.numerator(),
|
||||
r.denominator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OlivePluginInstance::timeLineGetBounds(double &t1, double &t2)
|
||||
{
|
||||
if (active_viewer_provider_) {
|
||||
OakNodeNode viewer = active_viewer_provider_();
|
||||
if (viewer.ctx) {
|
||||
int len_num = 0, len_den = 1;
|
||||
if (oaknode_sequence_get_length(
|
||||
oaknode_sequence_from_node(viewer), &len_num,
|
||||
&len_den) == OAKNODE_OK && len_den != 0) {
|
||||
t1 = 0.0;
|
||||
t2 = double(len_num) / len_den;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t1 = 0.0;
|
||||
t2 = 0.0;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::setCustomInArgs(const std::string &action,
|
||||
OFX::Host::Property::Set &in_args)
|
||||
{
|
||||
if (action == kOfxImageEffectActionRender ||
|
||||
action == kOfxImageEffectActionBeginSequenceRender ||
|
||||
action == kOfxImageEffectActionEndSequenceRender) {
|
||||
in_args.setIntProperty(kOfxImageEffectPropOpenGLEnabled,
|
||||
open_gl_enabled_ ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
OFX::Host::ImageEffect::ClipInstance *OlivePluginInstance::newClipInstance(
|
||||
OFX::Host::ImageEffect::Instance *plugin,
|
||||
OFX::Host::ImageEffect::ClipDescriptor *descriptor, int index)
|
||||
{
|
||||
// Create a new clip instance
|
||||
OliveClipInstance *clip_instance =
|
||||
new OliveClipInstance(plugin, *descriptor, params_);
|
||||
|
||||
// Initialize base class clip properties from VideoParams so that
|
||||
// setupClipPreferencesArgs and plugin constructors (which may fetch
|
||||
// clips and query their properties before getClipPreferences is called)
|
||||
// have valid defaults instead of kOfxImageComponentNone / kOfxBitDepthNone.
|
||||
std::string depth = kOfxBitDepthFloat; // host default
|
||||
std::string comp = kOfxImageComponentRGBA; // host default
|
||||
|
||||
int format = -1;
|
||||
int channels = 0;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_format(params_, &format);
|
||||
oakcommon_videoparams_get_channel_count(params_, &channels);
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case OAKCOMMON_PIXEL_FORMAT_U8:
|
||||
depth = kOfxBitDepthByte;
|
||||
break;
|
||||
case OAKCOMMON_PIXEL_FORMAT_U16:
|
||||
depth = kOfxBitDepthShort;
|
||||
break;
|
||||
case OAKCOMMON_PIXEL_FORMAT_F16:
|
||||
depth = kOfxBitDepthHalf;
|
||||
break;
|
||||
case OAKCOMMON_PIXEL_FORMAT_F32:
|
||||
depth = kOfxBitDepthFloat;
|
||||
break;
|
||||
default:
|
||||
break; // keep F32 default
|
||||
}
|
||||
|
||||
switch (channels) {
|
||||
case 1:
|
||||
comp = kOfxImageComponentAlpha;
|
||||
break;
|
||||
case 3:
|
||||
comp = kOfxImageComponentRGB;
|
||||
break;
|
||||
case 4:
|
||||
comp = kOfxImageComponentRGBA;
|
||||
break;
|
||||
default:
|
||||
break; // keep RGBA default
|
||||
}
|
||||
|
||||
clip_instance->setPixelDepth(depth);
|
||||
clip_instance->setComponents(comp);
|
||||
|
||||
return clip_instance;
|
||||
}
|
||||
|
||||
OlivePluginInstance::~OlivePluginInstance()
|
||||
{
|
||||
if (node_.ctx) {
|
||||
register_node_instance(oaknode_node_identity(node_), nullptr);
|
||||
}
|
||||
_created = false;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_free(¶ms_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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_OLIVE_INSTANCE_H
|
||||
#define OAK_OLIVE_INSTANCE_H
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxImageEffect.h"
|
||||
#include "ofxhImageEffect.h"
|
||||
|
||||
#include "common/videoparams.h"
|
||||
#include "node/node.h"
|
||||
#include "pluginprogressreporter.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Whether the calling thread is the registered main (GUI) thread
|
||||
*
|
||||
* The facade registers the main thread at startup with
|
||||
* set_main_thread_id(); before registration the first caller's thread
|
||||
* is treated as main (headless default).
|
||||
*/
|
||||
bool is_gui_thread();
|
||||
|
||||
void set_main_thread_id(std::thread::id id);
|
||||
|
||||
class PluginProgressReporter;
|
||||
enum class ErrorType { error, warning, message };
|
||||
struct PersistentErrors {
|
||||
ErrorType type;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provider returning the currently active viewer as an oaknode
|
||||
* handle
|
||||
*
|
||||
* Registered by the facade. Without a provider, the OFX timeline suite
|
||||
* falls back to its safe defaults (current time 0, empty bounds,
|
||||
* seeking does nothing).
|
||||
*/
|
||||
using ActiveViewerProvider = std::function<OakNodeNode()>;
|
||||
|
||||
void set_active_viewer_provider(ActiveViewerProvider provider);
|
||||
|
||||
/**
|
||||
* @brief Undo submission callback (facade pushes onto its undo stack)
|
||||
*
|
||||
* The command handle is consumed by the callback (the facade stack
|
||||
* takes a reference; see oakundo push semantics). Without a callback,
|
||||
* commands are redone immediately and released.
|
||||
*/
|
||||
using UndoSubmitFn = std::function<void(OakUndoCommand command,
|
||||
const std::string &label)>;
|
||||
|
||||
void set_undo_submit_callback(UndoSubmitFn fn);
|
||||
|
||||
class OlivePluginInstance : public OFX::Host::ImageEffect::Instance {
|
||||
public:
|
||||
OlivePluginInstance(OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
|
||||
OFX::Host::ImageEffect::Descriptor &desc,
|
||||
const std::string &context, bool interactive)
|
||||
: OFX::Host::ImageEffect::Instance(plugin, desc, context, interactive)
|
||||
{
|
||||
}
|
||||
OlivePluginInstance(OlivePluginInstance &instance)
|
||||
: Instance(instance._plugin, *instance._descriptor, instance._context,
|
||||
instance._interactive)
|
||||
{
|
||||
// Do NOT shallow-copy _clips: Instance::~Instance() deletes them,
|
||||
// which would cause a double-free. Clips are re-created in populate().
|
||||
_created = instance._created;
|
||||
_clipPrefsDirty = instance._clipPrefsDirty;
|
||||
_continuousSamples = instance._continuousSamples;
|
||||
_frameVarying = instance._frameVarying;
|
||||
_outputPreMultiplication = instance._outputPreMultiplication;
|
||||
_outputFielding = instance._outputFielding;
|
||||
_outputFrameRate = instance._outputFrameRate;
|
||||
}
|
||||
explicit OlivePluginInstance(Instance &instance)
|
||||
: Instance(instance) {};
|
||||
~OlivePluginInstance() override;
|
||||
const std::string &getDefaultOutputFielding() const override;
|
||||
|
||||
void setVideoParam(OakVideoParams params)
|
||||
{
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_free(¶ms_);
|
||||
}
|
||||
params_ = params;
|
||||
if (params_.ctx) {
|
||||
params_.addref(params_.ctx);
|
||||
}
|
||||
}
|
||||
void set_node_handle(OakNodeNode node);
|
||||
OakNodeNode node_handle() const
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
void setOpenGLEnabled(bool enabled)
|
||||
{
|
||||
open_gl_enabled_ = enabled;
|
||||
}
|
||||
bool isCreated() const
|
||||
{
|
||||
return _created;
|
||||
}
|
||||
OFX::Host::ImageEffect::ClipInstance *
|
||||
newClipInstance(OFX::Host::ImageEffect::Instance *plugin,
|
||||
OFX::Host::ImageEffect::ClipDescriptor *descriptor,
|
||||
int index) override;
|
||||
|
||||
OfxStatus vmessage(const char *type, const char *id, const char *format,
|
||||
va_list args) override;
|
||||
|
||||
OfxStatus setPersistentMessage(const char *type, const char *id,
|
||||
const char *format, va_list args) override;
|
||||
|
||||
OfxStatus clearPersistentMessage() override;
|
||||
int persistent_message_count() const
|
||||
{
|
||||
return int(persistent_errors_.size());
|
||||
}
|
||||
const std::vector<PersistentErrors> &persistent_messages() const
|
||||
{
|
||||
return persistent_errors_;
|
||||
}
|
||||
|
||||
void getProjectSize(double &x_size, double &y_size) const override;
|
||||
void getProjectOffset(double &x_offset, double &y_offset) const override;
|
||||
void getProjectExtent(double &x_size, double &y_size) const override;
|
||||
// The pixel aspect ratio of the current project
|
||||
double getProjectPixelAspectRatio() const override;
|
||||
|
||||
// The duration of the effect
|
||||
// This contains the duration of the plug-in effect, in frames.
|
||||
double getEffectDuration() const override;
|
||||
|
||||
// For an instance, this is the frame rate of the project the effect is in.
|
||||
double getFrameRate() const override;
|
||||
|
||||
/// This is called whenever a param is changed by the plugin so that
|
||||
/// the recursive instanceChangedAction will be fed the correct frame
|
||||
double getFrameRecursive() const override;
|
||||
|
||||
/// This is called whenever a param is changed by the plugin so that
|
||||
/// the recursive instanceChangedAction will be fed the correct
|
||||
/// renderScale
|
||||
void getRenderScaleRecursive(double &x, double &y) const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// overridden for Param::SetInstance
|
||||
|
||||
/// make a parameter instance
|
||||
OFX::Host::Param::Instance *
|
||||
newParam(const std::string &name,
|
||||
OFX::Host::Param::Descriptor &descriptor) override;
|
||||
|
||||
void submit_undo_command(OakUndoCommand command, const std::string &label);
|
||||
|
||||
/// Triggered when the plug-in calls OfxParameterSuiteV1::paramEditBegin
|
||||
virtual OfxStatus editBegin(const std::string &name) override;
|
||||
|
||||
/// Triggered when the plug-in calls OfxParameterSuiteV1::paramEditEnd
|
||||
virtual OfxStatus editEnd() override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// overridden for Progress::ProgressI
|
||||
|
||||
/// Start doing progress.
|
||||
virtual void progressStart(const std::string &message,
|
||||
const std::string &messageid) override;
|
||||
|
||||
/// finish yer progress
|
||||
virtual void progressEnd() override;
|
||||
|
||||
/// set the progress to some level of completion, returns
|
||||
/// false if you should abandon processing, true to continue
|
||||
virtual bool progressUpdate(double t) override;
|
||||
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
virtual OfxStatus contextAttachedAction() override;
|
||||
virtual OfxStatus contextDetachedAction() override;
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// overridden for TimeLine::TimeLineI
|
||||
|
||||
/// get the current time on the timeline. This is not necessarily the same
|
||||
/// time as being passed to an action (eg render)
|
||||
double timeLineGetTime() override;
|
||||
|
||||
/// set the timeline to a specific time
|
||||
void timeLineGotoTime(double t) override;
|
||||
|
||||
/// get the first and last times available on the effect's timeline
|
||||
void timeLineGetBounds(double &t1, double &t2) override;
|
||||
|
||||
void setCustomInArgs(const std::string &action,
|
||||
OFX::Host::Property::Set &in_args) override;
|
||||
|
||||
private:
|
||||
std::vector<PersistentErrors> persistent_errors_;
|
||||
OakVideoParams params_ = {};
|
||||
OakNodeNode node_ = {};
|
||||
int edit_depth_ = 0;
|
||||
OakUndoCommand edit_command_ = {};
|
||||
std::string edit_label_;
|
||||
std::string edit_first_label_;
|
||||
int edit_param_count_ = 0;
|
||||
std::unique_ptr<PluginProgressReporter> progress_reporter_;
|
||||
bool progress_cancelled_ = false;
|
||||
bool progress_active_ = false;
|
||||
bool open_gl_enabled_ = false;
|
||||
|
||||
public:
|
||||
std::mutex &mutex()
|
||||
{
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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 "paraminstance.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "oliveplugininstance.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Node-identity → plugin instance registry, maintained by
|
||||
* OlivePluginInstance::set_node_handle()
|
||||
*/
|
||||
std::map<uintptr_t, OlivePluginInstance *> &instance_registry()
|
||||
{
|
||||
static std::map<uintptr_t, OlivePluginInstance *> registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_node_instance(uintptr_t identity, OlivePluginInstance *instance)
|
||||
{
|
||||
if (instance) {
|
||||
instance_registry()[identity] = instance;
|
||||
} else {
|
||||
instance_registry().erase(identity);
|
||||
}
|
||||
}
|
||||
|
||||
void submit_undo_command(OakNodeNode node, OakUndoCommand command,
|
||||
const std::string &label)
|
||||
{
|
||||
if (!command.ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.ctx) {
|
||||
auto it = instance_registry().find(oaknode_node_identity(node));
|
||||
if (it != instance_registry().end() && it->second) {
|
||||
it->second->submit_undo_command(command, label);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No instance bound: run immediately and release
|
||||
oakundo_command_redo_now(command);
|
||||
oakundo_command_free(&command);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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 "pluginprogressreporter.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief No-op reporter used when no UI factory is registered
|
||||
*
|
||||
* Never reports cancellation, so processing always continues.
|
||||
*/
|
||||
class NullPluginProgressReporter : public PluginProgressReporter {
|
||||
public:
|
||||
void set_progress(double value) override
|
||||
{
|
||||
(void)value;
|
||||
}
|
||||
|
||||
void show() override
|
||||
{
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
PluginProgressReporterFactory reporter_factory_;
|
||||
|
||||
}
|
||||
|
||||
void set_plugin_progress_reporter_factory(
|
||||
PluginProgressReporterFactory factory)
|
||||
{
|
||||
reporter_factory_ = std::move(factory);
|
||||
}
|
||||
|
||||
PluginProgressReporter *
|
||||
create_plugin_progress_reporter(const std::string &message,
|
||||
const std::string &title)
|
||||
{
|
||||
if (reporter_factory_) {
|
||||
return reporter_factory_(message, title);
|
||||
}
|
||||
|
||||
return new NullPluginProgressReporter();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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_PLUGIN_PROGRESS_REPORTER_H
|
||||
#define OAK_PLUGIN_PROGRESS_REPORTER_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief UI-independent interface for reporting plugin progress
|
||||
*
|
||||
* The engine cannot show UI itself, so OFX progress reporting goes through
|
||||
* this interface. The UI layer registers a factory (see
|
||||
* set_plugin_progress_reporter_factory()) that creates a reporter wrapping a
|
||||
* ProgressDialog; without a factory, a no-op reporter is used instead.
|
||||
*
|
||||
* Cancellation is delivered through a C-style callback.
|
||||
*/
|
||||
class PluginProgressReporter {
|
||||
public:
|
||||
PluginProgressReporter() = default;
|
||||
|
||||
virtual ~PluginProgressReporter() = default;
|
||||
|
||||
virtual void set_progress(double value) = 0;
|
||||
|
||||
virtual void show() = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
/**
|
||||
* @brief Register a callback to be invoked when the user cancels.
|
||||
*
|
||||
* Only one callback is supported; subsequent calls replace the
|
||||
* previous registration. Pass nullptr to clear.
|
||||
*/
|
||||
void set_cancel_callback(std::function<void(void *)> cb, void *userdata)
|
||||
{
|
||||
cancel_callback_ = std::move(cb);
|
||||
cancel_callback_userdata_ = userdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark this reporter as cancelled and notify the registered
|
||||
* callback.
|
||||
*/
|
||||
void set_cancelled()
|
||||
{
|
||||
cancelled_ = true;
|
||||
if (cancel_callback_) {
|
||||
cancel_callback_(cancel_callback_userdata_);
|
||||
}
|
||||
}
|
||||
|
||||
bool cancelled() const
|
||||
{
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(void *)> cancel_callback_;
|
||||
void *cancel_callback_userdata_ = nullptr;
|
||||
bool cancelled_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Factory creating a PluginProgressReporter for a progress session
|
||||
*
|
||||
* Registered by the UI layer at startup. The caller takes ownership of the
|
||||
* returned reporter.
|
||||
*/
|
||||
using PluginProgressReporterFactory =
|
||||
std::function<PluginProgressReporter *(const std::string &message,
|
||||
const std::string &title)>;
|
||||
|
||||
void set_plugin_progress_reporter_factory(
|
||||
PluginProgressReporterFactory factory);
|
||||
|
||||
/**
|
||||
* @brief Create a progress reporter through the registered factory
|
||||
*
|
||||
* Without a factory, returns a no-op reporter so engine code can run
|
||||
* headless. The caller takes ownership of the returned reporter.
|
||||
*/
|
||||
PluginProgressReporter *
|
||||
create_plugin_progress_reporter(const std::string &message,
|
||||
const std::string &title);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OAK_PLUGIN_PROGRESS_REPORTER_H
|
||||
@@ -0,0 +1,168 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2025 mikesolar
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(oakplugin-standalone CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# oakplugin manipulates node graphs through the oaknode C ABI; the
|
||||
# not-yet-split engine modules' symbols are allowed to dangle, resolved
|
||||
# by transitional stubs in src/{node,render}/transition.
|
||||
set(OAK_REPO_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
|
||||
list(APPEND CMAKE_MODULE_PATH "${OAK_REPO_ROOT}/cmake")
|
||||
|
||||
set(BUILD_TESTS ON CACHE BOOL "" FORCE)
|
||||
set(OLIVECORE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
# The /opt/otio OpenTimelineIO dylibs use @loader_path install names that
|
||||
# break when linked from a build tree; oakplugin does not need OTIO.
|
||||
set(CMAKE_DISABLE_FIND_PACKAGE_OpenTimelineIO ON)
|
||||
|
||||
find_package(EXPAT REQUIRED)
|
||||
find_package(OpenColorIO CONFIG REQUIRED)
|
||||
find_package(OpenImageIO CONFIG REQUIRED)
|
||||
set(OCIO_LIBRARIES OpenColorIO::OpenColorIO)
|
||||
set(OCIO_INCLUDE_DIRS ${OpenColorIO_INCLUDE_DIRS})
|
||||
set(OIIO_LIBRARIES OpenImageIO::OpenImageIO)
|
||||
set(OIIO_INCLUDE_DIRS ${OpenImageIO_INCLUDE_DIRS})
|
||||
|
||||
add_subdirectory(${OAK_REPO_ROOT}/core ${CMAKE_BINARY_DIR}/core)
|
||||
target_include_directories(olivecore PUBLIC
|
||||
${OAK_REPO_ROOT}/third_party/openfx/include
|
||||
)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/ffmpeg_bridge ${CMAKE_BINARY_DIR}/ffmpeg_bridge)
|
||||
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/undo ${CMAKE_BINARY_DIR}/undo)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/common ${CMAKE_BINARY_DIR}/common)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/src ${CMAKE_BINARY_DIR}/render)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/c_api ${CMAKE_BINARY_DIR}/render_c_api)
|
||||
set(BUILD_TESTS ON)
|
||||
|
||||
enable_testing()
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/timeline ${CMAKE_BINARY_DIR}/timeline)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/codec ${CMAKE_BINARY_DIR}/codec)
|
||||
set(BUILD_TESTS ON)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/node ${CMAKE_BINARY_DIR}/node)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/task)
|
||||
|
||||
target_include_directories(oakcodec PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
${OAK_REPO_ROOT}/src/node/src
|
||||
)
|
||||
foreach(t oakcodec oaktimeline)
|
||||
if(TARGET ${t})
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# oaknode needs its transition stubs and the oakrender real headers
|
||||
# (src/node/transition/render/* bridge there); src/render/transition
|
||||
# must precede src/node/transition.
|
||||
target_include_directories(oaknode BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
)
|
||||
target_include_directories(oaknode PUBLIC
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
/opt/homebrew/include
|
||||
/opt/homebrew/include/Imath
|
||||
)
|
||||
target_link_options(oaknode PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
|
||||
target_include_directories(oakrender BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
)
|
||||
target_include_directories(oakrender PUBLIC
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
/opt/homebrew/include
|
||||
/opt/homebrew/include/Imath
|
||||
/opt/homebrew/include/OpenEXR
|
||||
)
|
||||
foreach(t oakrender oakgl oakgl2 oakvulkan)
|
||||
if(TARGET ${t})
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
target_include_directories(oakplugin PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
${OAK_REPO_ROOT}/src/node/src
|
||||
)
|
||||
target_link_options(oakplugin PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
|
||||
target_link_libraries(oakrender PRIVATE
|
||||
oaknode
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
ffmpeg_bridge
|
||||
${OCIO_LIBRARIES}
|
||||
${OIIO_LIBRARIES}
|
||||
"-framework OpenGL"
|
||||
"-framework CoreVideo"
|
||||
"-framework Metal"
|
||||
"-framework QuartzCore"
|
||||
)
|
||||
|
||||
target_link_libraries(oaknode-gtest PRIVATE oakrender oaktimeline oakcodec)
|
||||
|
||||
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
|
||||
# dynamic_lookup; the real implementation (engine/src/capi/ipc.cpp) is still
|
||||
# Qt-based, so test binaries link an inert shim instead.
|
||||
target_sources(oaknode-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/src/node/standalone/oakengine_ipc_shim.cpp
|
||||
)
|
||||
target_include_directories(oaknode-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
)
|
||||
|
||||
|
||||
# OFX typeinfo is referenced via dynamic_lookup; force-load the host
|
||||
# support archive so RTTI resolves (same as oaknode's test wiring).
|
||||
# oakplugin-gtest wires itself up in src/plugin/tests/CMakeLists.txt.
|
||||
find_library(OAKNODE_OFX_HOST_ARCHIVE NAMES OfxHost
|
||||
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport
|
||||
NO_DEFAULT_PATH)
|
||||
if(NOT OAKNODE_OFX_HOST_ARCHIVE)
|
||||
message(FATAL_ERROR
|
||||
"libOfxHost.a not found; run the full-tree build once or set "
|
||||
"OAKNODE_OFX_HOST_ARCHIVE")
|
||||
endif()
|
||||
target_link_options(oaknode-gtest PRIVATE
|
||||
"-Wl,-force_load,${OAKNODE_OFX_HOST_ARCHIVE}")
|
||||
|
||||
# TimelineAddTrackCommand, which lives in liboakplugin; the two
|
||||
# libraries resolve each other at runtime (dynamic_lookup).
|
||||
target_link_libraries(oaknode-gtest PRIVATE oaktimeline)
|
||||
@@ -0,0 +1,65 @@
|
||||
# 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/>.
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
# In a full-tree build the repo root is CMAKE_SOURCE_DIR; a standalone
|
||||
# build (see src/plugin/standalone) sets OAK_REPO_ROOT explicitly.
|
||||
if(NOT DEFINED OAK_REPO_ROOT)
|
||||
set(OAK_REPO_ROOT ${CMAKE_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
add_executable(oakplugin-gtest
|
||||
plugin_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(oakplugin-gtest PRIVATE
|
||||
oakplugin
|
||||
oakrender
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
# liboakrender/liboaknode dangle OFX host symbols (-undefined
|
||||
# dynamic_lookup); force-load the host support archive into the test
|
||||
# process so dyld finds them in the flat namespace at startup. Mirrors
|
||||
# src/render/tests/CMakeLists.txt.
|
||||
if(NOT DEFINED OAKRENDER_OFX_HOST_ARCHIVE)
|
||||
find_library(OAKRENDER_OFX_HOST_ARCHIVE NAMES OfxHost
|
||||
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport)
|
||||
endif()
|
||||
if(NOT OAKRENDER_OFX_HOST_ARCHIVE)
|
||||
message(FATAL_ERROR
|
||||
"libOfxHost.a not found; run the full-tree build once or set "
|
||||
"OAKRENDER_OFX_HOST_ARCHIVE")
|
||||
endif()
|
||||
target_link_options(oakplugin-gtest PRIVATE
|
||||
"-Wl,-force_load,${OAKRENDER_OFX_HOST_ARCHIVE}")
|
||||
|
||||
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
|
||||
# dynamic_lookup; the test binary links the inert shim from
|
||||
# src/node/standalone instead.
|
||||
target_sources(oakplugin-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/src/node/standalone/oakengine_ipc_shim.cpp)
|
||||
target_include_directories(oakplugin-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
)
|
||||
|
||||
gtest_discover_tests(oakplugin-gtest
|
||||
DISCOVERY_MODE PRE_TEST
|
||||
PROPERTIES ENVIRONMENT
|
||||
"OCIO=${OAK_REPO_ROOT}/engine/render/ocioconf/config.ocio")
|
||||
@@ -0,0 +1,106 @@
|
||||
/***
|
||||
|
||||
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 <cstring>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "plugin/host.h"
|
||||
#include "plugin/instance.h"
|
||||
|
||||
TEST(OakPluginHost, InitAndEnumerate)
|
||||
{
|
||||
EXPECT_EQ(oakplugin_host_init(), OAKPLUGIN_OK);
|
||||
|
||||
int count = oakplugin_host_plugin_count();
|
||||
EXPECT_GE(count, 0);
|
||||
|
||||
char buf[256];
|
||||
EXPECT_EQ(oakplugin_host_plugin_id_at(-1, buf, sizeof(buf)),
|
||||
OAKPLUGIN_E_NOT_FOUND);
|
||||
EXPECT_EQ(oakplugin_host_plugin_id_at(count + 100, buf, sizeof(buf)),
|
||||
OAKPLUGIN_E_NOT_FOUND);
|
||||
EXPECT_EQ(oakplugin_host_plugin_label(nullptr, buf, sizeof(buf)),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
|
||||
const char *dirs[] = { "/nonexistent/ofx/path" };
|
||||
EXPECT_EQ(oakplugin_host_scan(dirs, 1), OAKPLUGIN_OK);
|
||||
EXPECT_EQ(oakplugin_host_scan(nullptr, 1), OAKPLUGIN_E_INVALID);
|
||||
|
||||
oakplugin_host_shutdown();
|
||||
}
|
||||
|
||||
TEST(OakPluginHost, MessageHandler)
|
||||
{
|
||||
static int calls = 0;
|
||||
calls = 0;
|
||||
|
||||
oakplugin_host_set_message_handler(
|
||||
[](const char *type, const char *message, void *userdata) -> int {
|
||||
int *counter = static_cast<int *>(userdata);
|
||||
(*counter)++;
|
||||
EXPECT_STREQ(type, "Message");
|
||||
EXPECT_STRNE(message, "");
|
||||
return OAKPLUGIN_MESSAGE_ANSWER_NO;
|
||||
},
|
||||
&calls);
|
||||
|
||||
// No plugin loaded here; just verify registration does not crash and
|
||||
// the default path works after clearing
|
||||
oakplugin_host_set_message_handler(nullptr, nullptr);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(OakPluginInstance, CreateUnknownIdFails)
|
||||
{
|
||||
EXPECT_EQ(oakplugin_host_init(), OAKPLUGIN_OK);
|
||||
|
||||
OakPluginInstance instance =
|
||||
oakplugin_instance_create("com.example.nonexistent.plugin");
|
||||
EXPECT_EQ(instance.ctx, nullptr);
|
||||
oakplugin_instance_free(&instance); // no-op on empty
|
||||
|
||||
EXPECT_EQ(oakplugin_debug_alive_count(), 0);
|
||||
}
|
||||
|
||||
TEST(OakPluginInstance, InvalidArgs)
|
||||
{
|
||||
OakPluginInstance empty = {};
|
||||
|
||||
oakplugin_instance_free(nullptr); // no-op
|
||||
oakplugin_instance_free(&empty); // no-op
|
||||
|
||||
oaknode_value value = {};
|
||||
EXPECT_EQ(oakplugin_instance_set_param(empty, "p", &value),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_get_param(empty, "p", &value),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_set_param_string(empty, "p", "x"),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_get_param_string(empty, "p", nullptr, 0),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
|
||||
EXPECT_EQ(oakplugin_instance_render(empty, OakRenderTexture{},
|
||||
OakRenderTexture{}, 0.0),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_set_progress_cb(empty, nullptr, nullptr),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_cancel(empty), OAKPLUGIN_E_INVALID);
|
||||
}
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
#include "alivecount.h"
|
||||
|
||||
#include "avframeptr.h"
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "colorprocessor.h"
|
||||
#include "texture.h"
|
||||
@@ -70,6 +72,7 @@ struct OakRenderTextureImpl {
|
||||
*/
|
||||
struct OakCodecFrameImpl {
|
||||
olive::FramePtr ptr;
|
||||
olive::AVFramePtr avptr; /**< Set when wrapping a texture's CPU copy. */
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -363,6 +363,9 @@ void *oakrender_codec_frame_data(OakCodecFrame frame)
|
||||
if (!f || !f->ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (f->avptr) {
|
||||
return f->avptr->data(0);
|
||||
}
|
||||
return f->ptr->data();
|
||||
}
|
||||
|
||||
@@ -380,6 +383,9 @@ int oakrender_codec_frame_linesize_bytes(OakCodecFrame frame)
|
||||
{
|
||||
OakCodecFrameImpl *f =
|
||||
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
|
||||
if (f && f->avptr) {
|
||||
return f->avptr->linesize(0);
|
||||
}
|
||||
if (!f || !f->ptr) {
|
||||
return 0;
|
||||
}
|
||||
@@ -508,3 +514,84 @@ int oakrender_current_backend(char *buf, int n)
|
||||
}
|
||||
return write_string(g_requested_backend, buf, n);
|
||||
}
|
||||
|
||||
int oakrender_display_texture_is_dummy(OakRenderTexture texture)
|
||||
{
|
||||
if (!texture.ctx) {
|
||||
return 0;
|
||||
}
|
||||
return oakrender_c_api::to_native<OakRenderTextureImpl>(texture)
|
||||
->ptr->is_dummy()
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
int oakrender_display_texture_get_frame(OakRenderTexture texture,
|
||||
OakCodecFrame *out)
|
||||
{
|
||||
if (!texture.ctx || !out) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
*out = OakCodecFrame{};
|
||||
|
||||
olive::AVFramePtr frame =
|
||||
oakrender_c_api::to_native<OakRenderTextureImpl>(texture)->ptr->frame();
|
||||
if (!frame) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
// The texture's CPU copy is an AVFramePtr (ffmpeg_bridge RAII); wrap
|
||||
// the shared_ptr so the frame stays alive with the handle.
|
||||
auto *fimpl = new OakCodecFrameImpl;
|
||||
fimpl->avptr = frame;
|
||||
*out = oakrender_c_api::make_handle<OakCodecFrame>(
|
||||
fimpl, true, &oakrender_c_api::delete_as<OakCodecFrameImpl>);
|
||||
return out->ctx ? OAKRENDER_OK : OAKRENDER_E_NOMEM;
|
||||
}
|
||||
|
||||
OakRenderTexture oakrender_display_texture_wrap_native(
|
||||
const olive::TexturePtr &texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return OakRenderTexture{};
|
||||
}
|
||||
auto *impl = new OakRenderTextureImpl;
|
||||
impl->ptr = texture;
|
||||
return oakrender_c_api::make_handle<OakRenderTexture>(
|
||||
impl, true, &oakrender_c_api::delete_as<OakRenderTextureImpl>);
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_width(OakCodecFrame frame)
|
||||
{
|
||||
OakCodecFrameImpl *f =
|
||||
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
|
||||
if (!f) {
|
||||
return 0;
|
||||
}
|
||||
if (f->avptr) {
|
||||
return f->avptr->width();
|
||||
}
|
||||
return f->ptr ? f->ptr->width() : 0;
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_height(OakCodecFrame frame)
|
||||
{
|
||||
OakCodecFrameImpl *f =
|
||||
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
|
||||
if (!f) {
|
||||
return 0;
|
||||
}
|
||||
if (f->avptr) {
|
||||
return f->avptr->height();
|
||||
}
|
||||
return f->ptr ? f->ptr->height() : 0;
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_fb_format(OakCodecFrame frame)
|
||||
{
|
||||
OakCodecFrameImpl *f =
|
||||
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
|
||||
if (!f || !f->avptr) {
|
||||
return -1;
|
||||
}
|
||||
return f->avptr->format();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#ifndef OAK_RENDERTEXTURE_H
|
||||
#define OAK_RENDERTEXTURE_H
|
||||
|
||||
#include "common/avframeptr.h"
|
||||
#include "avframeptr.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
@@ -154,3 +154,4 @@ if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/tests ${CMAKE_BINARY_DIR}/render_tests)
|
||||
endif()
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/plugin)
|
||||
|
||||
@@ -28,6 +28,7 @@ target_link_libraries(oakrender-gtest PRIVATE
|
||||
oaktimeline
|
||||
oakrender
|
||||
oaknode
|
||||
oakplugin
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// Transitional copy of engine/common/avframeptr.h(本身已无 Qt,待下沉 oakcommon)。只增不删。
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_AVFRAMEPTR_H
|
||||
#define OAK_AVFRAMEPTR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief C++ adapter around the ffmpeg_bridge frame handle
|
||||
*
|
||||
* Mirrors the AVFrame field access the codebase used to perform directly,
|
||||
* but every operation goes through the pure C bridge API so the editor
|
||||
* never touches FFmpeg itself. The underlying frame object always lives
|
||||
* inside the bridge library.
|
||||
*/
|
||||
class AVFrame {
|
||||
public:
|
||||
AVFrame() :
|
||||
handle_(fb_frame_alloc())
|
||||
{
|
||||
}
|
||||
|
||||
explicit AVFrame(FBFrame *handle) :
|
||||
handle_(handle)
|
||||
{
|
||||
}
|
||||
|
||||
~AVFrame()
|
||||
{
|
||||
if (handle_) {
|
||||
fb_frame_free(&handle_);
|
||||
}
|
||||
}
|
||||
|
||||
AVFrame(const AVFrame &) = delete;
|
||||
AVFrame &operator=(const AVFrame &) = delete;
|
||||
|
||||
FBFrame *handle() const { return handle_; }
|
||||
|
||||
int width() const { return fb_frame_get_width(handle_); }
|
||||
void set_width(int w) { fb_frame_set_width(handle_, w); }
|
||||
int height() const { return fb_frame_get_height(handle_); }
|
||||
void set_height(int h) { fb_frame_set_height(handle_, h); }
|
||||
int format() const { return fb_frame_get_format(handle_); }
|
||||
void set_format(int f) { fb_frame_set_format(handle_, f); }
|
||||
int64_t pts() const { return fb_frame_get_pts(handle_); }
|
||||
void set_pts(int64_t p) { fb_frame_set_pts(handle_, p); }
|
||||
int64_t best_effort_timestamp() const
|
||||
{
|
||||
return fb_frame_get_best_effort_timestamp(handle_);
|
||||
}
|
||||
int nb_samples() const { return fb_frame_get_nb_samples(handle_); }
|
||||
void set_nb_samples(int n) { fb_frame_set_nb_samples(handle_, n); }
|
||||
int sample_rate() const { return fb_frame_get_sample_rate(handle_); }
|
||||
void set_sample_rate(int r) { fb_frame_set_sample_rate(handle_, r); }
|
||||
int color_range() const { return fb_frame_get_color_range(handle_); }
|
||||
void set_color_range(int r) { fb_frame_set_color_range(handle_, r); }
|
||||
int colorspace() const { return fb_frame_get_colorspace(handle_); }
|
||||
void set_colorspace(int cs) { fb_frame_set_colorspace(handle_, cs); }
|
||||
uint64_t channel_layout_mask() const
|
||||
{
|
||||
return fb_frame_get_channel_layout_mask(handle_);
|
||||
}
|
||||
void set_channel_layout_mask(uint64_t m)
|
||||
{
|
||||
fb_frame_set_channel_layout_mask(handle_, m);
|
||||
}
|
||||
|
||||
bool is_hw() const { return fb_frame_is_hw(handle_) != 0; }
|
||||
int hw_transfer_data(const AVFrame *src)
|
||||
{
|
||||
return fb_frame_hw_transfer_data(handle_, src->handle_);
|
||||
}
|
||||
int get_buffer(int align) { return fb_frame_get_buffer(handle_, align); }
|
||||
int make_writable() { return fb_frame_make_writable(handle_); }
|
||||
|
||||
uint8_t *data(int plane) { return fb_frame_get_data(handle_, plane); }
|
||||
const uint8_t *data(int plane) const
|
||||
{
|
||||
return fb_frame_get_data_const(handle_, plane);
|
||||
}
|
||||
void set_data(int plane, uint8_t *d)
|
||||
{
|
||||
fb_frame_set_data(handle_, plane, d);
|
||||
}
|
||||
int linesize(int plane) const
|
||||
{
|
||||
return fb_frame_get_linesize(handle_, plane);
|
||||
}
|
||||
void set_linesize(int plane, int l)
|
||||
{
|
||||
fb_frame_set_linesize(handle_, plane, l);
|
||||
}
|
||||
|
||||
private:
|
||||
FBFrame *handle_;
|
||||
};
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr(FBFrame *f)
|
||||
{
|
||||
return std::make_shared<AVFrame>(f);
|
||||
}
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr()
|
||||
{
|
||||
return std::make_shared<AVFrame>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_AVFRAMEPTR_H
|
||||
@@ -175,3 +175,4 @@ target_link_options(oaknode-gtest PRIVATE
|
||||
# TimelineAddTrackCommand, which lives in liboaktask; the two
|
||||
# libraries resolve each other at runtime (dynamic_lookup).
|
||||
target_link_libraries(oaknode-gtest PRIVATE oaktimeline)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/plugin)
|
||||
|
||||
@@ -11,6 +11,7 @@ target_compile_definitions(oaktask-gtest PRIVATE
|
||||
target_link_libraries(oaktask-gtest PRIVATE
|
||||
oaktask
|
||||
oaktimeline
|
||||
oakplugin
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
@@ -51,6 +51,7 @@ set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/undo ${CMAKE_BINARY_DIR}/undo)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/common ${CMAKE_BINARY_DIR}/common)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/src ${CMAKE_BINARY_DIR}/render)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/c_api ${CMAKE_BINARY_DIR}/render_c_api)
|
||||
set(BUILD_TESTS ON)
|
||||
|
||||
enable_testing()
|
||||
@@ -156,3 +157,4 @@ target_link_options(oaknode-gtest PRIVATE
|
||||
# TimelineAddTrackCommand, which lives in liboaktimeline; the two
|
||||
# libraries resolve each other at runtime (dynamic_lookup).
|
||||
target_link_libraries(oaknode-gtest PRIVATE oaktimeline)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/plugin)
|
||||
|
||||
@@ -8,6 +8,7 @@ add_executable(oaktimeline-gtest
|
||||
|
||||
target_link_libraries(oaktimeline-gtest PRIVATE
|
||||
oaktimeline
|
||||
oakplugin
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user