- Linux GCC rejects QMetaType instantiation over incomplete types (static_assert(sizeof(T))); Q_DECLARE_OPAQUE_POINTER every opaque OakEngine* handle in the public headers so QList<OakEngineTask*> etc compile - Windows: gtest discovery ran the fresh exe before the DLLs were next to it; use DISCOVERY_MODE PRE_TEST on WIN32 and copy the DLLs for ctest time
450 lines
17 KiB
C
450 lines
17 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 OAKENGINE_VIEWER_H
|
|
#define OAKENGINE_VIEWER_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "export.h"
|
|
#include "init.h"
|
|
#include "node.h"
|
|
#include "timeline.h"
|
|
#include "videoparams.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/**
|
|
* @file viewer.h
|
|
* @brief C ABI for viewer nodes (olive::ViewerOutput and subclasses:
|
|
* Sequence, Footage)
|
|
*
|
|
* A viewer node is the bridge between a node graph and a monitor: it owns
|
|
* a playhead, a length, per-stream video/audio/subtitle parameters, a
|
|
* workarea and a marker list. This family covers the application-side
|
|
* uses of olive::ViewerOutput that are not already exposed through the
|
|
* sequence (timeline.h) or node (node.h) families.
|
|
*
|
|
* Handles: a viewer handle is simply an OakEngineNode* whose engine object
|
|
* is a ViewerOutput (validate with oakengine_viewer_from_node()). Borrowed,
|
|
* same lifetime rules as node.h. Change notifications (length/playhead/
|
|
* params/workarea-adjacent) are delivered through the event mechanism --
|
|
* subscribe with the OAKENGINE_EVENT_VIEWER_* ids from oakengine/events.h
|
|
* on the node handle.
|
|
*
|
|
* Conventions match the rest of the facade: rationals are int64
|
|
* numerator/denominator pairs (seconds), booleans are int, 0
|
|
* (OAKENGINE_OK)/negative OAKENGINE_E_* return codes, NULL handles are
|
|
* no-ops returning OAKENGINE_E_INVALID.
|
|
*/
|
|
|
|
/**
|
|
* @brief POD snapshot of a viewer's workarea (olive::TimelineWorkArea:
|
|
* range in/out + enabled flag). Rationals in seconds.
|
|
*/
|
|
typedef struct oakengine_viewer_workarea {
|
|
int64_t in_num;
|
|
int64_t in_den;
|
|
int64_t out_num;
|
|
int64_t out_den;
|
|
int enabled;
|
|
} oakengine_viewer_workarea;
|
|
|
|
/**
|
|
* @brief Return `node` if its engine object is a viewer (olive::ViewerOutput
|
|
* or subclass, e.g. Sequence/Footage), NULL otherwise. Replaces
|
|
* dynamic_cast<ViewerOutput*> at the app boundary; also the canonical way
|
|
* to validate a handle for this family.
|
|
*/
|
|
OAKENGINE_API OakEngineNode *oakengine_viewer_from_node(OakEngineNode *node);
|
|
|
|
/** @brief const overload of oakengine_viewer_from_node(). */
|
|
OAKENGINE_API const OakEngineNode *
|
|
oakengine_viewer_from_const_node(const OakEngineNode *node);
|
|
|
|
/* ---- Input ids / constants (ViewerOutput::k_* statics) ------------------ */
|
|
|
|
/** @brief ViewerOutput::k_video_params_input. Static string, never freed. */
|
|
OAKENGINE_API const char *oakengine_viewer_video_params_input_id(void);
|
|
/** @brief ViewerOutput::k_audio_params_input. */
|
|
OAKENGINE_API const char *oakengine_viewer_audio_params_input_id(void);
|
|
/** @brief ViewerOutput::k_subtitle_params_input. */
|
|
OAKENGINE_API const char *oakengine_viewer_subtitle_params_input_id(void);
|
|
/** @brief ViewerOutput::k_texture_input. */
|
|
OAKENGINE_API const char *oakengine_viewer_texture_input_id(void);
|
|
/** @brief ViewerOutput::k_samples_input. */
|
|
OAKENGINE_API const char *oakengine_viewer_samples_input_id(void);
|
|
/** @brief ViewerOutput::k_default_sample_format (olive::core::SampleFormat). */
|
|
OAKENGINE_API int oakengine_viewer_default_sample_format(void);
|
|
|
|
/* ---- Playhead / length --------------------------------------------------- */
|
|
|
|
/** @brief Current playhead in seconds (ViewerOutput::get_playhead()). */
|
|
OAKENGINE_API int oakengine_viewer_get_playhead(const OakEngineNode *self,
|
|
int64_t *num, int64_t *den);
|
|
|
|
/** @brief Move the playhead (ViewerOutput::set_playhead()). Emits
|
|
* OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED. */
|
|
OAKENGINE_API int oakengine_viewer_set_playhead(OakEngineNode *self,
|
|
int64_t num, int64_t den);
|
|
|
|
/**
|
|
* @brief Set the video parameters of stream `index` on `self`
|
|
* (ViewerOutput::set_video_params()). `self` must be a viewer node.
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_set_video_params(OakEngineNode *self,
|
|
const oak_video_params *params,
|
|
int index);
|
|
|
|
/**
|
|
* @brief Set the audio parameters of stream `index` on `self`
|
|
* (ViewerOutput::set_audio_params()). `self` must be a viewer node.
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_set_audio_params(OakEngineNode *self,
|
|
int sample_rate,
|
|
uint64_t channel_layout,
|
|
int format, int index);
|
|
|
|
/** @brief Content length in seconds (ViewerOutput::get_length()). */
|
|
OAKENGINE_API int oakengine_viewer_get_length(const OakEngineNode *self,
|
|
int64_t *num, int64_t *den);
|
|
|
|
/** @brief Video content length in seconds (ViewerOutput::get_video_length()). */
|
|
OAKENGINE_API int oakengine_viewer_get_video_length(const OakEngineNode *self,
|
|
int64_t *num,
|
|
int64_t *den);
|
|
|
|
/** @brief Audio content length in seconds (ViewerOutput::get_audio_length()). */
|
|
OAKENGINE_API int oakengine_viewer_get_audio_length(const OakEngineNode *self,
|
|
int64_t *num,
|
|
int64_t *den);
|
|
|
|
/* ---- Stream parameters ---------------------------------------------------- */
|
|
|
|
/**
|
|
* @brief Video params of stream `index` (ViewerOutput::get_video_params()).
|
|
* `out` is always written; an out-of-range index yields a zeroed struct
|
|
* (width/height 0 = invalid, matches an invalid olive::VideoParams).
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_get_video_params(
|
|
const OakEngineNode *self, int index, oak_video_params *out);
|
|
|
|
/**
|
|
* @brief Audio params of stream `index` (ViewerOutput::get_audio_params()).
|
|
* Any of the out pointers may be NULL. `format` is an
|
|
* olive::core::SampleFormat value; out-of-range index yields 0/0/0.
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_get_audio_params(
|
|
const OakEngineNode *self, int index, int *sample_rate,
|
|
uint64_t *channel_layout, int *format);
|
|
|
|
/** @brief Number of video streams (ViewerOutput::get_video_stream_count()). */
|
|
OAKENGINE_API int oakengine_viewer_get_video_stream_count(
|
|
const OakEngineNode *self);
|
|
/** @brief Number of audio streams (ViewerOutput::get_audio_stream_count()). */
|
|
OAKENGINE_API int oakengine_viewer_get_audio_stream_count(
|
|
const OakEngineNode *self);
|
|
/** @brief Number of subtitle streams (ViewerOutput::get_subtitle_stream_count()). */
|
|
OAKENGINE_API int oakengine_viewer_get_subtitle_stream_count(
|
|
const OakEngineNode *self);
|
|
|
|
/**
|
|
* @brief 1 if stream `index` of `track_type` (OAKENGINE_TRACK_TYPE_*) is
|
|
* enabled (VideoParams/AudioParams/SubtitleParams::enabled()), else 0;
|
|
* OAKENGINE_E_INVALID (< 0) on bad arguments.
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_get_stream_enabled(
|
|
const OakEngineNode *self, int track_type, int index);
|
|
|
|
/**
|
|
* @brief Number of subtitles in subtitle stream `index`
|
|
* (SubtitleParams::size()); < 0 on bad arguments.
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_get_subtitle_count(
|
|
const OakEngineNode *self, int index);
|
|
|
|
/**
|
|
* @brief Borrowed pointer to subtitle `sub_index` of subtitle stream
|
|
* `index` (a const olive::Subtitle*; the application copies the value out,
|
|
* it must not free or store it beyond the footage's lifetime). NULL on
|
|
* bad arguments.
|
|
*/
|
|
OAKENGINE_API const void *oakengine_viewer_get_subtitle_at(
|
|
const OakEngineNode *self, int index, int sub_index);
|
|
|
|
/**
|
|
* @brief 1 if the viewer has at least one enabled stream of `track_type`
|
|
* (OAKENGINE_TRACK_TYPE_* from timeline.h), else 0
|
|
* (ViewerOutput::has_enabled_video/audio/subtitle_streams()).
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_has_enabled_streams(
|
|
const OakEngineNode *self, int track_type);
|
|
|
|
/**
|
|
* @brief Params of the first enabled video stream
|
|
* (ViewerOutput::get_first_enabled_video_stream()); zeroed struct when
|
|
* none is enabled.
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_get_first_enabled_video_stream(
|
|
const OakEngineNode *self, oak_video_params *out);
|
|
|
|
/**
|
|
* @brief Number of enabled streams of all types
|
|
* (ViewerOutput::get_enabled_streams_as_references().size()).
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_get_enabled_stream_count(
|
|
const OakEngineNode *self);
|
|
|
|
/**
|
|
* @brief Write the enabled stream references
|
|
* (ViewerOutput::get_enabled_streams_as_references()) into caller arrays:
|
|
* `types[k]` = OAKENGINE_TRACK_TYPE_*, `indices[k]` = stream index within
|
|
* that type. At most `max` entries are written; returns the total count
|
|
* (call with max=0/NULL arrays to query, or use
|
|
* oakengine_viewer_get_enabled_stream_count()).
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_get_enabled_streams(
|
|
const OakEngineNode *self, int *types, int *indices, int max);
|
|
|
|
/* ---- Workarea -------------------------------------------------------------- */
|
|
|
|
/** @brief Snapshot of the viewer's workarea (ViewerOutput::get_work_area()
|
|
* range/enabled as POD). */
|
|
OAKENGINE_API int oakengine_viewer_get_workarea(
|
|
const OakEngineNode *self, oakengine_viewer_workarea *out);
|
|
|
|
/** @brief Set the workarea range (TimelineWorkArea::set_range()). Emits the
|
|
* workarea range notification on the underlying workarea object. */
|
|
OAKENGINE_API int oakengine_viewer_set_workarea_range(OakEngineNode *self,
|
|
int64_t in_num,
|
|
int64_t in_den,
|
|
int64_t out_num,
|
|
int64_t out_den);
|
|
|
|
/** @brief Enable/disable the workarea (TimelineWorkArea::set_enabled()). */
|
|
OAKENGINE_API int oakengine_viewer_set_workarea_enabled(OakEngineNode *self,
|
|
int enabled);
|
|
|
|
/* ---- Parameter setup / waveform --------------------------------------------- */
|
|
|
|
/** @brief Apply the application default parameters
|
|
* (ViewerOutput::set_default_parameters(): width/height/pixel aspect/
|
|
* interlacing/audio layout from Config, frame rate from
|
|
* DefaultSequenceFrameRate). */
|
|
OAKENGINE_API int oakengine_viewer_set_default_parameters(OakEngineNode *self);
|
|
|
|
/**
|
|
* @brief Create a command that sets the viewer's preview resolution divider
|
|
* (changes the k_video_params_input standard value). Returns an opaque command
|
|
* pointer, or NULL when `self` is not a viewer or `divider` is invalid.
|
|
*/
|
|
OAKENGINE_API void *oakengine_viewer_set_preview_divider_command(
|
|
OakEngineNode *self, int divider);
|
|
|
|
/**
|
|
* @brief Adopt the parameters of the given footage viewers
|
|
* (ViewerOutput::set_parameters_from_footage()). Every element of
|
|
* `footage` must itself be a viewer handle.
|
|
*/
|
|
OAKENGINE_API int oakengine_viewer_set_parameters_from_footage(
|
|
OakEngineNode *self, OakEngineNode *const *footage, int count);
|
|
|
|
/** @brief Enable/disable waveform cache requests
|
|
* (ViewerOutput::set_waveform_enabled()). */
|
|
OAKENGINE_API int oakengine_viewer_set_waveform_enabled(OakEngineNode *self,
|
|
int enabled);
|
|
|
|
/**
|
|
* @brief The waveform cache of the connected sample output, or NULL
|
|
* (ViewerOutput::get_connected_waveform()). Opaque borrowed pointer; the
|
|
* application only passes it through to its own audio monitor, it must not
|
|
* dereference it.
|
|
*/
|
|
OAKENGINE_API const void *
|
|
oakengine_viewer_get_connected_waveform(const OakEngineNode *self);
|
|
|
|
/**
|
|
* @brief Borrowed handle of the viewer's timeline marker list
|
|
* (ViewerOutput::get_markers()), for the oakengine_marker_list_* family
|
|
* and the OAKENGINE_EVENT_MARKER_LIST_* events. NULL when `self` is not a
|
|
* viewer.
|
|
*/
|
|
OAKENGINE_API OakEngineMarkerList *
|
|
oakengine_viewer_get_marker_list(OakEngineNode *self);
|
|
|
|
/**
|
|
* @brief Borrowed handle of the viewer's workarea
|
|
* (ViewerOutput::get_work_area()), for the oakengine_workarea_* family and
|
|
* the OAKENGINE_EVENT_WORKAREA_* events. NULL when `self` is not a viewer.
|
|
*/
|
|
OAKENGINE_API OakEngineWorkarea *
|
|
oakengine_viewer_get_workarea_handle(OakEngineNode *self);
|
|
|
|
/* ---- Playback cache / frame cache ------------------------------------------ */
|
|
|
|
/**
|
|
* @brief Opaque playback cache handle (olive::PlaybackCache).
|
|
*/
|
|
typedef struct OakEnginePlaybackCache OakEnginePlaybackCache;
|
|
|
|
/**
|
|
* @brief Opaque frame cache handle (olive::FrameHashCache).
|
|
*/
|
|
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
|
|
* ClipBlock::connected_video_cache()). Returns NULL when not available
|
|
* or when `self` is not a viewer/clip node.
|
|
*/
|
|
OAKENGINE_API OakEnginePlaybackCache *
|
|
oakengine_viewer_get_playback_cache(OakEngineNode *self);
|
|
|
|
/**
|
|
* @brief Static indicator height for playback cache rendering
|
|
* (PlaybackCache::get_cache_indicator_height()). > 0.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_cache_indicator_height(void);
|
|
|
|
/**
|
|
* @brief Fill `ranges` with the valid (cached) time ranges from the
|
|
* playback cache. `ranges` is an array of (in_num,in_den,out_num,out_den)
|
|
* int64_t quads; at most `max` ranges are written. Returns the number of
|
|
* ranges written, or OAKENGINE_E_INVALID on NULL cache.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_cache_valid_ranges(
|
|
OakEnginePlaybackCache *cache, int64_t *ranges, int max);
|
|
|
|
/**
|
|
* @brief Borrowed frame hash cache (FrameHashCache) of a viewer node
|
|
* (ViewerOutput has a get_video_cache(), etc.). Returns NULL when not
|
|
* available or when `self` is not a viewer node.
|
|
*/
|
|
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
|
|
}
|
|
Q_DECLARE_OPAQUE_POINTER(OakEnginePlaybackCache *)
|
|
Q_DECLARE_OPAQUE_POINTER(OakEngineFrameCache *)
|
|
Q_DECLARE_OPAQUE_POINTER(OakEngineThumbnailCache *)
|
|
Q_DECLARE_OPAQUE_POINTER(OakEngineWaveformCache *)
|
|
#endif
|
|
|
|
#endif /* OAKENGINE_VIEWER_H */
|