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
+43
View File
@@ -147,6 +147,49 @@ OAKENGINE_API void *oakengine_disk_get_open_folder(const char *path);
OAKENGINE_API int oakengine_disk_invalidate_project(
OakEngineProject *project);
/* ---- DiskCacheFolder accessors -------------------------------------------------
*
* Accessors for a borrowed folder handle from
* oakengine_disk_get_open_folder() (olive::DiskCacheFolder, passed as a
* plain `void *` like it is returned). The limit is a byte count carried
* as a double (the engine stores a qint64; a double is exact up to
* 2**53 bytes, far beyond any cache size).
*/
/**
* @brief Cache size limit of the folder in bytes
* (DiskCacheFolder::get_limit()). 0 on a NULL handle.
*/
OAKENGINE_API double oakengine_disk_folder_get_limit(const void *folder);
/**
* @brief Set the cache size limit in bytes
* (DiskCacheFolder::set_limit()). `limit` < 0 yields
* OAKENGINE_E_INVALID.
*/
OAKENGINE_API int oakengine_disk_folder_set_limit(void *folder, double limit);
/**
* @brief 1 if the folder is cleared when the application closes
* (DiskCacheFolder::get_clear_on_close()). 0 on a NULL handle.
*/
OAKENGINE_API int oakengine_disk_folder_get_clear_on_close(
const void *folder);
/**
* @brief Set the clear-on-close flag (DiskCacheFolder::set_clear_on_close()).
* Returns OAKENGINE_OK or OAKENGINE_E_INVALID.
*/
OAKENGINE_API int oakengine_disk_folder_set_clear_on_close(void *folder,
int clear);
/**
* @brief The folder's path (DiskCacheFolder::get_path(); buf/size
* convention). Returns OAKENGINE_E_INVALID on a NULL handle.
*/
OAKENGINE_API int oakengine_disk_folder_get_path(const void *folder,
char *buf, int buf_size);
#ifdef __cplusplus
}
#endif
+11
View File
@@ -236,6 +236,17 @@ OAKENGINE_API OakEngineFootage *oakengine_project_import_footage(
*/
OAKENGINE_API OakEngineFootage *oakengine_footage_borrow(OakEngineNode *node);
/**
* @brief 1 if the footage node is valid (Footage::is_valid(): the media
* was probed successfully and is ready to use), 0 otherwise.
*
* Takes the footage NODE handle directly (like oakengine_footage_borrow())
* rather than an OakEngineFootage wrapper, so callers that already hold a
* project node do not need to borrow/free a wrapper for a one-shot check.
* Returns 0 on a NULL handle or a node that is not a Footage.
*/
OAKENGINE_API int oakengine_footage_is_valid(const OakEngineNode *node);
/* ---- Media management: relink and proxies -----------------------------------
*
* These functions operate on BORROWED import handles (footage nodes living
+42
View File
@@ -156,6 +156,48 @@ OAKENGINE_API int oakengine_gizmo_drag_move(void *gizmo,
*/
OAKENGINE_API int oakengine_gizmo_drag_end(void *gizmo, void *command);
/**
* @brief 1 if the gizmo is visible (NodeGizmo::is_visible()).
*/
OAKENGINE_API int oakengine_gizmo_is_visible(void *gizmo);
/**
* @brief Draw the gizmo using the given QPainter (passed as void*).
* The painter must be a valid QPainter*. Returns OAKENGINE_OK or
* OAKENGINE_E_INVALID.
*/
OAKENGINE_API int oakengine_gizmo_draw(void *gizmo, void *painter);
/**
* @brief Set the globals on a gizmo (NodeGizmo::set_globals()).
* `video_width`/`video_height` describe the resolution; `time_num`/
* `time_den` are rational seconds.
*/
OAKENGINE_API int oakengine_gizmo_set_globals(void *gizmo,
int video_width, int video_height,
int64_t time_num, int64_t time_den);
/**
* @brief Unified gizmo hit-test used by the viewer to pick a gizmo under
* the cursor (replaces the app-side dynamic_cast chain over PointGizmo /
* PolygonGizmo / PathGizmo / ScreenGizmo).
*
* Returns 1 when the gizmo is visible AND the point (`px`,`py`, in gizmo
* scene space) hits it, 0 otherwise. `transform6` is the affine QTransform
* used for drawing, passed as six doubles in the order
* {m11, m12, m21, m22, dx, dy} (it is only needed by point gizmos, whose
* clicking rect depends on the draw transform; other types may ignore it).
*
* Per-type semantics mirror the original viewer logic:
* - PointGizmo: get_clicking_rect(transform).contains(p)
* - PolygonGizmo: get_polygon().containsPoint(p, Qt::OddEvenFill)
* - PathGizmo: get_path().contains(p)
* - ScreenGizmo: always hittable (returns 1 when visible)
* - TextGizmo / other: never hittable via this call (returns 0)
*/
OAKENGINE_API int oakengine_gizmo_hit_test(void *gizmo,
const double *transform6, double px, double py);
#ifdef __cplusplus
}
#endif
+469 -9
View File
@@ -170,6 +170,75 @@ OAKENGINE_API int oakengine_node_factory_name_from_id(const char *type_id,
OAKENGINE_API OakEngineNode *
oakengine_node_factory_node_at(int index);
/**
* @brief Number of category IDs assigned to this node
* (Node::category().size()). 0 for NULL.
*/
OAKENGINE_API int oakengine_node_category_count(const OakEngineNode *self);
/**
* @brief The category ID (Node::CategoryID ordinal) at `index` in the
* node's category list (Node::category()). Returns -1 for NULL or an
* out-of-range index.
*/
OAKENGINE_API int oakengine_node_category_at(const OakEngineNode *self,
int index);
/**
* @brief The node's flags (Node::get_flags()), an OR-combination of
* Node::Flag values. 0 for NULL.
*/
OAKENGINE_API uint64_t oakengine_node_get_flags(const OakEngineNode *self);
/**
* @brief The value of the Node::k_dont_show_in_create_menu flag.
*/
OAKENGINE_API uint64_t oakengine_node_flag_dont_show_in_create_menu(void);
/**
* @brief The value of the Node::k_dont_show_in_param_view flag.
*/
OAKENGINE_API uint64_t oakengine_node_flag_dont_show_in_param_view(void);
/**
* @brief The value of the Node::k_video_effect flag.
*/
OAKENGINE_API uint64_t oakengine_node_flag_video_effect(void);
/**
* @brief The value of the Node::k_audio_effect flag.
*/
OAKENGINE_API uint64_t oakengine_node_flag_audio_effect(void);
/**
* @brief Refresh the node's translated strings (Node::retranslate()).
* No-op for NULL.
*/
OAKENGINE_API void oakengine_node_retranslate(OakEngineNode *self);
/**
* @brief The node's sub-category for secondary grouping
* (Node::sub_category()). buf/size convention.
*/
OAKENGINE_API int oakengine_node_get_sub_category(const OakEngineNode *self,
char *buf, int buf_size);
/**
* @brief The node's description (Node::description()). buf/size
* convention.
*/
OAKENGINE_API int oakengine_node_get_description(const OakEngineNode *self,
char *buf, int buf_size);
/**
* @brief Create a copy of the node (Node::copy()). Unlike
* oakengine_node_copy_in_graph(), the copy is standalone: the caller
* owns it and it is NOT added to any project or undo command.
* Returns NULL for NULL.
*/
OAKENGINE_API OakEngineNode *
oakengine_node_create_copy(const OakEngineNode *self);
/* ---- Metadata -------------------------------------------------------------- */
/**
@@ -186,6 +255,13 @@ OAKENGINE_API int oakengine_node_get_type_id(const OakEngineNode *self,
OAKENGINE_API int oakengine_node_get_name(const OakEngineNode *self,
char *buf, int buf_size);
/**
* @brief The node's short display name (Node::short_name(), the virtual
* used by the node graph item). buf/size convention.
*/
OAKENGINE_API int oakengine_node_get_short_name(const OakEngineNode *self,
char *buf, int buf_size);
/**
* @brief The node's user label (Node::get_label()). buf/size convention.
*/
@@ -266,6 +342,27 @@ OAKENGINE_API void *oakengine_node_set_color_label_command(
*/
OAKENGINE_API int oakengine_node_get_color_label(const OakEngineNode *self);
/**
* @brief The node's effective color-label index (Node::color()'s index:
* the override color when set, otherwise the category-based "CatColor<N>"
* config value). Feed into the app's ColorCoding::get_color().
*/
OAKENGINE_API int oakengine_node_get_effective_color_label(
const OakEngineNode *self);
/**
* @brief The node's title-bar brush (Node::brush()), written into a
* caller-provided QBrush.
*
* QBrush is a Qt value type and crosses the ABI as an opaque pointer
* (same precedent as QPainter* in oakengine_playback_cache_draw()):
* `out_qbrush` must point to a live, constructed QBrush which receives
* the result via copy assignment. No-op for NULL arguments.
*/
OAKENGINE_API void oakengine_node_get_brush(const OakEngineNode *self,
double top, double bottom,
void *out_qbrush);
/* ---- Input introspection ---------------------------------------------------- */
/**
@@ -454,6 +551,22 @@ OAKENGINE_API int oakengine_project_remove_node(OakEngineProject *project,
*/
OAKENGINE_API void oakengine_node_delete_later(OakEngineNode *node);
/**
* @brief Destroy an OWNED node immediately (C++ `delete`). NULL-safe
* no-op.
*
* ONLY valid for owned handles that were never added to a project and
* never referenced by an undo command -- i.e. the products of
* oakengine_node_factory_create_from_id(), oakengine_node_create_copy()
* and oakengine_clip_create_empty() while they are still orphaned. Once a
* node lives in a project graph its lifetime belongs to the project (and
* to any undo command referencing it); freeing such a node, or freeing
* the same owned handle twice, is a use-after-free. Unlike
* oakengine_node_delete_later() the destruction is synchronous and does
* not need an event loop.
*/
OAKENGINE_API void oakengine_node_free(OakEngineNode *node);
/**
* @brief Connect `output_node`'s output into `input_node`'s `input_id`
* (undoable, olive::NodeEdgeAddCommand).
@@ -759,6 +872,13 @@ OAKENGINE_API int oakengine_node_input_array_size(
OAKENGINE_API int oakengine_node_input_get_flags(
const OakEngineNode *self, const char *input_id);
/**
* @brief The input's data type (NodeValue::Type enum ordinal; -1 on NULL
* or unknown input).
*/
OAKENGINE_API int oakengine_node_input_get_data_type(
const OakEngineNode *self, const char *input_id);
/**
* @brief 1 if the input can accept a connection (connectable).
*/
@@ -772,11 +892,18 @@ OAKENGINE_API int oakengine_node_input_is_keyframable(
const OakEngineNode *self, const char *input_id);
/**
* @brief 1 if keyframing is enabled for this input on any track
* (keyframed_ex; pass -1 for all tracks).
* @brief 1 if the input is hidden (k_input_flag_hidden).
*/
OAKENGINE_API int oakengine_node_input_is_hidden(
const OakEngineNode *self, const char *input_id);
/**
* @brief 1 if keyframing is enabled for this input
* (Node::is_input_keyframing()). `element` addresses the input's array
* element (-1 for non-array inputs).
*/
OAKENGINE_API int oakengine_node_input_is_keyframed_ex(
const OakEngineNode *self, const char *input_id, int track);
const OakEngineNode *self, const char *input_id, int element);
/**
* @brief The node's label and name combined (buf/size).
@@ -804,6 +931,26 @@ OAKENGINE_API int oakengine_node_input_get_default_value(
OAKENGINE_API OakEngineProject *oakengine_node_get_project(
const OakEngineNode *self);
/**
* @brief The node's parent project (Node::parent(); NULL on NULL input).
* Same value as oakengine_node_get_project(); provided for graph-parent
* semantics parity with the engine API.
*/
OAKENGINE_API OakEngineProject *oakengine_node_parent(
const OakEngineNode *self);
/**
* @brief 1 if the node is an "item" (Node::is_item(), i.e. appears in
* the project tree / footage management).
*/
OAKENGINE_API int oakengine_node_is_item(const OakEngineNode *self);
/**
* @brief The folder this item node belongs to (Node::folder(); NULL if
* the node is not an item or has no folder).
*/
OAKENGINE_API OakEngineNode *oakengine_node_folder(const OakEngineNode *self);
/**
* @brief The node connected to the input, or NULL (element -1 for
* non-array inputs).
@@ -893,6 +1040,20 @@ OAKENGINE_API int oakengine_node_input_get_property_rational(
const OakEngineNode *self, const char *input_id, const char *key,
int *num, int *den);
/**
* @brief Read a numeric input property as the per-track component for
* `track` (the curve view's "offset" path).
*
* The property value (the input's declared type, e.g. a QVector2D for a
* vec2 input) is split into its keyframe tracks and the `track`-th
* component is returned in `out`. For single-track types `track` must be
* 0. Returns OAKENGINE_OK, OAKENGINE_E_NOT_FOUND when the property is
* missing, or OAKENGINE_E_INVALID for a non-numeric property / bad track.
*/
OAKENGINE_API int oakengine_node_input_get_property_track_number(
const OakEngineNode *self, const char *input_id, const char *key,
int track, double *out);
/**
* @brief The number of properties on the input.
*/
@@ -1297,21 +1458,22 @@ OAKENGINE_API OakEngineKeyframe *oakengine_node_keyframe_handle_on_track(
int track, int index);
/**
* @brief Borrowed handle of the keyframe at the given time on a track,
* or NULL.
* @brief Borrowed handle of the keyframe at the given rational time on a
* track, or NULL (Node::get_keyframe_at_time_on_track()).
*/
OAKENGINE_API 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);
/**
* @brief Fill an array with keyframe handles at a given time. Returns
* @brief Fill an array with the keyframe handles at a given rational time
* across all tracks of the input (Node::get_keyframes_at_time()). Returns
* the number filled.
*/
OAKENGINE_API int oakengine_node_keyframes_at_time(
const OakEngineNode *self, const char *input_id, int element,
int64_t time_ts, int track, OakEngineKeyframe **out_handles,
int max_handles);
int64_t time_num, int64_t time_den,
OakEngineKeyframe **out_handles, int max_handles);
/**
* @brief Enable or disable keyframing on an input for a given element
@@ -1391,6 +1553,20 @@ OAKENGINE_API int oakengine_keyframe_opposing_bezier_type(int type);
OAKENGINE_API int oakengine_keyframe_get_value(
const OakEngineKeyframe *self, oak_node_value *out);
/**
* @brief Compute the combined node value to use when inserting
* `keyframe` onto `target_node` (the keyframe paste path).
*
* Takes the target node's split value at the keyframe's time, replaces
* the keyframe's own track with the keyframe's value, and combines the
* per-track components into a single normal value (mirrors the old
* app-side KeyframeToOakNodeValue helper). Returns OAKENGINE_OK on
* success.
*/
OAKENGINE_API int oakengine_keyframe_compute_paste_value(
OakEngineNode *target_node, OakEngineKeyframe *keyframe,
oak_node_value *out);
/**
* @brief 1 if there is a sibling keyframe at the given time on a different
* track of the same input.
@@ -1615,6 +1791,290 @@ OAKENGINE_API int oakengine_node_value_split_to_tracks(int c_type,
OAKENGINE_API int oakengine_node_value_combine_tracks(int c_type,
const oak_node_value *tracks, int track_count, oak_node_value *normal_out);
/* ---- Node type queries (dynamic_cast replacements) ------------------------- */
/**
* @brief 1 if the node is a ClipBlock (or subclass thereof).
*/
OAKENGINE_API int oakengine_node_is_clip(const OakEngineNode *self);
/**
* @brief 1 if the node is a Track.
*/
OAKENGINE_API int oakengine_node_is_track(const OakEngineNode *self);
/**
* @brief 1 if the node is a ViewerOutput (or subclass: Sequence, Footage).
*/
OAKENGINE_API int oakengine_node_is_viewer_output(const OakEngineNode *self);
/**
* @brief 1 if the node is a Footage.
*/
OAKENGINE_API int oakengine_node_is_footage(const OakEngineNode *self);
/**
* @brief 1 if the node is a Sequence.
*/
OAKENGINE_API int oakengine_node_is_sequence(const OakEngineNode *self);
/**
* @brief 1 if the node is a Folder.
*/
OAKENGINE_API int oakengine_node_is_folder(const OakEngineNode *self);
/* ---- Clip / Track specific ------------------------------------------------- */
/**
* @brief The track that owns this clip block (ClipBlock::track()).
* Returns NULL when the node is not a clip or has no parent track.
*/
OAKENGINE_API OakEngineNode *oakengine_clip_get_track(
const OakEngineNode *clip);
/**
* @brief The track type (Track::Type enum: 0=video, 1=audio, 2=subtitle;
* -1 for NULL or non-track node).
*/
OAKENGINE_API int oakengine_track_get_type(const OakEngineNode *track);
/**
* @brief The track index within its sequence (-1 for NULL or non-track).
*/
OAKENGINE_API int oakengine_track_get_index(const OakEngineNode *track);
/**
* @brief The sequence that owns this track (Track::sequence()). Returns
* NULL when the node is not a track or the track has no parent sequence.
*/
OAKENGINE_API OakEngineNode *oakengine_track_get_sequence(
const OakEngineNode *track);
/**
* @brief The block's length as rational seconds (out - in).
* Returns OAKENGINE_OK or OAKENGINE_E_INVALID.
*/
OAKENGINE_API int oakengine_block_get_length_rational(
const OakEngineNode *block, int *num, int *den);
/**
* @brief The block's in-point as rational seconds.
*/
OAKENGINE_API int oakengine_block_get_in_rational(
const OakEngineNode *block, int *num, int *den);
/**
* @brief The block's out-point as rational seconds.
*/
OAKENGINE_API int oakengine_block_get_out_rational(
const OakEngineNode *block, int *num, int *den);
/* ---- ViewerOutput specific ------------------------------------------------- */
/**
* @brief The node connected to the viewer's texture input
* (ViewerOutput::get_connected_texture_output()). NULL when none.
*/
OAKENGINE_API OakEngineNode *oakengine_viewer_output_get_connected_texture(
const OakEngineNode *self);
/* ---- Gizmo access ---------------------------------------------------------- */
/**
* @brief 1 if the node has any gizmos (Node::has_gizmos()).
*/
OAKENGINE_API int oakengine_node_has_gizmos(const OakEngineNode *self);
/**
* @brief Number of gizmos on the node (Node::get_gizmos().size()).
*/
OAKENGINE_API int oakengine_node_gizmo_count(const OakEngineNode *self);
/**
* @brief Borrowed opaque gizmo handle at `index`, or NULL when out of
* range. The handle is a NodeGizmo* internally; use gizmo.h APIs.
*/
OAKENGINE_API void *oakengine_node_gizmo_at(const OakEngineNode *self,
int index);
/**
* @brief Recalculate gizmo positions for the given time
* (Node::update_gizmo_positions()). `node_value_row` is an opaque
* pointer to the engine's NodeValueRow (the traverse result); pass NULL
* for an empty row. `time_num`/`time_den` are rational seconds.
* `video_width`/`video_height` describe the resolution context.
* No-op for NULL or nodes without gizmos.
*/
OAKENGINE_API 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);
/* ---- Graph topology -------------------------------------------------------- */
/**
* @brief 1 if this node directly (or recursively when `recursive` != 0)
* receives input from `other` (Node::inputs_from()).
*/
OAKENGINE_API int oakengine_node_inputs_from(const OakEngineNode *self,
const OakEngineNode *other,
int recursive);
/**
* @brief Number of output connections from this node
* (Node::output_connections().size()).
*/
OAKENGINE_API int oakengine_node_output_connection_count(
const OakEngineNode *self);
/**
* @brief Read the output connection at `index`: the receiving node into
* `*input_node`, the input id into `input_id_buf` (buf/size), and the
* array element into `*element`. Returns OAKENGINE_OK or
* OAKENGINE_E_NOT_FOUND for out-of-range.
*/
OAKENGINE_API int oakengine_node_output_connection_at(
const OakEngineNode *self, int index, OakEngineNode **input_node,
char *input_id_buf, int input_id_size, int *element);
/**
* @brief Like oakengine_node_output_connection_at(), additionally reporting
* whether the receiving input is hidden (`*hidden` = 1 when
* NodeInput::is_hidden()). `hidden` may be NULL.
*/
OAKENGINE_API 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);
/**
* @brief Total number of input connections on this node
* (Node::input_connections().size()), i.e. a flat enumeration over all
* connected inputs regardless of input id/element.
*/
OAKENGINE_API int oakengine_node_input_connection_count_all(
const OakEngineNode *self);
/**
* @brief Read the input connection at flat `index` (Node::input_connections()
* iteration order): the connected input lives on `*input_node` with id
* `input_id_buf` (buf/size) and `*element`; `*source_node` receives the
* output node feeding it; `*hidden` reports NodeInput::is_hidden() (may be
* NULL). Returns OAKENGINE_OK or OAKENGINE_E_NOT_FOUND for out-of-range.
*/
OAKENGINE_API 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);
/**
* @brief Number of connections feeding a specific input
* (Node::input_connections() filtered by input_id/element).
*/
OAKENGINE_API int oakengine_node_input_connection_count(
const OakEngineNode *self, const char *input_id, int element);
/**
* @brief The output node feeding `input_id`/`element` at connection
* `index`. Returns NULL when out of range or not connected.
*/
OAKENGINE_API OakEngineNode *oakengine_node_input_connection_at(
const OakEngineNode *self, const char *input_id, int element,
int index);
/* ---- Node data (project tree columns) ------------------------------------- */
/**
* @brief Read a node's display data (Node::data(DataType)) as a POD.
*
* `role` selects the data kind: 0=icon, 1=duration, 2=created_time,
* 3=modified_time, 4=frequency_rate, 5=tooltip. On return `*out_type`
* describes the variant: 0=invalid (no data), 1=string (written to
* `out_str` using buf/size), 2=int64 (written to `*out_int`). Any of the
* out pointers may be NULL. Returns OAKENGINE_OK or OAKENGINE_E_INVALID.
*/
OAKENGINE_API int oakengine_node_get_data(const OakEngineNode *self, int role,
int *out_type, int64_t *out_int,
char *out_str, int out_str_size);
/**
* @brief Number of exclusive dependencies (Node::get_exclusive_dependencies()
* size): nodes that should be removed together with this node.
*/
OAKENGINE_API int oakengine_node_get_exclusive_dependency_count(
const OakEngineNode *self);
/**
* @brief Borrowed handle of the exclusive dependency at `index`, or NULL
* when out of range.
*/
OAKENGINE_API OakEngineNode *oakengine_node_get_exclusive_dependency_at(
const OakEngineNode *self, int index);
/* ---- Plugin messages ------------------------------------------------------- */
/**
* @brief 1 if the node has an OFX plugin instance attached
* (Node::getPluginInstance() != nullptr), 0 otherwise.
*/
OAKENGINE_API int oakengine_node_has_plugin(const OakEngineNode *self);
/**
* @brief Number of persistent messages on the node's plugin instance
* (0 when the node has no plugin or no messages).
*/
OAKENGINE_API int oakengine_node_plugin_message_count(
const OakEngineNode *self);
/**
* @brief Read the plugin message at `index`: type into `*type`
* (0=error, 1=warning, 2=message) and text into `msg_buf` (buf/size).
* Returns OAKENGINE_OK or OAKENGINE_E_NOT_FOUND.
*/
OAKENGINE_API int oakengine_node_plugin_message_at(
const OakEngineNode *self, int index, int *type, char *msg_buf,
int msg_buf_size);
/**
* @brief Clear all persistent messages on the node's plugin instance.
*/
OAKENGINE_API int oakengine_node_plugin_clear_messages(
OakEngineNode *self);
/* ---- Node cache objects -----------------------------------------------------
*
* Borrowed handles of the caches every node owns (engine/node/node.h:
* Node::thumbnail_cache() / waveform_cache() / video_frame_cache()). The
* handle types are defined in oakengine/viewer.h (forward-declared here
* like project.h does for OakEnginePlaybackCache); the application only
* passes them on to the cache accessor families there. NULL on a NULL
* handle. Handles become invalid with their owning node.
*/
typedef struct OakEngineFrameCache OakEngineFrameCache;
typedef struct OakEngineThumbnailCache OakEngineThumbnailCache;
typedef struct OakEngineWaveformCache OakEngineWaveformCache;
/**
* @brief The node's thumbnail cache (Node::thumbnail_cache(); an
* olive::ThumbnailCache, a FrameHashCache subclass).
*/
OAKENGINE_API OakEngineThumbnailCache *
oakengine_node_get_thumbnail_cache(const OakEngineNode *self);
/**
* @brief The node's audio waveform cache (Node::waveform_cache(); an
* olive::AudioWaveformCache), for the oakengine_waveform_cache_* family.
*/
OAKENGINE_API OakEngineWaveformCache *
oakengine_node_get_waveform_cache(const OakEngineNode *self);
/**
* @brief The node's video frame cache (Node::video_frame_cache(); an
* olive::FrameHashCache).
*/
OAKENGINE_API OakEngineFrameCache *
oakengine_node_get_video_frame_cache(const OakEngineNode *self);
#ifdef __cplusplus
}
#endif
+15
View File
@@ -226,6 +226,21 @@ OAKENGINE_API int oakengine_folder_has_child_recursive(
OAKENGINE_API int oakengine_folder_index_of_child(
const OakEngineNode *folder, const OakEngineNode *child);
/**
* @brief Number of direct item children of a folder
* (Folder::item_child_count()). 0 when `folder` is NULL or not a Folder.
*/
OAKENGINE_API int oakengine_folder_item_child_count(
const OakEngineNode *folder);
/**
* @brief Borrowed handle of the item child at `index`
* (Folder::item_child()). NULL when out of range or `folder` is not a
* Folder.
*/
OAKENGINE_API OakEngineNode *oakengine_folder_item_child(
const OakEngineNode *folder, int index);
/**
* @brief Static input key string for Folder children (Folder::k_child_input).
* Never freed.
+130
View File
@@ -343,6 +343,15 @@ typedef struct OakEngineTrack OakEngineTrack;
*/
typedef struct OakEngineBlock OakEngineBlock;
/**
* @brief Opaque track list handle (olive::TrackList).
*
* The per-type track container of a sequence. Borrowed from
* oakengine_sequence_track_list(). Invalidated when the owning sequence is
* freed.
*/
typedef struct OakEngineTrackList OakEngineTrackList;
/**
* @brief Human-readable reason for the last failed editing call on this
* thread (buf/size convention). Editing calls return NULL or a negative
@@ -1073,6 +1082,12 @@ oakengine_track_nearest_block_after_or_at(const OakEngineTrack *track,
/** @brief 1 if the block is a GapBlock, 0 otherwise. 0 on NULL. */
OAKENGINE_API int oakengine_block_is_gap(const OakEngineBlock *block);
/** @brief The track the block sits on (Block::track()), or NULL when the
* block is not on a track. Borrowed handle; NULL on a NULL block.
* Generic-block counterpart of the clip-only oakengine_clip_get_track(). */
OAKENGINE_API OakEngineTrack *
oakengine_block_get_track(const OakEngineBlock *block);
/** @brief Next block in the track's linked list, or NULL. NULL on NULL. */
OAKENGINE_API OakEngineBlock *oakengine_block_next(const OakEngineBlock *block);
@@ -1176,6 +1191,121 @@ OAKENGINE_API int oakengine_multicam_switch_source(
int track_type, int track_index, double time_seconds,
void *command);
/* ---- Track lists, block/clip/transition navigation and links --------------
*
* Handle-level accessors mirroring the engine's Track / Block / ClipBlock /
* TransitionBlock navigation API. All handles are borrowed (same lifetime
* rules as the rest of the family); all times are frame timestamps in the
* owning sequence's frame-rate timebase.
*/
/**
* @brief Borrowed handle of the sequence's track list for `track_type`
* (OAKENGINE_TRACK_TYPE_*; Sequence::track_list()). NULL on a NULL handle
* or an out-of-range type.
*/
OAKENGINE_API OakEngineTrackList *
oakengine_sequence_track_list(OakEngineSequence *seq, int track_type);
/**
* @brief The block visible at `time_ts` on the track
* (Track::visible_block_at_time(): the block whose range contains the
* time, gaps included), or NULL when the time is past the track's end.
* Borrowed handle; NULL on a NULL track.
*/
OAKENGINE_API OakEngineBlock *
oakengine_track_visible_block_at_time(OakEngineTrack *track, int64_t time_ts);
/**
* @brief 1 if the node is a block of any kind (a ClipBlock, GapBlock,
* TransitionBlock, ...), 0 otherwise. 0 on a NULL handle.
*
* This is an is-a check on the engine class (dynamic_cast), deliberately
* NOT a type-id string comparison: Block and TransitionBlock are abstract
* and carry no own type id -- only their concrete subclasses
* (ClipBlock, GapBlock, CrossDissolveTransition, ...) have one -- so no
* single type-id string can express "any block" or "any transition".
* Use oakengine_node_get_type_id() for exact concrete-type comparisons.
*/
OAKENGINE_API int oakengine_node_is_block(const OakEngineNode *node);
/**
* @brief 1 if the node is a transition block (any TransitionBlock
* subclass), 0 otherwise. 0 on a NULL handle. See
* oakengine_node_is_block() for why this is a class check.
*/
OAKENGINE_API int oakengine_node_is_transition(const OakEngineNode *node);
/**
* @brief Set the block's length keeping its in-point, extending the media
* out-point (undoable; olive::BlockResizeCommand wrapping
* Block::set_length_and_media_out(), like the other block edits).
*
* `length_ts` is the new length in frame timestamps of the owning track's
* sequence timebase and must be > 0. The block must be on a track
* (OAKENGINE_E_STATE otherwise).
*/
OAKENGINE_API int
oakengine_block_set_length_and_media_out(OakEngineBlock *block,
int64_t length_ts);
/**
* @brief Number of blocks linked to this block (the block's Node::links()
* filtered to blocks; for a ClipBlock this matches
* ClipBlock::block_links()). 0 on a NULL handle.
*/
OAKENGINE_API int oakengine_block_link_count(const OakEngineBlock *block);
/**
* @brief Borrowed handle of the linked block at `index` (same ordering as
* Node::links()), or NULL when out of range or on a NULL handle.
*/
OAKENGINE_API OakEngineBlock *
oakengine_block_link_at(const OakEngineBlock *block, int index);
/**
* @brief Borrowed handle of the transition attached to the clip's in-point
* (ClipBlock::in_transition()), or NULL when the block is not a clip or
* has no in-transition.
*/
OAKENGINE_API OakEngineBlock *
oakengine_clip_in_transition(const OakEngineBlock *clip);
/**
* @brief Borrowed handle of the transition attached to the clip's
* out-point (ClipBlock::out_transition()), or NULL when the block is not
* a clip or has no out-transition.
*/
OAKENGINE_API OakEngineBlock *
oakengine_clip_out_transition(const OakEngineBlock *clip);
/**
* @brief Borrowed handle of the clip feeding the transition's in side
* (TransitionBlock::connected_in_block(): the FOLLOWING clip of an
* in-transition or dual transition), or NULL when the block is not a
* transition or nothing is connected.
*/
OAKENGINE_API OakEngineBlock *
oakengine_transition_connected_in_block(const OakEngineBlock *transition);
/**
* @brief Borrowed handle of the clip feeding the transition's out side
* (TransitionBlock::connected_out_block(): the PRECEDING clip of an
* out-transition or dual transition), or NULL when the block is not a
* transition or nothing is connected.
*/
OAKENGINE_API OakEngineBlock *
oakengine_transition_connected_out_block(const OakEngineBlock *transition);
/**
* @brief Borrowed node handle of the viewer the clip is connected to
* (ClipBlock::connected_viewer(): the Footage or nested Sequence feeding
* the clip's buffer input), or NULL when the block is not a clip or has
* no connected viewer.
*/
OAKENGINE_API OakEngineNode *
oakengine_clip_get_connected_viewer(const OakEngineBlock *clip);
#ifdef __cplusplus
}
#endif
+95
View File
@@ -311,6 +311,17 @@ typedef struct OakEnginePlaybackCache OakEnginePlaybackCache;
*/
typedef struct OakEngineFrameCache OakEngineFrameCache;
/**
* @brief Opaque thumbnail cache handle (olive::ThumbnailCache, a
* FrameHashCache subclass).
*/
typedef struct OakEngineThumbnailCache OakEngineThumbnailCache;
/**
* @brief Opaque audio waveform cache handle (olive::AudioWaveformCache).
*/
typedef struct OakEngineWaveformCache OakEngineWaveformCache;
/**
* @brief Borrowed playback cache of a viewer's connected output
* (ViewerOutput::get_connected_video_cache() for video, or from the
@@ -343,6 +354,90 @@ OAKENGINE_API int oakengine_playback_cache_valid_ranges(
OAKENGINE_API OakEngineFrameCache *
oakengine_viewer_get_frame_cache(OakEngineNode *self);
/* ---- Playback cache accessors ------------------------------------------------
*
* These take the cache as a plain `void *` pass-through (like
* oakengine_viewer_get_connected_waveform()): any borrowed playback-cache
* pointer (OakEnginePlaybackCache*, OakEngineFrameCache*, an engine-side
* PlaybackCache*, ...) converts implicitly. Qt types cross the boundary as
* opaque `void *` (same precedent as oakengine_undo_undo_action()'s
* QAction *).
*/
/**
* @brief 1 if the playback cache has any validated (cached) ranges
* (PlaybackCache::has_validated_ranges()). 0 on a NULL cache.
*/
OAKENGINE_API int
oakengine_playback_cache_has_validated_ranges(const void *cache);
/**
* @brief Borrowed node handle of the node that owns the playback cache
* (PlaybackCache::parent()), or NULL on a NULL cache.
*/
OAKENGINE_API OakEngineNode *oakengine_playback_cache_parent(void *cache);
/**
* @brief Draw the cache's validated-range indicator
* (PlaybackCache::draw()).
*
* `qpainter` is an opaque `QPainter *` (NULL-safe no-op). `in_ts` is the
* timeline time at the LEFT edge of the painted area as a frame timestamp
* in the timebase of the cache's parent viewer (its frame rate flipped;
* the engine default 1001/30000 when the cache has no viewer parent).
* `scale` is pixels per second and `height` the indicator strip height in
* pixels; the painted rectangle spans the painter's viewport horizontally.
*/
OAKENGINE_API void oakengine_playback_cache_draw(void *cache, void *qpainter,
int64_t in_ts, double scale,
int height);
/* ---- Audio waveform cache accessors -------------------------------------------
*
* Accessors for a borrowed AudioWaveformCache handle (from
* oakengine_node_get_waveform_cache(), ClipBlock::waveform() equivalents,
* or oakengine_viewer_get_connected_waveform()). Pass-through `void *`
* like the playback cache accessors above. All times are SAMPLE frames at
* the cache's own sample rate (oakengine_waveform_cache_sample_rate()),
* i.e. the timestamp timebase is 1/sample_rate seconds.
*/
/**
* @brief Length of the cached waveform in sample frames at the cache's
* sample rate (AudioWaveformCache::length()). 0 on a NULL cache or when
* the cache has no valid sample rate.
*/
OAKENGINE_API int64_t oakengine_waveform_cache_length(const void *cache);
/**
* @brief Sample rate of the cache's audio parameters
* (AudioWaveformCache::get_parameters().sample_rate()). 0 on a NULL
* cache.
*/
OAKENGINE_API int oakengine_waveform_cache_sample_rate(const void *cache);
/**
* @brief 1 if the waveform cache has any validated ranges
* (PlaybackCache::has_validated_ranges()). 0 on a NULL cache.
*/
OAKENGINE_API int oakengine_waveform_cache_has_validated_ranges(
const void *cache);
/**
* @brief Per-channel min/max summary of the waveform over
* [start_ts, end_ts) in sample frames
* (AudioWaveformCache::get_summary_from_time()).
*
* `min_out`/`max_out` each receive one linear sample value per channel,
* up to `max_channels` entries; `channels_out` (may be NULL) receives the
* number of channels written. Requires end_ts >= start_ts and a cache
* with a valid sample rate (OAKENGINE_E_STATE otherwise; the summary
* timebase depends on it).
*/
OAKENGINE_API 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);
#ifdef __cplusplus
}
#endif