/*** 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 . ***/ #ifndef OAKENGINE_VIEWER_H #define OAKENGINE_VIEWER_H #include #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 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 } #endif #endif /* OAKENGINE_VIEWER_H */