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:
2026-08-07 22:18:36 +08:00
parent 0462842f8c
commit fd2111d560
54 changed files with 6014 additions and 324 deletions
+25
View File
@@ -98,3 +98,28 @@ ldd liboakengine.so | grep oak # 只见 oak* 模
- instanceShadertoy 类插件(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 ABInode↔instance 的身份路由用新增的
oaknode_node_identity() 注册表实现。
- oliveclip 的纹理为 OakRenderTexture 值句柄,oakrender 因此补了
texture/copier/ticket 一族 C API。
- oaknode 的 transition stubsrc/node/transition/pluginSupport/
桥接 oakplugin 真身头,liboaknode 运行期引用 oakplugin 符号
dynamic_lookup);所有构建 oaknode 的 standalone 树都要把
src/plugin 加进构建,测试二进制必须链接 oakplugin。
验证:build-oakplugin 100/100codec 22/22、audio 40/40、
task 110/110、render 49/49、timeline 121/121(均含 oakplugin
接入后的全量回归)。
+32
View File
@@ -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 六个类去QtQMessageBox/QApplication 全部
删除,宿主消息与 undo 提交改为 facade 回调
set_host_message_handler / set_undo_submit_callback /
ActiveViewerProvider / set_main_thread_id)。
- paraminstance 的参数桥改走 oaknode C APInode_ 为 OakNodeNode
值句柄);undo 命令走 oakundo C APInode↔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 ABIinclude/plugin/{error,host,instance}.h
OakPluginInstance 为引用计数值句柄。
- oaknode 的 transition stubsrc/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-0EXC_BAD_ACCESS
address=0x0)。
- 修复 ffmpegdecoder.cpp 被 3d004c081 误删出
src/codec/src/ffmpeg/CMakeLists.txt 的回归:FFmpegDecoder 构造
函数在 flat namespace 悬空,decoder probe/decode/encoder
roundtrip 三个测试 jump-to-0。
+33
View File
@@ -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
+6
View File
@@ -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.
*
+40
View File
@@ -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
+69
View File
@@ -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
+103
View File
@@ -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
+37
View File
@@ -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. */
+1
View File
@@ -6,3 +6,4 @@ add_subdirectory(codec)
add_subdirectory(audio)
add_subdirectory(timeline)
add_subdirectory(task)
add_subdirectory(plugin)
+1
View File
@@ -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)
+1
View File
@@ -36,6 +36,7 @@ target_link_libraries(oakaudio-gtest PRIVATE
oakcodec
oakrender
oaknode
oakplugin
oakcommon
oakundo
olivecore
+2 -1
View File
@@ -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
+1 -1
View File
@@ -31,7 +31,7 @@
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include "decoder.h"
#include "ffmpeg/avframeptr.h"
#include "avframeptr.h"
namespace olive
{
+1
View File
@@ -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)
+1
View File
@@ -34,6 +34,7 @@ target_link_libraries(oakcodec-gtest PRIVATE
oakcodec
oakrender
oaknode
oakplugin
oakcommon
oakundo
olivecore
+1
View File
@@ -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
+94
View File
@@ -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;
}
}
+8
View File
@@ -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.
*/
+14
View File
@@ -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);
}
+3 -1
View File
@@ -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
-140
View File
@@ -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
+2 -11
View File
@@ -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"
+6
View File
@@ -0,0 +1,6 @@
add_subdirectory(src)
add_subdirectory(c_api)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
+4
View File
@@ -0,0 +1,4 @@
target_sources(oakplugin PRIVATE
host.cpp
instance.cpp
)
+155
View File
@@ -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;
});
}
+399
View File
@@ -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 *> &params_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 *> &params_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 *> &params_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 *> &params_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();
}
+51
View File
@@ -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
)
+235
View File
@@ -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 &params, 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 &params,
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 &params,
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
+81
View File
@@ -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 &params, 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 &params, const OfxRectI &bounds,
const OfxRectI &rod, bool clear = true);
void ensure_allocated_from_params(const VideoParams &params,
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
+124
View File
@@ -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(&params_);
}
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 &params);
#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
+321
View File
@@ -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(&current);
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;
}
+132
View File
@@ -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
+608
View File
@@ -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(&params_);
}
}
}
}
+253
View File
@@ -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(&params_);
}
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
+75
View File
@@ -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
+70
View File
@@ -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();
}
}
}
+113
View File
@@ -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
+168
View File
@@ -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)
+65
View File
@@ -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")
+106
View File
@@ -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);
}
+3
View File
@@ -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. */
};
/**
+87
View File
@@ -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();
}
+1 -1
View File
@@ -22,7 +22,7 @@
#ifndef OAK_RENDERTEXTURE_H
#define OAK_RENDERTEXTURE_H
#include "common/avframeptr.h"
#include "avframeptr.h"
#include <atomic>
#include <memory>
+1
View File
@@ -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)
+1
View File
@@ -28,6 +28,7 @@ target_link_libraries(oakrender-gtest PRIVATE
oaktimeline
oakrender
oaknode
oakplugin
oakcommon
oakundo
olivecore
-140
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -11,6 +11,7 @@ target_compile_definitions(oaktask-gtest PRIVATE
target_link_libraries(oaktask-gtest PRIVATE
oaktask
oaktimeline
oakplugin
GTest::gtest
GTest::gtest_main
)
+2
View File
@@ -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)
+1
View File
@@ -8,6 +8,7 @@ add_executable(oaktimeline-gtest
target_link_libraries(oaktimeline-gtest PRIVATE
oaktimeline
oakplugin
GTest::gtest
GTest::gtest_main
)