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:
@@ -55,6 +55,10 @@ add_library(oakengine-obj OBJECT
|
||||
|
||||
add_library(oakengine SHARED $<TARGET_OBJECTS:oakengine-obj>)
|
||||
|
||||
if (COMMAND oak_copy_otio_runtime)
|
||||
oak_copy_otio_runtime(oakengine)
|
||||
endif()
|
||||
|
||||
# macOS: hides the render worker's dock icon; called from the worker main in
|
||||
# src/capi/worker.cpp (declared there as a plain C++ symbol).
|
||||
if (APPLE)
|
||||
@@ -158,6 +162,9 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
render/opengl/openglrenderer.h
|
||||
)
|
||||
target_link_libraries(oakgl PRIVATE oakengine)
|
||||
if (COMMAND oak_copy_otio_runtime)
|
||||
oak_copy_otio_runtime(oakgl)
|
||||
endif()
|
||||
target_include_directories(oakgl PRIVATE ${OLIVE_INCLUDE_DIRS})
|
||||
target_compile_definitions(oakgl PRIVATE OAK_RENDER_BACKEND_PLUGIN)
|
||||
set_target_properties(oakgl PROPERTIES
|
||||
@@ -182,6 +189,9 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
render/vulkan/vulkanrenderer.h
|
||||
)
|
||||
target_link_libraries(oakvulkan PRIVATE oakengine)
|
||||
if (COMMAND oak_copy_otio_runtime)
|
||||
oak_copy_otio_runtime(oakvulkan)
|
||||
endif()
|
||||
target_include_directories(oakvulkan PRIVATE ${OLIVE_INCLUDE_DIRS})
|
||||
target_link_libraries(oakvulkan PRIVATE Vulkan::Vulkan)
|
||||
target_compile_definitions(oakvulkan PRIVATE OAK_HAS_VULKAN)
|
||||
@@ -262,6 +272,9 @@ if (BUILD_TESTS)
|
||||
tests/gtest_main.cpp
|
||||
$<TARGET_OBJECTS:olive-version-obj>
|
||||
)
|
||||
if (COMMAND oak_copy_otio_runtime)
|
||||
oak_copy_otio_runtime(${name})
|
||||
endif()
|
||||
# Link the object library directly to bypass the version-script
|
||||
# restrictions on liboakengine.so (tests are internal consumers).
|
||||
target_link_libraries(${name} PRIVATE oakengine-obj GTest::gtest)
|
||||
|
||||
@@ -23,7 +23,14 @@
|
||||
|
||||
#ifdef USE_OTIO
|
||||
#include <opentimelineio/version.h>
|
||||
// OTIO >= 0.18 splits the version triple (OPENTIMELINEIO_VERSION, e.g.
|
||||
// v0_19_0) from the actual C++ namespace (OPENTIMELINEIO_VERSION_NS, e.g.
|
||||
// v0_19). Older releases (0.16) only have the former.
|
||||
#if defined(OPENTIMELINEIO_VERSION_NS)
|
||||
namespace OTIO = opentimelineio::OPENTIMELINEIO_VERSION_NS;
|
||||
#else
|
||||
namespace OTIO = opentimelineio::OPENTIMELINEIO_VERSION;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // OTIOUTILS
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "timeline/timelineundogeneral.h"
|
||||
#include "window/mainwindow/mainwindowundo.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
@@ -56,23 +55,23 @@ LoadOTIOTask::LoadOTIOTask(const QString &s)
|
||||
{
|
||||
}
|
||||
|
||||
bool LoadOTIOTask::Run()
|
||||
bool LoadOTIOTask::run()
|
||||
{
|
||||
OTIO::ErrorStatus es;
|
||||
|
||||
auto root = OTIO::SerializableObjectWithMetadata::from_json_file(
|
||||
GetFilename().toStdString(), &es);
|
||||
get_filename().toStdString(), &es);
|
||||
|
||||
if (es.outcome != OTIO::ErrorStatus::Outcome::OK) {
|
||||
SetError(
|
||||
set_error(
|
||||
tr("Failed to load OpenTimelineIO from file \"%1\" \n\nOpenTimelineIO Error:\n\n%2")
|
||||
.arg(GetFilename(),
|
||||
.arg(get_filename(),
|
||||
QString::fromStdString(es.full_description)));
|
||||
return false;
|
||||
}
|
||||
|
||||
project_ = new Project();
|
||||
project_->Initialize();
|
||||
project_->initialize();
|
||||
project_->set_modified(true);
|
||||
|
||||
std::vector<OTIO::Timeline *> timelines;
|
||||
@@ -93,7 +92,7 @@ bool LoadOTIOTask::Run()
|
||||
timelines.push_back(static_cast<OTIO::Timeline *>(root));
|
||||
} else {
|
||||
// Unknown root, we don't know what to do with this
|
||||
SetError(tr("Unknown OpenTimelineIO root element"));
|
||||
set_error(tr("Unknown OpenTimelineIO root element"));
|
||||
delete project_;
|
||||
project_ = nullptr;
|
||||
return false;
|
||||
@@ -113,12 +112,12 @@ bool LoadOTIOTask::Run()
|
||||
foreach (auto timeline, timelines) {
|
||||
Sequence *sequence = new Sequence();
|
||||
if (!timeline->name().empty()) {
|
||||
sequence->SetLabel(QString::fromStdString(timeline->name()));
|
||||
sequence->set_label(QString::fromStdString(timeline->name()));
|
||||
} else {
|
||||
// If the otio timeline does not provide a name, create a default one here
|
||||
unnamed_sequence_count++;
|
||||
QString label = tr("Sequence %1").arg(unnamed_sequence_count);
|
||||
sequence->SetLabel(QString::fromStdString(label.toStdString()));
|
||||
sequence->set_label(QString::fromStdString(label.toStdString()));
|
||||
}
|
||||
// Set default params incase they aren't edited.
|
||||
sequence->set_default_parameters();
|
||||
@@ -152,7 +151,7 @@ bool LoadOTIOTask::Run()
|
||||
|
||||
// Create a folder for this sequence's footage
|
||||
Folder *sequence_footage = new Folder();
|
||||
sequence_footage->SetLabel(QString::fromStdString(timeline->name()));
|
||||
sequence_footage->set_label(QString::fromStdString(timeline->name()));
|
||||
sequence_footage->setParent(project_);
|
||||
FolderAddChild(project_->root(), sequence_footage).redo_now();
|
||||
|
||||
@@ -169,9 +168,9 @@ bool LoadOTIOTask::Run()
|
||||
Track::Type type;
|
||||
|
||||
if (otio_track->kind() == "Video") {
|
||||
type = Track::kVideo;
|
||||
type = Track::k_video;
|
||||
} else {
|
||||
type = Track::kAudio;
|
||||
type = Track::k_audio;
|
||||
}
|
||||
|
||||
// Create track
|
||||
@@ -187,7 +186,7 @@ bool LoadOTIOTask::Run()
|
||||
// Get clips from track
|
||||
auto clip_map = otio_track->children();
|
||||
if (es.outcome != OTIO::ErrorStatus::Outcome::OK) {
|
||||
SetError(tr("Failed to load clip"));
|
||||
set_error(tr("Failed to load clip"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -217,21 +216,21 @@ bool LoadOTIOTask::Run()
|
||||
}
|
||||
|
||||
block->setParent(project_);
|
||||
block->SetLabel(QString::fromStdString(otio_block->name()));
|
||||
block->set_label(QString::fromStdString(otio_block->name()));
|
||||
|
||||
track->AppendBlock(block);
|
||||
track->append_block(block);
|
||||
|
||||
Rational start_time;
|
||||
Rational duration;
|
||||
|
||||
if (otio_block->schema_name() == "Clip" ||
|
||||
otio_block->schema_name() == "Gap") {
|
||||
start_time = Rational::fromDouble(
|
||||
start_time = Rational::from_double(
|
||||
static_cast<OTIO::Item *>(otio_block)
|
||||
->source_range()
|
||||
->start_time()
|
||||
.to_seconds());
|
||||
duration = Rational::fromDouble(
|
||||
duration = Rational::from_double(
|
||||
static_cast<OTIO::Item *>(otio_block)
|
||||
->source_range()
|
||||
->duration()
|
||||
@@ -248,9 +247,9 @@ bool LoadOTIOTask::Run()
|
||||
if (prev_block_transition) {
|
||||
TransitionBlock *previous_transition_block =
|
||||
static_cast<TransitionBlock *>(previous_block);
|
||||
Node::ConnectEdge(
|
||||
Node::connect_edge(
|
||||
block, NodeInput(previous_transition_block,
|
||||
TransitionBlock::kInBlockInput));
|
||||
TransitionBlock::k_in_block_input));
|
||||
prev_block_transition = false;
|
||||
}
|
||||
|
||||
@@ -268,10 +267,10 @@ bool LoadOTIOTask::Run()
|
||||
otio_block_transition->out_offset()));
|
||||
|
||||
if (previous_block) {
|
||||
Node::ConnectEdge(
|
||||
Node::connect_edge(
|
||||
previous_block,
|
||||
NodeInput(transition_block,
|
||||
TransitionBlock::kOutBlockInput));
|
||||
TransitionBlock::k_out_block_input));
|
||||
}
|
||||
prev_block_transition = true;
|
||||
|
||||
@@ -279,7 +278,7 @@ bool LoadOTIOTask::Run()
|
||||
block->setParent(sequence->parent());
|
||||
|
||||
// Position transition in its own context
|
||||
block->SetNodePositionInContext(block, QPointF(0, 0));
|
||||
block->set_node_position_in_context(block, QPointF(0, 0));
|
||||
}
|
||||
|
||||
if (otio_block->schema_name() == "Gap") {
|
||||
@@ -287,7 +286,7 @@ bool LoadOTIOTask::Run()
|
||||
block->setParent(sequence->parent());
|
||||
|
||||
// Position transition in its own context
|
||||
block->SetNodePositionInContext(block, QPointF(0, 0));
|
||||
block->set_node_position_in_context(block, QPointF(0, 0));
|
||||
}
|
||||
|
||||
// Update this after it's used but before any continue statements
|
||||
@@ -316,7 +315,7 @@ bool LoadOTIOTask::Run()
|
||||
probed_item->setParent(project_);
|
||||
|
||||
QFileInfo info(probed_item->filename());
|
||||
probed_item->SetLabel(info.fileName());
|
||||
probed_item->set_label(info.fileName());
|
||||
|
||||
FolderAddChild add(sequence_footage, probed_item);
|
||||
add.redo_now();
|
||||
@@ -326,44 +325,44 @@ bool LoadOTIOTask::Run()
|
||||
block->setParent(sequence->parent());
|
||||
|
||||
// Position clip in its own context
|
||||
block->SetNodePositionInContext(block, QPointF(0, 0));
|
||||
block->set_node_position_in_context(block, QPointF(0, 0));
|
||||
|
||||
// Position footage in its context
|
||||
block->SetNodePositionInContext(probed_item,
|
||||
QPointF(-2, 0));
|
||||
block->set_node_position_in_context(probed_item,
|
||||
QPointF(-2, 0));
|
||||
|
||||
if (track->type() == Track::kVideo) {
|
||||
if (track->type() == Track::k_video) {
|
||||
TransformDistortNode *transform =
|
||||
new TransformDistortNode();
|
||||
transform->setParent(sequence->parent());
|
||||
|
||||
Node::ConnectEdge(
|
||||
Node::connect_edge(
|
||||
probed_item,
|
||||
NodeInput(transform,
|
||||
TransformDistortNode::kTextureInput));
|
||||
Node::ConnectEdge(transform,
|
||||
TransformDistortNode::k_texture_input));
|
||||
Node::connect_edge(transform,
|
||||
NodeInput(block,
|
||||
ClipBlock::kBufferIn));
|
||||
block->SetNodePositionInContext(transform,
|
||||
QPointF(-1, 0));
|
||||
ClipBlock::k_buffer_in));
|
||||
block->set_node_position_in_context(transform,
|
||||
QPointF(-1, 0));
|
||||
} else {
|
||||
VolumeNode *volume_node = new VolumeNode();
|
||||
volume_node->setParent(sequence->parent());
|
||||
|
||||
Node::ConnectEdge(
|
||||
Node::connect_edge(
|
||||
probed_item,
|
||||
NodeInput(volume_node,
|
||||
VolumeNode::kSamplesInput));
|
||||
Node::ConnectEdge(volume_node,
|
||||
VolumeNode::k_samples_input));
|
||||
Node::connect_edge(volume_node,
|
||||
NodeInput(block,
|
||||
ClipBlock::kBufferIn));
|
||||
block->SetNodePositionInContext(volume_node,
|
||||
QPointF(-1, 0));
|
||||
ClipBlock::k_buffer_in));
|
||||
block->set_node_position_in_context(volume_node,
|
||||
QPointF(-1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
clips_done++;
|
||||
emit ProgressChanged(clips_done / number_of_clips);
|
||||
emit progress_changed(clips_done / number_of_clips);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
LoadOTIOTask(const QString &filename);
|
||||
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
virtual bool run() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -41,16 +41,16 @@ namespace olive
|
||||
SaveOTIOTask::SaveOTIOTask(Project *project)
|
||||
: project_(project)
|
||||
{
|
||||
SetTitle(tr("Exporting project to OpenTimelineIO"));
|
||||
set_title(tr("Exporting project to OpenTimelineIO"));
|
||||
}
|
||||
|
||||
bool SaveOTIOTask::Run()
|
||||
bool SaveOTIOTask::run()
|
||||
{
|
||||
QVector<Sequence *> sequences =
|
||||
project_->root()->ListChildrenOfType<Sequence>();
|
||||
project_->root()->list_children_of_type<Sequence>();
|
||||
|
||||
if (sequences.isEmpty()) {
|
||||
SetError(tr("Project contains no sequences to export."));
|
||||
set_error(tr("Project contains no sequences to export."));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ bool SaveOTIOTask::Run()
|
||||
}
|
||||
|
||||
// Error out of function
|
||||
SetError(
|
||||
tr("Failed to serialize sequence \"%1\"").arg(seq->GetLabel()));
|
||||
set_error(
|
||||
tr("Failed to serialize sequence \"%1\"").arg(seq->get_label()));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -101,21 +101,21 @@ bool SaveOTIOTask::Run()
|
||||
|
||||
OTIO::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequence)
|
||||
{
|
||||
auto otio_timeline = new OTIO::Timeline(sequence->GetLabel().toStdString());
|
||||
auto otio_timeline = new OTIO::Timeline(sequence->get_label().toStdString());
|
||||
// Retainers clean themselves up when the final user is removed
|
||||
OTIO::Timeline::Retainer<OTIO::Timeline> *timeline_retainer =
|
||||
new OTIO::Timeline::Retainer<OTIO::Timeline>(otio_timeline);
|
||||
// Suppress unused variable warning
|
||||
Q_UNUSED(timeline_retainer);
|
||||
|
||||
double rate = sequence->GetVideoParams().frame_rate().toDouble();
|
||||
double rate = sequence->get_video_params().frame_rate().to_double();
|
||||
if (qIsNaN(rate)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!SerializeTrackList(sequence->track_list(Track::kVideo), otio_timeline,
|
||||
if (!SerializeTrackList(sequence->track_list(Track::k_video), otio_timeline,
|
||||
rate) ||
|
||||
!SerializeTrackList(sequence->track_list(Track::kAudio), otio_timeline,
|
||||
!SerializeTrackList(sequence->track_list(Track::k_audio), otio_timeline,
|
||||
rate)) {
|
||||
otio_timeline->possibly_delete();
|
||||
return nullptr;
|
||||
@@ -132,10 +132,10 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate,
|
||||
OTIO::ErrorStatus es;
|
||||
|
||||
switch (track->type()) {
|
||||
case Track::kVideo:
|
||||
case Track::k_video:
|
||||
otio_track->set_kind("Video");
|
||||
break;
|
||||
case Track::kAudio:
|
||||
case Track::k_audio:
|
||||
otio_track->set_kind("Audio");
|
||||
break;
|
||||
default:
|
||||
@@ -144,17 +144,17 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
foreach (Block *block, track->Blocks()) {
|
||||
foreach (Block *block, track->blocks()) {
|
||||
OTIO::Composable *otio_block = nullptr;
|
||||
|
||||
if (dynamic_cast<ClipBlock *>(block)) {
|
||||
auto otio_clip = new OTIO::Clip(block->GetLabel().toStdString());
|
||||
auto otio_clip = new OTIO::Clip(block->get_label().toStdString());
|
||||
|
||||
otio_clip->set_source_range(
|
||||
OTIO::TimeRange(block->in().toRationalTime(sequence_rate),
|
||||
block->length().toRationalTime(sequence_rate)));
|
||||
|
||||
QVector<Footage *> media_nodes = block->FindInputNodes<Footage>();
|
||||
QVector<Footage *> media_nodes = block->find_input_nodes<Footage>();
|
||||
if (!media_nodes.isEmpty()) {
|
||||
OTIO::TimeRange available_range;
|
||||
if (otio_track->kind().compare("Video") == 0) {
|
||||
@@ -162,22 +162,22 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate,
|
||||
// the sequences rate
|
||||
double source_frame_rate = static_cast<ClipBlock *>(block)
|
||||
->connected_viewer()
|
||||
->GetVideoParams()
|
||||
->get_video_params()
|
||||
.frame_rate()
|
||||
.toDouble();
|
||||
.to_double();
|
||||
available_range = OTIO::TimeRange(
|
||||
OTIO::RationalTime(0, source_frame_rate),
|
||||
OTIO::RationalTime(
|
||||
media_nodes.first()->GetVideoParams().duration(),
|
||||
media_nodes.first()->get_video_params().duration(),
|
||||
source_frame_rate));
|
||||
} else if (otio_track->kind().compare("Audio") == 0) {
|
||||
available_range = OTIO::TimeRange(
|
||||
OTIO::RationalTime(
|
||||
0,
|
||||
media_nodes.first()->GetAudioParams().sample_rate()),
|
||||
media_nodes.first()->get_audio_params().sample_rate()),
|
||||
OTIO::RationalTime(
|
||||
media_nodes.first()->GetAudioParams().duration(),
|
||||
media_nodes.first()->GetAudioParams().sample_rate()));
|
||||
media_nodes.first()->get_audio_params().duration(),
|
||||
media_nodes.first()->get_audio_params().sample_rate()));
|
||||
}
|
||||
auto media_ref = new OTIO::ExternalReference(
|
||||
media_nodes.first()->filename().toStdString(),
|
||||
@@ -190,10 +190,10 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate,
|
||||
otio_block =
|
||||
new OTIO::Gap(OTIO::TimeRange(block->in().toRationalTime(),
|
||||
block->length().toRationalTime()),
|
||||
block->GetLabel().toStdString());
|
||||
block->get_label().toStdString());
|
||||
} else if (dynamic_cast<TransitionBlock *>(block)) {
|
||||
auto otio_transition =
|
||||
new OTIO::Transition(block->GetLabel().toStdString());
|
||||
new OTIO::Transition(block->get_label().toStdString());
|
||||
|
||||
TransitionBlock *our_transition =
|
||||
static_cast<TransitionBlock *>(block);
|
||||
@@ -219,8 +219,8 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate,
|
||||
}
|
||||
|
||||
// All OTIO tracks must have the same duration so we add a Gap to fill the remaining time
|
||||
if (otio_track->duration(&es).to_seconds() < max_track_length.toDouble()) {
|
||||
double time_left = max_track_length.toDouble() -
|
||||
if (otio_track->duration(&es).to_seconds() < max_track_length.to_double()) {
|
||||
double time_left = max_track_length.to_double() -
|
||||
otio_track->duration(&es).to_seconds();
|
||||
|
||||
OTIO::Gap *gap = new OTIO::Gap(OTIO::TimeRange(
|
||||
@@ -248,13 +248,13 @@ bool SaveOTIOTask::SerializeTrackList(TrackList *list,
|
||||
|
||||
Rational max_track_length = RATIONAL_MIN;
|
||||
|
||||
foreach (Track *track, list->GetTracks()) {
|
||||
foreach (Track *track, list->get_tracks()) {
|
||||
if (track->track_length() > max_track_length) {
|
||||
max_track_length = track->track_length();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Track *track, list->GetTracks()) {
|
||||
foreach (Track *track, list->get_tracks()) {
|
||||
auto otio_track =
|
||||
SerializeTrack(track, sequence_rate, max_track_length);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
SaveOTIOTask(Project *project);
|
||||
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
virtual bool run() override;
|
||||
|
||||
private:
|
||||
OTIO::Timeline *SerializeTimeline(Sequence *sequence);
|
||||
|
||||
Reference in New Issue
Block a user