- 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
239 lines
7.1 KiB
C++
239 lines
7.1 KiB
C++
/***
|
|
|
|
Oak - Non-Linear Video Editor
|
|
Copyright (C) 2026 Oak Team
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
#ifndef OAK_CLIPHANDLE_H
|
|
#define OAK_CLIPHANDLE_H
|
|
|
|
#include <cstring>
|
|
|
|
#include <olive/core/core.h>
|
|
|
|
#include "oakengine/node.h"
|
|
#include "oakengine/timeline.h"
|
|
|
|
namespace olive
|
|
{
|
|
|
|
class FrameHashCache;
|
|
class AudioWaveformCache;
|
|
|
|
using olive::core::Rational;
|
|
using olive::core::TimeRange;
|
|
|
|
/**
|
|
* @brief Facade accessors for clip blocks held by the timeline UI.
|
|
*
|
|
* The engine's header-inline clip convenience accessors (speed()/loop_mode()/
|
|
* thumbnails()/waveform()/connected_video_cache()/...) reference the
|
|
* input-id statics (k_speed_input/k_buffer_in/...), which are engine
|
|
* symbols the app must no longer pull across the liboakengine boundary.
|
|
* These helpers route the same queries through the C ABI instead (same
|
|
* pattern as app/widget/keyframeview/keyframehandle.h). Clips are passed
|
|
* as OakEngineBlock* handles; the engine cache types
|
|
* (FrameHashCache/AudioWaveformCache) are forward-declared here and only
|
|
* dereferenced by callers that still see the engine class definition.
|
|
*/
|
|
|
|
inline OakEngineClip *cliphandle(OakEngineBlock *clip)
|
|
{
|
|
return reinterpret_cast<OakEngineClip *>(clip);
|
|
}
|
|
|
|
/** @brief The node feeding the clip's buffer input (the inline
|
|
* get_connected_output(k_buffer_in) uses; borrowed, may be null). */
|
|
inline OakEngineNode *clip_connected_node(OakEngineBlock *clip)
|
|
{
|
|
return oakengine_node_input_get_connected_node(
|
|
reinterpret_cast<OakEngineNode *>(clip),
|
|
oakengine_clip_buffer_input_id(), -1);
|
|
}
|
|
|
|
inline FrameHashCache *clip_thumbnails(OakEngineBlock *clip)
|
|
{
|
|
OakEngineNode *n = clip_connected_node(clip);
|
|
return n ? reinterpret_cast<FrameHashCache *>(
|
|
oakengine_node_get_thumbnail_cache(n)) :
|
|
nullptr;
|
|
}
|
|
|
|
inline AudioWaveformCache *clip_waveform(OakEngineBlock *clip)
|
|
{
|
|
OakEngineNode *n = clip_connected_node(clip);
|
|
return n ? reinterpret_cast<AudioWaveformCache *>(
|
|
oakengine_node_get_waveform_cache(n)) :
|
|
nullptr;
|
|
}
|
|
|
|
inline FrameHashCache *clip_connected_video_cache(OakEngineBlock *clip)
|
|
{
|
|
OakEngineNode *n = clip_connected_node(clip);
|
|
return n ? reinterpret_cast<FrameHashCache *>(
|
|
oakengine_node_get_video_frame_cache(n)) :
|
|
nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Owning track handle of a block of any kind (the block's track).
|
|
*
|
|
* Wraps the generic oakengine_block_get_track(); the timeline family's
|
|
* OakEngineTrack* is reinterpreted to the node family's OakEngineNode*
|
|
* (same underlying track object; the track accessors in node.h take the
|
|
* latter). NULL when the block is not on a track.
|
|
*/
|
|
inline OakEngineNode *block_track_handle(OakEngineBlock *block)
|
|
{
|
|
return reinterpret_cast<OakEngineNode *>(oakengine_block_get_track(block));
|
|
}
|
|
|
|
/** @brief The clip's speed through the facade input getter. */
|
|
inline double clip_speed(OakEngineBlock *clip)
|
|
{
|
|
oak_node_value v;
|
|
memset(&v, 0, sizeof(v));
|
|
if (oakengine_node_get_input(reinterpret_cast<OakEngineNode *>(clip),
|
|
oakengine_clip_speed_input_id(),
|
|
&v) != OAKENGINE_OK) {
|
|
return 1.0;
|
|
}
|
|
return v.f[0];
|
|
}
|
|
|
|
/** @brief The clip's loop mode value (an OAKENGINE_LOOP_MODE_* int). */
|
|
inline int clip_loop_mode(OakEngineBlock *clip)
|
|
{
|
|
oak_node_value v;
|
|
memset(&v, 0, sizeof(v));
|
|
if (oakengine_node_get_input(reinterpret_cast<OakEngineNode *>(clip),
|
|
oakengine_clip_loop_mode_input_id(),
|
|
&v) != OAKENGINE_OK) {
|
|
return 0;
|
|
}
|
|
return int(v.num);
|
|
}
|
|
|
|
/** @brief The clip's reverse flag through the facade input getter. */
|
|
inline bool clip_is_reversed(OakEngineBlock *clip)
|
|
{
|
|
oak_node_value v;
|
|
memset(&v, 0, sizeof(v));
|
|
return oakengine_node_get_input(reinterpret_cast<OakEngineNode *>(clip),
|
|
oakengine_clip_reverse_input_id(),
|
|
&v) == OAKENGINE_OK &&
|
|
v.num != 0;
|
|
}
|
|
|
|
/** @brief The clip's maintain-audio-pitch flag through the facade. */
|
|
inline bool clip_maintain_audio_pitch(OakEngineBlock *clip)
|
|
{
|
|
oak_node_value v;
|
|
memset(&v, 0, sizeof(v));
|
|
return oakengine_node_get_input(
|
|
reinterpret_cast<OakEngineNode *>(clip),
|
|
oakengine_clip_maintain_audio_pitch_input_id(), &v) ==
|
|
OAKENGINE_OK &&
|
|
v.num != 0;
|
|
}
|
|
|
|
/** @brief Create a new empty clip block through the C ABI. */
|
|
inline OakEngineBlock *clip_create_empty(const char *label = nullptr)
|
|
{
|
|
return reinterpret_cast<OakEngineBlock *>(oakengine_clip_create_empty(label));
|
|
}
|
|
|
|
/** @brief The clip's media in-point through the facade. */
|
|
inline Rational clip_media_in(OakEngineBlock *clip)
|
|
{
|
|
int64_t num = 0, den = 1;
|
|
if (oakengine_clip_get_media_in_rational(cliphandle(clip), &num, &den) ==
|
|
OAKENGINE_OK) {
|
|
return Rational(static_cast<int>(num), static_cast<int>(den));
|
|
}
|
|
return Rational(0, 1);
|
|
}
|
|
|
|
/** @brief The clip's media range through the facade. */
|
|
inline TimeRange clip_media_range(OakEngineBlock *clip)
|
|
{
|
|
int64_t in_num = 0, in_den = 1, out_num = 0, out_den = 1;
|
|
if (oakengine_clip_get_media_range_rational(
|
|
cliphandle(clip), &in_num, &in_den, &out_num, &out_den) ==
|
|
OAKENGINE_OK) {
|
|
return TimeRange(Rational(static_cast<int>(in_num),
|
|
static_cast<int>(in_den)),
|
|
Rational(static_cast<int>(out_num),
|
|
static_cast<int>(out_den)));
|
|
}
|
|
return TimeRange(0, 0);
|
|
}
|
|
|
|
/** @brief Set the clip's media in-point directly (rational seconds). */
|
|
inline void clip_set_media_in(OakEngineBlock *clip, const Rational &media_in,
|
|
bool undoable = false)
|
|
{
|
|
if (!clip) {
|
|
return;
|
|
}
|
|
oakengine_clip_set_media_in_rational(cliphandle(clip),
|
|
media_in.numerator(),
|
|
media_in.denominator(),
|
|
undoable ? 1 : 0);
|
|
}
|
|
|
|
/** @brief The clip's auto-cache flag through the facade. */
|
|
inline bool clip_is_autocaching(OakEngineBlock *clip)
|
|
{
|
|
oak_node_value v;
|
|
memset(&v, 0, sizeof(v));
|
|
return oakengine_node_get_input(
|
|
reinterpret_cast<OakEngineNode *>(clip),
|
|
oakengine_clip_auto_cache_input_id(), &v) ==
|
|
OAKENGINE_OK &&
|
|
v.num != 0;
|
|
}
|
|
|
|
/** @brief Request invalidation from the clip's connected node, through the
|
|
* facade. */
|
|
inline void clip_request_invalidate_connected(
|
|
OakEngineBlock *clip, bool force_all = false,
|
|
const TimeRange &intersect = TimeRange())
|
|
{
|
|
if (!clip) {
|
|
return;
|
|
}
|
|
|
|
int64_t in_num = 0, in_den = 0, out_num = 0, out_den = 0;
|
|
if (!intersect.length().isNull()) {
|
|
const Rational in = intersect.in();
|
|
const Rational out = intersect.out();
|
|
in_num = in.numerator();
|
|
in_den = in.denominator();
|
|
out_num = out.numerator();
|
|
out_den = out.denominator();
|
|
}
|
|
|
|
oakengine_clip_request_invalidate_connected(cliphandle(clip), force_all ? 1 : 0,
|
|
in_num, in_den, out_num,
|
|
out_den);
|
|
}
|
|
|
|
} // namespace olive
|
|
|
|
#endif // OAK_CLIPHANDLE_H
|