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:
@@ -0,0 +1,6 @@
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(c_api)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +1,4 @@
|
||||
target_sources(oakplugin PRIVATE
|
||||
host.cpp
|
||||
instance.cpp
|
||||
)
|
||||
@@ -0,0 +1,155 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "plugin/host.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../src/olivehost.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::plugin::HostMessageHandler forwarder_fn;
|
||||
oakplugin_message_fn user_fn;
|
||||
void *user_userdata;
|
||||
|
||||
int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
{
|
||||
int needed = int(value.size()) + 1;
|
||||
if (buf && buf_size >= needed) {
|
||||
memcpy(buf, value.c_str(), needed);
|
||||
}
|
||||
return needed;
|
||||
}
|
||||
|
||||
std::vector<std::string> plugin_ids()
|
||||
{
|
||||
std::vector<std::string> ids;
|
||||
OFX::Host::PluginCache *cache =
|
||||
OFX::Host::PluginCache::getPluginCache();
|
||||
if (!cache) {
|
||||
return ids;
|
||||
}
|
||||
for (const auto &plugin : cache->getPlugins()) {
|
||||
if (plugin) {
|
||||
ids.push_back(plugin->getIdentifier());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int oakplugin_host_init(void)
|
||||
{
|
||||
try {
|
||||
olive::plugin::load_plugins();
|
||||
return OAKPLUGIN_OK;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakplugin_host_shutdown(void)
|
||||
{
|
||||
// The OFX plugin cache is process-global; nothing to tear down here
|
||||
// (the Current slots release their references on their own).
|
||||
}
|
||||
|
||||
int oakplugin_host_scan(const char *const *bundle_dirs, int dir_count)
|
||||
{
|
||||
if (!bundle_dirs || dir_count < 0) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < dir_count; i++) {
|
||||
if (bundle_dirs[i]) {
|
||||
olive::plugin::load_plugins(bundle_dirs[i]);
|
||||
}
|
||||
}
|
||||
return OAKPLUGIN_OK;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_host_plugin_count(void)
|
||||
{
|
||||
try {
|
||||
return int(plugin_ids().size());
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_host_plugin_id_at(int index, char *buf, int buf_size)
|
||||
{
|
||||
try {
|
||||
std::vector<std::string> ids = plugin_ids();
|
||||
if (index < 0 || index >= int(ids.size())) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
return copy_string(ids[size_t(index)], buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_host_plugin_label(const char *plugin_id, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!plugin_id) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const std::string &id : plugin_ids()) {
|
||||
if (id == plugin_id) {
|
||||
return copy_string(id, buf, buf_size);
|
||||
}
|
||||
}
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakplugin_host_set_message_handler(oakplugin_message_fn fn,
|
||||
void *userdata)
|
||||
{
|
||||
user_fn = fn;
|
||||
user_userdata = userdata;
|
||||
|
||||
if (!fn) {
|
||||
olive::plugin::set_host_message_handler(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
olive::plugin::set_host_message_handler(
|
||||
[](const char *type, const std::string &message) -> OfxStatus {
|
||||
int answer = user_fn(type, message.c_str(), user_userdata);
|
||||
return answer == OAKPLUGIN_MESSAGE_ANSWER_YES ? kOfxStatReplyYes
|
||||
: kOfxStatReplyNo;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "plugin/instance.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
#include <ofxhImageEffectAPI.h>
|
||||
#include <ofxhPluginCache.h>
|
||||
|
||||
#include "../src/oliveclip.h"
|
||||
#include "../src/oliveplugininstance.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct InstanceBox {
|
||||
olive::plugin::OlivePluginInstance *instance;
|
||||
std::atomic<uint32_t> refs;
|
||||
oakplugin_progress_fn progress_fn;
|
||||
void *progress_userdata;
|
||||
|
||||
InstanceBox(olive::plugin::OlivePluginInstance *i)
|
||||
: instance(i)
|
||||
, refs(1)
|
||||
, progress_fn(nullptr)
|
||||
, progress_userdata(nullptr)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<int> g_alive{ 0 };
|
||||
|
||||
void box_addref(void *ctx)
|
||||
{
|
||||
if (ctx) {
|
||||
static_cast<InstanceBox *>(ctx)->refs.fetch_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
void box_release(void *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
InstanceBox *box = static_cast<InstanceBox *>(ctx);
|
||||
if (box->refs.fetch_sub(1) == 1) {
|
||||
delete box->instance;
|
||||
delete box;
|
||||
g_alive--;
|
||||
}
|
||||
}
|
||||
|
||||
OakPluginInstance make_handle(olive::plugin::OlivePluginInstance *instance)
|
||||
{
|
||||
OakPluginInstance handle = {};
|
||||
if (!instance) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
InstanceBox *box = new (std::nothrow) InstanceBox(instance);
|
||||
if (!box) {
|
||||
delete instance;
|
||||
return handle;
|
||||
}
|
||||
|
||||
handle.ctx = box;
|
||||
handle.addref = box_addref;
|
||||
handle.release = box_release;
|
||||
handle.abi_version = OAKPLUGIN_ABI_VERSION;
|
||||
g_alive++;
|
||||
return handle;
|
||||
}
|
||||
|
||||
InstanceBox *impl(OakPluginInstance h)
|
||||
{
|
||||
if (!h.ctx) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<InstanceBox *>(h.ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakPluginInstance oakplugin_instance_create(const char *plugin_id)
|
||||
{
|
||||
if (!plugin_id) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
|
||||
try {
|
||||
OFX::Host::PluginCache *cache =
|
||||
OFX::Host::PluginCache::getPluginCache();
|
||||
if (!cache) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
|
||||
for (const auto &plugin : cache->getPlugins()) {
|
||||
if (plugin && plugin->getIdentifier() == plugin_id) {
|
||||
auto *effect_plugin =
|
||||
dynamic_cast<OFX::Host::ImageEffect::ImageEffectPlugin *>(
|
||||
plugin);
|
||||
if (!effect_plugin) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
auto *instance = effect_plugin->createInstance(
|
||||
kOfxImageEffectContextFilter, nullptr);
|
||||
auto *olive_instance =
|
||||
dynamic_cast<olive::plugin::OlivePluginInstance *>(
|
||||
instance);
|
||||
if (!olive_instance) {
|
||||
delete instance;
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
return make_handle(olive_instance);
|
||||
}
|
||||
}
|
||||
return OakPluginInstance{};
|
||||
} catch (...) {
|
||||
return OakPluginInstance{};
|
||||
}
|
||||
}
|
||||
|
||||
void oakplugin_instance_free(OakPluginInstance *instance)
|
||||
{
|
||||
if (!instance || !instance->ctx) {
|
||||
return;
|
||||
}
|
||||
instance->release(instance->ctx);
|
||||
instance->ctx = NULL;
|
||||
}
|
||||
|
||||
int oakplugin_instance_set_param(OakPluginInstance instance,
|
||||
const char *param_id,
|
||||
const oaknode_value *value)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id || !value) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
|
||||
switch (value->type) {
|
||||
case OAKNODE_VALUE_INT:
|
||||
case OAKNODE_VALUE_COMBO:
|
||||
return static_cast<OFX::Host::Param::IntegerInstance *>(param)
|
||||
->set(int(value->num)) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_BOOL:
|
||||
return static_cast<OFX::Host::Param::BooleanInstance *>(param)
|
||||
->set(value->num != 0) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_FLOAT:
|
||||
return static_cast<OFX::Host::Param::DoubleInstance *>(param)
|
||||
->set(value->f[0]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_VEC2:
|
||||
return static_cast<OFX::Host::Param::Double2DInstance *>(param)
|
||||
->set(value->f[0], value->f[1]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_VEC3:
|
||||
return static_cast<OFX::Host::Param::Double3DInstance *>(param)
|
||||
->set(value->f[0], value->f[1],
|
||||
value->f[2]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
case OAKNODE_VALUE_COLOR:
|
||||
case OAKNODE_VALUE_VEC4:
|
||||
return static_cast<OFX::Host::Param::RGBAInstance *>(param)
|
||||
->set(value->f[0], value->f[1], value->f[2],
|
||||
value->f[3]) == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
default:
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_get_param(OakPluginInstance instance,
|
||||
const char *param_id, oaknode_value *out)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id || !out) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
|
||||
out->type = OAKNODE_VALUE_NONE;
|
||||
const std::string &type = param->getType();
|
||||
if (type == kOfxParamTypeInteger || type == kOfxParamTypeChoice) {
|
||||
int v = 0;
|
||||
static_cast<OFX::Host::Param::IntegerInstance *>(param)->get(v);
|
||||
out->type = OAKNODE_VALUE_INT;
|
||||
out->num = v;
|
||||
} else if (type == kOfxParamTypeBoolean) {
|
||||
bool v = false;
|
||||
static_cast<OFX::Host::Param::BooleanInstance *>(param)->get(v);
|
||||
out->type = OAKNODE_VALUE_BOOL;
|
||||
out->num = v ? 1 : 0;
|
||||
} else if (type == kOfxParamTypeDouble) {
|
||||
double v = 0;
|
||||
static_cast<OFX::Host::Param::DoubleInstance *>(param)->get(v);
|
||||
out->type = OAKNODE_VALUE_FLOAT;
|
||||
out->f[0] = v;
|
||||
} else if (type == kOfxParamTypeDouble2D) {
|
||||
double x = 0, y = 0;
|
||||
static_cast<OFX::Host::Param::Double2DInstance *>(param)->get(x,
|
||||
y);
|
||||
out->type = OAKNODE_VALUE_VEC2;
|
||||
out->f[0] = x;
|
||||
out->f[1] = y;
|
||||
} else if (type == kOfxParamTypeDouble3D) {
|
||||
double x = 0, y = 0, z = 0;
|
||||
static_cast<OFX::Host::Param::Double3DInstance *>(param)->get(x,
|
||||
y,
|
||||
z);
|
||||
out->type = OAKNODE_VALUE_VEC3;
|
||||
out->f[0] = x;
|
||||
out->f[1] = y;
|
||||
out->f[2] = z;
|
||||
} else if (type == kOfxParamTypeRGBA) {
|
||||
double r = 0, g = 0, b = 0, a = 0;
|
||||
static_cast<OFX::Host::Param::RGBAInstance *>(param)->get(r, g,
|
||||
b, a);
|
||||
out->type = OAKNODE_VALUE_COLOR;
|
||||
out->f[0] = r;
|
||||
out->f[1] = g;
|
||||
out->f[2] = b;
|
||||
out->f[3] = a;
|
||||
} else {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
return OAKPLUGIN_OK;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_set_param_string(OakPluginInstance instance,
|
||||
const char *param_id,
|
||||
const char *value)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
return static_cast<OFX::Host::Param::StringInstance *>(param)
|
||||
->set(value ? value : "") == kOfxStatOK
|
||||
? OAKPLUGIN_OK
|
||||
: OAKPLUGIN_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_get_param_string(OakPluginInstance instance,
|
||||
const char *param_id, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box || !param_id) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms_map =
|
||||
box->instance->getParams();
|
||||
auto it = params_map.find(param_id);
|
||||
if (it == params_map.end() || !it->second) {
|
||||
return OAKPLUGIN_E_NOT_FOUND;
|
||||
}
|
||||
OFX::Host::Param::Instance *param = it->second;
|
||||
std::string value;
|
||||
static_cast<OFX::Host::Param::StringInstance *>(param)->get(value);
|
||||
int needed = int(value.size()) + 1;
|
||||
if (buf && buf_size >= needed) {
|
||||
memcpy(buf, value.c_str(), needed);
|
||||
}
|
||||
return needed;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_render(OakPluginInstance instance,
|
||||
OakRenderTexture dst, OakRenderTexture src,
|
||||
double time_seconds)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const char *clip_names[] = { "Output", "Source" };
|
||||
for (const char *clip_name : clip_names) {
|
||||
auto *clip =
|
||||
dynamic_cast<olive::plugin::OliveClipInstance *>(
|
||||
box->instance->getClip(clip_name));
|
||||
if (!clip) {
|
||||
continue;
|
||||
}
|
||||
if (clip->isOutput()) {
|
||||
clip->setOutputTexture(dst, time_seconds);
|
||||
} else if (src.ctx) {
|
||||
clip->setInputTexture(src, time_seconds, false);
|
||||
}
|
||||
}
|
||||
|
||||
OfxRectI roi = { 0, 0, 0, 0 };
|
||||
OfxPointD scale = { 1.0, 1.0 };
|
||||
OfxStatus status = box->instance->renderAction(
|
||||
time_seconds, kOfxImageFieldNone, roi, scale, false, false,
|
||||
false);
|
||||
return status == kOfxStatOK ? OAKPLUGIN_OK : OAKPLUGIN_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKPLUGIN_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakplugin_instance_set_progress_cb(OakPluginInstance instance,
|
||||
oakplugin_progress_fn fn,
|
||||
void *userdata)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
box->progress_fn = fn;
|
||||
box->progress_userdata = userdata;
|
||||
return OAKPLUGIN_OK;
|
||||
}
|
||||
|
||||
int oakplugin_instance_cancel(OakPluginInstance instance)
|
||||
{
|
||||
InstanceBox *box = impl(instance);
|
||||
if (!box) {
|
||||
return OAKPLUGIN_E_INVALID;
|
||||
}
|
||||
if (box->progress_fn) {
|
||||
box->progress_fn(1.0, box->progress_userdata);
|
||||
}
|
||||
return OAKPLUGIN_OK;
|
||||
}
|
||||
|
||||
int oakplugin_debug_alive_count(void)
|
||||
{
|
||||
return g_alive.load();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2025 mikesolar
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
file(GLOB OAKPLUGIN_SOURCES CONFIGURE_DEPENDS *.cpp)
|
||||
|
||||
add_library(oakplugin SHARED ${OAKPLUGIN_SOURCES})
|
||||
|
||||
target_include_directories(oakplugin PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${OAK_REPO_ROOT}/include
|
||||
${OAK_REPO_ROOT}/src/common/src
|
||||
${OAK_REPO_ROOT}/src/undo/src
|
||||
${OAK_REPO_ROOT}/core/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
${OAK_REPO_ROOT}/ffmpeg_bridge/include
|
||||
)
|
||||
|
||||
find_library(OAKPLUGIN_OFX_HOST_ARCHIVE NAMES OfxHost
|
||||
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport
|
||||
NO_DEFAULT_PATH)
|
||||
if(NOT OAKPLUGIN_OFX_HOST_ARCHIVE)
|
||||
message(FATAL_ERROR
|
||||
"libOfxHost.a not found; run the full-tree build once or set "
|
||||
"OAKPLUGIN_OFX_HOST_ARCHIVE")
|
||||
endif()
|
||||
target_link_options(oakplugin PUBLIC
|
||||
"-Wl,-force_load,${OAKPLUGIN_OFX_HOST_ARCHIVE}")
|
||||
|
||||
target_link_libraries(oakplugin PUBLIC
|
||||
oaknode
|
||||
oakrender
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
ffmpeg_bridge
|
||||
)
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2026 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "image.h"
|
||||
|
||||
#include "ofxImageEffect.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
static const char *pixel_depth_to_ofx(core::PixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case core::PixelFormat::u8:
|
||||
return kOfxBitDepthByte;
|
||||
case core::PixelFormat::u16:
|
||||
return kOfxBitDepthShort;
|
||||
case core::PixelFormat::f16:
|
||||
return kOfxBitDepthHalf;
|
||||
default:
|
||||
break;
|
||||
case core::PixelFormat::f32:
|
||||
return kOfxBitDepthFloat;
|
||||
case core::PixelFormat::u10:
|
||||
case core::PixelFormat::invalid:
|
||||
case core::PixelFormat::count:
|
||||
break;
|
||||
}
|
||||
|
||||
return kOfxBitDepthNone;
|
||||
}
|
||||
|
||||
static const char *components_to_ofx(int channel_count)
|
||||
{
|
||||
switch (channel_count) {
|
||||
case 1:
|
||||
return kOfxImageComponentAlpha;
|
||||
case 3:
|
||||
return kOfxImageComponentRGB;
|
||||
case 4:
|
||||
return kOfxImageComponentRGBA;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return kOfxImageComponentNone;
|
||||
}
|
||||
|
||||
Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance)
|
||||
: OFX::Host::ImageEffect::Image(clip_instance)
|
||||
, width_(0)
|
||||
, height_(0)
|
||||
, format_(core::PixelFormat::invalid)
|
||||
, premultiplied_alpha_(false)
|
||||
, channel_count_(0)
|
||||
, row_bytes_(0)
|
||||
, bounds_{ 0, 0, 0, 0 }
|
||||
, rod_{ 0, 0, 0, 0 }
|
||||
{
|
||||
}
|
||||
|
||||
Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
|
||||
const VideoParams ¶ms, const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear)
|
||||
: OFX::Host::ImageEffect::Image(clip_instance)
|
||||
, width_(0)
|
||||
, height_(0)
|
||||
, format_(core::PixelFormat::invalid)
|
||||
, premultiplied_alpha_(false)
|
||||
, channel_count_(0)
|
||||
, row_bytes_(0)
|
||||
, bounds_{ 0, 0, 0, 0 }
|
||||
, rod_{ 0, 0, 0, 0 }
|
||||
{
|
||||
allocate_from_params(params, bounds, rod, clear);
|
||||
}
|
||||
|
||||
Image::~Image()
|
||||
{
|
||||
}
|
||||
|
||||
void Image::allocate_from_params(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds, const OfxRectI &rod,
|
||||
bool clear)
|
||||
{
|
||||
allocate(bounds.x2 - bounds.x1, bounds.y2 - bounds.y1, params.format(),
|
||||
params.channel_count(), params.premultiplied_alpha(), bounds, rod,
|
||||
clear);
|
||||
}
|
||||
|
||||
void Image::ensure_allocated_from_params(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear)
|
||||
{
|
||||
bool same = (width_ == bounds.x2 - bounds.x1) &&
|
||||
(height_ == bounds.y2 - bounds.y1) &&
|
||||
(format_ == params.format()) &&
|
||||
(channel_count_ == params.channel_count()) &&
|
||||
(premultiplied_alpha_ == params.premultiplied_alpha()) &&
|
||||
(bounds_.x1 == bounds.x1) && (bounds_.y1 == bounds.y1) &&
|
||||
(bounds_.x2 == bounds.x2) && (bounds_.y2 == bounds.y2) &&
|
||||
(rod_.x1 == rod.x1) && (rod_.y1 == rod.y1) &&
|
||||
(rod_.x2 == rod.x2) && (rod_.y2 == rod.y2);
|
||||
|
||||
if (!same) {
|
||||
allocate_from_params(params, bounds, rod, clear);
|
||||
} else if (clear && !image_.empty()) {
|
||||
std::fill(image_.begin(), image_.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Image::allocate(int width, int height, core::PixelFormat format,
|
||||
int channel_count, bool premultiplied_alpha,
|
||||
const OfxRectI &bounds, const OfxRectI &rod, bool clear)
|
||||
{
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
channel_count_ = channel_count;
|
||||
premultiplied_alpha_ = premultiplied_alpha;
|
||||
bounds_ = bounds;
|
||||
rod_ = rod;
|
||||
|
||||
int bytes_per_component = format_.byte_count();
|
||||
row_bytes_ = width_ * channel_count_ * bytes_per_component;
|
||||
int buffer_size = row_bytes_ * height_;
|
||||
if (buffer_size < 0) {
|
||||
buffer_size = 0;
|
||||
}
|
||||
|
||||
image_.resize(static_cast<size_t>(buffer_size));
|
||||
if (clear && !image_.empty()) {
|
||||
std::fill(image_.begin(), image_.end(), 0);
|
||||
}
|
||||
|
||||
setPointerProperty(kOfxImagePropData, image_.data());
|
||||
setIntProperty(kOfxImagePropRowBytes, row_bytes_);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.x1, 0);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.y1, 1);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.x2, 2);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.y2, 3);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.x1, 0);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.y1, 1);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.x2, 2);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.y2, 3);
|
||||
setStringProperty(kOfxImageEffectPropComponents,
|
||||
components_to_ofx(channel_count_));
|
||||
setStringProperty(kOfxImageEffectPropPixelDepth, pixel_depth_to_ofx(format_));
|
||||
setStringProperty(kOfxImageEffectPropPreMultiplication,
|
||||
premultiplied_alpha_ ? kOfxImagePreMultiplied :
|
||||
kOfxImageUnPreMultiplied);
|
||||
}
|
||||
|
||||
core::PixelFormat Image::pixel_format()
|
||||
{
|
||||
if (format_ != core::PixelFormat::invalid) {
|
||||
return format_;
|
||||
}
|
||||
|
||||
std::string type = getStringProperty(kOfxImageEffectPropPixelDepth);
|
||||
if (type == kOfxBitDepthByte) {
|
||||
format_ = core::PixelFormat::u8;
|
||||
} else if (type == kOfxBitDepthShort) {
|
||||
format_ = core::PixelFormat::u16;
|
||||
} else if (type == kOfxBitDepthHalf) {
|
||||
format_ = core::PixelFormat::f16;
|
||||
} else if (type == kOfxBitDepthFloat) {
|
||||
format_ = core::PixelFormat::f32;
|
||||
} else {
|
||||
format_ = core::PixelFormat::invalid;
|
||||
}
|
||||
return format_;
|
||||
}
|
||||
|
||||
bool Image::premultiplied_alpha()
|
||||
{
|
||||
std::string premultiplied =
|
||||
getStringProperty(kOfxImageEffectPropPreMultiplication);
|
||||
premultiplied_alpha_ = (premultiplied == kOfxImagePreMultiplied);
|
||||
return premultiplied_alpha_;
|
||||
}
|
||||
|
||||
int Image::width()
|
||||
{
|
||||
int bounds[4] = { 0 };
|
||||
getIntPropertyN(kOfxImagePropBounds, bounds, 4);
|
||||
width_ = bounds[2] - bounds[0];
|
||||
return width_;
|
||||
}
|
||||
|
||||
int Image::height()
|
||||
{
|
||||
int bounds[4] = { 0 };
|
||||
getIntPropertyN(kOfxImagePropBounds, bounds, 4);
|
||||
height_ = bounds[3] - bounds[1];
|
||||
return height_;
|
||||
}
|
||||
|
||||
int Image::channel_count()
|
||||
{
|
||||
std::string type = getStringProperty(kOfxImageEffectPropComponents);
|
||||
if (type == kOfxImageComponentAlpha) {
|
||||
channel_count_ = 1;
|
||||
} else if (type == kOfxImageComponentRGBA) {
|
||||
channel_count_ = 4;
|
||||
} else if (type == kOfxImageComponentRGB) {
|
||||
channel_count_ = 3;
|
||||
} else {
|
||||
channel_count_ = 0;
|
||||
}
|
||||
return channel_count_;
|
||||
}
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2026 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef OAK_OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
#define OAK_OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxImageEffect.h"
|
||||
#include "ofxhClip.h"
|
||||
#include "olive/core/render/pixelformat.h"
|
||||
#include "loopmode.h"
|
||||
#include "videoparams.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
class Image : public OFX::Host::ImageEffect::Image {
|
||||
public:
|
||||
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance);
|
||||
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
|
||||
const VideoParams ¶ms, const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear = true);
|
||||
~Image();
|
||||
uint8_t *data()
|
||||
{
|
||||
return (uint8_t *)getPointerProperty(kOfxImagePropData);
|
||||
}
|
||||
int width();
|
||||
int height();
|
||||
core::PixelFormat pixel_format();
|
||||
bool premultiplied_alpha();
|
||||
int channel_count();
|
||||
|
||||
void allocate_from_params(const VideoParams ¶ms, const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear = true);
|
||||
void ensure_allocated_from_params(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds, const OfxRectI &rod,
|
||||
bool clear = false);
|
||||
void allocate(int width, int height, core::PixelFormat format,
|
||||
int channel_count, bool premultiplied_alpha,
|
||||
const OfxRectI &bounds, const OfxRectI &rod,
|
||||
bool clear = true);
|
||||
int row_bytes() const
|
||||
{
|
||||
return row_bytes_;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> image_;
|
||||
int width_;
|
||||
int height_;
|
||||
core::PixelFormat format_;
|
||||
bool premultiplied_alpha_;
|
||||
int channel_count_;
|
||||
int row_bytes_;
|
||||
OfxRectI bounds_;
|
||||
OfxRectI rod_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //OAK_OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef OAK_OLIVECLIP_H
|
||||
#define OAK_OLIVECLIP_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxhClip.h"
|
||||
|
||||
#include "common/videoparams.h"
|
||||
#include "image.h"
|
||||
#include "render/renderer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
class OliveClipInstance : public OFX::Host::ImageEffect::ClipInstance {
|
||||
public:
|
||||
OliveClipInstance(OFX::Host::ImageEffect::Instance *effect_instance,
|
||||
OFX::Host::ImageEffect::ClipDescriptor &desc,
|
||||
OakVideoParams params)
|
||||
: ClipInstance(effect_instance, desc)
|
||||
, params_(params)
|
||||
, name_(desc.getName())
|
||||
{
|
||||
if (params_.ctx) {
|
||||
params_.addref(params_.ctx);
|
||||
}
|
||||
default_region_of_definition_ = { 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
~OliveClipInstance() override
|
||||
{
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_free(¶ms_);
|
||||
}
|
||||
for (auto &pair : images_) {
|
||||
delete pair.second;
|
||||
}
|
||||
for (auto &pair : input_textures_) {
|
||||
oakrender_display_texture_free(&pair.second);
|
||||
}
|
||||
for (auto &pair : output_textures_) {
|
||||
oakrender_display_texture_free(&pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
OFX::Host::ImageEffect::Image *getOutputImage(OfxTime time);
|
||||
|
||||
const std::string &getUnmappedBitDepth() const override;
|
||||
const std::string &getUnmappedComponents() const override;
|
||||
const std::string &getPremult() const override;
|
||||
double getAspectRatio() const override;
|
||||
double getFrameRate() const override;
|
||||
void getFrameRange(double &start_frame, double &end_frame) const override;
|
||||
const std::string &getFieldOrder() const override;
|
||||
bool getConnected() const override;
|
||||
double getUnmappedFrameRate() const override;
|
||||
void getUnmappedFrameRange(double &start_frame,
|
||||
double &end_frame) const override;
|
||||
bool getContinuousSamples() const override;
|
||||
OFX::Host::ImageEffect::Image *
|
||||
getImage(OfxTime time, const OfxRectD *optional_bounds) override;
|
||||
OfxRectD getRegionOfDefinition(OfxTime time) const override;
|
||||
|
||||
void setRegionOfDefinition(OfxRectD region_of_definition, OfxTime time);
|
||||
void setDefaultRegionOfDefinition(OfxRectD region_of_definition);
|
||||
void setParams(const olive::VideoParams ¶ms);
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
OFX::Host::ImageEffect::Texture *
|
||||
loadTexture(OfxTime time, const char *format,
|
||||
const OfxRectD *optional_bounds) override;
|
||||
#endif
|
||||
|
||||
void setInputTexture(OakRenderTexture texture, OfxTime time,
|
||||
bool readback_cpu = true);
|
||||
void setOutputTexture(OakRenderTexture texture, OfxTime time);
|
||||
|
||||
// Get the plugin-preferred VideoParams based on base class _pixelDepth/_components
|
||||
OakVideoParams getPluginPreferredParams() const;
|
||||
|
||||
// Prune old entries from the images_ cache to prevent unbounded growth.
|
||||
// Output clip images are not pruned (they are typically single-frame).
|
||||
void prune_images_cache();
|
||||
|
||||
static constexpr int k_max_input_image_cache = 8;
|
||||
|
||||
private:
|
||||
OakVideoParams params_;
|
||||
|
||||
std::map<OfxTime, OfxRectD> region_of_definitions_;
|
||||
|
||||
OfxRectD default_region_of_definition_;
|
||||
|
||||
std::string name_;
|
||||
std::map<OfxTime, Image *> images_;
|
||||
std::map<OfxTime, OakRenderTexture> input_textures_;
|
||||
std::map<OfxTime, OakRenderTexture> output_textures_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //OAK_OLIVECLIP_H
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "olivehost.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include <ofxhBinary.h>
|
||||
#include <ofxhPluginCache.h>
|
||||
#include <ofxMessage.h>
|
||||
|
||||
#include "common/current.h"
|
||||
#include "oliveplugininstance.h"
|
||||
|
||||
using namespace OFX::Host;
|
||||
using namespace olive::plugin;
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
class PluginNode;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef OAK_APP_VERSION
|
||||
#define OAK_APP_VERSION "0.0.0"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::plugin::HostMessageHandler message_handler_;
|
||||
|
||||
void add_plugin_path(OFX::Host::PluginCache *cache, const std::string &path,
|
||||
bool recurse = true)
|
||||
{
|
||||
if (!cache || path.empty()) {
|
||||
return;
|
||||
}
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(path, ec)) {
|
||||
return;
|
||||
}
|
||||
cache->addFileToPath(
|
||||
std::filesystem::weakly_canonical(path, ec).string(), recurse);
|
||||
}
|
||||
|
||||
void add_plugin_paths_from_env(OFX::Host::PluginCache *cache,
|
||||
const char *env_var)
|
||||
{
|
||||
const char *raw = std::getenv(env_var);
|
||||
if (!raw || !*raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char separator =
|
||||
#if defined(_WIN32)
|
||||
';';
|
||||
#else
|
||||
':';
|
||||
#endif
|
||||
|
||||
std::string remaining(raw);
|
||||
size_t pos;
|
||||
while ((pos = remaining.find(separator)) != std::string::npos) {
|
||||
std::string path = remaining.substr(0, pos);
|
||||
remaining.erase(0, pos + 1);
|
||||
if (!path.empty()) {
|
||||
add_plugin_path(cache, path);
|
||||
}
|
||||
}
|
||||
if (!remaining.empty()) {
|
||||
add_plugin_path(cache, remaining);
|
||||
}
|
||||
}
|
||||
|
||||
std::string format_message(const char *format, va_list args)
|
||||
{
|
||||
char buffer[1024];
|
||||
buffer[0] = '\0';
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void olive::plugin::set_host_message_handler(HostMessageHandler handler)
|
||||
{
|
||||
message_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
olive::plugin::HostMessageHandler olive::plugin::get_host_message_handler()
|
||||
{
|
||||
return message_handler_;
|
||||
}
|
||||
|
||||
void olive::plugin::load_plugins(const std::string &path)
|
||||
{
|
||||
OakCurrent current = oakcommon_current_instance();
|
||||
|
||||
void *host_ptr = nullptr;
|
||||
void *cache_ptr = nullptr;
|
||||
oakcommon_current_get_plugin_host(current, &host_ptr);
|
||||
oakcommon_current_get_plugin_cache(current, &cache_ptr);
|
||||
|
||||
std::shared_ptr<OliveHost> host;
|
||||
std::shared_ptr<ImageEffect::PluginCache> image_effect_plugin_cache;
|
||||
|
||||
if (host_ptr) {
|
||||
host = *static_cast<std::shared_ptr<OliveHost> *>(host_ptr);
|
||||
}
|
||||
if (cache_ptr) {
|
||||
image_effect_plugin_cache =
|
||||
*static_cast<std::shared_ptr<ImageEffect::PluginCache> *>(
|
||||
cache_ptr);
|
||||
}
|
||||
|
||||
if (!host || !image_effect_plugin_cache) {
|
||||
host = std::make_shared<OliveHost>();
|
||||
image_effect_plugin_cache =
|
||||
std::make_shared<ImageEffect::PluginCache>(*host);
|
||||
|
||||
// The Current slots hold owning shared_ptr copies; the destroy
|
||||
// callbacks free those copies when the slots are replaced or the
|
||||
// current object dies.
|
||||
auto *host_slot = new std::shared_ptr<OliveHost>(host);
|
||||
oakcommon_current_set_plugin_host(
|
||||
current, host_slot,
|
||||
[](void *p) { delete static_cast<std::shared_ptr<OliveHost> *>(p); });
|
||||
|
||||
auto *cache_slot =
|
||||
new std::shared_ptr<ImageEffect::PluginCache>(
|
||||
image_effect_plugin_cache);
|
||||
oakcommon_current_set_plugin_cache(
|
||||
current, cache_slot,
|
||||
[](void *p) {
|
||||
delete static_cast<std::shared_ptr<ImageEffect::PluginCache> *>(
|
||||
p);
|
||||
});
|
||||
|
||||
image_effect_plugin_cache->registerInCache(
|
||||
*OFX::Host::PluginCache::getPluginCache());
|
||||
}
|
||||
oakcommon_current_free(¤t);
|
||||
|
||||
OFX::Host::PluginCache *cache = OFX::Host::PluginCache::getPluginCache();
|
||||
cache->setPluginHostPath("Olive");
|
||||
|
||||
const std::string home_path = std::getenv("HOME") ? std::getenv("HOME") : "";
|
||||
if (!home_path.empty()) {
|
||||
add_plugin_path(cache, home_path + "/.OFX/Plugins");
|
||||
add_plugin_path(cache, home_path + "/.local/share/OFX/Plugins");
|
||||
add_plugin_path(cache,
|
||||
home_path + "/.local/share/olive/ofx/Plugins");
|
||||
}
|
||||
|
||||
// Application-relative plugin paths are resolved by the facade; scan
|
||||
// the default locations relative to the working directory here.
|
||||
add_plugin_path(cache, "../OFX/Plugins");
|
||||
add_plugin_path(cache, "../share/olive/ofx/Plugins");
|
||||
add_plugin_path(cache, "../lib/olive/ofx/Plugins");
|
||||
|
||||
add_plugin_paths_from_env(cache, "OLIVE_OFX_PLUGIN_PATH");
|
||||
add_plugin_paths_from_env(cache, "OLIVE_PLUGIN_PATH");
|
||||
|
||||
if (!path.empty()) {
|
||||
add_plugin_path(cache, path, true);
|
||||
}
|
||||
cache->scanPluginFiles();
|
||||
}
|
||||
|
||||
OliveHost::OliveHost()
|
||||
{
|
||||
// Identify the host to plugins; HostSupport seeds these with "UNKNOWN".
|
||||
_properties.setStringProperty(kOfxPropName, "Oak Video Editor");
|
||||
_properties.setStringProperty(kOfxPropLabel, "Oak Video Editor");
|
||||
_properties.setStringProperty(kOfxPropVersionLabel, OAK_APP_VERSION);
|
||||
|
||||
// Numeric version for plugins that query kOfxPropVersion directly.
|
||||
int version_parts[3] = { 0, 0, 0 };
|
||||
sscanf(OAK_APP_VERSION, "%d.%d.%d", &version_parts[0], &version_parts[1],
|
||||
&version_parts[2]);
|
||||
_properties.setIntProperty(kOfxPropVersion, version_parts[0], 0);
|
||||
_properties.setIntProperty(kOfxPropVersion, version_parts[1], 1);
|
||||
_properties.setIntProperty(kOfxPropVersion, version_parts[2], 2);
|
||||
}
|
||||
|
||||
OliveHost::~OliveHost()
|
||||
{
|
||||
}
|
||||
|
||||
void OliveHost::destroy_instance(OFX::Host::ImageEffect::Instance *instance)
|
||||
{
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
for (auto it = instances_.begin(); it != instances_.end(); ++it) {
|
||||
if (it->get() == instance) {
|
||||
instances_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
OliveHost::makeDescriptor(ImageEffect::ImageEffectPlugin *plugin)
|
||||
{
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor> desc =
|
||||
std::make_shared<ImageEffect::Descriptor>(plugin);
|
||||
descriptors_.push_back(std::shared_ptr<ImageEffect::Descriptor>(desc));
|
||||
return desc;
|
||||
}
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
OliveHost::makeDescriptor(const ImageEffect::Descriptor &root_context,
|
||||
ImageEffect::ImageEffectPlugin *plugin)
|
||||
{
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor> desc =
|
||||
std::make_shared<ImageEffect::Descriptor>(root_context, plugin);
|
||||
descriptors_.push_back(std::shared_ptr<ImageEffect::Descriptor>(desc));
|
||||
return desc;
|
||||
}
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
OliveHost::makeDescriptor(const std::string &bundle_path,
|
||||
ImageEffect::ImageEffectPlugin *plugin)
|
||||
{
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor> desc =
|
||||
std::make_shared<ImageEffect::Descriptor>(bundle_path, plugin);
|
||||
descriptors_.push_back(std::shared_ptr<ImageEffect::Descriptor>(desc));
|
||||
return desc;
|
||||
}
|
||||
|
||||
ImageEffect::Instance *
|
||||
OliveHost::newInstance(void *client_data, ImageEffect::ImageEffectPlugin *plugin,
|
||||
ImageEffect::Descriptor &desc,
|
||||
const std::string &context)
|
||||
{
|
||||
auto *instance = new OlivePluginInstance(plugin, desc, context, true);
|
||||
if (client_data) {
|
||||
instance->set_node_handle(
|
||||
*static_cast<OakNodeNode *>(client_data));
|
||||
}
|
||||
instances_.push_back(std::shared_ptr<OlivePluginInstance>(instance));
|
||||
return instance;
|
||||
}
|
||||
|
||||
OfxStatus OliveHost::vmessage(const char *type, const char *id,
|
||||
const char *format, va_list args)
|
||||
{
|
||||
if (!type || !format) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
std::string message = format_message(format, args);
|
||||
|
||||
if (message_handler_) {
|
||||
return message_handler_(type, message);
|
||||
}
|
||||
|
||||
// Headless default: log to stderr, questions get "no".
|
||||
fprintf(stderr, "OFX message: %s %s\n", type, message.c_str());
|
||||
if (strcmp(type, kOfxMessageQuestion) == 0) {
|
||||
return kOfxStatReplyNo;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OliveHost::setPersistentMessage(const char *type, const char *id,
|
||||
const char *format, va_list args)
|
||||
{
|
||||
if (!type || !format) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
std::string message = format_message(format, args);
|
||||
|
||||
if (strcmp(type, kOfxMessageError) == 0) {
|
||||
persistent_messages_.push_back({ HostMessageType::error, message });
|
||||
} else if (strcmp(type, kOfxMessageWarning) == 0) {
|
||||
persistent_messages_.push_back({ HostMessageType::warning, message });
|
||||
} else if (strcmp(type, kOfxMessageMessage) == 0) {
|
||||
persistent_messages_.push_back({ HostMessageType::message, message });
|
||||
} else {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
if (message_handler_) {
|
||||
return message_handler_(type, message);
|
||||
}
|
||||
|
||||
fprintf(stderr, "OFX %s: %s\n", type, message.c_str());
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OliveHost::clearPersistentMessage()
|
||||
{
|
||||
persistent_messages_.clear();
|
||||
return kOfxStatOK;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef OAK_OLIVE_HOST_H
|
||||
#define OAK_OLIVE_HOST_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxhHost.h"
|
||||
#include "ofxhImageEffect.h"
|
||||
#include "ofxhImageEffectAPI.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
enum class HostMessageType { error, warning, message };
|
||||
struct HostPersistentMessage {
|
||||
HostMessageType type;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UI message handler for OFX host messages
|
||||
*
|
||||
* Registered by the facade. Return kOfxStatReplyYes/No for questions,
|
||||
* kOfxStatOK otherwise. Without a handler, messages are logged to
|
||||
* stderr and questions get kOfxStatReplyNo (headless default).
|
||||
*/
|
||||
using HostMessageHandler =
|
||||
std::function<OfxStatus(const char *type, const std::string &message)>;
|
||||
|
||||
void set_host_message_handler(HostMessageHandler handler);
|
||||
|
||||
/** @brief Currently registered handler (may be empty). */
|
||||
HostMessageHandler get_host_message_handler();
|
||||
|
||||
void load_plugins(const std::string &path = std::string());
|
||||
|
||||
class OliveHost : public OFX::Host::ImageEffect::Host {
|
||||
public:
|
||||
OliveHost();
|
||||
~OliveHost() override;
|
||||
void destroy_instance(OFX::Host::ImageEffect::Instance *instance);
|
||||
|
||||
bool pluginSupported(OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
|
||||
std::string &reason) const override
|
||||
{
|
||||
if (!plugin) {
|
||||
reason = "null plugin";
|
||||
return false;
|
||||
}
|
||||
if (plugin->getContexts().empty()) {
|
||||
reason = "no supported contexts (describe failed)";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
OFX::Host::ImageEffect::Instance *
|
||||
newInstance(void *client_data,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
|
||||
OFX::Host::ImageEffect::Descriptor &desc,
|
||||
const std::string &context) override;
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
makeDescriptor(OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
makeDescriptor(const OFX::Host::ImageEffect::Descriptor &root_context,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Descriptor>
|
||||
makeDescriptor(const std::string &bundle_path,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||
/// vmessage
|
||||
OfxStatus vmessage(const char *type, const char *id,
|
||||
const char *format, va_list args) override;
|
||||
|
||||
/// vmessage
|
||||
OfxStatus setPersistentMessage(const char *type, const char *id,
|
||||
const char *format, va_list args) override;
|
||||
/// vmessage
|
||||
OfxStatus clearPersistentMessage() override;
|
||||
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
/// @see OfxImageEffectOpenGLRenderSuiteV1.flushResources()
|
||||
virtual OfxStatus flushOpenGLResources() const override
|
||||
{
|
||||
return kOfxStatFailed;
|
||||
};
|
||||
#endif
|
||||
|
||||
int persistent_message_count() const
|
||||
{
|
||||
return int(persistent_messages_.size());
|
||||
}
|
||||
|
||||
const std::vector<HostPersistentMessage> &persistent_messages() const
|
||||
{
|
||||
return persistent_messages_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<OFX::Host::ImageEffect::Descriptor>>
|
||||
descriptors_;
|
||||
std::vector<std::shared_ptr<OFX::Host::ImageEffect::Instance>> instances_;
|
||||
std::vector<HostPersistentMessage> persistent_messages_;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "oliveplugininstance.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "ofxGPURender.h"
|
||||
#include "node/sequence.h"
|
||||
#include "olivehost.h"
|
||||
#include "ofxMessage.h"
|
||||
#include "oliveclip.h"
|
||||
#include "paraminstance.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
const std::string k_image_field_none_str(kOfxImageFieldNone);
|
||||
const std::string k_image_field_upper_str(kOfxImageFieldUpper);
|
||||
const std::string k_image_field_lower_str(kOfxImageFieldLower);
|
||||
|
||||
std::string format_ofx_message(const char *format, va_list args)
|
||||
{
|
||||
char buffer[1024];
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
const int needed = vsnprintf(buffer, sizeof(buffer), format, args_copy);
|
||||
va_end(args_copy);
|
||||
if (needed < 0) {
|
||||
return std::string();
|
||||
}
|
||||
if (needed < static_cast<int>(sizeof(buffer))) {
|
||||
return std::string(buffer);
|
||||
}
|
||||
std::vector<char> dynamic_buffer(size_t(needed) + 1);
|
||||
const int written =
|
||||
vsnprintf(dynamic_buffer.data(), dynamic_buffer.size(), format, args);
|
||||
if (written < 0) {
|
||||
return std::string();
|
||||
}
|
||||
return std::string(dynamic_buffer.data());
|
||||
}
|
||||
|
||||
const std::string &field_order_for_params(OakVideoParams params)
|
||||
{
|
||||
int interlacing = 0;
|
||||
if (params.ctx) {
|
||||
oakcommon_videoparams_get_interlacing(params, &interlacing);
|
||||
}
|
||||
switch (interlacing) {
|
||||
case OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST:
|
||||
return k_image_field_upper_str;
|
||||
case OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST:
|
||||
return k_image_field_lower_str;
|
||||
default:
|
||||
return k_image_field_none_str;
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::id main_thread_id_;
|
||||
bool main_thread_id_set_ = false;
|
||||
|
||||
ActiveViewerProvider active_viewer_provider_;
|
||||
|
||||
UndoSubmitFn undo_submit_fn_;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool is_gui_thread()
|
||||
{
|
||||
if (!main_thread_id_set_) {
|
||||
main_thread_id_ = std::this_thread::get_id();
|
||||
main_thread_id_set_ = true;
|
||||
}
|
||||
return std::this_thread::get_id() == main_thread_id_;
|
||||
}
|
||||
|
||||
void set_main_thread_id(std::thread::id id)
|
||||
{
|
||||
main_thread_id_ = id;
|
||||
main_thread_id_set_ = true;
|
||||
}
|
||||
|
||||
void set_active_viewer_provider(ActiveViewerProvider provider)
|
||||
{
|
||||
active_viewer_provider_ = std::move(provider);
|
||||
}
|
||||
|
||||
void set_undo_submit_callback(UndoSubmitFn fn)
|
||||
{
|
||||
undo_submit_fn_ = std::move(fn);
|
||||
}
|
||||
|
||||
const std::string &OlivePluginInstance::getDefaultOutputFielding() const
|
||||
{
|
||||
return field_order_for_params(params_);
|
||||
}
|
||||
|
||||
void register_node_instance(uintptr_t identity,
|
||||
OlivePluginInstance *instance);
|
||||
|
||||
void OlivePluginInstance::set_node_handle(OakNodeNode node)
|
||||
{
|
||||
if (node_.ctx) {
|
||||
register_node_instance(oaknode_node_identity(node_), nullptr);
|
||||
}
|
||||
node_ = node;
|
||||
if (node_.ctx) {
|
||||
register_node_instance(oaknode_node_identity(node_), this);
|
||||
}
|
||||
for (const auto &entry : getParams()) {
|
||||
if (!entry.second) {
|
||||
continue;
|
||||
}
|
||||
if (auto *bound = dynamic_cast<NodeBoundParam *>(entry.second)) {
|
||||
bound->set_node(node_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id,
|
||||
const char *format, va_list args)
|
||||
{
|
||||
const std::string message = format_ofx_message(format, args);
|
||||
if (message.empty()) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
// UI messages route through the host's registered handler
|
||||
HostMessageHandler handler = get_host_message_handler();
|
||||
if (handler) {
|
||||
return handler(type, message);
|
||||
}
|
||||
|
||||
fprintf(stderr, "OFX message: %s %s\n", type, message.c_str());
|
||||
if (strcmp(type, kOfxMessageQuestion) == 0) {
|
||||
return kOfxStatReplyNo;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::setPersistentMessage(const char *type,
|
||||
const char *id,
|
||||
const char *format,
|
||||
va_list args)
|
||||
{
|
||||
const std::string message = format_ofx_message(format, args);
|
||||
if (message.empty()) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
ErrorType error_type;
|
||||
// If this is an error message
|
||||
if (strncmp(type, kOfxMessageError, strlen(kOfxMessageError)) == 0) {
|
||||
error_type = ErrorType::error;
|
||||
}
|
||||
// A warning
|
||||
else if (strncmp(type, kOfxMessageWarning, strlen(kOfxMessageWarning)) ==
|
||||
0) {
|
||||
error_type = ErrorType::warning;
|
||||
}
|
||||
// A simple information
|
||||
else if (strncmp(type, kOfxMessageMessage, strlen(kOfxMessageMessage)) ==
|
||||
0) {
|
||||
error_type = ErrorType::message;
|
||||
} else {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
persistent_errors_.push_back({ error_type, message });
|
||||
|
||||
HostMessageHandler handler = get_host_message_handler();
|
||||
if (handler) {
|
||||
return handler(type, message);
|
||||
}
|
||||
|
||||
fprintf(stderr, "OFX %s: %s\n", type, message.c_str());
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::clearPersistentMessage()
|
||||
{
|
||||
persistent_errors_.clear();
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getProjectSize(double &x_size, double &y_size) const
|
||||
{
|
||||
double par = 1.0;
|
||||
int par_num = 0, par_den = 1;
|
||||
int width = 0, height = 0;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(params_, &par_num,
|
||||
&par_den);
|
||||
oakcommon_videoparams_get_width(params_, &width);
|
||||
oakcommon_videoparams_get_height(params_, &height);
|
||||
}
|
||||
if (par_den != 0) {
|
||||
par = double(par_num) / par_den;
|
||||
}
|
||||
x_size = width * par;
|
||||
y_size = height;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getProjectOffset(double &x_offset,
|
||||
double &y_offset) const
|
||||
{
|
||||
double par = 1.0;
|
||||
int par_num = 0, par_den = 1;
|
||||
float x = 0, y = 0;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(params_, &par_num,
|
||||
&par_den);
|
||||
oakcommon_videoparams_get_x(params_, &x);
|
||||
oakcommon_videoparams_get_y(params_, &y);
|
||||
}
|
||||
if (par_den != 0) {
|
||||
par = double(par_num) / par_den;
|
||||
}
|
||||
x_offset = x * par;
|
||||
y_offset = y;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getProjectExtent(double &x_size, double &y_size) const
|
||||
{
|
||||
getProjectSize(x_size, y_size);
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getProjectPixelAspectRatio() const
|
||||
{
|
||||
int par_num = 0, par_den = 1;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(params_, &par_num,
|
||||
&par_den);
|
||||
}
|
||||
if (par_den == 0) {
|
||||
return 1.0;
|
||||
}
|
||||
double par = double(par_num) / par_den;
|
||||
if (par == 0.0) {
|
||||
return 1.0; // default PAR when not explicitly set
|
||||
}
|
||||
return par;
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getFrameRate() const
|
||||
{
|
||||
int num = 0, den = 1;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_frame_rate(params_, &num, &den);
|
||||
}
|
||||
return den ? double(num) / den : 0.0;
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getEffectDuration() const
|
||||
{
|
||||
// Return a default duration value
|
||||
return 100.0;
|
||||
}
|
||||
|
||||
double OlivePluginInstance::getFrameRecursive() const
|
||||
{
|
||||
// Return current frame (this would typically be set by the host during rendering)
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::getRenderScaleRecursive(double &x, double &y) const
|
||||
{
|
||||
// Return default render scale (1.0, 1.0)
|
||||
x = 1.0;
|
||||
y = 1.0;
|
||||
}
|
||||
|
||||
OFX::Host::Param::Instance *
|
||||
OlivePluginInstance::newParam(const std::string &name,
|
||||
OFX::Host::Param::Descriptor &desc)
|
||||
{
|
||||
const std::string &type = desc.getType();
|
||||
|
||||
if (type == kOfxParamTypeInteger) {
|
||||
return new IntegerInstance(node_, desc, this);
|
||||
} else if (type == kOfxParamTypeDouble) {
|
||||
return new DoubleInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeBoolean) {
|
||||
return new BooleanInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeChoice) {
|
||||
return new ChoiceInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeString) {
|
||||
return new StringInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeRGBA) {
|
||||
return new RGBAInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeRGB) {
|
||||
return new RGBInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeDouble2D) {
|
||||
return new Double2DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeInteger2D) {
|
||||
return new Integer2DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeDouble3D) {
|
||||
return new Double3DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeInteger3D) {
|
||||
return new Integer3DInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeCustom || type == kOfxParamTypeBytes) {
|
||||
return new CustomInstance(node_, name, desc, this);
|
||||
} else if (type == kOfxParamTypeGroup) {
|
||||
return new GroupInstance(desc, this);
|
||||
} else if (type == kOfxParamTypePage) {
|
||||
return new PageInstance(desc, this);
|
||||
} else if (type == kOfxParamTypePushButton) {
|
||||
return new PushbuttonInstance(node_, name, desc, this);
|
||||
}
|
||||
|
||||
return nullptr; // 未实现的类型
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::editBegin(const std::string &name)
|
||||
{
|
||||
edit_depth_++;
|
||||
if (edit_depth_ == 1) {
|
||||
edit_command_ = {};
|
||||
edit_label_.clear();
|
||||
edit_first_label_.clear();
|
||||
edit_param_count_ = 0;
|
||||
if (!name.empty()) {
|
||||
edit_first_label_ = "Change " + name;
|
||||
}
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::editEnd()
|
||||
{
|
||||
if (edit_depth_ > 0) {
|
||||
edit_depth_--;
|
||||
}
|
||||
if (edit_depth_ == 0 && edit_command_.ctx) {
|
||||
std::string label = edit_label_;
|
||||
if (label.empty()) {
|
||||
if (edit_param_count_ <= 1 && !edit_first_label_.empty()) {
|
||||
label = edit_first_label_;
|
||||
} else if (edit_param_count_ > 1 && !edit_first_label_.empty()) {
|
||||
label = edit_first_label_ + " (+" +
|
||||
std::to_string(edit_param_count_ - 1) + ")";
|
||||
} else {
|
||||
label = "Edit Parameters";
|
||||
}
|
||||
}
|
||||
if (undo_submit_fn_) {
|
||||
undo_submit_fn_(edit_command_, label);
|
||||
} else {
|
||||
oakundo_command_redo_now(edit_command_);
|
||||
oakundo_command_free(&edit_command_);
|
||||
}
|
||||
edit_command_ = {};
|
||||
edit_label_.clear();
|
||||
edit_first_label_.clear();
|
||||
edit_param_count_ = 0;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::submit_undo_command(OakUndoCommand command,
|
||||
const std::string &label)
|
||||
{
|
||||
if (!command.ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (edit_depth_ > 0) {
|
||||
if (!edit_command_.ctx) {
|
||||
edit_command_ = oakundo_command_init_multi();
|
||||
}
|
||||
edit_param_count_++;
|
||||
if (!label.empty() && edit_first_label_.empty()) {
|
||||
edit_first_label_ = label;
|
||||
}
|
||||
|
||||
oakundo_command_redo_now(command);
|
||||
oakundo_command_multi_add_child(edit_command_, command);
|
||||
return;
|
||||
}
|
||||
|
||||
if (undo_submit_fn_ && is_gui_thread()) {
|
||||
undo_submit_fn_(command, label);
|
||||
return;
|
||||
}
|
||||
|
||||
oakundo_command_redo_now(command);
|
||||
oakundo_command_free(&command);
|
||||
}
|
||||
|
||||
void OlivePluginInstance::progressStart(const std::string &message,
|
||||
const std::string &messageid)
|
||||
{
|
||||
(void)messageid;
|
||||
progress_cancelled_ = false;
|
||||
progress_active_ = true;
|
||||
|
||||
if (progress_reporter_) {
|
||||
progress_reporter_->close();
|
||||
progress_reporter_.reset();
|
||||
}
|
||||
|
||||
std::string dialog_message =
|
||||
message.empty() ? "Processing..." : message;
|
||||
|
||||
progress_reporter_.reset(
|
||||
create_plugin_progress_reporter(dialog_message, "OpenFX"));
|
||||
progress_reporter_->set_cancel_callback(
|
||||
[this](void *) { progress_cancelled_ = true; }, nullptr);
|
||||
progress_reporter_->show();
|
||||
}
|
||||
|
||||
void OlivePluginInstance::progressEnd()
|
||||
{
|
||||
progress_active_ = false;
|
||||
progress_cancelled_ = false;
|
||||
|
||||
if (progress_reporter_) {
|
||||
progress_reporter_->close();
|
||||
progress_reporter_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool OlivePluginInstance::progressUpdate(double t)
|
||||
{
|
||||
if (!progress_active_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (progress_reporter_) {
|
||||
double clamped = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
|
||||
progress_reporter_->set_progress(clamped);
|
||||
}
|
||||
|
||||
return !progress_cancelled_;
|
||||
}
|
||||
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
OfxStatus OlivePluginInstance::contextAttachedAction()
|
||||
{
|
||||
if (!open_gl_enabled_) {
|
||||
return kOfxStatReplyDefault;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus OlivePluginInstance::contextDetachedAction()
|
||||
{
|
||||
if (!open_gl_enabled_) {
|
||||
return kOfxStatReplyDefault;
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
#endif
|
||||
|
||||
double OlivePluginInstance::timeLineGetTime()
|
||||
{
|
||||
if (active_viewer_provider_) {
|
||||
OakNodeNode viewer = active_viewer_provider_();
|
||||
if (viewer.ctx) {
|
||||
// Playhead as seconds
|
||||
int num = 0, den = 1;
|
||||
if (oaknode_sequence_get_playhead(
|
||||
oaknode_sequence_from_node(viewer),
|
||||
&num, &den) == OAKNODE_OK && den != 0) {
|
||||
return double(num) / den;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::timeLineGotoTime(double t)
|
||||
{
|
||||
if (active_viewer_provider_) {
|
||||
OakNodeNode viewer = active_viewer_provider_();
|
||||
if (viewer.ctx) {
|
||||
olive::core::Rational r = olive::core::Rational::from_double(t);
|
||||
oaknode_sequence_set_playhead(
|
||||
oaknode_sequence_from_node(viewer), r.numerator(),
|
||||
r.denominator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OlivePluginInstance::timeLineGetBounds(double &t1, double &t2)
|
||||
{
|
||||
if (active_viewer_provider_) {
|
||||
OakNodeNode viewer = active_viewer_provider_();
|
||||
if (viewer.ctx) {
|
||||
int len_num = 0, len_den = 1;
|
||||
if (oaknode_sequence_get_length(
|
||||
oaknode_sequence_from_node(viewer), &len_num,
|
||||
&len_den) == OAKNODE_OK && len_den != 0) {
|
||||
t1 = 0.0;
|
||||
t2 = double(len_num) / len_den;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t1 = 0.0;
|
||||
t2 = 0.0;
|
||||
}
|
||||
|
||||
void OlivePluginInstance::setCustomInArgs(const std::string &action,
|
||||
OFX::Host::Property::Set &in_args)
|
||||
{
|
||||
if (action == kOfxImageEffectActionRender ||
|
||||
action == kOfxImageEffectActionBeginSequenceRender ||
|
||||
action == kOfxImageEffectActionEndSequenceRender) {
|
||||
in_args.setIntProperty(kOfxImageEffectPropOpenGLEnabled,
|
||||
open_gl_enabled_ ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
OFX::Host::ImageEffect::ClipInstance *OlivePluginInstance::newClipInstance(
|
||||
OFX::Host::ImageEffect::Instance *plugin,
|
||||
OFX::Host::ImageEffect::ClipDescriptor *descriptor, int index)
|
||||
{
|
||||
// Create a new clip instance
|
||||
OliveClipInstance *clip_instance =
|
||||
new OliveClipInstance(plugin, *descriptor, params_);
|
||||
|
||||
// Initialize base class clip properties from VideoParams so that
|
||||
// setupClipPreferencesArgs and plugin constructors (which may fetch
|
||||
// clips and query their properties before getClipPreferences is called)
|
||||
// have valid defaults instead of kOfxImageComponentNone / kOfxBitDepthNone.
|
||||
std::string depth = kOfxBitDepthFloat; // host default
|
||||
std::string comp = kOfxImageComponentRGBA; // host default
|
||||
|
||||
int format = -1;
|
||||
int channels = 0;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_get_format(params_, &format);
|
||||
oakcommon_videoparams_get_channel_count(params_, &channels);
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case OAKCOMMON_PIXEL_FORMAT_U8:
|
||||
depth = kOfxBitDepthByte;
|
||||
break;
|
||||
case OAKCOMMON_PIXEL_FORMAT_U16:
|
||||
depth = kOfxBitDepthShort;
|
||||
break;
|
||||
case OAKCOMMON_PIXEL_FORMAT_F16:
|
||||
depth = kOfxBitDepthHalf;
|
||||
break;
|
||||
case OAKCOMMON_PIXEL_FORMAT_F32:
|
||||
depth = kOfxBitDepthFloat;
|
||||
break;
|
||||
default:
|
||||
break; // keep F32 default
|
||||
}
|
||||
|
||||
switch (channels) {
|
||||
case 1:
|
||||
comp = kOfxImageComponentAlpha;
|
||||
break;
|
||||
case 3:
|
||||
comp = kOfxImageComponentRGB;
|
||||
break;
|
||||
case 4:
|
||||
comp = kOfxImageComponentRGBA;
|
||||
break;
|
||||
default:
|
||||
break; // keep RGBA default
|
||||
}
|
||||
|
||||
clip_instance->setPixelDepth(depth);
|
||||
clip_instance->setComponents(comp);
|
||||
|
||||
return clip_instance;
|
||||
}
|
||||
|
||||
OlivePluginInstance::~OlivePluginInstance()
|
||||
{
|
||||
if (node_.ctx) {
|
||||
register_node_instance(oaknode_node_identity(node_), nullptr);
|
||||
}
|
||||
_created = false;
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_free(¶ms_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef OAK_OLIVE_INSTANCE_H
|
||||
#define OAK_OLIVE_INSTANCE_H
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxImageEffect.h"
|
||||
#include "ofxhImageEffect.h"
|
||||
|
||||
#include "common/videoparams.h"
|
||||
#include "node/node.h"
|
||||
#include "pluginprogressreporter.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Whether the calling thread is the registered main (GUI) thread
|
||||
*
|
||||
* The facade registers the main thread at startup with
|
||||
* set_main_thread_id(); before registration the first caller's thread
|
||||
* is treated as main (headless default).
|
||||
*/
|
||||
bool is_gui_thread();
|
||||
|
||||
void set_main_thread_id(std::thread::id id);
|
||||
|
||||
class PluginProgressReporter;
|
||||
enum class ErrorType { error, warning, message };
|
||||
struct PersistentErrors {
|
||||
ErrorType type;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provider returning the currently active viewer as an oaknode
|
||||
* handle
|
||||
*
|
||||
* Registered by the facade. Without a provider, the OFX timeline suite
|
||||
* falls back to its safe defaults (current time 0, empty bounds,
|
||||
* seeking does nothing).
|
||||
*/
|
||||
using ActiveViewerProvider = std::function<OakNodeNode()>;
|
||||
|
||||
void set_active_viewer_provider(ActiveViewerProvider provider);
|
||||
|
||||
/**
|
||||
* @brief Undo submission callback (facade pushes onto its undo stack)
|
||||
*
|
||||
* The command handle is consumed by the callback (the facade stack
|
||||
* takes a reference; see oakundo push semantics). Without a callback,
|
||||
* commands are redone immediately and released.
|
||||
*/
|
||||
using UndoSubmitFn = std::function<void(OakUndoCommand command,
|
||||
const std::string &label)>;
|
||||
|
||||
void set_undo_submit_callback(UndoSubmitFn fn);
|
||||
|
||||
class OlivePluginInstance : public OFX::Host::ImageEffect::Instance {
|
||||
public:
|
||||
OlivePluginInstance(OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
|
||||
OFX::Host::ImageEffect::Descriptor &desc,
|
||||
const std::string &context, bool interactive)
|
||||
: OFX::Host::ImageEffect::Instance(plugin, desc, context, interactive)
|
||||
{
|
||||
}
|
||||
OlivePluginInstance(OlivePluginInstance &instance)
|
||||
: Instance(instance._plugin, *instance._descriptor, instance._context,
|
||||
instance._interactive)
|
||||
{
|
||||
// Do NOT shallow-copy _clips: Instance::~Instance() deletes them,
|
||||
// which would cause a double-free. Clips are re-created in populate().
|
||||
_created = instance._created;
|
||||
_clipPrefsDirty = instance._clipPrefsDirty;
|
||||
_continuousSamples = instance._continuousSamples;
|
||||
_frameVarying = instance._frameVarying;
|
||||
_outputPreMultiplication = instance._outputPreMultiplication;
|
||||
_outputFielding = instance._outputFielding;
|
||||
_outputFrameRate = instance._outputFrameRate;
|
||||
}
|
||||
explicit OlivePluginInstance(Instance &instance)
|
||||
: Instance(instance) {};
|
||||
~OlivePluginInstance() override;
|
||||
const std::string &getDefaultOutputFielding() const override;
|
||||
|
||||
void setVideoParam(OakVideoParams params)
|
||||
{
|
||||
if (params_.ctx) {
|
||||
oakcommon_videoparams_free(¶ms_);
|
||||
}
|
||||
params_ = params;
|
||||
if (params_.ctx) {
|
||||
params_.addref(params_.ctx);
|
||||
}
|
||||
}
|
||||
void set_node_handle(OakNodeNode node);
|
||||
OakNodeNode node_handle() const
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
void setOpenGLEnabled(bool enabled)
|
||||
{
|
||||
open_gl_enabled_ = enabled;
|
||||
}
|
||||
bool isCreated() const
|
||||
{
|
||||
return _created;
|
||||
}
|
||||
OFX::Host::ImageEffect::ClipInstance *
|
||||
newClipInstance(OFX::Host::ImageEffect::Instance *plugin,
|
||||
OFX::Host::ImageEffect::ClipDescriptor *descriptor,
|
||||
int index) override;
|
||||
|
||||
OfxStatus vmessage(const char *type, const char *id, const char *format,
|
||||
va_list args) override;
|
||||
|
||||
OfxStatus setPersistentMessage(const char *type, const char *id,
|
||||
const char *format, va_list args) override;
|
||||
|
||||
OfxStatus clearPersistentMessage() override;
|
||||
int persistent_message_count() const
|
||||
{
|
||||
return int(persistent_errors_.size());
|
||||
}
|
||||
const std::vector<PersistentErrors> &persistent_messages() const
|
||||
{
|
||||
return persistent_errors_;
|
||||
}
|
||||
|
||||
void getProjectSize(double &x_size, double &y_size) const override;
|
||||
void getProjectOffset(double &x_offset, double &y_offset) const override;
|
||||
void getProjectExtent(double &x_size, double &y_size) const override;
|
||||
// The pixel aspect ratio of the current project
|
||||
double getProjectPixelAspectRatio() const override;
|
||||
|
||||
// The duration of the effect
|
||||
// This contains the duration of the plug-in effect, in frames.
|
||||
double getEffectDuration() const override;
|
||||
|
||||
// For an instance, this is the frame rate of the project the effect is in.
|
||||
double getFrameRate() const override;
|
||||
|
||||
/// This is called whenever a param is changed by the plugin so that
|
||||
/// the recursive instanceChangedAction will be fed the correct frame
|
||||
double getFrameRecursive() const override;
|
||||
|
||||
/// This is called whenever a param is changed by the plugin so that
|
||||
/// the recursive instanceChangedAction will be fed the correct
|
||||
/// renderScale
|
||||
void getRenderScaleRecursive(double &x, double &y) const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// overridden for Param::SetInstance
|
||||
|
||||
/// make a parameter instance
|
||||
OFX::Host::Param::Instance *
|
||||
newParam(const std::string &name,
|
||||
OFX::Host::Param::Descriptor &descriptor) override;
|
||||
|
||||
void submit_undo_command(OakUndoCommand command, const std::string &label);
|
||||
|
||||
/// Triggered when the plug-in calls OfxParameterSuiteV1::paramEditBegin
|
||||
virtual OfxStatus editBegin(const std::string &name) override;
|
||||
|
||||
/// Triggered when the plug-in calls OfxParameterSuiteV1::paramEditEnd
|
||||
virtual OfxStatus editEnd() override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// overridden for Progress::ProgressI
|
||||
|
||||
/// Start doing progress.
|
||||
virtual void progressStart(const std::string &message,
|
||||
const std::string &messageid) override;
|
||||
|
||||
/// finish yer progress
|
||||
virtual void progressEnd() override;
|
||||
|
||||
/// set the progress to some level of completion, returns
|
||||
/// false if you should abandon processing, true to continue
|
||||
virtual bool progressUpdate(double t) override;
|
||||
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
virtual OfxStatus contextAttachedAction() override;
|
||||
virtual OfxStatus contextDetachedAction() override;
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// overridden for TimeLine::TimeLineI
|
||||
|
||||
/// get the current time on the timeline. This is not necessarily the same
|
||||
/// time as being passed to an action (eg render)
|
||||
double timeLineGetTime() override;
|
||||
|
||||
/// set the timeline to a specific time
|
||||
void timeLineGotoTime(double t) override;
|
||||
|
||||
/// get the first and last times available on the effect's timeline
|
||||
void timeLineGetBounds(double &t1, double &t2) override;
|
||||
|
||||
void setCustomInArgs(const std::string &action,
|
||||
OFX::Host::Property::Set &in_args) override;
|
||||
|
||||
private:
|
||||
std::vector<PersistentErrors> persistent_errors_;
|
||||
OakVideoParams params_ = {};
|
||||
OakNodeNode node_ = {};
|
||||
int edit_depth_ = 0;
|
||||
OakUndoCommand edit_command_ = {};
|
||||
std::string edit_label_;
|
||||
std::string edit_first_label_;
|
||||
int edit_param_count_ = 0;
|
||||
std::unique_ptr<PluginProgressReporter> progress_reporter_;
|
||||
bool progress_cancelled_ = false;
|
||||
bool progress_active_ = false;
|
||||
bool open_gl_enabled_ = false;
|
||||
|
||||
public:
|
||||
std::mutex &mutex()
|
||||
{
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "paraminstance.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "oliveplugininstance.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Node-identity → plugin instance registry, maintained by
|
||||
* OlivePluginInstance::set_node_handle()
|
||||
*/
|
||||
std::map<uintptr_t, OlivePluginInstance *> &instance_registry()
|
||||
{
|
||||
static std::map<uintptr_t, OlivePluginInstance *> registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_node_instance(uintptr_t identity, OlivePluginInstance *instance)
|
||||
{
|
||||
if (instance) {
|
||||
instance_registry()[identity] = instance;
|
||||
} else {
|
||||
instance_registry().erase(identity);
|
||||
}
|
||||
}
|
||||
|
||||
void submit_undo_command(OakNodeNode node, OakUndoCommand command,
|
||||
const std::string &label)
|
||||
{
|
||||
if (!command.ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.ctx) {
|
||||
auto it = instance_registry().find(oaknode_node_identity(node));
|
||||
if (it != instance_registry().end() && it->second) {
|
||||
it->second->submit_undo_command(command, label);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No instance bound: run immediately and release
|
||||
oakundo_command_redo_now(command);
|
||||
oakundo_command_free(&command);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "pluginprogressreporter.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief No-op reporter used when no UI factory is registered
|
||||
*
|
||||
* Never reports cancellation, so processing always continues.
|
||||
*/
|
||||
class NullPluginProgressReporter : public PluginProgressReporter {
|
||||
public:
|
||||
void set_progress(double value) override
|
||||
{
|
||||
(void)value;
|
||||
}
|
||||
|
||||
void show() override
|
||||
{
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
PluginProgressReporterFactory reporter_factory_;
|
||||
|
||||
}
|
||||
|
||||
void set_plugin_progress_reporter_factory(
|
||||
PluginProgressReporterFactory factory)
|
||||
{
|
||||
reporter_factory_ = std::move(factory);
|
||||
}
|
||||
|
||||
PluginProgressReporter *
|
||||
create_plugin_progress_reporter(const std::string &message,
|
||||
const std::string &title)
|
||||
{
|
||||
if (reporter_factory_) {
|
||||
return reporter_factory_(message, title);
|
||||
}
|
||||
|
||||
return new NullPluginProgressReporter();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifndef OAK_PLUGIN_PROGRESS_REPORTER_H
|
||||
#define OAK_PLUGIN_PROGRESS_REPORTER_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief UI-independent interface for reporting plugin progress
|
||||
*
|
||||
* The engine cannot show UI itself, so OFX progress reporting goes through
|
||||
* this interface. The UI layer registers a factory (see
|
||||
* set_plugin_progress_reporter_factory()) that creates a reporter wrapping a
|
||||
* ProgressDialog; without a factory, a no-op reporter is used instead.
|
||||
*
|
||||
* Cancellation is delivered through a C-style callback.
|
||||
*/
|
||||
class PluginProgressReporter {
|
||||
public:
|
||||
PluginProgressReporter() = default;
|
||||
|
||||
virtual ~PluginProgressReporter() = default;
|
||||
|
||||
virtual void set_progress(double value) = 0;
|
||||
|
||||
virtual void show() = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
/**
|
||||
* @brief Register a callback to be invoked when the user cancels.
|
||||
*
|
||||
* Only one callback is supported; subsequent calls replace the
|
||||
* previous registration. Pass nullptr to clear.
|
||||
*/
|
||||
void set_cancel_callback(std::function<void(void *)> cb, void *userdata)
|
||||
{
|
||||
cancel_callback_ = std::move(cb);
|
||||
cancel_callback_userdata_ = userdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark this reporter as cancelled and notify the registered
|
||||
* callback.
|
||||
*/
|
||||
void set_cancelled()
|
||||
{
|
||||
cancelled_ = true;
|
||||
if (cancel_callback_) {
|
||||
cancel_callback_(cancel_callback_userdata_);
|
||||
}
|
||||
}
|
||||
|
||||
bool cancelled() const
|
||||
{
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(void *)> cancel_callback_;
|
||||
void *cancel_callback_userdata_ = nullptr;
|
||||
bool cancelled_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Factory creating a PluginProgressReporter for a progress session
|
||||
*
|
||||
* Registered by the UI layer at startup. The caller takes ownership of the
|
||||
* returned reporter.
|
||||
*/
|
||||
using PluginProgressReporterFactory =
|
||||
std::function<PluginProgressReporter *(const std::string &message,
|
||||
const std::string &title)>;
|
||||
|
||||
void set_plugin_progress_reporter_factory(
|
||||
PluginProgressReporterFactory factory);
|
||||
|
||||
/**
|
||||
* @brief Create a progress reporter through the registered factory
|
||||
*
|
||||
* Without a factory, returns a no-op reporter so engine code can run
|
||||
* headless. The caller takes ownership of the returned reporter.
|
||||
*/
|
||||
PluginProgressReporter *
|
||||
create_plugin_progress_reporter(const std::string &message,
|
||||
const std::string &title);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OAK_PLUGIN_PROGRESS_REPORTER_H
|
||||
@@ -0,0 +1,168 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2025 mikesolar
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(oakplugin-standalone CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# oakplugin manipulates node graphs through the oaknode C ABI; the
|
||||
# not-yet-split engine modules' symbols are allowed to dangle, resolved
|
||||
# by transitional stubs in src/{node,render}/transition.
|
||||
set(OAK_REPO_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
|
||||
list(APPEND CMAKE_MODULE_PATH "${OAK_REPO_ROOT}/cmake")
|
||||
|
||||
set(BUILD_TESTS ON CACHE BOOL "" FORCE)
|
||||
set(OLIVECORE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
# The /opt/otio OpenTimelineIO dylibs use @loader_path install names that
|
||||
# break when linked from a build tree; oakplugin does not need OTIO.
|
||||
set(CMAKE_DISABLE_FIND_PACKAGE_OpenTimelineIO ON)
|
||||
|
||||
find_package(EXPAT REQUIRED)
|
||||
find_package(OpenColorIO CONFIG REQUIRED)
|
||||
find_package(OpenImageIO CONFIG REQUIRED)
|
||||
set(OCIO_LIBRARIES OpenColorIO::OpenColorIO)
|
||||
set(OCIO_INCLUDE_DIRS ${OpenColorIO_INCLUDE_DIRS})
|
||||
set(OIIO_LIBRARIES OpenImageIO::OpenImageIO)
|
||||
set(OIIO_INCLUDE_DIRS ${OpenImageIO_INCLUDE_DIRS})
|
||||
|
||||
add_subdirectory(${OAK_REPO_ROOT}/core ${CMAKE_BINARY_DIR}/core)
|
||||
target_include_directories(olivecore PUBLIC
|
||||
${OAK_REPO_ROOT}/third_party/openfx/include
|
||||
)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/ffmpeg_bridge ${CMAKE_BINARY_DIR}/ffmpeg_bridge)
|
||||
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/undo ${CMAKE_BINARY_DIR}/undo)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/common ${CMAKE_BINARY_DIR}/common)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/src ${CMAKE_BINARY_DIR}/render)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/c_api ${CMAKE_BINARY_DIR}/render_c_api)
|
||||
set(BUILD_TESTS ON)
|
||||
|
||||
enable_testing()
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/timeline ${CMAKE_BINARY_DIR}/timeline)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/codec ${CMAKE_BINARY_DIR}/codec)
|
||||
set(BUILD_TESTS ON)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/node ${CMAKE_BINARY_DIR}/node)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/plugin ${CMAKE_BINARY_DIR}/task)
|
||||
|
||||
target_include_directories(oakcodec PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
${OAK_REPO_ROOT}/src/node/src
|
||||
)
|
||||
foreach(t oakcodec oaktimeline)
|
||||
if(TARGET ${t})
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# oaknode needs its transition stubs and the oakrender real headers
|
||||
# (src/node/transition/render/* bridge there); src/render/transition
|
||||
# must precede src/node/transition.
|
||||
target_include_directories(oaknode BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
)
|
||||
target_include_directories(oaknode PUBLIC
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
/opt/homebrew/include
|
||||
/opt/homebrew/include/Imath
|
||||
)
|
||||
target_link_options(oaknode PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
|
||||
target_include_directories(oakrender BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
)
|
||||
target_include_directories(oakrender PUBLIC
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
/opt/homebrew/include
|
||||
/opt/homebrew/include/Imath
|
||||
/opt/homebrew/include/OpenEXR
|
||||
)
|
||||
foreach(t oakrender oakgl oakgl2 oakvulkan)
|
||||
if(TARGET ${t})
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
target_include_directories(oakplugin PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
${OAK_REPO_ROOT}/src/node/src
|
||||
)
|
||||
target_link_options(oakplugin PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
|
||||
target_link_libraries(oakrender PRIVATE
|
||||
oaknode
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
ffmpeg_bridge
|
||||
${OCIO_LIBRARIES}
|
||||
${OIIO_LIBRARIES}
|
||||
"-framework OpenGL"
|
||||
"-framework CoreVideo"
|
||||
"-framework Metal"
|
||||
"-framework QuartzCore"
|
||||
)
|
||||
|
||||
target_link_libraries(oaknode-gtest PRIVATE oakrender oaktimeline oakcodec)
|
||||
|
||||
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
|
||||
# dynamic_lookup; the real implementation (engine/src/capi/ipc.cpp) is still
|
||||
# Qt-based, so test binaries link an inert shim instead.
|
||||
target_sources(oaknode-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/src/node/standalone/oakengine_ipc_shim.cpp
|
||||
)
|
||||
target_include_directories(oaknode-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
)
|
||||
|
||||
|
||||
# OFX typeinfo is referenced via dynamic_lookup; force-load the host
|
||||
# support archive so RTTI resolves (same as oaknode's test wiring).
|
||||
# oakplugin-gtest wires itself up in src/plugin/tests/CMakeLists.txt.
|
||||
find_library(OAKNODE_OFX_HOST_ARCHIVE NAMES OfxHost
|
||||
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport
|
||||
NO_DEFAULT_PATH)
|
||||
if(NOT OAKNODE_OFX_HOST_ARCHIVE)
|
||||
message(FATAL_ERROR
|
||||
"libOfxHost.a not found; run the full-tree build once or set "
|
||||
"OAKNODE_OFX_HOST_ARCHIVE")
|
||||
endif()
|
||||
target_link_options(oaknode-gtest PRIVATE
|
||||
"-Wl,-force_load,${OAKNODE_OFX_HOST_ARCHIVE}")
|
||||
|
||||
# TimelineAddTrackCommand, which lives in liboakplugin; the two
|
||||
# libraries resolve each other at runtime (dynamic_lookup).
|
||||
target_link_libraries(oaknode-gtest PRIVATE oaktimeline)
|
||||
@@ -0,0 +1,65 @@
|
||||
# Oak Video Editor - Non-Linear Video Editor
|
||||
# Copyright (C) 2026 Oak Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
# In a full-tree build the repo root is CMAKE_SOURCE_DIR; a standalone
|
||||
# build (see src/plugin/standalone) sets OAK_REPO_ROOT explicitly.
|
||||
if(NOT DEFINED OAK_REPO_ROOT)
|
||||
set(OAK_REPO_ROOT ${CMAKE_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
add_executable(oakplugin-gtest
|
||||
plugin_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(oakplugin-gtest PRIVATE
|
||||
oakplugin
|
||||
oakrender
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
# liboakrender/liboaknode dangle OFX host symbols (-undefined
|
||||
# dynamic_lookup); force-load the host support archive into the test
|
||||
# process so dyld finds them in the flat namespace at startup. Mirrors
|
||||
# src/render/tests/CMakeLists.txt.
|
||||
if(NOT DEFINED OAKRENDER_OFX_HOST_ARCHIVE)
|
||||
find_library(OAKRENDER_OFX_HOST_ARCHIVE NAMES OfxHost
|
||||
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport)
|
||||
endif()
|
||||
if(NOT OAKRENDER_OFX_HOST_ARCHIVE)
|
||||
message(FATAL_ERROR
|
||||
"libOfxHost.a not found; run the full-tree build once or set "
|
||||
"OAKRENDER_OFX_HOST_ARCHIVE")
|
||||
endif()
|
||||
target_link_options(oakplugin-gtest PRIVATE
|
||||
"-Wl,-force_load,${OAKRENDER_OFX_HOST_ARCHIVE}")
|
||||
|
||||
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
|
||||
# dynamic_lookup; the test binary links the inert shim from
|
||||
# src/node/standalone instead.
|
||||
target_sources(oakplugin-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/src/node/standalone/oakengine_ipc_shim.cpp)
|
||||
target_include_directories(oakplugin-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
)
|
||||
|
||||
gtest_discover_tests(oakplugin-gtest
|
||||
DISCOVERY_MODE PRE_TEST
|
||||
PROPERTIES ENVIRONMENT
|
||||
"OCIO=${OAK_REPO_ROOT}/engine/render/ocioconf/config.ocio")
|
||||
@@ -0,0 +1,106 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "plugin/host.h"
|
||||
#include "plugin/instance.h"
|
||||
|
||||
TEST(OakPluginHost, InitAndEnumerate)
|
||||
{
|
||||
EXPECT_EQ(oakplugin_host_init(), OAKPLUGIN_OK);
|
||||
|
||||
int count = oakplugin_host_plugin_count();
|
||||
EXPECT_GE(count, 0);
|
||||
|
||||
char buf[256];
|
||||
EXPECT_EQ(oakplugin_host_plugin_id_at(-1, buf, sizeof(buf)),
|
||||
OAKPLUGIN_E_NOT_FOUND);
|
||||
EXPECT_EQ(oakplugin_host_plugin_id_at(count + 100, buf, sizeof(buf)),
|
||||
OAKPLUGIN_E_NOT_FOUND);
|
||||
EXPECT_EQ(oakplugin_host_plugin_label(nullptr, buf, sizeof(buf)),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
|
||||
const char *dirs[] = { "/nonexistent/ofx/path" };
|
||||
EXPECT_EQ(oakplugin_host_scan(dirs, 1), OAKPLUGIN_OK);
|
||||
EXPECT_EQ(oakplugin_host_scan(nullptr, 1), OAKPLUGIN_E_INVALID);
|
||||
|
||||
oakplugin_host_shutdown();
|
||||
}
|
||||
|
||||
TEST(OakPluginHost, MessageHandler)
|
||||
{
|
||||
static int calls = 0;
|
||||
calls = 0;
|
||||
|
||||
oakplugin_host_set_message_handler(
|
||||
[](const char *type, const char *message, void *userdata) -> int {
|
||||
int *counter = static_cast<int *>(userdata);
|
||||
(*counter)++;
|
||||
EXPECT_STREQ(type, "Message");
|
||||
EXPECT_STRNE(message, "");
|
||||
return OAKPLUGIN_MESSAGE_ANSWER_NO;
|
||||
},
|
||||
&calls);
|
||||
|
||||
// No plugin loaded here; just verify registration does not crash and
|
||||
// the default path works after clearing
|
||||
oakplugin_host_set_message_handler(nullptr, nullptr);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(OakPluginInstance, CreateUnknownIdFails)
|
||||
{
|
||||
EXPECT_EQ(oakplugin_host_init(), OAKPLUGIN_OK);
|
||||
|
||||
OakPluginInstance instance =
|
||||
oakplugin_instance_create("com.example.nonexistent.plugin");
|
||||
EXPECT_EQ(instance.ctx, nullptr);
|
||||
oakplugin_instance_free(&instance); // no-op on empty
|
||||
|
||||
EXPECT_EQ(oakplugin_debug_alive_count(), 0);
|
||||
}
|
||||
|
||||
TEST(OakPluginInstance, InvalidArgs)
|
||||
{
|
||||
OakPluginInstance empty = {};
|
||||
|
||||
oakplugin_instance_free(nullptr); // no-op
|
||||
oakplugin_instance_free(&empty); // no-op
|
||||
|
||||
oaknode_value value = {};
|
||||
EXPECT_EQ(oakplugin_instance_set_param(empty, "p", &value),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_get_param(empty, "p", &value),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_set_param_string(empty, "p", "x"),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_get_param_string(empty, "p", nullptr, 0),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
|
||||
EXPECT_EQ(oakplugin_instance_render(empty, OakRenderTexture{},
|
||||
OakRenderTexture{}, 0.0),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_set_progress_cb(empty, nullptr, nullptr),
|
||||
OAKPLUGIN_E_INVALID);
|
||||
EXPECT_EQ(oakplugin_instance_cancel(empty), OAKPLUGIN_E_INVALID);
|
||||
}
|
||||
Reference in New Issue
Block a user