R8: finish app/ pure C ABI migration (P3-P9) and make OTIO required

- app/ no longer includes engine C++ headers nor holds engine C++ types:
  engine access goes through the oakengine C ABI plus C++ wrappers
  (oakutil/oaknode.h, oakutil/oakvideo.h) and app-local mirror types
  (tooltypes, trackreferencehandle, timelinecommonapp, keyframetypes,
  subtitleapp, serializedlayoutinfoapp, nodevaluehandle, sliderdisplaytypeapp)
- engine: new C ABI functions for block/track/clip/transition navigation
  and predicates, links, caches, waveform/playback, disk folder,
  sequence_track_list, node_free, footage_is_valid, block_get_track,
  get_brush; loadotio/saveotio ported to the current engine API
- OTIO is now a required dependency: CI and CD build it on every
  platform, FindOpenTimelineIO fixed for OTIO 0.16/0.19 (the old deps
  include requirement silently disabled OTIO everywhere), runtime
  libraries are bundled into packages and copied next to macOS binaries
  (oak_copy_otio_runtime)
- fix ProjectViewModel drag&drop mime read/write size mismatch (segfault)
- unify color label naming (k_olive -> "Oak") in the app-side mirror
- docs: OTIO required, FFmpeg minimum corrected to 6.0 (en/zh)
- gtest suite: 1925 passed, 0 failed
This commit is contained in:
2026-07-31 22:46:52 +08:00
parent 18aed979a2
commit 66d761b4b7
285 changed files with 13261 additions and 5895 deletions
+55
View File
@@ -185,3 +185,58 @@ extern "C" int oakengine_disk_invalidate_project(OakEngineProject *project)
emit m->invalidate_project(reinterpret_cast<olive::Project *>(project));
return OAKENGINE_OK;
}
/* ---- DiskCacheFolder accessors ------------------------------------------------- */
extern "C" double oakengine_disk_folder_get_limit(const void *folder)
{
if (!folder) {
return 0.0;
}
return static_cast<double>(
reinterpret_cast<const olive::DiskCacheFolder *>(folder)
->get_limit());
}
extern "C" int oakengine_disk_folder_set_limit(void *folder, double limit)
{
if (!folder || limit < 0.0) {
return OAKENGINE_E_INVALID;
}
reinterpret_cast<olive::DiskCacheFolder *>(folder)->set_limit(
qRound64(limit));
return OAKENGINE_OK;
}
extern "C" int oakengine_disk_folder_get_clear_on_close(const void *folder)
{
if (!folder) {
return 0;
}
return reinterpret_cast<const olive::DiskCacheFolder *>(folder)
->get_clear_on_close()
? 1
: 0;
}
extern "C" int oakengine_disk_folder_set_clear_on_close(void *folder,
int clear)
{
if (!folder) {
return OAKENGINE_E_INVALID;
}
reinterpret_cast<olive::DiskCacheFolder *>(folder)->set_clear_on_close(
clear != 0);
return OAKENGINE_OK;
}
extern "C" int oakengine_disk_folder_get_path(const void *folder, char *buf,
int buf_size)
{
if (!folder) {
return OAKENGINE_E_INVALID;
}
return write_string(
reinterpret_cast<const olive::DiskCacheFolder *>(folder)->get_path(),
buf, buf_size);
}
+10
View File
@@ -566,6 +566,16 @@ OakEngineFootage *oakengine_footage_borrow(OakEngineNode *node)
return wrap(state);
}
int oakengine_footage_is_valid(const OakEngineNode *node)
{
if (!node) {
return 0;
}
const auto *footage = dynamic_cast<const olive::Footage *>(
reinterpret_cast<const olive::Node *>(node));
return footage && footage->is_valid() ? 1 : 0;
}
int oakengine_footage_relink(OakEngineFootage *footage, const char *new_path)
{
set_error(QString());
+80
View File
@@ -20,12 +20,20 @@
#include "oakengine/gizmo.h"
#include <QPainter>
#include <QString>
#include "node/gizmo/draggable.h"
#include "node/gizmo/path.h"
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/gizmo/screen.h"
#include "node/gizmo/text.h"
#include "node/globals.h"
#include "node/generator/text/textv3.h"
#include "node/node.h"
#include "render/loopmode.h"
#include "render/videoparams.h"
extern "C" {
@@ -265,4 +273,76 @@ int oakengine_gizmo_drag_end(void *gizmo, void *command)
return OAKENGINE_OK;
}
int oakengine_gizmo_is_visible(void *gizmo)
{
if (!gizmo) {
return 0;
}
return static_cast<olive::NodeGizmo *>(gizmo)->is_visible() ? 1 : 0;
}
int oakengine_gizmo_draw(void *gizmo, void *painter)
{
if (!gizmo || !painter) {
return OAKENGINE_E_INVALID;
}
static_cast<olive::NodeGizmo *>(gizmo)->draw(
static_cast<QPainter *>(painter));
return OAKENGINE_OK;
}
int oakengine_gizmo_set_globals(void *gizmo,
int video_width, int video_height,
int64_t time_num, int64_t time_den)
{
if (!gizmo) {
return OAKENGINE_E_INVALID;
}
olive::VideoParams vp;
if (video_width > 0 && video_height > 0) {
vp.set_width(video_width);
vp.set_height(video_height);
}
olive::NodeGlobals globals(
vp, olive::AudioParams(),
olive::Rational(time_num, time_den),
olive::LoopMode::k_loop_mode_off);
static_cast<olive::NodeGizmo *>(gizmo)->set_globals(globals);
return OAKENGINE_OK;
}
int oakengine_gizmo_hit_test(void *gizmo,
const double *transform6, double px, double py)
{
if (!gizmo) {
return 0;
}
auto *g = static_cast<olive::NodeGizmo *>(gizmo);
if (!g->is_visible()) {
return 0;
}
const QPointF p(px, py);
if (auto *point = dynamic_cast<olive::PointGizmo *>(g)) {
if (!transform6) {
return 0;
}
const QTransform t(transform6[0], transform6[1], transform6[2],
transform6[3], transform6[4], transform6[5]);
return point->get_clicking_rect(t).contains(p) ? 1 : 0;
}
if (auto *poly = dynamic_cast<olive::PolygonGizmo *>(g)) {
return poly->get_polygon().containsPoint(p, Qt::OddEvenFill) ? 1 : 0;
}
if (auto *path = dynamic_cast<olive::PathGizmo *>(g)) {
return path->get_path().contains(p) ? 1 : 0;
}
if (dynamic_cast<olive::ScreenGizmo *>(g)) {
// Screen gizmos are hittable anywhere (mirrors the viewer logic).
return 1;
}
return 0;
}
} // extern "C"
+838 -11
View File
@@ -24,6 +24,7 @@
#include <cstring>
#include <QByteArray>
#include <QBrush>
#include <QString>
#include <QVariant>
#include <QVector2D>
@@ -31,6 +32,7 @@
#include <QVector4D>
#include "coreengine.h"
#include "config/config.h"
#include "node/factory.h"
#include "node/keyframe.h"
#include "node/node.h"
@@ -44,6 +46,13 @@
#include "node/distort/transform/transformdistortnode.h"
#include "node/block/transition/transition.h"
#include "node/block/subtitle/subtitle.h"
#include "node/block/clip/clip.h"
#include "node/output/track/track.h"
#include "node/output/viewer/viewer.h"
#include "node/project/footage/footage.h"
#include "node/project/folder/folder.h"
#include "node/gizmo/gizmo.h"
#include "pluginSupport/oliveplugininstance.h"
#include "node/generator/shape/shapenodebase.h"
#include "audio/audiovisualwaveform.h"
#include "node/inputimmediate.h"
@@ -617,6 +626,78 @@ OakEngineNode *oakengine_node_factory_node_at(int index)
return wrap(lib.at(index));
}
int oakengine_node_category_count(const OakEngineNode *self)
{
return self ? impl(self)->category().size() : 0;
}
int oakengine_node_category_at(const OakEngineNode *self, int index)
{
if (!self) {
return -1;
}
const QVector<olive::Node::CategoryID> cats = impl(self)->category();
if (index < 0 || index >= cats.size()) {
return -1;
}
return int(cats.at(index));
}
uint64_t oakengine_node_get_flags(const OakEngineNode *self)
{
return self ? impl(self)->get_flags() : 0;
}
uint64_t oakengine_node_flag_dont_show_in_create_menu(void)
{
return olive::Node::k_dont_show_in_create_menu;
}
uint64_t oakengine_node_flag_dont_show_in_param_view(void)
{
return olive::Node::k_dont_show_in_param_view;
}
uint64_t oakengine_node_flag_video_effect(void)
{
return olive::Node::k_video_effect;
}
uint64_t oakengine_node_flag_audio_effect(void)
{
return olive::Node::k_audio_effect;
}
void oakengine_node_retranslate(OakEngineNode *self)
{
if (self) {
impl(self)->retranslate();
}
}
int oakengine_node_get_sub_category(const OakEngineNode *self, char *buf,
int buf_size)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
return string_to_buf(impl(self)->sub_category(), buf, buf_size);
}
int oakengine_node_get_description(const OakEngineNode *self, char *buf,
int buf_size)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
return string_to_buf(impl(self)->description(), buf, buf_size);
}
OakEngineNode *oakengine_node_create_copy(const OakEngineNode *self)
{
return self ? wrap(impl(self)->copy()) : nullptr;
}
int oakengine_node_get_type_id(const OakEngineNode *self, char *buf,
int buf_size)
{
@@ -635,6 +716,15 @@ int oakengine_node_get_name(const OakEngineNode *self, char *buf,
return string_to_buf(impl(self)->name(), buf, buf_size);
}
int oakengine_node_get_short_name(const OakEngineNode *self, char *buf,
int buf_size)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
return string_to_buf(impl(self)->short_name(), buf, buf_size);
}
int oakengine_node_get_label(const OakEngineNode *self, char *buf,
int buf_size)
{
@@ -760,11 +850,35 @@ int oakengine_node_get_color_label(const OakEngineNode *self)
return impl(self)->get_override_color();
}
int oakengine_node_get_effective_color_label(const OakEngineNode *self)
{
if (!self) {
return -1;
}
const olive::Node *n = impl(self);
int c = n->get_override_color();
if (c < 0) {
c = olive::Config::current()[QStringLiteral("CatColor%1").arg(
n->category().first())]
.toInt();
}
return c;
}
int oakengine_node_input_count(const OakEngineNode *self)
{
return self ? impl(self)->inputs().size() : 0;
}
void oakengine_node_get_brush(const OakEngineNode *self, double top,
double bottom, void *out_qbrush)
{
if (!self || !out_qbrush) {
return;
}
*static_cast<QBrush *>(out_qbrush) = impl(self)->brush(top, bottom);
}
int oakengine_node_input_id(const OakEngineNode *self, int index, char *buf,
int buf_size)
{
@@ -1293,6 +1407,13 @@ void oakengine_node_delete_later(OakEngineNode *node)
}
}
void oakengine_node_free(OakEngineNode *node)
{
// Immediate destruction of an owned, orphaned node (see the header
// comment for the strict preconditions).
delete impl(node);
}
int oakengine_node_connect(OakEngineNode *output_node,
OakEngineNode *input_node, const char *input_id)
{
@@ -2025,6 +2146,15 @@ int oakengine_node_input_get_flags(const OakEngineNode *self,
return int(impl(self)->get_input_flags(QString::fromUtf8(input_id)));
}
int oakengine_node_input_get_data_type(const OakEngineNode *self,
const char *input_id)
{
if (!self || !input_id) {
return -1;
}
return int(impl(self)->get_input_data_type(QString::fromUtf8(input_id)));
}
int oakengine_node_input_is_connectable(const OakEngineNode *self,
const char *input_id)
{
@@ -2043,14 +2173,23 @@ int oakengine_node_input_is_keyframable(const OakEngineNode *self,
return impl(self)->is_input_keyframable(QString::fromUtf8(input_id)) ? 1 : 0;
}
int oakengine_node_input_is_keyframed_ex(const OakEngineNode *self,
const char *input_id, int track)
int oakengine_node_input_is_hidden(const OakEngineNode *self,
const char *input_id)
{
if (!self || !input_id) {
return 0;
}
(void)track;
return impl(self)->is_input_keyframing(QString::fromUtf8(input_id)) ? 1 : 0;
return impl(self)->is_input_hidden(QString::fromUtf8(input_id)) ? 1 : 0;
}
int oakengine_node_input_is_keyframed_ex(const OakEngineNode *self,
const char *input_id, int element)
{
if (!self || !input_id) {
return 0;
}
return impl(self)->is_input_keyframing(QString::fromUtf8(input_id),
element) ? 1 : 0;
}
int oakengine_node_get_label_and_name(const OakEngineNode *self, char *buf,
@@ -2132,6 +2271,31 @@ OakEngineProject *oakengine_node_get_project(const OakEngineNode *self)
return reinterpret_cast<OakEngineProject *>(p);
}
OakEngineProject *oakengine_node_parent(const OakEngineNode *self)
{
if (!self) {
return nullptr;
}
olive::Project *p = impl(self)->parent();
return reinterpret_cast<OakEngineProject *>(p);
}
int oakengine_node_is_item(const OakEngineNode *self)
{
if (!self) {
return 0;
}
return impl(self)->is_item() ? 1 : 0;
}
OakEngineNode *oakengine_node_folder(const OakEngineNode *self)
{
if (!self) {
return nullptr;
}
return wrap(const_cast<olive::Folder *>(impl(self)->folder()));
}
OakEngineNode *oakengine_node_input_get_connected_node(
const OakEngineNode *self, const char *input_id, int element)
{
@@ -2520,6 +2684,51 @@ int oakengine_node_input_get_property_rational(const OakEngineNode *self,
return OAKENGINE_OK;
}
int oakengine_node_input_get_property_track_number(const OakEngineNode *self,
const char *input_id,
const char *key, int track,
double *out)
{
set_error(QString());
if (!self || !input_id || !key || !out) {
set_error(QStringLiteral("invalid arguments"));
return OAKENGINE_E_INVALID;
}
const olive::Node *node = impl(self);
const QString id = QString::fromUtf8(input_id);
if (!node->has_input_property(id, QString::fromUtf8(key))) {
set_error(QStringLiteral("property \"%1\" not found on \"%2\"")
.arg(QString::fromUtf8(key)).arg(id));
return OAKENGINE_E_NOT_FOUND;
}
const QVariant v = node->get_input_property(id, QString::fromUtf8(key));
const olive::NodeValue::Type dt = node->get_input_data_type(id);
const int c_type = to_c_type(dt);
oak_node_value normal;
memset(&normal, 0, sizeof(normal));
normal.type = c_type;
if (!qvariant_to_pod(dt, v, &normal)) {
set_error(QStringLiteral("property \"%1\" is not numeric")
.arg(QString::fromUtf8(key)));
return OAKENGINE_E_INVALID;
}
const int tc = olive::NodeValue::get_number_of_keyframe_tracks(dt);
QVector<oak_node_value> track_vals(tc);
if (oakengine_node_value_split_to_tracks(c_type, &normal,
track_vals.data(), tc) !=
OAKENGINE_OK) {
set_error(QStringLiteral("property \"%1\" could not be split")
.arg(QString::fromUtf8(key)));
return OAKENGINE_E_INVALID;
}
if (track < 0 || track >= tc) {
set_error(QStringLiteral("track %1 out of range").arg(track));
return OAKENGINE_E_INVALID;
}
*out = track_vals.at(track).f[0];
return OAKENGINE_OK;
}
int oakengine_node_input_get_property_count(const OakEngineNode *self,
const char *input_id)
{
@@ -3627,22 +3836,21 @@ OakEngineKeyframe *oakengine_node_keyframe_handle_on_track(
OakEngineKeyframe *oakengine_node_keyframe_handle_at_time(
const OakEngineNode *self, const char *input_id, int element,
int track, int64_t time_ts, int track_for_time)
int track, int64_t time_num, int64_t time_den)
{
if (!self || !input_id) {
return nullptr;
}
const olive::Node *node = impl(self);
const QString id = QString::fromUtf8(input_id);
// Facade contract: time_ts is in SECONDS and track_for_time is 1-based.
olive::NodeKeyframe *key = node->get_keyframe_at_time_on_track(
id, olive::Rational(time_ts), track_for_time - 1, element);
id, olive::Rational(int(time_num), int(time_den)), track, element);
return wrap_kf(key);
}
int oakengine_node_keyframes_at_time(const OakEngineNode *self,
const char *input_id, int element,
int64_t time_ts, int track,
int64_t time_num, int64_t time_den,
OakEngineKeyframe **out_handles,
int max_handles)
{
@@ -3651,15 +3859,14 @@ int oakengine_node_keyframes_at_time(const OakEngineNode *self,
}
const olive::Node *node = impl(self);
const QString id = QString::fromUtf8(input_id);
// Facade contract: time_ts is in SECONDS.
const olive::Rational time(time_ts);
const olive::Rational t{int(time_num), int(time_den)};
olive::NodeInputImmediate *imm =
const_cast<olive::Node *>(node)->get_immediate(id, element);
if (!imm) {
return 0;
}
const QVector<olive::NodeKeyframe *> at_time =
imm->get_keyframe_at_time(time);
imm->get_keyframe_at_time(t);
const int n = qMin(at_time.size(), max_handles);
for (int i = 0; i < n; i++) {
out_handles[i] = wrap_kf(at_time[i]);
@@ -3870,6 +4077,92 @@ int oakengine_keyframe_get_value(const OakEngineKeyframe *self,
OAKENGINE_OK : OAKENGINE_E_INVALID;
}
// Convert a single track's component QVariant into the POD, mirroring the
// application's NodeTrackComponentToOakNodeValue helper (per-track scalar in
// f[0]/num with the input's declared type).
static bool track_component_to_pod(olive::NodeValue::Type type,
const QVariant &v, oak_node_value *out)
{
memset(out, 0, sizeof(*out));
switch (type) {
case olive::NodeValue::k_int:
out->type = OAK_NODE_VALUE_INT;
out->num = v.toLongLong();
return true;
case olive::NodeValue::k_combo:
out->type = OAK_NODE_VALUE_COMBO;
out->num = v.toLongLong();
return true;
case olive::NodeValue::k_float:
case olive::NodeValue::k_bezier:
out->type = OAK_NODE_VALUE_FLOAT;
out->f[0] = v.toDouble();
return true;
case olive::NodeValue::k_boolean:
out->type = OAK_NODE_VALUE_BOOL;
out->num = v.toBool() ? 1 : 0;
return true;
case olive::NodeValue::k_rational: {
const olive::Rational r = v.value<olive::Rational>();
out->type = OAK_NODE_VALUE_RATIONAL;
out->num = r.numerator();
out->den = r.denominator();
return true;
}
case olive::NodeValue::k_color:
out->type = OAK_NODE_VALUE_COLOR;
out->f[0] = v.toFloat();
return true;
case olive::NodeValue::k_vec2:
out->type = OAK_NODE_VALUE_VEC2;
out->f[0] = v.toFloat();
return true;
case olive::NodeValue::k_vec3:
out->type = OAK_NODE_VALUE_VEC3;
out->f[0] = v.toFloat();
return true;
case olive::NodeValue::k_vec4:
out->type = OAK_NODE_VALUE_VEC4;
out->f[0] = v.toFloat();
return true;
default:
return false;
}
}
int oakengine_keyframe_compute_paste_value(OakEngineNode *target_node,
OakEngineKeyframe *keyframe,
oak_node_value *out)
{
set_error(QString());
if (!target_node || !keyframe || !out) {
set_error(QStringLiteral("invalid arguments"));
return OAKENGINE_E_INVALID;
}
olive::Node *node = impl(target_node);
olive::NodeKeyframe *key = impl_kf(keyframe);
const QString &input_id = key->input();
if (!node->inputs().contains(input_id)) {
set_error(QStringLiteral("unknown input id \"%1\"").arg(input_id));
return OAKENGINE_E_INVALID;
}
const olive::NodeValue::Type type = node->get_input_data_type(input_id);
olive::SplitValue split = node->get_split_value_at_time(
olive::NodeInput(node, input_id, key->element()), key->time());
if (key->track() >= 0 && key->track() < split.size()) {
split[key->track()] = key->value();
}
QVector<oak_node_value> tracks(split.size());
for (int i = 0; i < split.size(); i++) {
if (!track_component_to_pod(type, split.at(i), &tracks[i])) {
set_error(QStringLiteral("unsupported value type"));
return OAKENGINE_E_INVALID;
}
}
return oakengine_node_value_combine_tracks(
to_c_type(type), tracks.constData(), tracks.size(), out);
}
int oakengine_keyframe_has_sibling_at_time(const OakEngineKeyframe *self,
int64_t time_ts, int track)
{
@@ -4574,4 +4867,538 @@ int oakengine_node_value_combine_tracks(int c_type,
return OAKENGINE_OK;
}
/* ---- Node type queries ---------------------------------------------------- */
int oakengine_node_is_clip(const OakEngineNode *self)
{
return self && dynamic_cast<const olive::ClipBlock *>(impl(self)) ? 1 : 0;
}
int oakengine_node_is_track(const OakEngineNode *self)
{
return self && dynamic_cast<const olive::Track *>(impl(self)) ? 1 : 0;
}
int oakengine_node_is_viewer_output(const OakEngineNode *self)
{
return self && dynamic_cast<const olive::ViewerOutput *>(impl(self)) ? 1 : 0;
}
int oakengine_node_is_footage(const OakEngineNode *self)
{
return self && dynamic_cast<const olive::Footage *>(impl(self)) ? 1 : 0;
}
int oakengine_node_is_sequence(const OakEngineNode *self)
{
return self && dynamic_cast<const olive::Sequence *>(impl(self)) ? 1 : 0;
}
int oakengine_node_is_folder(const OakEngineNode *self)
{
return self && dynamic_cast<const olive::Folder *>(impl(self)) ? 1 : 0;
}
/* ---- Node data (project tree columns) ------------------------------------- */
int oakengine_node_get_data(const OakEngineNode *self, int role,
int *out_type, int64_t *out_int,
char *out_str, int out_str_size)
{
if (out_type) *out_type = 0;
if (out_int) *out_int = 0;
if (out_str && out_str_size > 0) out_str[0] = '\0';
if (!self) {
return OAKENGINE_E_INVALID;
}
olive::Node::DataType dt;
switch (role) {
case 0: dt = olive::Node::icon; break;
case 1: dt = olive::Node::duration; break;
case 2: dt = olive::Node::created_time; break;
case 3: dt = olive::Node::modified_time; break;
case 4: dt = olive::Node::frequency_rate; break;
case 5: dt = olive::Node::tooltip; break;
default: return OAKENGINE_E_INVALID;
}
const QVariant v = impl(self)->data(dt);
if (!v.isValid()) {
if (out_type) *out_type = 0;
return OAKENGINE_OK;
}
switch (v.userType()) {
case QMetaType::QString: {
if (out_type) *out_type = 1;
if (out_str) string_to_buf(v.toString(), out_str, out_str_size);
break;
}
case QMetaType::LongLong:
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::ULongLong: {
if (out_type) *out_type = 2;
if (out_int) *out_int = v.toLongLong();
break;
}
default:
// Unsupported variant kind: report as invalid.
if (out_type) *out_type = 0;
break;
}
return OAKENGINE_OK;
}
int oakengine_node_get_exclusive_dependency_count(const OakEngineNode *self)
{
if (!self) {
return 0;
}
return impl(self)->get_exclusive_dependencies().size();
}
OakEngineNode *oakengine_node_get_exclusive_dependency_at(
const OakEngineNode *self, int index)
{
if (!self) {
return nullptr;
}
const QVector<olive::Node *> deps =
impl(self)->get_exclusive_dependencies();
if (index < 0 || index >= deps.size()) {
return nullptr;
}
return wrap(deps.at(index));
}
/* ---- Clip / Track specific ------------------------------------------------ */
OakEngineNode *oakengine_clip_get_track(const OakEngineNode *clip)
{
if (!clip) {
return nullptr;
}
auto *c = dynamic_cast<olive::ClipBlock *>(impl(const_cast<OakEngineNode *>(clip)));
if (!c) {
return nullptr;
}
return wrap(c->track());
}
int oakengine_track_get_type(const OakEngineNode *track)
{
if (!track) {
return -1;
}
auto *t = dynamic_cast<olive::Track *>(impl(const_cast<OakEngineNode *>(track)));
if (!t) {
return -1;
}
return static_cast<int>(t->type());
}
int oakengine_track_get_index(const OakEngineNode *track)
{
if (!track) {
return -1;
}
auto *t = dynamic_cast<olive::Track *>(impl(const_cast<OakEngineNode *>(track)));
if (!t) {
return -1;
}
return t->index();
}
OakEngineNode *oakengine_track_get_sequence(const OakEngineNode *track)
{
if (!track) {
return nullptr;
}
auto *t = dynamic_cast<olive::Track *>(impl(const_cast<OakEngineNode *>(track)));
if (!t) {
return nullptr;
}
return wrap(t->sequence());
}
int oakengine_block_get_length_rational(
const OakEngineNode *block, int *num, int *den)
{
if (!block) {
return OAKENGINE_E_INVALID;
}
auto *b = dynamic_cast<olive::Block *>(impl(const_cast<OakEngineNode *>(block)));
if (!b) {
return OAKENGINE_E_INVALID;
}
olive::Rational l = b->length();
if (num) *num = l.numerator();
if (den) *den = l.denominator();
return OAKENGINE_OK;
}
int oakengine_block_get_in_rational(
const OakEngineNode *block, int *num, int *den)
{
if (!block) {
return OAKENGINE_E_INVALID;
}
auto *b = dynamic_cast<olive::Block *>(impl(const_cast<OakEngineNode *>(block)));
if (!b) {
return OAKENGINE_E_INVALID;
}
olive::Rational v = b->in();
if (num) *num = v.numerator();
if (den) *den = v.denominator();
return OAKENGINE_OK;
}
int oakengine_block_get_out_rational(
const OakEngineNode *block, int *num, int *den)
{
if (!block) {
return OAKENGINE_E_INVALID;
}
auto *b = dynamic_cast<olive::Block *>(impl(const_cast<OakEngineNode *>(block)));
if (!b) {
return OAKENGINE_E_INVALID;
}
olive::Rational v = b->out();
if (num) *num = v.numerator();
if (den) *den = v.denominator();
return OAKENGINE_OK;
}
/* ---- ViewerOutput specific ------------------------------------------------ */
OakEngineNode *oakengine_viewer_output_get_connected_texture(
const OakEngineNode *self)
{
if (!self) {
return nullptr;
}
auto *v = dynamic_cast<olive::ViewerOutput *>(
impl(const_cast<OakEngineNode *>(self)));
if (!v) {
return nullptr;
}
return wrap(v->get_connected_texture_output());
}
/* ---- Gizmo access --------------------------------------------------------- */
int oakengine_node_has_gizmos(const OakEngineNode *self)
{
return self && impl(self)->has_gizmos() ? 1 : 0;
}
int oakengine_node_gizmo_count(const OakEngineNode *self)
{
if (!self) {
return 0;
}
return impl(self)->get_gizmos().size();
}
void *oakengine_node_gizmo_at(const OakEngineNode *self, int index)
{
if (!self) {
return nullptr;
}
const auto &gizmos = impl(self)->get_gizmos();
if (index < 0 || index >= gizmos.size()) {
return nullptr;
}
return gizmos.at(index);
}
int oakengine_node_update_gizmo_positions(
OakEngineNode *self, void *node_value_row,
int video_width, int video_height,
int64_t time_num, int64_t time_den)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
olive::Node *n = impl(self);
if (!n->has_gizmos()) {
return OAKENGINE_OK;
}
// Use the caller's NodeValueRow when provided, otherwise empty.
const olive::NodeValueRow &row = node_value_row
? *static_cast<const olive::NodeValueRow *>(node_value_row)
: olive::NodeValueRow();
olive::VideoParams vp;
if (video_width > 0 && video_height > 0) {
vp.set_width(video_width);
vp.set_height(video_height);
}
olive::NodeGlobals globals(
vp, olive::AudioParams(),
olive::Rational(time_num, time_den),
olive::LoopMode::k_loop_mode_off);
n->update_gizmo_positions(row, globals);
return OAKENGINE_OK;
}
/* ---- Graph topology ------------------------------------------------------- */
int oakengine_node_inputs_from(const OakEngineNode *self,
const OakEngineNode *other, int recursive)
{
if (!self || !other) {
return 0;
}
return impl(self)->inputs_from(
impl(const_cast<OakEngineNode *>(other)), recursive != 0) ? 1 : 0;
}
int oakengine_node_output_connection_count(const OakEngineNode *self)
{
if (!self) {
return 0;
}
return int(impl(self)->output_connections().size());
}
int oakengine_node_output_connection_at(
const OakEngineNode *self, int index, OakEngineNode **input_node,
char *input_id_buf, int input_id_size, int *element)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
const auto &conns = impl(self)->output_connections();
if (index < 0 || index >= int(conns.size())) {
return OAKENGINE_E_NOT_FOUND;
}
const auto &conn = conns[size_t(index)];
// conn.first = input Node*, conn.second = NodeInput on that node
if (input_node) {
*input_node = wrap(conn.first);
}
if (input_id_buf && input_id_size > 0) {
string_to_buf(conn.second.input(), input_id_buf, input_id_size);
}
if (element) {
*element = conn.second.element();
}
return OAKENGINE_OK;
}
int oakengine_node_output_connection_at_ex(
const OakEngineNode *self, int index, OakEngineNode **input_node,
char *input_id_buf, int input_id_size, int *element, int *hidden)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
const auto &conns = impl(self)->output_connections();
if (index < 0 || index >= int(conns.size())) {
return OAKENGINE_E_NOT_FOUND;
}
const auto &conn = conns[size_t(index)];
// conn.first = input Node*, conn.second = NodeInput on that node
if (input_node) {
*input_node = wrap(conn.first);
}
if (input_id_buf && input_id_size > 0) {
string_to_buf(conn.second.input(), input_id_buf, input_id_size);
}
if (element) {
*element = conn.second.element();
}
if (hidden) {
*hidden = conn.second.is_hidden() ? 1 : 0;
}
return OAKENGINE_OK;
}
int oakengine_node_input_connection_count_all(const OakEngineNode *self)
{
if (!self) {
return 0;
}
return int(impl(self)->input_connections().size());
}
int oakengine_node_input_connection_at_all(
const OakEngineNode *self, int index, OakEngineNode **input_node,
char *input_id_buf, int input_id_size, int *element,
OakEngineNode **source_node, int *hidden)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
const auto &conns = impl(self)->input_connections();
if (index < 0 || index >= int(conns.size())) {
return OAKENGINE_E_NOT_FOUND;
}
auto it = conns.cbegin();
for (int i = 0; i < index; i++) {
++it;
}
// it->first = NodeInput (the connected input on this node),
// it->second = source Node* feeding it.
if (input_node) {
*input_node = wrap(it->first.node());
}
if (input_id_buf && input_id_size > 0) {
string_to_buf(it->first.input(), input_id_buf, input_id_size);
}
if (element) {
*element = it->first.element();
}
if (source_node) {
*source_node = wrap(it->second);
}
if (hidden) {
*hidden = it->first.is_hidden() ? 1 : 0;
}
return OAKENGINE_OK;
}
int oakengine_node_input_connection_count(
const OakEngineNode *self, const char *input_id, int element)
{
if (!self || !input_id) {
return 0;
}
const auto &conns = impl(self)->input_connections();
int count = 0;
for (auto it = conns.cbegin(); it != conns.cend(); it++) {
if (it->first.input() == QString::fromUtf8(input_id) &&
it->first.element() == element) {
count++;
}
}
return count;
}
OakEngineNode *oakengine_node_input_connection_at(
const OakEngineNode *self, const char *input_id, int element, int index)
{
if (!self || !input_id || index < 0) {
return nullptr;
}
const auto &conns = impl(self)->input_connections();
int seen = 0;
for (auto it = conns.cbegin(); it != conns.cend(); it++) {
if (it->first.input() == QString::fromUtf8(input_id) &&
it->first.element() == element) {
if (seen == index) {
return wrap(it->second);
}
seen++;
}
}
return nullptr;
}
/* ---- Plugin messages ------------------------------------------------------ */
int oakengine_node_has_plugin(const OakEngineNode *self)
{
if (!self) {
return 0;
}
return impl(self)->getPluginInstance() != nullptr ? 1 : 0;
}
int oakengine_node_plugin_message_count(const OakEngineNode *self)
{
if (!self) {
return 0;
}
auto *instance = impl(self)->getPluginInstance();
auto *olive_inst =
dynamic_cast<olive::plugin::OlivePluginInstance *>(instance);
if (!olive_inst) {
return 0;
}
return olive_inst->persistent_message_count();
}
int oakengine_node_plugin_message_at(
const OakEngineNode *self, int index, int *type, char *msg_buf,
int msg_buf_size)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
auto *instance = impl(self)->getPluginInstance();
auto *olive_inst =
dynamic_cast<olive::plugin::OlivePluginInstance *>(instance);
if (!olive_inst) {
return OAKENGINE_E_NOT_FOUND;
}
const auto &msgs = olive_inst->persistent_messages();
if (index < 0 || index >= msgs.size()) {
return OAKENGINE_E_NOT_FOUND;
}
if (type) {
switch (msgs.at(index).type) {
case olive::plugin::ErrorType::error:
*type = 0;
break;
case olive::plugin::ErrorType::warning:
*type = 1;
break;
default:
*type = 2;
break;
}
}
if (msg_buf && msg_buf_size > 0) {
string_to_buf(msgs.at(index).message, msg_buf, msg_buf_size);
}
return OAKENGINE_OK;
}
int oakengine_node_plugin_clear_messages(OakEngineNode *self)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
auto *instance = impl(self)->getPluginInstance();
auto *olive_inst =
dynamic_cast<olive::plugin::OlivePluginInstance *>(instance);
if (!olive_inst) {
return OAKENGINE_E_NOT_FOUND;
}
olive_inst->clearPersistentMessage();
return OAKENGINE_OK;
}
/* ---- Node cache objects ------------------------------------------------------ */
OakEngineThumbnailCache *
oakengine_node_get_thumbnail_cache(const OakEngineNode *self)
{
if (!self) {
return nullptr;
}
return reinterpret_cast<OakEngineThumbnailCache *>(
impl(self)->thumbnail_cache());
}
OakEngineWaveformCache *
oakengine_node_get_waveform_cache(const OakEngineNode *self)
{
if (!self) {
return nullptr;
}
return reinterpret_cast<OakEngineWaveformCache *>(
impl(self)->waveform_cache());
}
OakEngineFrameCache *
oakengine_node_get_video_frame_cache(const OakEngineNode *self)
{
if (!self) {
return nullptr;
}
return reinterpret_cast<OakEngineFrameCache *>(
impl(self)->video_frame_cache());
}
} // extern "C"
+29
View File
@@ -475,6 +475,35 @@ int oakengine_folder_index_of_child(const OakEngineNode *folder,
return idx >= 0 ? idx : OAKENGINE_E_NOT_FOUND;
}
int oakengine_folder_item_child_count(const OakEngineNode *folder)
{
if (!folder) {
return 0;
}
const olive::Folder *f =
dynamic_cast<const olive::Folder *>(impl(
const_cast<OakEngineNode *>(folder)));
if (!f) {
return 0;
}
return f->item_child_count();
}
OakEngineNode *oakengine_folder_item_child(const OakEngineNode *folder,
int index)
{
if (!folder) {
return nullptr;
}
const olive::Folder *f =
dynamic_cast<const olive::Folder *>(impl(
const_cast<OakEngineNode *>(folder)));
if (!f || index < 0 || index >= f->item_child_count()) {
return nullptr;
}
return reinterpret_cast<OakEngineNode *>(f->item_child(index));
}
const char *oakengine_folder_child_input_key(void)
{
static const QByteArray s = olive::Folder::k_child_input.toUtf8();
+174
View File
@@ -30,6 +30,7 @@
#include "node/block/block.h"
#include "node/block/clip/clip.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "node/nodeundo.h"
#include "node/output/track/track.h"
#include "node/project.h"
@@ -2908,6 +2909,14 @@ int oakengine_block_is_gap(const OakEngineBlock *block)
? 1 : 0;
}
OakEngineTrack *oakengine_block_get_track(const OakEngineBlock *block)
{
if (!block) {
return nullptr;
}
return reinterpret_cast<OakEngineTrack *>(block_impl(block)->track());
}
OakEngineBlock *oakengine_block_next(const OakEngineBlock *block)
{
if (!block) {
@@ -2945,4 +2954,169 @@ int oakengine_block_get_range(const OakEngineBlock *block, int64_t *in,
return OAKENGINE_OK;
}
/* ---- Track lists, block/clip/transition navigation and links -------------- */
OakEngineTrackList *oakengine_sequence_track_list(OakEngineSequence *seq,
int track_type)
{
if (!seq || track_type < OAKENGINE_TRACK_TYPE_VIDEO ||
track_type > OAKENGINE_TRACK_TYPE_SUBTITLE) {
return nullptr;
}
return reinterpret_cast<OakEngineTrackList *>(
impl(seq)->track_list(to_track_type(track_type)));
}
OakEngineBlock *
oakengine_track_visible_block_at_time(OakEngineTrack *track, int64_t time_ts)
{
if (!track) {
return nullptr;
}
const olive::Rational tb = track_time_base(track_impl(track));
const olive::Rational time = track_ts_to_time(time_ts, tb);
olive::Block *b = track_impl(track)->visible_block_at_time(time);
return reinterpret_cast<OakEngineBlock *>(b);
}
int oakengine_node_is_block(const OakEngineNode *node)
{
if (!node) {
return 0;
}
return dynamic_cast<const olive::Block *>(
reinterpret_cast<const olive::Node *>(node))
? 1
: 0;
}
int oakengine_node_is_transition(const OakEngineNode *node)
{
if (!node) {
return 0;
}
return dynamic_cast<const olive::TransitionBlock *>(
reinterpret_cast<const olive::Node *>(node))
? 1
: 0;
}
int oakengine_block_set_length_and_media_out(OakEngineBlock *block,
int64_t length_ts)
{
set_seq_error(QString());
if (!block || length_ts <= 0) {
set_seq_error(QStringLiteral("invalid arguments"));
return OAKENGINE_E_INVALID;
}
olive::Block *b = block_impl(block);
if (!b->track()) {
set_seq_error(QStringLiteral("block is not on a track"));
return OAKENGINE_E_STATE;
}
const olive::Rational tb = track_time_base(b->track());
push_or_run(new olive::BlockResizeCommand(
b, track_ts_to_time(length_ts, tb)),
QStringLiteral("Set Block Length"));
return OAKENGINE_OK;
}
int oakengine_block_link_count(const OakEngineBlock *block)
{
if (!block) {
return 0;
}
int count = 0;
for (olive::Node *n : block_impl(block)->links()) {
if (dynamic_cast<olive::Block *>(n)) {
count++;
}
}
return count;
}
OakEngineBlock *oakengine_block_link_at(const OakEngineBlock *block, int index)
{
if (!block || index < 0) {
return nullptr;
}
int seen = 0;
for (olive::Node *n : block_impl(block)->links()) {
if (olive::Block *b = dynamic_cast<olive::Block *>(n)) {
if (seen == index) {
return reinterpret_cast<OakEngineBlock *>(b);
}
seen++;
}
}
return nullptr;
}
OakEngineBlock *oakengine_clip_in_transition(const OakEngineBlock *clip)
{
if (!clip) {
return nullptr;
}
olive::ClipBlock *c = dynamic_cast<olive::ClipBlock *>(
const_cast<olive::Block *>(block_impl(clip)));
if (!c) {
return nullptr;
}
return reinterpret_cast<OakEngineBlock *>(c->in_transition());
}
OakEngineBlock *oakengine_clip_out_transition(const OakEngineBlock *clip)
{
if (!clip) {
return nullptr;
}
olive::ClipBlock *c = dynamic_cast<olive::ClipBlock *>(
const_cast<olive::Block *>(block_impl(clip)));
if (!c) {
return nullptr;
}
return reinterpret_cast<OakEngineBlock *>(c->out_transition());
}
OakEngineBlock *
oakengine_transition_connected_in_block(const OakEngineBlock *transition)
{
if (!transition) {
return nullptr;
}
const olive::TransitionBlock *t =
dynamic_cast<const olive::TransitionBlock *>(block_impl(transition));
if (!t) {
return nullptr;
}
return reinterpret_cast<OakEngineBlock *>(t->connected_in_block());
}
OakEngineBlock *
oakengine_transition_connected_out_block(const OakEngineBlock *transition)
{
if (!transition) {
return nullptr;
}
const olive::TransitionBlock *t =
dynamic_cast<const olive::TransitionBlock *>(block_impl(transition));
if (!t) {
return nullptr;
}
return reinterpret_cast<OakEngineBlock *>(t->connected_out_block());
}
OakEngineNode *oakengine_clip_get_connected_viewer(const OakEngineBlock *clip)
{
if (!clip) {
return nullptr;
}
const olive::ClipBlock *c =
dynamic_cast<const olive::ClipBlock *>(block_impl(clip));
if (!c) {
return nullptr;
}
return reinterpret_cast<OakEngineNode *>(c->connected_viewer());
}
}
+122
View File
@@ -32,6 +32,7 @@
#include "node/block/clip/clip.h"
#include "node/nodeundo.h"
#include "node/param.h"
#include "render/audiowaveformcache.h"
#include "render/playbackcache.h"
#include "render/framehashcache.h"
#include "render/videoparams.h"
@@ -616,4 +617,125 @@ OakEngineFrameCache *oakengine_viewer_get_frame_cache(OakEngineNode *self)
return nullptr;
}
/* ---- Playback cache accessors ------------------------------------------------ */
int oakengine_playback_cache_has_validated_ranges(const void *cache)
{
if (!cache) {
return 0;
}
return reinterpret_cast<const olive::PlaybackCache *>(cache)
->has_validated_ranges()
? 1
: 0;
}
OakEngineNode *oakengine_playback_cache_parent(void *cache)
{
if (!cache) {
return nullptr;
}
return reinterpret_cast<OakEngineNode *>(
reinterpret_cast<olive::PlaybackCache *>(cache)->parent());
}
void oakengine_playback_cache_draw(void *cache, void *qpainter, int64_t in_ts,
double scale, int height)
{
if (!cache || !qpainter) {
return;
}
olive::PlaybackCache *pc =
reinterpret_cast<olive::PlaybackCache *>(cache);
QPainter *painter = static_cast<QPainter *>(qpainter);
// Timebase of the cache's parent viewer (frame rate flipped), falling
// back to the engine default when the cache has no viewer parent.
olive::Rational tb(1001, 30000);
if (olive::ViewerOutput *v =
dynamic_cast<olive::ViewerOutput *>(pc->parent())) {
const olive::Rational fr = v->get_video_params().frame_rate();
if (!fr.isNull() && !fr.isNaN()) {
tb = fr.flipped();
}
}
const olive::Rational start =
olive::core::Timecode::timestamp_to_time(in_ts, tb);
const QRect viewport = painter->viewport();
pc->draw(painter, start, scale,
QRect(viewport.x(), viewport.y(), viewport.width(), height));
}
/* ---- Audio waveform cache accessors ------------------------------------------- */
int64_t oakengine_waveform_cache_length(const void *cache)
{
if (!cache) {
return 0;
}
const olive::AudioWaveformCache *wc =
reinterpret_cast<const olive::AudioWaveformCache *>(cache);
const olive::Rational tb = wc->get_parameters().sample_rate_as_time_base();
if (tb.isNull() || tb.isNaN()) {
return 0;
}
return olive::core::Timecode::time_to_timestamp(
wc->length(), tb, olive::core::Timecode::k_round);
}
int oakengine_waveform_cache_sample_rate(const void *cache)
{
if (!cache) {
return 0;
}
return reinterpret_cast<const olive::AudioWaveformCache *>(cache)
->get_parameters()
.sample_rate();
}
int oakengine_waveform_cache_has_validated_ranges(const void *cache)
{
if (!cache) {
return 0;
}
return reinterpret_cast<const olive::AudioWaveformCache *>(cache)
->has_validated_ranges()
? 1
: 0;
}
int oakengine_waveform_cache_get_summary(const void *cache, int64_t start_ts,
int64_t end_ts, double *min_out,
double *max_out, int max_channels,
int *channels_out)
{
if (channels_out) {
*channels_out = 0;
}
if (!cache || !min_out || !max_out || max_channels < 0 ||
end_ts < start_ts) {
return OAKENGINE_E_INVALID;
}
const olive::AudioWaveformCache *wc =
reinterpret_cast<const olive::AudioWaveformCache *>(cache);
const int sample_rate = wc->get_parameters().sample_rate();
if (sample_rate <= 0) {
return OAKENGINE_E_STATE;
}
const olive::AudioVisualWaveform::Sample summary =
wc->get_summary_from_time(
olive::Rational(start_ts, sample_rate),
olive::Rational(end_ts - start_ts, sample_rate));
const int count = qMin(max_channels, int(summary.size()));
for (int i = 0; i < count; i++) {
min_out[i] = summary[size_t(i)].min;
max_out[i] = summary[size_t(i)].max;
}
if (channels_out) {
*channels_out = count;
}
return OAKENGINE_OK;
}
} // extern "C"