refactor(codec): de-Qt oakcodec and wrap it in a pure C ABI; switch common handles to refcounted value structs
- oakcodec: de-Qt all 20 sources (QThread decode loop -> std::thread,
QObject/signals -> callbacks), pure C ABI in include/codec with
refcounted neutral handles (OakFrame/OakDecoder/OakEncoder),
framemanager moved in from render, frame_to_buffer/buffer_to_frame
moved in from oakcommon oiioutils, codec->task via submit callback
(M8 will register), all cross-module calls go through the other
side's C API, -fvisibility=hidden + OAKCODEC_API
- oakcommon: handles become refcounted value structs
{ctx, addref, release, abi_version} (FFmpeg-style), pass-by-value
signatures, free() as release wrapper; init_from_native/get_native
for copyable value objects; OakCommonXxx renamed to OakXxx
- oakcommon: add logging (log_debug/info/warning/critical with level
filtering and sink injection) + printf-style oakcommon_log C wrapper
- oakrender: add CancelAtom C API family; complete
oakrender_color_processor_convert_frame; fix get_processor() missing
definition and OCIO env var lookup
- tests: oakcommon 174, oaknode 96, oakrender 42, oakcodec 18, all
green in their standalone builds
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_CODEC_CONFORM_H
|
||||
#define OAK_EDITOR_CODEC_CONFORM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file conform.h
|
||||
* @brief C ABI for the oakcodec audio conform manager
|
||||
* (olive::ConformManager): pcm waveform cache files used for fast
|
||||
* audio scrubbing.
|
||||
*
|
||||
* Interim state (pre-M8): actual conform work is delegated to the global
|
||||
* task submit callback (see task.h). While no callback is registered,
|
||||
* state queries report OAKCODEC_CONFORM_UNAVAILABLE.
|
||||
*/
|
||||
|
||||
#define OAKCODEC_CONFORM_EXISTS 0
|
||||
#define OAKCODEC_CONFORM_GENERATING 1
|
||||
#define OAKCODEC_CONFORM_UNAVAILABLE 2
|
||||
|
||||
/**
|
||||
* @brief Create the ConformManager singleton (no-op when it exists).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_conform_create_instance(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy the ConformManager singleton (no-op when absent).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_conform_destroy_instance(void);
|
||||
|
||||
/**
|
||||
* @brief Query the conform state of one audio stream, starting the
|
||||
* conform when needed and possible.
|
||||
*
|
||||
* Addresses the source by filename/stream_index and the target audio
|
||||
* format by sample_rate/channel_layout/sample_format
|
||||
* (olive::core::SampleFormat::Format as int).
|
||||
*
|
||||
* When the conform files do not exist and a task submit callback is
|
||||
* registered (task.h), the conform is submitted synchronously and the
|
||||
* filesystem is re-checked; `wait` only controls whether a post-submit
|
||||
* miss is reported as OAKCODEC_CONFORM_UNAVAILABLE (wait != 0) or
|
||||
* OAKCODEC_CONFORM_GENERATING (wait == 0). Without a registrar the
|
||||
* result is always OAKCODEC_CONFORM_UNAVAILABLE.
|
||||
*
|
||||
* @return One of OAKCODEC_CONFORM_* (non-negative), or a negative
|
||||
* OAKCODEC_E_* code for invalid arguments.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_conform_get_state(const char *cache_path,
|
||||
const char *source_filename, int stream_index,
|
||||
int sample_rate, uint64_t channel_layout,
|
||||
int sample_format, int wait);
|
||||
|
||||
/**
|
||||
* @brief Number of conform (pcm) files for the given stream/params — one
|
||||
* per channel; 0 on invalid arguments.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_conform_filename_count(const char *cache_path,
|
||||
const char *source_filename, int stream_index,
|
||||
int sample_rate, uint64_t channel_layout,
|
||||
int sample_format);
|
||||
|
||||
/**
|
||||
* @brief The `index`-th conform filename (buf/size getter).
|
||||
*
|
||||
* @return Required buffer size including NUL (non-negative), or a
|
||||
* negative OAKCODEC_E_* code (OAKCODEC_E_NOT_FOUND when index is
|
||||
* out of range).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_conform_filename_at(const char *cache_path,
|
||||
const char *source_filename,
|
||||
int stream_index, int sample_rate,
|
||||
uint64_t channel_layout, int sample_format,
|
||||
int index, char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_CODEC_CONFORM_H
|
||||
@@ -0,0 +1,212 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_CODEC_DECODER_H
|
||||
#define OAK_EDITOR_CODEC_DECODER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "frame.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file decoder.h
|
||||
* @brief C ABI for oakcodec media decoders (olive::Decoder and its
|
||||
* FFmpeg/OIIO implementations): probing, stream enumeration and
|
||||
* CPU-frame decoding.
|
||||
*
|
||||
* Handles follow the neutral by-value convention documented in frame.h
|
||||
* (and oakcommon's common/handle.h). Two usage patterns share the
|
||||
* OakDecoder handle:
|
||||
*
|
||||
* - Probe: oakcodec_decoder_probe() inspects a file WITHOUT opening a
|
||||
* decode session; the stream getters describe what was found.
|
||||
* - Decode: oakcodec_decoder_init() + oakcodec_decoder_open() attach a
|
||||
* decoder instance to one (filename, stream) pair; the decode
|
||||
* functions then produce frames/audio.
|
||||
*/
|
||||
|
||||
typedef struct OakDecoder {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCODEC_ABI_VERSION. */
|
||||
} OakDecoder;
|
||||
|
||||
/**
|
||||
* @brief POD description of one probed video stream.
|
||||
*
|
||||
* duration_ts counts units of the stream's time base;
|
||||
* time_base_num/den is seconds per time-base unit. color_primaries and
|
||||
* color_trc carry the ISO/IEC 23001-8 code points the decoder reports
|
||||
* (0 = unknown). interlaced is 1 when the stream is interlaced.
|
||||
* format is an OakPixelFormat value (the decoder's native delivery
|
||||
* format), channel_count its plane channel count.
|
||||
*/
|
||||
typedef struct oakcodec_video_stream_info {
|
||||
int stream_index;
|
||||
int width;
|
||||
int height;
|
||||
int frame_rate_num;
|
||||
int frame_rate_den;
|
||||
int64_t duration_ts;
|
||||
int time_base_num;
|
||||
int time_base_den;
|
||||
int format;
|
||||
int channel_count;
|
||||
int color_primaries;
|
||||
int color_trc;
|
||||
int interlaced;
|
||||
} oakcodec_video_stream_info;
|
||||
|
||||
/**
|
||||
* @brief POD description of one probed audio stream.
|
||||
*
|
||||
* channel_layout is the ffmpeg-style channel mask (e.g. 0x3 = stereo).
|
||||
*/
|
||||
typedef struct oakcodec_audio_stream_info {
|
||||
int stream_index;
|
||||
int sample_rate;
|
||||
uint64_t channel_layout;
|
||||
int channel_count;
|
||||
int64_t duration_ts;
|
||||
int time_base_num;
|
||||
int time_base_den;
|
||||
} oakcodec_audio_stream_info;
|
||||
|
||||
/* ---- Probe (stateless inspection) ---------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Probe a media file: decoder name plus stream inventory.
|
||||
*
|
||||
* Tries each available decoder implementation (FFmpeg, then OIIO) and
|
||||
* wraps the first one that recognizes the file. The returned handle only
|
||||
* carries probe results; it cannot decode (use init + open for that).
|
||||
*
|
||||
* @return Handle with reference count 1, or an empty handle (ctx == NULL)
|
||||
* when no decoder recognizes the file (oakcodec_probe_last_error()
|
||||
* carries the reason).
|
||||
*/
|
||||
OAKCODEC_API OakDecoder oakcodec_decoder_probe(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Thread-local error detail of the last failed probe on this
|
||||
* thread (buf/size string getter convention).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_probe_last_error(char *buf, int buf_size);
|
||||
|
||||
/** @brief Probed decoder id ("ffmpeg"/"oiio", buf/size getter). */
|
||||
OAKCODEC_API int oakcodec_decoder_probe_decoder_name(OakDecoder probe, char *buf,
|
||||
int buf_size);
|
||||
|
||||
OAKCODEC_API int oakcodec_decoder_probe_video_stream_count(OakDecoder probe);
|
||||
OAKCODEC_API int oakcodec_decoder_probe_audio_stream_count(OakDecoder probe);
|
||||
OAKCODEC_API int oakcodec_decoder_probe_subtitle_stream_count(OakDecoder probe);
|
||||
|
||||
/**
|
||||
* @brief Fill `out` with the video stream at `index` (0-based within the
|
||||
* video stream list).
|
||||
*
|
||||
* @return OAKCODEC_OK, OAKCODEC_E_INVALID, or OAKCODEC_E_NOT_FOUND when
|
||||
* index is out of range.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_decoder_probe_get_video_stream(OakDecoder probe, int index,
|
||||
oakcodec_video_stream_info *out);
|
||||
OAKCODEC_API int oakcodec_decoder_probe_get_audio_stream(OakDecoder probe, int index,
|
||||
oakcodec_audio_stream_info *out);
|
||||
|
||||
/* ---- Decode session ------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Create a closed decoder handle (count 1).
|
||||
*/
|
||||
OAKCODEC_API OakDecoder oakcodec_decoder_init(void);
|
||||
|
||||
/**
|
||||
* @brief Release one reference to a decoder. No-op on NULL/empty.
|
||||
*/
|
||||
OAKCODEC_API void oakcodec_decoder_free(OakDecoder *decoder);
|
||||
|
||||
/**
|
||||
* @brief Open `filename`'s stream `stream_index` for decoding.
|
||||
*
|
||||
* The decoder implementation is chosen automatically from the probe
|
||||
* results. Opening an already-open decoder on the same stream is a
|
||||
* successful no-op.
|
||||
*
|
||||
* @return OAKCODEC_OK on success, OAKCODEC_E_NOT_FOUND when the file
|
||||
* does not exist, OAKCODEC_E_FAILED otherwise (see
|
||||
* oakcodec_decoder_last_error()).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_decoder_open(OakDecoder decoder, const char *filename,
|
||||
int stream_index);
|
||||
|
||||
/** @brief Close the current stream (safe when closed). */
|
||||
OAKCODEC_API int oakcodec_decoder_close(OakDecoder decoder);
|
||||
|
||||
/** @brief 1 when a stream is open, 0 otherwise. */
|
||||
OAKCODEC_API int oakcodec_decoder_is_open(OakDecoder decoder);
|
||||
|
||||
/**
|
||||
* @brief Decode the video frame at `numerator/denominator` seconds.
|
||||
*
|
||||
* Before the start of the footage the first frame is returned, after the
|
||||
* end the last frame.
|
||||
*
|
||||
* @return A frame handle with reference count 1 (caller releases), or an
|
||||
* empty handle (ctx == NULL) on error/EOF — check
|
||||
* oakcodec_decoder_last_error().
|
||||
*/
|
||||
OAKCODEC_API OakFrame oakcodec_decoder_decode_video(OakDecoder decoder, int numerator,
|
||||
int denominator);
|
||||
|
||||
/**
|
||||
* @brief Decode audio into a float buffer.
|
||||
*
|
||||
* Decodes the interleaved audio covering [in, out) seconds (rational
|
||||
* pairs), resampled/laid out to `sample_rate`/`channel_layout`.
|
||||
* `buf` must hold at least `buf_frames` frames worth of interleaved
|
||||
* floats.
|
||||
*
|
||||
* @return The number of frames written (>= 0), or a negative
|
||||
* OAKCODEC_E_* code. Conform generation is NOT triggered by this
|
||||
* family in the current intermediate state (no task registrar);
|
||||
* media requiring a conform yields OAKCODEC_E_STATE.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_decoder_decode_audio(OakDecoder decoder, int in_num, int in_den,
|
||||
int out_num, int out_den, int sample_rate,
|
||||
uint64_t channel_layout, float *buf,
|
||||
int buf_frames);
|
||||
|
||||
/**
|
||||
* @brief Human-readable detail of the last error on this decoder
|
||||
* (buf/size string getter convention).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_decoder_last_error(OakDecoder decoder, char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_CODEC_DECODER_H
|
||||
@@ -0,0 +1,192 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_CODEC_ENCODER_H
|
||||
#define OAK_EDITOR_CODEC_ENCODER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "frame.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file encoder.h
|
||||
* @brief C ABI for oakcodec media encoders (olive::Encoder and its
|
||||
* FFmpeg/OIIO implementations).
|
||||
*
|
||||
* Handles follow the neutral by-value convention documented in frame.h.
|
||||
* The workflow is: fill an oakcodec_encoding_params POD (all fields,
|
||||
* zeroed = disabled) -> oakcodec_encoder_init() ->
|
||||
* oakcodec_encoder_open() -> oakcodec_encoder_write_*() ->
|
||||
* oakcodec_encoder_flush(). Encoder-specific options
|
||||
* (e.g. "crf" = "18") go through oakcodec_encoder_set_video_option()
|
||||
* between init and open.
|
||||
*
|
||||
* Enum int fields carry the engine's own enum values
|
||||
* (olive::ExportFormat::Format, olive::ExportCodec::Codec,
|
||||
* OakPixelFormat, olive::VideoParams::Interlacing,
|
||||
* olive::core::SampleFormat::Format) — the same values
|
||||
* oakengine/encoding.h documents.
|
||||
*/
|
||||
|
||||
typedef struct OakEncoder {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCODEC_ABI_VERSION. */
|
||||
} OakEncoder;
|
||||
|
||||
/** @brief olive::VideoParams::Interlacing values. */
|
||||
#define OAKCODEC_INTERLACE_NONE 0
|
||||
#define OAKCODEC_INTERLACE_TOP_FIRST 1
|
||||
#define OAKCODEC_INTERLACE_BOTTOM_FIRST 2
|
||||
|
||||
/** @brief EncodingParams::VideoScalingMethod values. */
|
||||
#define OAKCODEC_ENCODING_SCALING_FIT 0
|
||||
#define OAKCODEC_ENCODING_SCALING_STRETCH 1
|
||||
#define OAKCODEC_ENCODING_SCALING_CROP 2
|
||||
|
||||
/**
|
||||
* @brief Flattened encoding parameters (olive::EncodingParams).
|
||||
*
|
||||
* A zeroed struct describes an all-tracks-disabled configuration. The
|
||||
* filename (and image-sequence "[#####]" template when
|
||||
* video_is_image_sequence is set) lives in `filename`.
|
||||
* video_time_base_* is the frame duration (frame rate flipped), matching
|
||||
* oak_video_params' convention.
|
||||
*/
|
||||
typedef struct oakcodec_encoding_params {
|
||||
char filename[1024];
|
||||
int format; /**< olive::ExportFormat::Format. */
|
||||
|
||||
int video_enabled; /**< 1/0. */
|
||||
int video_codec; /**< olive::ExportCodec::Codec. */
|
||||
int video_width;
|
||||
int video_height;
|
||||
int video_time_base_num; /**< Frame duration numerator. */
|
||||
int video_time_base_den;
|
||||
int video_pixel_format; /**< OakPixelFormat (delivery format). */
|
||||
int video_interlacing; /**< OAKCODEC_INTERLACE_*. */
|
||||
int video_pixel_aspect_num;
|
||||
int video_pixel_aspect_den;
|
||||
int64_t video_bit_rate; /**< bit/s, 0 = codec default. */
|
||||
int64_t video_min_bit_rate;
|
||||
int64_t video_max_bit_rate;
|
||||
int64_t video_buffer_size; /**< bytes. */
|
||||
int video_threads; /**< 0 = auto. */
|
||||
char video_pix_fmt[64]; /**< Encoded pixel format name ("yuv420p"). */
|
||||
int video_is_image_sequence; /**< 1/0. */
|
||||
int video_scaling_method; /**< OAKCODEC_ENCODING_SCALING_*. */
|
||||
|
||||
int audio_enabled; /**< 1/0. */
|
||||
int audio_codec; /**< olive::ExportCodec::Codec. */
|
||||
int audio_sample_rate;
|
||||
uint64_t audio_channel_layout; /**< ffmpeg-style channel mask. */
|
||||
int audio_sample_format; /**< olive::core::SampleFormat::Format. */
|
||||
int64_t audio_bit_rate; /**< bit/s. */
|
||||
|
||||
int subtitles_enabled; /**< 1/0. */
|
||||
int subtitles_codec; /**< olive::ExportCodec::Codec. */
|
||||
int subtitles_are_sidecar; /**< 1/0. */
|
||||
int subtitles_sidecar_format; /**< olive::ExportFormat::Format. */
|
||||
|
||||
/** Output OCIO colorspace name; empty = reference space (no transform). */
|
||||
char color_transform_output[256];
|
||||
|
||||
int export_length_num; /**< Export length in seconds (rational). */
|
||||
int export_length_den;
|
||||
} oakcodec_encoding_params;
|
||||
|
||||
/**
|
||||
* @brief Create an encoder for `params` (count 1).
|
||||
*
|
||||
* The implementation (FFmpeg/OIIO) is chosen from params.format and the
|
||||
* enabled tracks. The file is NOT opened yet. Returns an empty handle
|
||||
* (ctx == NULL) when the configuration is invalid.
|
||||
*/
|
||||
OAKCODEC_API OakEncoder oakcodec_encoder_init(const oakcodec_encoding_params *params);
|
||||
|
||||
/** @brief Release one reference to an encoder. No-op on NULL/empty. */
|
||||
OAKCODEC_API void oakcodec_encoder_free(OakEncoder *encoder);
|
||||
|
||||
/**
|
||||
* @brief Set an encoder-specific video option (e.g. "crf" = "18").
|
||||
*
|
||||
* Only valid between init and open.
|
||||
*
|
||||
* @return OAKCODEC_OK, or OAKCODEC_E_STATE when already open.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_encoder_set_video_option(OakEncoder encoder, const char *key,
|
||||
const char *value);
|
||||
|
||||
/**
|
||||
* @brief Open the output file and write stream headers.
|
||||
*
|
||||
* @return OAKCODEC_OK, OAKCODEC_E_STATE (already open), or
|
||||
* OAKCODEC_E_FAILED (see oakcodec_encoder_last_error()).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_encoder_open(OakEncoder encoder);
|
||||
|
||||
/**
|
||||
* @brief Encode one video frame.
|
||||
*
|
||||
* The frame's parameters must match the encoding parameters (the encoder
|
||||
* converts the delivery pixel format to the encoded one internally).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_encoder_write_video(OakEncoder encoder, OakFrame frame);
|
||||
|
||||
/**
|
||||
* @brief Encode interleaved float audio samples.
|
||||
*
|
||||
* @param samples frame_count * channel_count interleaved floats.
|
||||
* @return OAKCODEC_OK or a negative OAKCODEC_E_* code.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_encoder_write_audio(OakEncoder encoder, const float *samples,
|
||||
int frame_count);
|
||||
|
||||
/**
|
||||
* @brief Encode one subtitle entry (times in seconds).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_encoder_write_subtitle(OakEncoder encoder, const char *text,
|
||||
double in_seconds, double out_seconds);
|
||||
|
||||
/**
|
||||
* @brief Flush the encoders, write the trailer and close the file.
|
||||
*
|
||||
* Idempotent; after a successful flush the encoder cannot be written to
|
||||
* (write calls return OAKCODEC_E_STATE).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_encoder_flush(OakEncoder encoder);
|
||||
|
||||
/**
|
||||
* @brief Human-readable detail of the last error on this encoder
|
||||
* (buf/size string getter convention).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_encoder_last_error(OakEncoder encoder, char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_CODEC_ENCODER_H
|
||||
@@ -0,0 +1,61 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_CODEC_ERROR_H
|
||||
#define OAK_EDITOR_CODEC_ERROR_H
|
||||
|
||||
/**
|
||||
* @brief Status and error codes shared by all oakcodec C API families.
|
||||
*
|
||||
* Return-code convention (mirrors the other split modules):
|
||||
* 0 (OAKCODEC_OK) on success, a negative OAKCODEC_E_* error code on
|
||||
* failure. String getters return the required buffer size in bytes
|
||||
* (including the terminating NUL) as a non-negative value instead.
|
||||
*/
|
||||
#define OAKCODEC_OK 0 /**< Success. */
|
||||
#define OAKCODEC_E_INVALID (-1) /**< NULL handle or invalid argument. */
|
||||
#define OAKCODEC_E_STATE (-2) /**< Call not valid in the current state. */
|
||||
#define OAKCODEC_E_FAILED (-3) /**< The underlying operation failed. */
|
||||
#define OAKCODEC_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
|
||||
#define OAKCODEC_E_NOMEM (-5) /**< Allocation failed. */
|
||||
#define OAKCODEC_E_CANCELLED (-6) /**< The operation was cancelled. */
|
||||
|
||||
/**
|
||||
* @brief Current ABI version stamped into every oakcodec handle.
|
||||
*
|
||||
* Bump whenever the handle layout or the semantics of any exported
|
||||
* function change incompatibly. Consumers should compare a handle's
|
||||
* abi_version field against the value they were compiled with before
|
||||
* dereferencing ctx.
|
||||
*/
|
||||
#define OAKCODEC_ABI_VERSION 1
|
||||
|
||||
/**
|
||||
* @brief Export macro for the oakcodec C ABI.
|
||||
*
|
||||
* oakcodec is built with -fvisibility=hidden (01 §1 rule 5): only the
|
||||
* oakcodec_* functions marked with this macro leave the shared library.
|
||||
* This also keeps codec-internal C++ classes (whose olive::* names may
|
||||
* collide with transition stubs inside other modules) from participating
|
||||
* in cross-library weak-symbol coalescing.
|
||||
*/
|
||||
#define OAKCODEC_API __attribute__((visibility("default")))
|
||||
|
||||
#endif //OAK_EDITOR_CODEC_ERROR_H
|
||||
@@ -0,0 +1,162 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_CODEC_FRAME_H
|
||||
#define OAK_EDITOR_CODEC_FRAME_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common/videoparams.h"
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file frame.h
|
||||
* @brief C ABI for the oakcodec frame object (olive::Frame), a CPU pixel
|
||||
* buffer plus an OakVideoParams parameter set.
|
||||
*
|
||||
* Handle convention (all oakcodec families): neutral by-value handles with
|
||||
* the same four fields as oakcommon (see oakcommon's common/handle.h):
|
||||
*
|
||||
* typedef struct OakFrame {
|
||||
* void *ctx; // opaque, points to the impl
|
||||
* void (*addref)(void *ctx); // atomic +1, owner-DLL code
|
||||
* void (*release)(void *ctx); // atomic -1, destroys at 0
|
||||
* uint32_t abi_version; // OAKCODEC_ABI_VERSION
|
||||
* } OakFrame;
|
||||
*
|
||||
* oakcodec_frame_init*() returns a handle whose underlying object has
|
||||
* reference count 1. Copying the struct copies the pointer, not the
|
||||
* count: call handle.addref(handle.ctx) for every additional long-lived
|
||||
* copy and handle.release(handle.ctx) (or oakcodec_frame_free()) when
|
||||
* done with each copy. Functions that only use a handle take it BY
|
||||
* VALUE; an empty handle (ctx == NULL) is reported as
|
||||
* OAKCODEC_E_INVALID. oakcodec_frame_free() takes a pointer so it can
|
||||
* null out the caller's ctx; NULL and ctx == NULL are no-ops.
|
||||
*/
|
||||
typedef struct OakFrame {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCODEC_ABI_VERSION. */
|
||||
} OakFrame;
|
||||
|
||||
/**
|
||||
* @brief Create an empty frame with default (invalid) video parameters.
|
||||
*
|
||||
* @return Handle with reference count 1; ctx is NULL on allocation
|
||||
* failure.
|
||||
*/
|
||||
OAKCODEC_API OakFrame oakcodec_frame_init(void);
|
||||
|
||||
/**
|
||||
* @brief Create a frame with a copy of the given parameter set.
|
||||
*
|
||||
* The params handle is addref'd internally; the caller keeps its own
|
||||
* reference. The frame is not allocated; call oakcodec_frame_allocate().
|
||||
*
|
||||
* @return Handle with reference count 1; ctx is NULL on failure.
|
||||
*/
|
||||
OAKCODEC_API OakFrame oakcodec_frame_init_with_params(OakVideoParams params);
|
||||
|
||||
/**
|
||||
* @brief Release one reference to a frame.
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx); nulls ctx
|
||||
* afterwards. No-op when frame is NULL or frame->ctx is NULL.
|
||||
*/
|
||||
OAKCODEC_API void oakcodec_frame_free(OakFrame *frame);
|
||||
|
||||
/**
|
||||
* @brief Get a copy of the frame's parameter set.
|
||||
*
|
||||
* @param out Receives an addref'd OakVideoParams; the caller must release
|
||||
* it with oakcommon_videoparams_free().
|
||||
* @return OAKCODEC_OK, or OAKCODEC_E_INVALID for bad arguments.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_frame_get_params(OakFrame frame, OakVideoParams *out);
|
||||
|
||||
/**
|
||||
* @brief Replace the frame's parameter set (the handle is addref'd
|
||||
* internally). Recomputes the line sizes; does not reallocate the
|
||||
* buffer.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_frame_set_params(OakFrame frame, OakVideoParams params);
|
||||
|
||||
/**
|
||||
* @brief Allocate the pixel buffer from the current parameters.
|
||||
*
|
||||
* @return OAKCODEC_OK on success (including already-allocated),
|
||||
* OAKCODEC_E_STATE when the parameters are invalid,
|
||||
* OAKCODEC_E_INVALID for an empty handle.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_frame_allocate(OakFrame frame);
|
||||
|
||||
/** @brief 1 when the pixel buffer is allocated, 0 otherwise. */
|
||||
OAKCODEC_API int oakcodec_frame_is_allocated(OakFrame frame);
|
||||
|
||||
/** @brief Writable pixel buffer, or NULL when unallocated/empty. */
|
||||
OAKCODEC_API void *oakcodec_frame_data(OakFrame frame);
|
||||
|
||||
/** @brief Const variant of oakcodec_frame_data(). */
|
||||
OAKCODEC_API const void *oakcodec_frame_const_data(OakFrame frame);
|
||||
|
||||
/** @brief Size of the pixel buffer in bytes (0 when unallocated). */
|
||||
OAKCODEC_API int oakcodec_frame_allocated_size(OakFrame frame);
|
||||
|
||||
/** @brief Distance between two rows in bytes (0 when params are unset). */
|
||||
OAKCODEC_API int oakcodec_frame_linesize_bytes(OakFrame frame);
|
||||
|
||||
/** @brief Distance between two rows in pixels. */
|
||||
OAKCODEC_API int oakcodec_frame_linesize_pixels(OakFrame frame);
|
||||
|
||||
/* Query helpers; all return 0 / OAKCOMMON_PIXEL_FORMAT_INVALID on an
|
||||
* empty handle. */
|
||||
OAKCODEC_API int oakcodec_frame_width(OakFrame frame);
|
||||
OAKCODEC_API int oakcodec_frame_height(OakFrame frame);
|
||||
OAKCODEC_API int oakcodec_frame_format(OakFrame frame); /**< OakPixelFormat value. */
|
||||
OAKCODEC_API int oakcodec_frame_channel_count(OakFrame frame);
|
||||
|
||||
/**
|
||||
* @brief Frame timestamp as a rational number of seconds.
|
||||
*
|
||||
* @return OAKCODEC_OK, or OAKCODEC_E_INVALID for bad arguments.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_frame_get_timestamp(OakFrame frame, int *numerator,
|
||||
int *denominator);
|
||||
OAKCODEC_API int oakcodec_frame_set_timestamp(OakFrame frame, int numerator,
|
||||
int denominator);
|
||||
|
||||
/**
|
||||
* @brief Number of live oakcodec handle objects (debug/leak checking).
|
||||
*
|
||||
* Counts every boxed object created by oakcodec_*_init*() that has not
|
||||
* been released yet, across all families (frame/decoder/encoder/...).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_debug_alive_count(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_CODEC_FRAME_H
|
||||
@@ -0,0 +1,140 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_CODEC_PROXY_H
|
||||
#define OAK_EDITOR_CODEC_PROXY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file proxy.h
|
||||
* @brief C ABI for the oakcodec proxy generation singleton
|
||||
* (olive::ProxyManager).
|
||||
*
|
||||
* Interim state (pre-M8): actual transcodes are delegated to the global
|
||||
* task submit callback (see task.h). While no callback is registered,
|
||||
* oakcodec_proxy_get_or_start() reports the proxy as missing instead of
|
||||
* starting background work.
|
||||
*/
|
||||
|
||||
#define OAKCODEC_PROXY_STATE_MISSING 0
|
||||
#define OAKCODEC_PROXY_STATE_GENERATING 1
|
||||
#define OAKCODEC_PROXY_STATE_READY 2
|
||||
#define OAKCODEC_PROXY_STATE_FAILED 3
|
||||
|
||||
/**
|
||||
* @brief POD proxy generation parameters (olive::ProxyManager::ProxyParams).
|
||||
*
|
||||
* divider: source resolution divider (1 = use absolute width/height,
|
||||
* 2/4/8 = fraction of the source resolution). extension/preset are the
|
||||
* ffmpeg output container and encoder preset (e.g. "mp4"/"veryfast").
|
||||
*/
|
||||
typedef struct oakcodec_proxy_params {
|
||||
int width;
|
||||
int height;
|
||||
int divider;
|
||||
int version;
|
||||
int crf;
|
||||
int include_audio; /**< 1/0. */
|
||||
char extension[32];
|
||||
char preset[32];
|
||||
} oakcodec_proxy_params;
|
||||
|
||||
typedef struct oakcodec_proxy_result {
|
||||
int state; /**< OAKCODEC_PROXY_STATE_* */
|
||||
char filename[1024];
|
||||
} oakcodec_proxy_result;
|
||||
|
||||
/**
|
||||
* @brief Create the ProxyManager singleton (no-op when it exists).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_proxy_create_instance(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy the ProxyManager singleton (no-op when absent).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_proxy_destroy_instance(void);
|
||||
|
||||
/**
|
||||
* @brief Compiled-in default proxy parameters (1280x720, divider 1, mp4,
|
||||
* crf 23, "veryfast", audio included). Interim state: until the config
|
||||
* milestone wires a real store these do not reflect user settings.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_proxy_params_default(oakcodec_proxy_params *out);
|
||||
|
||||
/**
|
||||
* @brief State of a proxy file on disk (OAKCODEC_PROXY_STATE_*;
|
||||
* OAKCODEC_PROXY_STATE_MISSING for NULL/empty/absent).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_proxy_get_state(const char *proxy_filename);
|
||||
|
||||
/** @brief Human-readable string for a proxy state (buf/size getter). */
|
||||
OAKCODEC_API int oakcodec_proxy_state_to_string(int state, char *buf, int buf_size);
|
||||
|
||||
/** @brief Proxy directory for a project cache path (buf/size getter). */
|
||||
OAKCODEC_API int oakcodec_proxy_get_proxy_directory(const char *cache_path, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Deterministic proxy filename for a source stream (buf/size
|
||||
* getter).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_proxy_get_proxy_filename(const char *cache_path,
|
||||
const char *source_filename,
|
||||
int stream_index,
|
||||
const oakcodec_proxy_params *params,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/** @brief Working (in-progress) filename of a proxy (buf/size getter). */
|
||||
OAKCODEC_API int oakcodec_proxy_get_working_filename(const char *proxy_filename,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Get or start generating a proxy for `source_filename`.
|
||||
*
|
||||
* `cache_path` is the project cache directory. On return `out->state`
|
||||
* and `out->filename` describe the proxy. When a task submit callback is
|
||||
* registered (task.h) and no proxy exists, generation is submitted
|
||||
* synchronously before the state is re-derived; without a registrar the
|
||||
* state stays OAKCODEC_PROXY_STATE_MISSING.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_proxy_get_or_start(const char *cache_path,
|
||||
const char *source_filename, int stream_index,
|
||||
const oakcodec_proxy_params *params,
|
||||
oakcodec_proxy_result *out);
|
||||
|
||||
/**
|
||||
* @brief Locate an ffmpeg executable for proxy generation (buf/size
|
||||
* getter; empty string when none is found).
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_proxy_find_ffmpeg(const char *configured_path, char *buf,
|
||||
int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_CODEC_PROXY_H
|
||||
@@ -0,0 +1,113 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_CODEC_TASK_H
|
||||
#define OAK_EDITOR_CODEC_TASK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Background task submission hook for oakcodec (interim state).
|
||||
*
|
||||
* The codec module occasionally needs background work (audio conforms,
|
||||
* proxy transcodes). The task system itself is split out at milestone M8;
|
||||
* until then oakcodec exposes a single global submit callback. A host
|
||||
* (M8: oaktask) registers a callback with oakcodec_set_task_submit_cb();
|
||||
* the conform/proxy managers call it whenever they need a task.
|
||||
*
|
||||
* While no callback is registered, managers report the work as
|
||||
* unavailable (they never crash and never block).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Kinds of background tasks oakcodec can request.
|
||||
*/
|
||||
enum OakCodecTaskKind {
|
||||
OAKCODEC_TASK_CONFORM = 0, /**< Audio conform to pcm cache files. */
|
||||
OAKCODEC_TASK_PROXY = 1 /**< Video proxy transcode. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Description of one background task request.
|
||||
*
|
||||
* All strings are borrowed and only valid for the duration of the
|
||||
* submit call; the callback must copy anything it retains.
|
||||
*
|
||||
* Field usage by kind:
|
||||
* - OAKCODEC_TASK_CONFORM: input_filename (source media), stream_index
|
||||
* (audio stream), output_filename (final path of the FIRST channel's
|
||||
* pcm file; the task derives the sibling per-channel paths and the
|
||||
* ".working" temporary names from the deterministic naming rule),
|
||||
* sample_rate / channel_layout / sample_format (target audio params,
|
||||
* sample_format is olive::core::SampleFormat::Format as int).
|
||||
* - OAKCODEC_TASK_PROXY: input_filename (source media), stream_index
|
||||
* (video stream), output_filename (final proxy path; the task owns
|
||||
* the ".working.mp4" temporary name and the rename on success),
|
||||
* proxy_width / proxy_height (absolute target size, both 0 when the
|
||||
* request is divider-based).
|
||||
*/
|
||||
typedef struct OakCodecTaskRequest {
|
||||
int kind; /**< OakCodecTaskKind. */
|
||||
const char *input_filename; /**< Source media filename. */
|
||||
const char *output_filename; /**< Final destination path (see above). */
|
||||
int stream_index; /**< Stream inside the source media. */
|
||||
int sample_rate; /**< conform: target sample rate. */
|
||||
uint64_t channel_layout; /**< conform: target channel layout mask. */
|
||||
int sample_format; /**< conform: target sample format (enum as int). */
|
||||
int proxy_width; /**< proxy: target width, 0 = unspecified/divider. */
|
||||
int proxy_height; /**< proxy: target height, 0 = unspecified/divider. */
|
||||
} OakCodecTaskRequest;
|
||||
|
||||
/**
|
||||
* @brief Task submit callback.
|
||||
*
|
||||
* @return 0 (OAKCODEC_OK) if the task was accepted - either completed
|
||||
* synchronously or queued; a negative OAKCODEC_E_* code if the request
|
||||
* was rejected.
|
||||
*/
|
||||
typedef int (*oakcodec_task_submit_fn)(const OakCodecTaskRequest *req,
|
||||
void *userdata);
|
||||
|
||||
/**
|
||||
* @brief Registers (or replaces) the global task submit callback.
|
||||
*
|
||||
* Thread-safe. Pass cb == NULL to unregister. Interim state (pre-M8):
|
||||
* nobody registers and all task-dependent work reports unavailable.
|
||||
*/
|
||||
OAKCODEC_API void oakcodec_set_task_submit_cb(oakcodec_task_submit_fn cb, void *userdata);
|
||||
|
||||
/**
|
||||
* @brief Returns 1 if a submit callback is currently registered, else 0.
|
||||
*
|
||||
* Thread-safe.
|
||||
*/
|
||||
OAKCODEC_API int oakcodec_task_submit_is_registered(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_CODEC_TASK_H
|
||||
@@ -22,24 +22,39 @@
|
||||
#define OAK_EDITOR_COLORTRANSFORM_H
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace olive
|
||||
{
|
||||
class ColorTransform;
|
||||
}
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a color transform description
|
||||
* @brief Neutral by-value handle to a color transform description
|
||||
* (olive::ColorTransform).
|
||||
*
|
||||
* Ownership/count semantics follow the convention in common/handle.h:
|
||||
* init functions return a handle whose object has reference count 1,
|
||||
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
|
||||
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCommonColorTransform OakCommonColorTransform;
|
||||
typedef struct OakColorTransform {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakColorTransform;
|
||||
|
||||
/**
|
||||
* @brief Create a plain output-colorspace transform.
|
||||
*
|
||||
* @param output Output colorspace name. Must not be NULL.
|
||||
* @return Transform handle, or NULL on failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on failure.
|
||||
*/
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_output(
|
||||
OakColorTransform oakcommon_colortransform_init_output(
|
||||
const char *output);
|
||||
|
||||
/**
|
||||
@@ -47,15 +62,47 @@ OakCommonColorTransform *oakcommon_colortransform_init_output(
|
||||
*
|
||||
* All three strings must not be NULL.
|
||||
*
|
||||
* @return Transform handle, or NULL on failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on failure.
|
||||
*/
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_display(
|
||||
OakColorTransform oakcommon_colortransform_init_display(
|
||||
const char *display, const char *view, const char *look);
|
||||
|
||||
#ifdef __cplusplus
|
||||
/**
|
||||
* @brief Destroy a transform. No-op on NULL.
|
||||
* @brief Copy a native olive::ColorTransform into a new handle.
|
||||
*
|
||||
* The source object is deep-copied; the handle does not keep any
|
||||
* reference to @p src, which may be destroyed immediately afterwards.
|
||||
* Only visible to C++ consumers.
|
||||
*
|
||||
* @return Handle with reference count 1; ctx is NULL if src is NULL or
|
||||
* on allocation failure.
|
||||
*/
|
||||
void oakcommon_colortransform_free(OakCommonColorTransform *transform);
|
||||
OakColorTransform oakcommon_colortransform_init_from_native(
|
||||
const olive::ColorTransform *src);
|
||||
|
||||
/**
|
||||
* @brief Borrow the native object behind a handle.
|
||||
*
|
||||
* The returned pointer is borrowed: it stays valid while the caller
|
||||
* holds a reference to the handle (i.e. until the matching release).
|
||||
* Only visible to C++ consumers.
|
||||
*
|
||||
* @return Borrowed pointer, or NULL if transform is NULL or
|
||||
* transform->ctx is NULL.
|
||||
*/
|
||||
const olive::ColorTransform *oakcommon_colortransform_get_native(
|
||||
OakColorTransform transform);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Release one reference to a transform.
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx): decrements the
|
||||
* atomic reference count and destroys the object when it reaches zero.
|
||||
* No-op when transform is NULL or transform->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_colortransform_free(OakColorTransform *transform);
|
||||
|
||||
/**
|
||||
* @brief Query whether this is a display/view/look transform.
|
||||
@@ -63,7 +110,7 @@ void oakcommon_colortransform_free(OakCommonColorTransform *transform);
|
||||
* @param is_display Receives the result. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_is_display(OakColorTransform transform,
|
||||
int *is_display);
|
||||
|
||||
/**
|
||||
@@ -72,7 +119,7 @@ int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_display(OakColorTransform transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -81,7 +128,7 @@ int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_output(OakColorTransform transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -90,7 +137,7 @@ int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_view(OakColorTransform transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -99,7 +146,7 @@ int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_colortransform_get_look(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_look(OakColorTransform transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -26,56 +26,82 @@
|
||||
#endif
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a command-line parser instance.
|
||||
* @brief Neutral by-value handle to a command-line parser instance.
|
||||
*
|
||||
* Ownership/count semantics follow the convention in common/handle.h:
|
||||
* init returns a handle whose object has reference count 1,
|
||||
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
|
||||
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCommonCommandLineParser OakCommonCommandLineParser;
|
||||
typedef struct OakCommandLineParser {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakCommandLineParser;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a registered command-line option.
|
||||
* @brief Neutral by-value handle to a registered command-line option.
|
||||
*
|
||||
* The handle wrapper is freed with oakcommon_commandlineoption_free();
|
||||
* the underlying option is owned by the parser and stays valid until
|
||||
* the parser is freed.
|
||||
* The handle is released with oakcommon_commandlineoption_free() (or
|
||||
* handle.release(handle.ctx)); the underlying option is owned by the
|
||||
* parser and stays valid until the parser is destroyed. abi_version is
|
||||
* always OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCommonCommandLineOption OakCommonCommandLineOption;
|
||||
typedef struct OakCommandLineOption {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakCommandLineOption;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a registered positional argument.
|
||||
* @brief Neutral by-value handle to a registered positional argument.
|
||||
*
|
||||
* The handle wrapper is freed with
|
||||
* oakcommon_commandlinepositionalargument_free(); the underlying argument
|
||||
* is owned by the parser and stays valid until the parser is freed.
|
||||
* The handle is released with
|
||||
* oakcommon_commandlinepositionalargument_free() (or
|
||||
* handle.release(handle.ctx)); the underlying argument is owned by the
|
||||
* parser and stays valid until the parser is destroyed. abi_version is
|
||||
* always OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCommonCommandLinePositionalArgument
|
||||
OakCommonCommandLinePositionalArgument;
|
||||
typedef struct OakCommandLinePositionalArgument {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakCommandLinePositionalArgument;
|
||||
|
||||
/**
|
||||
* @brief Create a command-line parser.
|
||||
*
|
||||
* @return Parser handle, or NULL on allocation failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on allocation
|
||||
* failure.
|
||||
*/
|
||||
OakCommonCommandLineParser *oakcommon_commandlineparser_init(void);
|
||||
OakCommandLineParser oakcommon_commandlineparser_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a command-line parser.
|
||||
* @brief Release one reference to a command-line parser.
|
||||
*
|
||||
* Destroys all option and positional-argument handles created from it.
|
||||
* NULL is a no-op.
|
||||
* Convenience wrapper around handle.release(handle.ctx): decrements the
|
||||
* atomic reference count and destroys the parser (invalidating all
|
||||
* option and positional-argument handles created from it) when the
|
||||
* count reaches zero. No-op when parser is NULL or parser->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_commandlineparser_free(OakCommonCommandLineParser *parser);
|
||||
void oakcommon_commandlineparser_free(OakCommandLineParser *parser);
|
||||
|
||||
/**
|
||||
* @brief Set the application name/version shown by print_help.
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
|
||||
int oakcommon_commandlineparser_set_app_info(OakCommandLineParser parser,
|
||||
const char *name,
|
||||
const char *version);
|
||||
|
||||
@@ -88,26 +114,28 @@ int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
|
||||
* @param takes_arg Non-zero if the option consumes the following argument.
|
||||
* @param arg_placeholder Placeholder shown in help, may be NULL.
|
||||
* @param hidden Non-zero to omit from help output.
|
||||
* @param out_option Receives the option handle. May be NULL if unused.
|
||||
* @param out_option Receives the option handle (reference count 1).
|
||||
* May be NULL if unused.
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineparser_add_option(
|
||||
OakCommonCommandLineParser *parser, const char *const *names, int name_count,
|
||||
OakCommandLineParser parser, const char *const *names, int name_count,
|
||||
const char *description, int takes_arg, const char *arg_placeholder,
|
||||
int hidden, OakCommonCommandLineOption **out_option);
|
||||
int hidden, OakCommandLineOption *out_option);
|
||||
|
||||
/**
|
||||
* @brief Register a positional argument.
|
||||
*
|
||||
* @param out_argument Receives the argument handle. May be NULL if unused.
|
||||
* @param out_argument Receives the argument handle (reference count 1).
|
||||
* May be NULL if unused.
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineparser_add_positional_argument(
|
||||
OakCommonCommandLineParser *parser, const char *name,
|
||||
OakCommandLineParser parser, const char *name,
|
||||
const char *description, int required,
|
||||
OakCommonCommandLinePositionalArgument **out_argument);
|
||||
OakCommandLinePositionalArgument *out_argument);
|
||||
|
||||
/**
|
||||
* @brief Parse an argv-style argument list.
|
||||
@@ -116,7 +144,7 @@ int oakcommon_commandlineparser_add_positional_argument(
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
|
||||
int oakcommon_commandlineparser_process(OakCommandLineParser parser,
|
||||
const char *const *argv, int argc);
|
||||
|
||||
/**
|
||||
@@ -124,7 +152,7 @@ int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
|
||||
int oakcommon_commandlineparser_print_help(OakCommandLineParser parser,
|
||||
const char *filename);
|
||||
|
||||
/**
|
||||
@@ -134,15 +162,17 @@ int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineoption_is_set(OakCommonCommandLineOption *option,
|
||||
int oakcommon_commandlineoption_is_set(OakCommandLineOption option,
|
||||
bool *is_set);
|
||||
|
||||
/**
|
||||
* @brief Free an option handle wrapper.
|
||||
* @brief Release one reference to an option handle.
|
||||
*
|
||||
* Does not unregister the option from the parser. NULL is a no-op.
|
||||
* Convenience wrapper around handle.release(handle.ctx). Does not
|
||||
* unregister the option from the parser. No-op when option is NULL or
|
||||
* option->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option);
|
||||
void oakcommon_commandlineoption_free(OakCommandLineOption *option);
|
||||
|
||||
/**
|
||||
* @brief Get an option's argument value (two-stage string getter).
|
||||
@@ -150,7 +180,7 @@ void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option);
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
|
||||
int oakcommon_commandlineoption_get_setting(OakCommandLineOption option,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -158,7 +188,7 @@ int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
|
||||
int oakcommon_commandlineoption_set_setting(OakCommandLineOption option,
|
||||
const char *value);
|
||||
|
||||
/**
|
||||
@@ -168,7 +198,7 @@ int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlinepositionalargument_get_setting(
|
||||
OakCommonCommandLinePositionalArgument *argument, char *buf, int buf_size);
|
||||
OakCommandLinePositionalArgument argument, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Set a positional argument's value.
|
||||
@@ -176,15 +206,17 @@ int oakcommon_commandlinepositionalargument_get_setting(
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_commandlinepositionalargument_set_setting(
|
||||
OakCommonCommandLinePositionalArgument *argument, const char *value);
|
||||
OakCommandLinePositionalArgument argument, const char *value);
|
||||
|
||||
/**
|
||||
* @brief Free a positional argument handle wrapper.
|
||||
* @brief Release one reference to a positional argument handle.
|
||||
*
|
||||
* Does not unregister the argument from the parser. NULL is a no-op.
|
||||
* Convenience wrapper around handle.release(handle.ctx). Does not
|
||||
* unregister the argument from the parser. No-op when argument is NULL
|
||||
* or argument->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_commandlinepositionalargument_free(
|
||||
OakCommonCommandLinePositionalArgument *argument);
|
||||
OakCommandLinePositionalArgument *argument);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+42
-26
@@ -22,12 +22,27 @@
|
||||
#define OAK_EDITOR_CURRENT_H
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct OakCommonCurrent OakCommonCurrent;
|
||||
/**
|
||||
* @brief Neutral by-value handle to the process-wide Current singleton.
|
||||
*
|
||||
* Uses the standard handle layout (see common/handle.h) but with
|
||||
* singleton semantics: ctx points to a statically allocated object that
|
||||
* lives until process exit, so addref() and release() are intentionally
|
||||
* no-ops and never destroy anything. abi_version is always
|
||||
* OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCurrent {
|
||||
void *ctx; /**< Opaque pointer to the singleton object. */
|
||||
void (*addref)(void *ctx); /**< No-op (singleton). */
|
||||
void (*release)(void *ctx); /**< No-op (singleton). */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakCurrent;
|
||||
|
||||
/**
|
||||
* @brief Destructor callback for objects handed to Current slots.
|
||||
@@ -35,23 +50,24 @@ typedef struct OakCommonCurrent OakCommonCurrent;
|
||||
* Called when the slot is overwritten or cleared. May be NULL if the
|
||||
* caller keeps ownership of the object.
|
||||
*/
|
||||
typedef void (*OakCommonDestroyFn)(void *obj);
|
||||
typedef void (*OakDestroyFn)(void *obj);
|
||||
|
||||
/**
|
||||
* @brief Return a handle to the process-wide Current singleton.
|
||||
*
|
||||
* The returned handle is borrowed: it is valid for the lifetime of the
|
||||
* process and must not be freed with oakcommon_current_free() more
|
||||
* than out of symmetry (free is a no-op for the singleton).
|
||||
* The returned handle is borrowed: its ctx is valid for the lifetime of
|
||||
* the process. addref/release on it are no-ops; calling
|
||||
* oakcommon_current_free() is allowed for symmetry and does nothing.
|
||||
*/
|
||||
OakCommonCurrent *oakcommon_current_instance(void);
|
||||
OakCurrent oakcommon_current_instance(void);
|
||||
|
||||
/**
|
||||
* @brief Release a Current handle.
|
||||
*
|
||||
* No-op: the underlying object is a singleton. Safe to call with NULL.
|
||||
* No-op: the underlying object is a singleton whose release() never
|
||||
* destroys anything. Safe to call with NULL or a ctx == NULL handle.
|
||||
*/
|
||||
void oakcommon_current_free(OakCommonCurrent *self);
|
||||
void oakcommon_current_free(OakCurrent *self);
|
||||
|
||||
/**
|
||||
* @brief Store a pointer in a Current slot, taking over destruction.
|
||||
@@ -63,16 +79,16 @@ void oakcommon_current_free(OakCommonCurrent *self);
|
||||
* @param obj Opaque pointer to the external object (e.g. a
|
||||
* VideoParams), or NULL to clear.
|
||||
* @param destroy Optional destructor invoked when the slot is replaced.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self is NULL.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self.ctx is NULL.
|
||||
*/
|
||||
int oakcommon_current_set_video_params(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy);
|
||||
int oakcommon_current_set_audio_params(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy);
|
||||
int oakcommon_current_set_plugin_host(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy);
|
||||
int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy);
|
||||
int oakcommon_current_set_video_params(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy);
|
||||
int oakcommon_current_set_audio_params(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy);
|
||||
int oakcommon_current_set_plugin_host(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy);
|
||||
int oakcommon_current_set_plugin_cache(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy);
|
||||
|
||||
/**
|
||||
* @brief Fetch the raw pointer currently stored in a slot.
|
||||
@@ -82,23 +98,23 @@ int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
|
||||
*
|
||||
* @param self Handle from oakcommon_current_instance().
|
||||
* @param out Receives the stored pointer.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self or out
|
||||
* is NULL.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self.ctx or
|
||||
* out is NULL.
|
||||
*/
|
||||
int oakcommon_current_get_video_params(OakCommonCurrent *self, void **out);
|
||||
int oakcommon_current_get_audio_params(OakCommonCurrent *self, void **out);
|
||||
int oakcommon_current_get_plugin_host(OakCommonCurrent *self, void **out);
|
||||
int oakcommon_current_get_plugin_cache(OakCommonCurrent *self, void **out);
|
||||
int oakcommon_current_get_video_params(OakCurrent self, void **out);
|
||||
int oakcommon_current_get_audio_params(OakCurrent self, void **out);
|
||||
int oakcommon_current_get_plugin_host(OakCurrent self, void **out);
|
||||
int oakcommon_current_get_plugin_cache(OakCurrent self, void **out);
|
||||
|
||||
/**
|
||||
* @brief Query whether the session is interactive.
|
||||
*
|
||||
* @param self Handle from oakcommon_current_instance().
|
||||
* @param out Receives 1 for interactive, 0 otherwise.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self or out
|
||||
* is NULL.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self.ctx or
|
||||
* out is NULL.
|
||||
*/
|
||||
int oakcommon_current_is_interactive(OakCommonCurrent *self, int *out);
|
||||
int oakcommon_current_is_interactive(OakCurrent self, int *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+41
-3
@@ -32,7 +32,7 @@ extern "C" {
|
||||
*
|
||||
* Mirrors olive::DebugLevel in src/common/src/debug.h.
|
||||
*/
|
||||
enum OakCommonDebugLevel {
|
||||
enum OakDebugLevel {
|
||||
OAKCOMMON_DEBUG_DEBUG = 0, /**< Verbose debug message. */
|
||||
OAKCOMMON_DEBUG_INFO = 1, /**< Informational message. */
|
||||
OAKCOMMON_DEBUG_WARNING = 2, /**< Warning message. */
|
||||
@@ -46,7 +46,7 @@ enum OakCommonDebugLevel {
|
||||
* De-Qt replacement for the old Qt message handler. The line is
|
||||
* flushed immediately.
|
||||
*
|
||||
* @param level One of OakCommonDebugLevel; out-of-range values print
|
||||
* @param level One of OakDebugLevel; out-of-range values print
|
||||
* as "UNKNOWN".
|
||||
* @param msg NUL-terminated message; NULL is treated as an empty
|
||||
* string.
|
||||
@@ -60,7 +60,7 @@ int oakcommon_debug_log(int level, const char *msg);
|
||||
* Two-segment string getter: if buf is NULL or buf_size is too small,
|
||||
* nothing is written.
|
||||
*
|
||||
* @param level One of OakCommonDebugLevel.
|
||||
* @param level One of OakDebugLevel.
|
||||
* @param buf Destination buffer, may be NULL to query the size.
|
||||
* @param buf_size Size of buf in bytes.
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
@@ -68,6 +68,44 @@ int oakcommon_debug_log(int level, const char *msg);
|
||||
*/
|
||||
int oakcommon_debug_level_name(int level, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief printf-style filtered log, replacing qDebug()/qInfo()/
|
||||
* qWarning()/qCritical() call sites.
|
||||
*
|
||||
* The message is formatted with vsnprintf into a dynamically sized
|
||||
* buffer (arbitrary length, no truncation, no fixed stack buffer) and
|
||||
* emitted as "[LEVEL] message\n" unless @p level is below the current
|
||||
* filter level (see oakcommon_log_set_level()).
|
||||
*
|
||||
* @param level One of OakDebugLevel; out-of-range values print
|
||||
* as "UNKNOWN" and are never filtered out below FATAL.
|
||||
* @param fmt printf-style format string. Must not be NULL.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if fmt is NULL,
|
||||
* OAKCOMMON_E_FAILED if formatting failed.
|
||||
*/
|
||||
int oakcommon_log(int level, const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief Set the minimum level emitted by oakcommon_log().
|
||||
*
|
||||
* Messages with a lower level are dropped. The default is
|
||||
* OAKCOMMON_DEBUG_INFO.
|
||||
*
|
||||
* @param level One of OakDebugLevel.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if level is
|
||||
* outside the OakDebugLevel range.
|
||||
*/
|
||||
int oakcommon_log_set_level(int level);
|
||||
|
||||
/**
|
||||
* @brief Query the current minimum level emitted by oakcommon_log().
|
||||
*
|
||||
* @param out_level Receives one of OakDebugLevel. Must not be NULL.
|
||||
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_level is
|
||||
* NULL.
|
||||
*/
|
||||
int oakcommon_log_get_level(int *out_level);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -35,7 +35,7 @@ extern "C" {
|
||||
* src/common/src/dropworkflowbehavior.h; enumerator order and values
|
||||
* must stay identical because the config layer persists them as ints.
|
||||
*/
|
||||
enum OakCommonDropWorkflowBehavior {
|
||||
enum OakDropWorkflowBehavior {
|
||||
OAKCOMMON_DWS_ASK = 0, /**< Ask the user every time. */
|
||||
OAKCOMMON_DWS_AUTO = 1, /**< Automatically create a sequence. */
|
||||
OAKCOMMON_DWS_MANUAL = 2, /**< Never create; import manually. */
|
||||
@@ -43,7 +43,7 @@ enum OakCommonDropWorkflowBehavior {
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Check whether value is a valid OakCommonDropWorkflowBehavior.
|
||||
* @brief Check whether value is a valid OakDropWorkflowBehavior.
|
||||
*
|
||||
* @param value Integer behavior value (e.g. read from config).
|
||||
* @return 1 if valid, 0 otherwise (this is a predicate, not a status
|
||||
@@ -57,7 +57,7 @@ int oakcommon_drop_workflow_behavior_is_valid(int value);
|
||||
* Two-segment string getter: if buf is NULL or buf_size is too small,
|
||||
* nothing is written. Invalid values yield "UNKNOWN".
|
||||
*
|
||||
* @param value One of OakCommonDropWorkflowBehavior.
|
||||
* @param value One of OakDropWorkflowBehavior.
|
||||
* @param buf Destination buffer, may be NULL to query the size.
|
||||
* @param buf_size Size of buf in bytes.
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
* (including the terminating NUL) as a non-negative value instead.
|
||||
*/
|
||||
#define OAKCOMMON_OK 0 /**< Success. */
|
||||
#define OAKCOMMON_E_INVALID (-1) /**< NULL handle or invalid argument. */
|
||||
#define OAKCOMMON_E_INVALID (-1) /**< Empty handle (ctx == NULL) or invalid argument. */
|
||||
#define OAKCOMMON_E_STATE (-2) /**< Call not valid in the current state. */
|
||||
#define OAKCOMMON_E_FAILED (-3) /**< The underlying operation failed. */
|
||||
#define OAKCOMMON_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
|
||||
|
||||
@@ -22,30 +22,44 @@
|
||||
#define OAK_EDITOR_FILEFUNCTIONS_H
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle for the filefunctions family
|
||||
* @brief Neutral by-value handle for the filefunctions family
|
||||
*
|
||||
* File functions are stateless; the handle only exists to keep the C API
|
||||
* shape uniform across oakcommon families.
|
||||
* shape uniform across oakcommon families. Ownership/count semantics
|
||||
* follow the convention in common/handle.h: init returns a handle whose
|
||||
* (empty) object has reference count 1, addref(ctx)/release(ctx) adjust
|
||||
* it atomically, and release destroys it at zero. abi_version is always
|
||||
* OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCommonFileFunctions OakCommonFileFunctions;
|
||||
typedef struct OakFileFunctions {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakFileFunctions;
|
||||
|
||||
/**
|
||||
* @brief Creates a filefunctions handle
|
||||
*
|
||||
* @return A new handle, or NULL on failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on failure.
|
||||
*/
|
||||
OakCommonFileFunctions *oakcommon_filefunctions_init(void);
|
||||
OakFileFunctions oakcommon_filefunctions_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroys a filefunctions handle (NULL is a no-op)
|
||||
* @brief Releases one reference to a filefunctions handle
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx): decrements the
|
||||
* atomic reference count and destroys the object when it reaches zero.
|
||||
* No-op when self is NULL or self->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_filefunctions_free(OakCommonFileFunctions *self);
|
||||
void oakcommon_filefunctions_free(OakFileFunctions *self);
|
||||
|
||||
/**
|
||||
* @brief Returns a deterministic identifier string for a file
|
||||
@@ -55,20 +69,20 @@ void oakcommon_filefunctions_free(OakCommonFileFunctions *self);
|
||||
* the file does not exist.
|
||||
*/
|
||||
int oakcommon_filefunctions_get_unique_file_identifier(
|
||||
OakCommonFileFunctions *self, const char *filename, char *buf,
|
||||
OakFileFunctions self, const char *filename, char *buf,
|
||||
int buf_size);
|
||||
|
||||
int oakcommon_filefunctions_get_configuration_location(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size);
|
||||
OakFileFunctions self, char *buf, int buf_size);
|
||||
|
||||
int oakcommon_filefunctions_get_application_path(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size);
|
||||
OakFileFunctions self, char *buf, int buf_size);
|
||||
|
||||
int oakcommon_filefunctions_get_temp_file_path(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size);
|
||||
OakFileFunctions self, char *buf, int buf_size);
|
||||
|
||||
int oakcommon_filefunctions_get_auto_recovery_root(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size);
|
||||
OakFileFunctions self, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Checks whether `source` can be copied to `dest` without
|
||||
@@ -77,13 +91,13 @@ int oakcommon_filefunctions_get_auto_recovery_root(
|
||||
* @param out Receives 1 (safe) or 0 (would overwrite).
|
||||
*/
|
||||
int oakcommon_filefunctions_can_copy_directory_without_overwriting(
|
||||
OakCommonFileFunctions *self, const char *source, const char *dest,
|
||||
OakFileFunctions self, const char *source, const char *dest,
|
||||
int *out);
|
||||
|
||||
/**
|
||||
* @brief Recursively copies a directory
|
||||
*/
|
||||
int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
|
||||
int oakcommon_filefunctions_copy_directory(OakFileFunctions self,
|
||||
const char *source,
|
||||
const char *dest, int overwrite);
|
||||
|
||||
@@ -93,7 +107,7 @@ int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
|
||||
* @param out Receives 1 (valid) or 0 (invalid).
|
||||
*/
|
||||
int oakcommon_filefunctions_directory_is_valid(
|
||||
OakCommonFileFunctions *self, const char *dir,
|
||||
OakFileFunctions self, const char *dir,
|
||||
int try_to_create_if_not_exists, int *out);
|
||||
|
||||
/**
|
||||
@@ -103,7 +117,7 @@ int oakcommon_filefunctions_directory_is_valid(
|
||||
* OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_filefunctions_ensure_filename_extension(
|
||||
OakCommonFileFunctions *self, const char *filename,
|
||||
OakFileFunctions self, const char *filename,
|
||||
const char *extension, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -114,7 +128,7 @@ int oakcommon_filefunctions_ensure_filename_extension(
|
||||
* the file cannot be read.
|
||||
*/
|
||||
int oakcommon_filefunctions_read_file_as_string(
|
||||
OakCommonFileFunctions *self, const char *filename, char *buf,
|
||||
OakFileFunctions self, const char *filename, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
@@ -124,7 +138,7 @@ int oakcommon_filefunctions_read_file_as_string(
|
||||
* OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_filefunctions_get_safe_temporary_filename(
|
||||
OakCommonFileFunctions *self, const char *original, char *buf,
|
||||
OakFileFunctions self, const char *original, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
@@ -133,7 +147,7 @@ int oakcommon_filefunctions_get_safe_temporary_filename(
|
||||
* @param out Receives 1 (renamed) or 0 (failed).
|
||||
*/
|
||||
int oakcommon_filefunctions_rename_file_allow_overwrite(
|
||||
OakCommonFileFunctions *self, const char *from, const char *to,
|
||||
OakFileFunctions self, const char *from, const char *to,
|
||||
int *out);
|
||||
|
||||
/**
|
||||
@@ -143,7 +157,7 @@ int oakcommon_filefunctions_rename_file_allow_overwrite(
|
||||
* OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_filefunctions_get_formatted_executable_for_platform(
|
||||
OakCommonFileFunctions *self, const char *unformatted, char *buf,
|
||||
OakFileFunctions self, const char *unformatted, char *buf,
|
||||
int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_HANDLE_H
|
||||
#define OAK_EDITOR_HANDLE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Current ABI version stamped into every oakcommon handle.
|
||||
*
|
||||
* Bump whenever the handle layout or the semantics of any exported
|
||||
* function change incompatibly. Consumers should compare a handle's
|
||||
* abi_version field against the value they were compiled with before
|
||||
* dereferencing ctx.
|
||||
*/
|
||||
#define OAKCOMMON_ABI_VERSION 1
|
||||
|
||||
/**
|
||||
* @brief Neutral handle convention shared by all oakcommon wrappers.
|
||||
*
|
||||
* Every wrapper type is a by-value struct with the same four fields:
|
||||
*
|
||||
* typedef struct OakXxx {
|
||||
* void *ctx; // opaque, points to the impl
|
||||
* void (*addref)(void *ctx); // atomic +1, owner-DLL code
|
||||
* void (*release)(void *ctx); // atomic -1, destroys at 0
|
||||
* uint32_t abi_version; // OAKCOMMON_ABI_VERSION
|
||||
* } OakXxx;
|
||||
*
|
||||
* Rules:
|
||||
* - oakcommon_<name>_init*() returns a handle whose underlying object
|
||||
* has reference count 1.
|
||||
* - Copying the struct copies the pointer, not the count: call
|
||||
* handle.addref(handle.ctx) for every additional long-lived copy and
|
||||
* handle.release(handle.ctx) (or the oakcommon_<name>_free()
|
||||
* convenience wrapper) when done with each copy.
|
||||
* - release() decrements the atomic count and destroys the underlying
|
||||
* object when it reaches zero; the destructor runs in the DLL that
|
||||
* created the object, so cross-DLL handing is safe.
|
||||
* - The struct itself carries no ownership: it is never heap-allocated
|
||||
* by the API, so it needs no destruction of its own.
|
||||
* - Functions that only read a handle take it BY VALUE (OakXxx self);
|
||||
* an empty handle (ctx == NULL) is reported as OAKCOMMON_E_INVALID.
|
||||
* oakcommon_<name>_free() deliberately stays a pointer API
|
||||
* (OakXxx *h, like av_frame_unref()/av_buffer_unref()) so it can
|
||||
* null out the caller's ctx after the final release; NULL and
|
||||
* ctx == NULL are no-ops. Out parameters that produce a handle
|
||||
* (e.g. option/positional-argument registration) also stay pointers.
|
||||
*/
|
||||
|
||||
#endif //OAK_EDITOR_HANDLE_H
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
* The numeric values must stay in sync with src/common/src/loopmode.h.
|
||||
* Pure enum: no functions are needed.
|
||||
*/
|
||||
enum OakCommonLoopMode {
|
||||
enum OakLoopMode {
|
||||
OAKCOMMON_LOOP_MODE_OFF = 0, /**< Looping disabled. */
|
||||
OAKCOMMON_LOOP_MODE_LOOP = 1, /**< Loop playback. */
|
||||
OAKCOMMON_LOOP_MODE_CLAMP = 2 /**< Clamp at the end. */
|
||||
|
||||
+28
-11
@@ -22,6 +22,7 @@
|
||||
#define OAK_EDITOR_OCIOUTILS_H
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -33,7 +34,7 @@ extern "C" {
|
||||
* The numeric values must stay in sync with
|
||||
* olive/core/render/pixelformat.h (Format enum).
|
||||
*/
|
||||
enum OakCommonPixelFormat {
|
||||
enum OakPixelFormat {
|
||||
OAKCOMMON_PIXEL_FORMAT_INVALID = -1, /**< Invalid/unknown format. */
|
||||
OAKCOMMON_PIXEL_FORMAT_U8 = 0, /**< 8-bit unsigned integer. */
|
||||
OAKCOMMON_PIXEL_FORMAT_U10 = 1, /**< 10-bit unsigned integer. */
|
||||
@@ -52,34 +53,50 @@ enum OakCommonPixelFormat {
|
||||
* BitDepth enum: 0 = unknown, 1 = uint8, 2 = uint10, 3 = uint12,
|
||||
* 4 = uint14, 5 = uint16, 6 = uint32, 7 = f16, 8 = f32 (OCIO v2).
|
||||
*/
|
||||
typedef struct OakCommonOCIOUtils OakCommonOCIOUtils;
|
||||
/**
|
||||
* @brief Neutral by-value handle for the OCIO utils family
|
||||
*
|
||||
* The object is stateless; the handle exists only to satisfy the C API
|
||||
* lifetime contract. Ownership/count semantics follow common/handle.h:
|
||||
* init returns a handle whose (empty) object has reference count 1 and
|
||||
* release destroys it at zero. abi_version is always
|
||||
* OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakOCIOUtils {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakOCIOUtils;
|
||||
|
||||
/**
|
||||
* @brief Creates an OCIOUtils handle
|
||||
*
|
||||
* The object is stateless; the handle exists only to satisfy the C API
|
||||
* lifetime contract. Returns NULL on failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on failure.
|
||||
*/
|
||||
OakCommonOCIOUtils *oakcommon_ocioutils_init(void);
|
||||
OakOCIOUtils oakcommon_ocioutils_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroys an OCIOUtils handle; no-op on NULL
|
||||
* @brief Releases one reference to an OCIOUtils handle
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx); no-op when
|
||||
* self is NULL or self->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_ocioutils_free(OakCommonOCIOUtils *self);
|
||||
void oakcommon_ocioutils_free(OakOCIOUtils *self);
|
||||
|
||||
/**
|
||||
* @brief Maps a native pixel format to an OCIO bit depth
|
||||
*
|
||||
* @param self handle from oakcommon_ocioutils_init()
|
||||
* @param pixel_format one of the OakCommonPixelFormat values
|
||||
* @param pixel_format one of the OakPixelFormat values
|
||||
* @param out_bit_depth receives the OCIO bit depth as an int (see the
|
||||
* OakCommonOCIOUtils typedef documentation); set to 0
|
||||
* OakOCIOUtils typedef documentation); set to 0
|
||||
* (BIT_DEPTH_UNKNOWN) for invalid formats
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx or
|
||||
* out_bit_depth is NULL or pixel_format is not a known code
|
||||
*/
|
||||
int oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
|
||||
OakCommonOCIOUtils *self, int pixel_format, int *out_bit_depth);
|
||||
OakOCIOUtils self, int pixel_format, int *out_bit_depth);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+34
-18
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
/* Reuses the OakCommonPixelFormat enum (mirroring
|
||||
/* Reuses the OakPixelFormat enum (mirroring
|
||||
* olive::core::PixelFormat) rather than redefining it here. */
|
||||
#include "common/ocioutils.h"
|
||||
|
||||
@@ -41,48 +41,64 @@ extern "C" {
|
||||
* 13 = STRING, 14 = PTR. OIIO >= 2.5 adds 15 = USTRINGHASH and shifts
|
||||
* LASTBASE, so the exact LASTBASE value is version-dependent.
|
||||
*/
|
||||
typedef struct OakCommonOIIOUtils OakCommonOIIOUtils;
|
||||
/**
|
||||
* @brief Neutral by-value handle for the OIIO utils family
|
||||
*
|
||||
* The object is stateless; the handle exists only to satisfy the C API
|
||||
* lifetime contract. Ownership/count semantics follow common/handle.h:
|
||||
* init returns a handle whose (empty) object has reference count 1 and
|
||||
* release destroys it at zero. abi_version is always
|
||||
* OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakOIIOUtils {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakOIIOUtils;
|
||||
|
||||
/**
|
||||
* @brief Creates an OIIOUtils handle
|
||||
*
|
||||
* The object is stateless; the handle exists only to satisfy the C API
|
||||
* lifetime contract. Returns NULL on failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on failure.
|
||||
*/
|
||||
OakCommonOIIOUtils *oakcommon_oiioutils_init(void);
|
||||
OakOIIOUtils oakcommon_oiioutils_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroys an OIIOUtils handle; no-op on NULL
|
||||
* @brief Releases one reference to an OIIOUtils handle
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx); no-op when
|
||||
* self is NULL or self->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_oiioutils_free(OakCommonOIIOUtils *self);
|
||||
void oakcommon_oiioutils_free(OakOIIOUtils *self);
|
||||
|
||||
/**
|
||||
* @brief Maps a native pixel format to an OIIO base type
|
||||
*
|
||||
* @param self handle from oakcommon_oiioutils_init()
|
||||
* @param pixel_format one of the OakCommonPixelFormat values
|
||||
* @param pixel_format one of the OakPixelFormat values
|
||||
* @param out_base_type receives the OIIO base type as an int (see the
|
||||
* OakCommonOIIOUtils typedef documentation); set to 0
|
||||
* OakOIIOUtils typedef documentation); set to 0
|
||||
* (TypeDesc::UNKNOWN) for invalid or unmappable formats
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or out_base_type
|
||||
* is NULL or pixel_format is not a known code
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx or
|
||||
* out_base_type is NULL or pixel_format is not a known code
|
||||
*/
|
||||
int oakcommon_oiioutils_get_oiio_base_type_from_format(
|
||||
OakCommonOIIOUtils *self, int pixel_format, int *out_base_type);
|
||||
OakOIIOUtils self, int pixel_format, int *out_base_type);
|
||||
|
||||
/**
|
||||
* @brief Maps an OIIO base type to a native pixel format
|
||||
*
|
||||
* @param self handle from oakcommon_oiioutils_init()
|
||||
* @param base_type an OIIO TypeDesc::BASETYPE value as an int
|
||||
* @param out_pixel_format receives one of the OakCommonPixelFormat
|
||||
* @param out_pixel_format receives one of the OakPixelFormat
|
||||
* values; set to OAKCOMMON_PIXEL_FORMAT_INVALID for unknown or
|
||||
* unmappable base types
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx or
|
||||
* out_pixel_format is NULL or base_type is negative
|
||||
*/
|
||||
int oakcommon_oiioutils_get_format_from_oiio_basetype(
|
||||
OakCommonOIIOUtils *self, int base_type, int *out_pixel_format);
|
||||
OakOIIOUtils self, int base_type, int *out_pixel_format);
|
||||
|
||||
/**
|
||||
* @brief Converts a PixelAspectRatio attribute value to a rational
|
||||
@@ -95,11 +111,11 @@ int oakcommon_oiioutils_get_format_from_oiio_basetype(
|
||||
* @param pixel_aspect_ratio the PixelAspectRatio attribute value
|
||||
* @param out_numerator receives the rational numerator
|
||||
* @param out_denominator receives the rational denominator
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self, out_numerator
|
||||
* or out_denominator is NULL
|
||||
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx,
|
||||
* out_numerator or out_denominator is NULL
|
||||
*/
|
||||
int oakcommon_oiioutils_get_pixel_aspect_ratio(
|
||||
OakCommonOIIOUtils *self, double pixel_aspect_ratio, int *out_numerator,
|
||||
OakOIIOUtils self, double pixel_aspect_ratio, int *out_numerator,
|
||||
int *out_denominator);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -22,53 +22,89 @@
|
||||
#define OAK_EDITOR_SUBTITLEPARAMS_H
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace olive
|
||||
{
|
||||
class SubtitleParams;
|
||||
}
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a subtitle parameter set (olive::SubtitleParams).
|
||||
* @brief Neutral by-value handle to a subtitle parameter set
|
||||
* (olive::SubtitleParams).
|
||||
*
|
||||
* Ownership/count semantics follow the convention in common/handle.h:
|
||||
* init functions return a handle whose object has reference count 1,
|
||||
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
|
||||
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCommonSubtitleParams OakCommonSubtitleParams;
|
||||
typedef struct OakSubtitleParams {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakSubtitleParams;
|
||||
|
||||
/**
|
||||
* @brief Create an empty subtitle parameter set.
|
||||
*
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on allocation
|
||||
* failure.
|
||||
*/
|
||||
OakCommonSubtitleParams *oakcommon_subtitleparams_init(void);
|
||||
OakSubtitleParams oakcommon_subtitleparams_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
/**
|
||||
* @brief Copy a native olive::SubtitleParams into a new handle.
|
||||
*
|
||||
* The source object is deep-copied; the handle does not keep any
|
||||
* reference to @p src, which may be destroyed immediately afterwards.
|
||||
* Only visible to C++ consumers.
|
||||
*
|
||||
* @return Handle with reference count 1; ctx is NULL if src is NULL or
|
||||
* on allocation failure.
|
||||
*/
|
||||
OakSubtitleParams oakcommon_subtitleparams_init_from_native(
|
||||
const olive::SubtitleParams *src);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Destroy a subtitle parameter set. No-op on NULL.
|
||||
* @brief Release one reference to a subtitle parameter set.
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx): decrements the
|
||||
* atomic reference count and destroys the object when it reaches zero.
|
||||
* No-op when params is NULL or params->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_subtitleparams_free(OakCommonSubtitleParams *params);
|
||||
void oakcommon_subtitleparams_free(OakSubtitleParams *params);
|
||||
|
||||
int oakcommon_subtitleparams_get_stream_index(
|
||||
OakCommonSubtitleParams *params, int *index);
|
||||
OakSubtitleParams params, int *index);
|
||||
int oakcommon_subtitleparams_set_stream_index(
|
||||
OakCommonSubtitleParams *params, int index);
|
||||
int oakcommon_subtitleparams_get_enabled(OakCommonSubtitleParams *params,
|
||||
OakSubtitleParams params, int index);
|
||||
int oakcommon_subtitleparams_get_enabled(OakSubtitleParams params,
|
||||
int *enabled);
|
||||
int oakcommon_subtitleparams_set_enabled(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_set_enabled(OakSubtitleParams params,
|
||||
int enabled);
|
||||
|
||||
/**
|
||||
* @brief Query whether the set contains at least one subtitle.
|
||||
*/
|
||||
int oakcommon_subtitleparams_is_valid(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_is_valid(OakSubtitleParams params,
|
||||
int *is_valid);
|
||||
|
||||
/**
|
||||
* @brief Number of subtitle entries.
|
||||
*/
|
||||
int oakcommon_subtitleparams_count(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_count(OakSubtitleParams params,
|
||||
int *count);
|
||||
|
||||
/**
|
||||
* @brief Out time of the last subtitle (0/1 when empty).
|
||||
*/
|
||||
int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_duration(OakSubtitleParams params,
|
||||
int *numerator, int *denominator);
|
||||
|
||||
/**
|
||||
@@ -77,14 +113,14 @@ int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
|
||||
* @param text Subtitle text. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_add_subtitle(OakSubtitleParams params,
|
||||
int in_num, int in_den, int out_num,
|
||||
int out_den, const char *text);
|
||||
|
||||
/**
|
||||
* @brief Remove all subtitle entries.
|
||||
*/
|
||||
int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params);
|
||||
int oakcommon_subtitleparams_clear(OakSubtitleParams params);
|
||||
|
||||
/**
|
||||
* @brief Get the time range of the subtitle at @p index.
|
||||
@@ -92,7 +128,7 @@ int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params);
|
||||
* @return OAKCOMMON_OK, OAKCOMMON_E_NOT_FOUND if @p index is out of range,
|
||||
* or another negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_get_subtitle(OakSubtitleParams params,
|
||||
int index, int *in_num, int *in_den,
|
||||
int *out_num, int *out_den);
|
||||
|
||||
@@ -103,7 +139,7 @@ int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
|
||||
* (non-negative), OAKCOMMON_E_NOT_FOUND if @p index is out of
|
||||
* range, or another negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_get_subtitle_text(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_get_subtitle_text(OakSubtitleParams params,
|
||||
int index, char *buf,
|
||||
int buf_size);
|
||||
|
||||
@@ -121,7 +157,7 @@ int oakcommon_subtitleparams_generate_ass_header(char *buf, int buf_size);
|
||||
* @param xml NUL-terminated XML text. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_load_xml(OakSubtitleParams params,
|
||||
const char *xml);
|
||||
|
||||
/**
|
||||
@@ -130,7 +166,7 @@ int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_save_xml(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_save_xml(OakSubtitleParams params,
|
||||
char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+141
-75
@@ -28,21 +28,37 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
#include "common/ocioutils.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace olive
|
||||
{
|
||||
class VideoParams;
|
||||
}
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a video parameter set (olive::VideoParams).
|
||||
* @brief Neutral by-value handle to a video parameter set
|
||||
* (olive::VideoParams).
|
||||
*
|
||||
* Ownership/count semantics follow the convention in common/handle.h:
|
||||
* init functions return a handle whose object has reference count 1,
|
||||
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
|
||||
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakCommonVideoParams OakCommonVideoParams;
|
||||
typedef struct OakVideoParams {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakVideoParams;
|
||||
|
||||
/**
|
||||
* @brief Interlacing modes, mirroring olive::VideoParams::Interlacing.
|
||||
*/
|
||||
enum OakCommonVideoInterlacing {
|
||||
enum OakVideoInterlacing {
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE = 0,
|
||||
OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST = 1,
|
||||
OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST = 2
|
||||
@@ -51,7 +67,7 @@ enum OakCommonVideoInterlacing {
|
||||
/**
|
||||
* @brief Video stream types, mirroring olive::VideoParams::Type.
|
||||
*/
|
||||
enum OakCommonVideoType {
|
||||
enum OakVideoType {
|
||||
OAKCOMMON_VIDEO_TYPE_VIDEO = 0,
|
||||
OAKCOMMON_VIDEO_TYPE_STILL = 1,
|
||||
OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE = 2
|
||||
@@ -60,7 +76,7 @@ enum OakCommonVideoType {
|
||||
/**
|
||||
* @brief Color range codes, mirroring olive::VideoParams::ColorRange.
|
||||
*/
|
||||
enum OakCommonVideoColorRange {
|
||||
enum OakVideoColorRange {
|
||||
OAKCOMMON_COLOR_RANGE_LIMITED = 0, /**< 16-235 */
|
||||
OAKCOMMON_COLOR_RANGE_FULL = 1 /**< 0-255 */
|
||||
};
|
||||
@@ -68,17 +84,19 @@ enum OakCommonVideoColorRange {
|
||||
/**
|
||||
* @brief Create a default (invalid) video parameter set.
|
||||
*
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on allocation
|
||||
* failure.
|
||||
*/
|
||||
OakCommonVideoParams *oakcommon_videoparams_init(void);
|
||||
OakVideoParams oakcommon_videoparams_init(void);
|
||||
|
||||
/**
|
||||
* @brief Create a video parameter set without a time base.
|
||||
*
|
||||
* @param pixel_format One of the OakCommonPixelFormat values.
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
* @param pixel_format One of the OakPixelFormat values.
|
||||
* @return Handle with reference count 1; ctx is NULL on allocation
|
||||
* failure.
|
||||
*/
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_basic(
|
||||
OakVideoParams oakcommon_videoparams_init_basic(
|
||||
int width, int height, int pixel_format, int nb_channels,
|
||||
int pixel_aspect_num, int pixel_aspect_den, int interlacing, int divider);
|
||||
|
||||
@@ -87,103 +105,136 @@ OakCommonVideoParams *oakcommon_videoparams_init_basic(
|
||||
*
|
||||
* The frame rate is derived as the flipped time base.
|
||||
*
|
||||
* @param pixel_format One of the OakCommonPixelFormat values.
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
* @param pixel_format One of the OakPixelFormat values.
|
||||
* @return Handle with reference count 1; ctx is NULL on allocation
|
||||
* failure.
|
||||
*/
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_with_time_base(
|
||||
OakVideoParams oakcommon_videoparams_init_with_time_base(
|
||||
int width, int height, int time_base_num, int time_base_den,
|
||||
int pixel_format, int nb_channels, int pixel_aspect_num,
|
||||
int pixel_aspect_den, int interlacing, int divider);
|
||||
|
||||
#ifdef __cplusplus
|
||||
/**
|
||||
* @brief Destroy a video parameter set. No-op on NULL.
|
||||
* @brief Copy a native olive::VideoParams into a new handle.
|
||||
*
|
||||
* The source object is deep-copied; the handle does not keep any
|
||||
* reference to @p src, which may be destroyed immediately afterwards.
|
||||
* Only visible to C++ consumers.
|
||||
*
|
||||
* @return Handle with reference count 1; ctx is NULL if src is NULL or
|
||||
* on allocation failure.
|
||||
*/
|
||||
void oakcommon_videoparams_free(OakCommonVideoParams *params);
|
||||
OakVideoParams oakcommon_videoparams_init_from_native(
|
||||
const olive::VideoParams *src);
|
||||
|
||||
int oakcommon_videoparams_get_width(OakCommonVideoParams *params, int *width);
|
||||
int oakcommon_videoparams_set_width(OakCommonVideoParams *params, int width);
|
||||
int oakcommon_videoparams_get_height(OakCommonVideoParams *params, int *height);
|
||||
int oakcommon_videoparams_set_height(OakCommonVideoParams *params, int height);
|
||||
int oakcommon_videoparams_get_depth(OakCommonVideoParams *params, int *depth);
|
||||
int oakcommon_videoparams_set_depth(OakCommonVideoParams *params, int depth);
|
||||
int oakcommon_videoparams_get_is_3d(OakCommonVideoParams *params, int *is_3d);
|
||||
/**
|
||||
* @brief Borrow the native object behind a handle.
|
||||
*
|
||||
* The returned pointer is borrowed: it stays valid while the caller
|
||||
* holds a reference to the handle (i.e. until the matching release).
|
||||
* Only visible to C++ consumers.
|
||||
*
|
||||
* @return Borrowed pointer, or NULL if params is NULL or params->ctx is
|
||||
* NULL.
|
||||
*/
|
||||
const olive::VideoParams *oakcommon_videoparams_get_native(
|
||||
OakVideoParams params);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Release one reference to a video parameter set.
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx): decrements the
|
||||
* atomic reference count and destroys the object when it reaches zero.
|
||||
* No-op when params is NULL or params->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_videoparams_free(OakVideoParams *params);
|
||||
|
||||
int oakcommon_videoparams_get_width(OakVideoParams params, int *width);
|
||||
int oakcommon_videoparams_set_width(OakVideoParams params, int width);
|
||||
int oakcommon_videoparams_get_height(OakVideoParams params, int *height);
|
||||
int oakcommon_videoparams_set_height(OakVideoParams params, int height);
|
||||
int oakcommon_videoparams_get_depth(OakVideoParams params, int *depth);
|
||||
int oakcommon_videoparams_set_depth(OakVideoParams params, int depth);
|
||||
int oakcommon_videoparams_get_is_3d(OakVideoParams params, int *is_3d);
|
||||
|
||||
/**
|
||||
* @brief Rational getters return the value as a numerator/denominator pair.
|
||||
*/
|
||||
int oakcommon_videoparams_get_time_base(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_time_base(OakVideoParams params,
|
||||
int *numerator, int *denominator);
|
||||
int oakcommon_videoparams_set_time_base(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_time_base(OakVideoParams params,
|
||||
int numerator, int denominator);
|
||||
int oakcommon_videoparams_get_frame_rate(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_frame_rate(OakVideoParams params,
|
||||
int *numerator, int *denominator);
|
||||
int oakcommon_videoparams_set_frame_rate(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_frame_rate(OakVideoParams params,
|
||||
int numerator, int denominator);
|
||||
int oakcommon_videoparams_frame_rate_as_time_base(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_frame_rate_as_time_base(OakVideoParams params,
|
||||
int *numerator,
|
||||
int *denominator);
|
||||
int oakcommon_videoparams_get_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_pixel_aspect_ratio(OakVideoParams params,
|
||||
int *numerator,
|
||||
int *denominator);
|
||||
int oakcommon_videoparams_set_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_pixel_aspect_ratio(OakVideoParams params,
|
||||
int numerator, int denominator);
|
||||
|
||||
/**
|
||||
* @brief Format getters/setters use the OakCommonPixelFormat codes.
|
||||
* @brief Format getters/setters use the OakPixelFormat codes.
|
||||
*/
|
||||
int oakcommon_videoparams_get_format(OakCommonVideoParams *params, int *format);
|
||||
int oakcommon_videoparams_set_format(OakCommonVideoParams *params, int format);
|
||||
int oakcommon_videoparams_get_channel_count(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_format(OakVideoParams params, int *format);
|
||||
int oakcommon_videoparams_set_format(OakVideoParams params, int format);
|
||||
int oakcommon_videoparams_get_channel_count(OakVideoParams params,
|
||||
int *count);
|
||||
int oakcommon_videoparams_set_channel_count(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_channel_count(OakVideoParams params,
|
||||
int count);
|
||||
int oakcommon_videoparams_get_interlacing(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_interlacing(OakVideoParams params,
|
||||
int *interlacing);
|
||||
int oakcommon_videoparams_set_interlacing(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_interlacing(OakVideoParams params,
|
||||
int interlacing);
|
||||
int oakcommon_videoparams_get_divider(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_divider(OakVideoParams params,
|
||||
int *divider);
|
||||
int oakcommon_videoparams_set_divider(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_divider(OakVideoParams params,
|
||||
int divider);
|
||||
int oakcommon_videoparams_get_enabled(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_enabled(OakVideoParams params,
|
||||
int *enabled);
|
||||
int oakcommon_videoparams_set_enabled(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_enabled(OakVideoParams params,
|
||||
int enabled);
|
||||
int oakcommon_videoparams_get_x(OakCommonVideoParams *params, float *x);
|
||||
int oakcommon_videoparams_set_x(OakCommonVideoParams *params, float x);
|
||||
int oakcommon_videoparams_get_y(OakCommonVideoParams *params, float *y);
|
||||
int oakcommon_videoparams_set_y(OakCommonVideoParams *params, float y);
|
||||
int oakcommon_videoparams_get_stream_index(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_x(OakVideoParams params, float *x);
|
||||
int oakcommon_videoparams_set_x(OakVideoParams params, float x);
|
||||
int oakcommon_videoparams_get_y(OakVideoParams params, float *y);
|
||||
int oakcommon_videoparams_set_y(OakVideoParams params, float y);
|
||||
int oakcommon_videoparams_get_stream_index(OakVideoParams params,
|
||||
int *index);
|
||||
int oakcommon_videoparams_set_stream_index(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_stream_index(OakVideoParams params,
|
||||
int index);
|
||||
int oakcommon_videoparams_get_video_type(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_video_type(OakVideoParams params,
|
||||
int *type);
|
||||
int oakcommon_videoparams_set_video_type(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_video_type(OakVideoParams params,
|
||||
int type);
|
||||
int oakcommon_videoparams_get_start_time(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_start_time(OakVideoParams params,
|
||||
int64_t *start_time);
|
||||
int oakcommon_videoparams_set_start_time(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_start_time(OakVideoParams params,
|
||||
int64_t start_time);
|
||||
int oakcommon_videoparams_get_duration(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_duration(OakVideoParams params,
|
||||
int64_t *duration);
|
||||
int oakcommon_videoparams_set_duration(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_duration(OakVideoParams params,
|
||||
int64_t duration);
|
||||
int oakcommon_videoparams_get_premultiplied_alpha(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_premultiplied_alpha(OakVideoParams params,
|
||||
int *premultiplied);
|
||||
int oakcommon_videoparams_set_premultiplied_alpha(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_premultiplied_alpha(OakVideoParams params,
|
||||
int premultiplied);
|
||||
int oakcommon_videoparams_get_color_range(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_color_range(OakVideoParams params,
|
||||
int *color_range);
|
||||
int oakcommon_videoparams_set_color_range(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_color_range(OakVideoParams params,
|
||||
int color_range);
|
||||
int oakcommon_videoparams_get_color_primaries(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_color_primaries(OakVideoParams params,
|
||||
int *primaries);
|
||||
int oakcommon_videoparams_set_color_primaries(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_color_primaries(OakVideoParams params,
|
||||
int primaries);
|
||||
int oakcommon_videoparams_get_color_transfer(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_color_transfer(OakVideoParams params,
|
||||
int *transfer);
|
||||
int oakcommon_videoparams_set_color_transfer(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_color_transfer(OakVideoParams params,
|
||||
int transfer);
|
||||
|
||||
/**
|
||||
@@ -192,29 +243,29 @@ int oakcommon_videoparams_set_color_transfer(OakCommonVideoParams *params,
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_videoparams_get_colorspace(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_colorspace(OakVideoParams params,
|
||||
char *buf, int buf_size);
|
||||
int oakcommon_videoparams_set_colorspace(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_colorspace(OakVideoParams params,
|
||||
const char *colorspace);
|
||||
|
||||
/**
|
||||
* @brief Width multiplied by the pixel aspect ratio.
|
||||
*/
|
||||
int oakcommon_videoparams_get_square_pixel_width(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_square_pixel_width(OakVideoParams params,
|
||||
int *width);
|
||||
int oakcommon_videoparams_get_effective_width(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_effective_width(OakVideoParams params,
|
||||
int *width);
|
||||
int oakcommon_videoparams_get_effective_height(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_effective_height(OakVideoParams params,
|
||||
int *height);
|
||||
int oakcommon_videoparams_get_effective_depth(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_effective_depth(OakVideoParams params,
|
||||
int *depth);
|
||||
int oakcommon_videoparams_get_is_valid(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_is_valid(OakVideoParams params,
|
||||
int *is_valid);
|
||||
int oakcommon_videoparams_get_bytes_per_channel(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_bytes_per_channel(OakVideoParams params,
|
||||
int *bytes);
|
||||
int oakcommon_videoparams_get_bytes_per_pixel(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_bytes_per_pixel(OakVideoParams params,
|
||||
int *bytes);
|
||||
int oakcommon_videoparams_get_buffer_size(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_buffer_size(OakVideoParams params,
|
||||
int *size);
|
||||
|
||||
/**
|
||||
@@ -224,14 +275,14 @@ int oakcommon_videoparams_get_buffer_size(OakCommonVideoParams *params,
|
||||
* set.
|
||||
*/
|
||||
int oakcommon_videoparams_get_time_in_timebase_units(
|
||||
OakCommonVideoParams *params, int time_num, int time_den,
|
||||
OakVideoParams params, int time_num, int time_den,
|
||||
int64_t *timestamp);
|
||||
|
||||
/**
|
||||
* @brief Compare two parameter sets for equality.
|
||||
*/
|
||||
int oakcommon_videoparams_equals(OakCommonVideoParams *params,
|
||||
OakCommonVideoParams *other, int *equal);
|
||||
int oakcommon_videoparams_equals(OakVideoParams params,
|
||||
OakVideoParams other, int *equal);
|
||||
|
||||
/**
|
||||
* @brief Load parameters from an XML fragment.
|
||||
@@ -239,7 +290,7 @@ int oakcommon_videoparams_equals(OakCommonVideoParams *params,
|
||||
* @param xml NUL-terminated XML text. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_load_xml(OakVideoParams params,
|
||||
const char *xml);
|
||||
|
||||
/**
|
||||
@@ -248,7 +299,7 @@ int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_videoparams_save_xml(OakCommonVideoParams *params, char *buf,
|
||||
int oakcommon_videoparams_save_xml(OakVideoParams params, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/* Static helpers (no handle required). */
|
||||
@@ -294,6 +345,21 @@ int oakcommon_videoparams_get_format_name(int pixel_format, char *buf,
|
||||
int oakcommon_videoparams_frame_rate_to_string(int numerator, int denominator,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Get bytes per channel.
|
||||
*
|
||||
* @return Bytes per channel.
|
||||
*/
|
||||
int oakcommon_videoparams_static_get_bytes_per_channel(OakPixelFormat format);
|
||||
|
||||
/**
|
||||
* @brief Get bytes per pixel.
|
||||
*
|
||||
* @return Bytes per pixel.
|
||||
*/
|
||||
int oakcommon_videoparams_static_get_bytes_per_pixel(OakPixelFormat format,
|
||||
int channels);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+59
-25
@@ -22,26 +22,56 @@
|
||||
#define OAK_EDITOR_XMLUTILS_H
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/handle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct OakCommonXmlReader OakCommonXmlReader;
|
||||
typedef struct OakCommonXmlWriter OakCommonXmlWriter;
|
||||
/**
|
||||
* @brief Neutral by-value handle to a streaming XML reader.
|
||||
*
|
||||
* Ownership/count semantics follow the convention in common/handle.h:
|
||||
* init returns a handle whose object has reference count 1,
|
||||
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
|
||||
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
|
||||
*/
|
||||
typedef struct OakXmlReader {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakXmlReader;
|
||||
|
||||
/**
|
||||
* @brief Neutral by-value handle to a streaming XML writer.
|
||||
*
|
||||
* Same ownership/count semantics as OakXmlReader.
|
||||
*/
|
||||
typedef struct OakXmlWriter {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
|
||||
} OakXmlWriter;
|
||||
|
||||
/**
|
||||
* @brief Create a streaming XML reader over a complete document.
|
||||
*
|
||||
* @param data NUL-terminated XML text. Must not be NULL.
|
||||
* @return A new reader, or NULL on failure (NULL data, out of memory).
|
||||
* @return Handle with reference count 1; ctx is NULL on failure
|
||||
* (NULL data, out of memory).
|
||||
*/
|
||||
OakCommonXmlReader *oakcommon_xml_reader_init(const char *data);
|
||||
OakXmlReader oakcommon_xml_reader_init(const char *data);
|
||||
|
||||
/**
|
||||
* @brief Destroy a reader. No-op on NULL.
|
||||
* @brief Release one reference to a reader.
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx): decrements the
|
||||
* atomic reference count and destroys the object when it reaches zero.
|
||||
* No-op when reader is NULL or reader->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_xml_reader_free(OakCommonXmlReader *reader);
|
||||
void oakcommon_xml_reader_free(OakXmlReader *reader);
|
||||
|
||||
/**
|
||||
* @brief Advance until the next start element, an end element, or the end
|
||||
@@ -51,7 +81,7 @@ void oakcommon_xml_reader_free(OakCommonXmlReader *reader);
|
||||
* @param found Out: 1 if positioned on a start element, 0 otherwise.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_read_next_start_element(OakXmlReader reader,
|
||||
int *found);
|
||||
|
||||
/**
|
||||
@@ -60,7 +90,7 @@ int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
|
||||
* @return Required buffer size in bytes (including NUL), or a negative
|
||||
* OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
|
||||
int oakcommon_xml_reader_name(OakXmlReader reader, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
@@ -72,7 +102,7 @@ int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
|
||||
* @return Required buffer size in bytes (including NUL), or a negative
|
||||
* OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_read_element_text(OakXmlReader reader,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -80,14 +110,14 @@ int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_xml_reader_skip_current_element(OakCommonXmlReader *reader);
|
||||
int oakcommon_xml_reader_skip_current_element(OakXmlReader reader);
|
||||
|
||||
/**
|
||||
* @brief Number of attributes on the current start element.
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_attribute_count(OakXmlReader reader,
|
||||
int *count);
|
||||
|
||||
/**
|
||||
@@ -96,7 +126,7 @@ int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
|
||||
* @return Required buffer size in bytes (including NUL), OAKCOMMON_E_NOT_FOUND
|
||||
* if @p index is out of range, or another negative OAKCOMMON_E_* code.
|
||||
*/
|
||||
int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
|
||||
int oakcommon_xml_reader_attribute_name(OakXmlReader reader, int index,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -105,7 +135,7 @@ int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
|
||||
* @return Required buffer size in bytes (including NUL), OAKCOMMON_E_NOT_FOUND
|
||||
* if @p index is out of range, or another negative OAKCOMMON_E_* code.
|
||||
*/
|
||||
int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_attribute_value(OakXmlReader reader,
|
||||
int index, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
@@ -113,32 +143,36 @@ int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
|
||||
*
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_xml_reader_has_error(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_has_error(OakXmlReader reader,
|
||||
int *has_error);
|
||||
|
||||
/**
|
||||
* @brief Create a streaming XML writer.
|
||||
*
|
||||
* @return A new writer, or NULL on failure.
|
||||
* @return Handle with reference count 1; ctx is NULL on failure.
|
||||
*/
|
||||
OakCommonXmlWriter *oakcommon_xml_writer_init(void);
|
||||
OakXmlWriter oakcommon_xml_writer_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a writer. No-op on NULL.
|
||||
* @brief Release one reference to a writer.
|
||||
*
|
||||
* Convenience wrapper around handle.release(handle.ctx): decrements the
|
||||
* atomic reference count and destroys the object when it reaches zero.
|
||||
* No-op when writer is NULL or writer->ctx is NULL.
|
||||
*/
|
||||
void oakcommon_xml_writer_free(OakCommonXmlWriter *writer);
|
||||
void oakcommon_xml_writer_free(OakXmlWriter *writer);
|
||||
|
||||
int oakcommon_xml_writer_write_start_element(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_start_element(OakXmlWriter writer,
|
||||
const char *name);
|
||||
int oakcommon_xml_writer_write_attribute(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_attribute(OakXmlWriter writer,
|
||||
const char *name, const char *value);
|
||||
int oakcommon_xml_writer_write_characters(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_characters(OakXmlWriter writer,
|
||||
const char *text);
|
||||
int oakcommon_xml_writer_write_text_element(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_text_element(OakXmlWriter writer,
|
||||
const char *name,
|
||||
const char *text);
|
||||
int oakcommon_xml_writer_write_end_element(OakCommonXmlWriter *writer);
|
||||
int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer);
|
||||
int oakcommon_xml_writer_write_end_element(OakXmlWriter writer);
|
||||
int oakcommon_xml_writer_write_end_document(OakXmlWriter writer);
|
||||
|
||||
/**
|
||||
* @brief The document written so far.
|
||||
@@ -146,7 +180,7 @@ int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer);
|
||||
* @return Required buffer size in bytes (including NUL), or a negative
|
||||
* OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_xml_writer_output(OakCommonXmlWriter *writer, char *buf,
|
||||
int oakcommon_xml_writer_output(OakXmlWriter writer, char *buf,
|
||||
int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -189,7 +189,7 @@ int oaknode_clip_get_maintain_audio_pitch(OakNodeBlock *clip, int *maintain);
|
||||
int oaknode_clip_set_maintain_audio_pitch(OakNodeBlock *clip, int maintain);
|
||||
|
||||
/**
|
||||
* @brief Loop mode, one of the OakCommonLoopMode values
|
||||
* @brief Loop mode, one of the OakLoopMode values
|
||||
* (olive::ClipBlock::loop_mode/set_loop_mode).
|
||||
*/
|
||||
int oaknode_clip_get_loop_mode(OakNodeBlock *clip, int *loop_mode);
|
||||
|
||||
@@ -174,13 +174,13 @@ int oaknode_colormanager_get_default_luma_coefs(OakNodeColorManager *manager,
|
||||
* colorspace) is clamped to what the active config offers
|
||||
* (olive::ColorManager::get_compliant_color_space(ColorTransform, bool)).
|
||||
*
|
||||
* `out` receives a NEW handle owned by the caller (release with
|
||||
* oakcommon_colortransform_free()). Requires a loaded config
|
||||
* (OAKNODE_E_STATE otherwise).
|
||||
* `out` receives a NEW by-value handle owned by the caller (reference
|
||||
* count 1, release with oakcommon_colortransform_free()). Requires a
|
||||
* loaded config (OAKNODE_E_STATE otherwise).
|
||||
*/
|
||||
int oaknode_colormanager_get_compliant_color_transform(
|
||||
OakNodeColorManager *manager, const OakCommonColorTransform *transform,
|
||||
int force_display, OakCommonColorTransform **out);
|
||||
OakNodeColorManager *manager, OakColorTransform transform,
|
||||
int force_display, OakColorTransform *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -128,20 +128,24 @@ int oaknode_sequence_get_audio_stream_count(OakNodeSequence *sequence,
|
||||
int *count);
|
||||
|
||||
/**
|
||||
* @brief Video parameters at `index` as a NEW handle owned by the caller
|
||||
* (release with oakcommon_videoparams_free()).
|
||||
* @brief Video parameters at `index` as a NEW by-value handle owned by
|
||||
* the caller (reference count 1, release with
|
||||
* oakcommon_videoparams_free()).
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID, OAKNODE_E_NOT_FOUND or
|
||||
* OAKNODE_E_NOMEM.
|
||||
*/
|
||||
int oaknode_sequence_get_video_params(OakNodeSequence *sequence, int index,
|
||||
OakCommonVideoParams **out);
|
||||
OakVideoParams *out);
|
||||
|
||||
/**
|
||||
* @brief Replace the video parameters at `index` with a copy of `params`.
|
||||
*
|
||||
* @return OAKNODE_E_INVALID if sequence is NULL, params.ctx is NULL, or
|
||||
* index is negative.
|
||||
*/
|
||||
int oaknode_sequence_set_video_params(OakNodeSequence *sequence, int index,
|
||||
const OakCommonVideoParams *params);
|
||||
OakVideoParams params);
|
||||
|
||||
/**
|
||||
* @brief Audio parameters at `index` as a NEW handle owned by the caller
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_EDITOR_RENDER_CANCELATOM_H
|
||||
#define OAK_EDITOR_RENDER_CANCELATOM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Current ABI version stamped into every oakrender handle.
|
||||
*
|
||||
* Bump whenever a handle layout or the semantics of any exported function
|
||||
* change incompatibly. Consumers should compare a handle's abi_version
|
||||
* field against the value they were compiled with before dereferencing
|
||||
* ctx.
|
||||
*/
|
||||
#define OAKRENDER_ABI_VERSION 1
|
||||
|
||||
/**
|
||||
* @file cancelatom.h
|
||||
* @brief C ABI for the oakrender cancellation primitive
|
||||
* (olive::CancelAtom), a thread-safe cancel flag shared between a
|
||||
* render/encode caller and its worker.
|
||||
*
|
||||
* OakCancelAtom follows the neutral by-value handle convention (see
|
||||
* oakcommon's common/handle.h): oakrender_cancelatom_init() returns a
|
||||
* handle whose underlying object has reference count 1, the addref and
|
||||
* release function pointers adjust that count atomically (release
|
||||
* destroys the object at zero), and abi_version is always
|
||||
* OAKRENDER_ABI_VERSION. Copying the struct copies the pointer, not the
|
||||
* count: call addref for every additional long-lived copy and release (or
|
||||
* oakrender_cancelatom_free()) when done with each copy. Functions that
|
||||
* only use a handle take it BY VALUE; an empty handle (ctx == NULL) is
|
||||
* reported as OAKRENDER_E_INVALID.
|
||||
*/
|
||||
typedef struct OakCancelAtom {
|
||||
void *ctx; /**< Opaque pointer to the reference-counted object. */
|
||||
void (*addref)(void *ctx); /**< Atomically increments the count. */
|
||||
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
|
||||
uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */
|
||||
} OakCancelAtom;
|
||||
|
||||
/**
|
||||
* @brief Create a cancellation atom in the not-cancelled state.
|
||||
*
|
||||
* @return Handle with reference count 1; ctx is NULL on allocation
|
||||
* failure.
|
||||
*/
|
||||
OakCancelAtom oakrender_cancelatom_init(void);
|
||||
|
||||
/**
|
||||
* @brief Release one reference to a cancellation atom.
|
||||
*
|
||||
* Convenience wrapper around atom->release(atom->ctx): decrements the
|
||||
* atomic reference count and destroys the object when it reaches zero,
|
||||
* then nulls atom->ctx. No-op when atom is NULL or atom->ctx is NULL.
|
||||
*/
|
||||
void oakrender_cancelatom_free(OakCancelAtom *atom);
|
||||
|
||||
/**
|
||||
* @brief Set the cancel flag (CancelAtom::cancel()). Thread-safe.
|
||||
*
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty handle.
|
||||
*/
|
||||
int oakrender_cancelatom_cancel(OakCancelAtom atom);
|
||||
|
||||
/**
|
||||
* @brief Read the cancel flag (CancelAtom::is_cancelled()).
|
||||
*
|
||||
* Reading a set flag also records that a consumer heard the
|
||||
* cancellation; see oakrender_cancelatom_heard_cancel().
|
||||
*
|
||||
* @param cancelled Receives 1 when cancelled, 0 otherwise.
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty handle or a
|
||||
* NULL out parameter.
|
||||
*/
|
||||
int oakrender_cancelatom_is_cancelled(OakCancelAtom atom, int *cancelled);
|
||||
|
||||
/**
|
||||
* @brief Whether any consumer has observed the cancel flag through
|
||||
* oakrender_cancelatom_is_cancelled() (CancelAtom::heard_cancel()).
|
||||
*
|
||||
* @param heard Receives 1 when the cancellation was heard, 0 otherwise.
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty handle or a
|
||||
* NULL out parameter.
|
||||
*/
|
||||
int oakrender_cancelatom_heard_cancel(OakCancelAtom atom, int *heard);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_CANCELATOM_H
|
||||
@@ -22,6 +22,7 @@
|
||||
#define OAK_EDITOR_RENDER_COLOR_H
|
||||
|
||||
#include "error.h"
|
||||
#include "renderer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -93,6 +94,23 @@ int oakrender_color_processor_convert(OakColorProcessor *processor,
|
||||
double ia, double *out_r, double *out_g,
|
||||
double *out_b, double *out_a);
|
||||
|
||||
/**
|
||||
* @brief Convert a CPU frame's pixels through the processor, in place
|
||||
* (olive::ColorProcessor::convert_frame()).
|
||||
*
|
||||
* The frame's data buffer is rewritten through an OCIO PackedImageDesc
|
||||
* view; nothing is allocated and the frame handle stays owned by the
|
||||
* caller. A processor whose underlying OCIO processor is null
|
||||
* (oakrender_color_processor_create() treats lookup failure as
|
||||
* non-fatal) is a pass-through and returns OAKRENDER_OK, mirroring the
|
||||
* C++ API.
|
||||
*
|
||||
* @return OAKRENDER_OK, OAKRENDER_E_INVALID for NULL/uninitialized
|
||||
* arguments, or OAKRENDER_E_FAILED on an internal exception.
|
||||
*/
|
||||
int oakrender_color_processor_convert_frame(OakColorProcessor *processor,
|
||||
OakCodecFrame *frame);
|
||||
|
||||
/* ---- ColorManager statics ------------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#ifndef OAKUTIL_OAKVIDEO_H
|
||||
#define OAKUTIL_OAKVIDEO_H
|
||||
|
||||
#include <QString>
|
||||
#include <string>
|
||||
|
||||
#include <olive/core/util/rational.h>
|
||||
|
||||
@@ -167,30 +167,30 @@ class ColorTransform
|
||||
public:
|
||||
ColorTransform() = default;
|
||||
|
||||
ColorTransform(const QString &output) : output_(output) {}
|
||||
ColorTransform(const std::string &output) : output_(output) {}
|
||||
|
||||
ColorTransform(const QString &display, const QString &view,
|
||||
const QString &look)
|
||||
ColorTransform(const std::string &display, const std::string &view,
|
||||
const std::string &look)
|
||||
: output_(display), is_display_(true), view_(view), look_(look)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_display() const { return is_display_; }
|
||||
|
||||
const QString &display() const { return output_; }
|
||||
const std::string &display() const { return output_; }
|
||||
|
||||
const QString &output() const { return output_; }
|
||||
const std::string &output() const { return output_; }
|
||||
|
||||
const QString &view() const { return view_; }
|
||||
const std::string &view() const { return view_; }
|
||||
|
||||
const QString &look() const { return look_; }
|
||||
const std::string &look() const { return look_; }
|
||||
|
||||
private:
|
||||
QString output_;
|
||||
std::string output_;
|
||||
|
||||
bool is_display_ = false;
|
||||
QString view_;
|
||||
QString look_;
|
||||
std::string view_;
|
||||
std::string look_;
|
||||
};
|
||||
|
||||
} // namespace oak
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(undo)add_subdirectory(node)
|
||||
add_subdirectory(undo)
|
||||
add_subdirectory(node)
|
||||
add_subdirectory(render)
|
||||
add_subdirectory(codec)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(c_api)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +1,71 @@
|
||||
# oakcodec 中间态与行为变化备忘(M5)
|
||||
|
||||
## 中间态(等待后续里程碑收口)
|
||||
|
||||
1. **Task 回调注册**(M8 收口):conform/proxy 的后台任务经
|
||||
`include/codec/task.h` 的全局提交回调(`oakcodec_set_task_submit_cb`)。
|
||||
未注册时:conform 查询返回 `k_conform_unavailable`,proxy 保持
|
||||
`k_proxy_missing`,不崩溃不阻塞。注册语义为同步提交(回调内完成或
|
||||
排队后立即返回);`SubmitTask` 持锁调回调,回调内不可重入注册函数。
|
||||
conform/proxy 任务的 working→finished 改名生命周期整体移交 M8 oaktask。
|
||||
2. **Config**(config 里程碑收口):`ProxyManager::proxy_params_from_config()`
|
||||
返回编译期默认值(1280x720/div1/mp4/crf23/veryfast/含音频);未引入
|
||||
内存态 stub(ffmpegencoder 当前版本已不读 Config)。
|
||||
3. **纹理路径功能回退**(oakrender 增补 shader-blit C API 后可恢复):
|
||||
oakrender C API 无通用 shader-blit,FFmpegDecoder 的 yuv2rgb GLSL 路径与
|
||||
去隔行 shader 路径已删除;YUV 帧改在 CPU 上 swscale 转 RGBA 后
|
||||
`oakrender_display_texture_upload`(功能保留但更慢;去隔行在纹理路径
|
||||
丢失,CPU 帧路径本就不做去隔行)。Texture 零拷贝持有 hw frame 一并删除。
|
||||
4. **FootageDescription 为 codec 内部结构**(src/codec/src/footagedescription.h):
|
||||
oaknode C API 无对应物;未实现探针缓存 XML load/save 与
|
||||
`get_type_of_stream()`(oaknode `Track::Type` 映射),oaknode footage
|
||||
侧需要时再补。
|
||||
5. **RenderMode**:oakrender C API 无对应物,codec 本地 enum
|
||||
(decoder.h,k_offline=0/k_online=1,值对齐 engine/render/rendermodes.h)。
|
||||
6. **无 adapter 层**(2026-08 第二轮拍板):codec 内部跨模块调用全部直调
|
||||
`oakcommon_*` / `oakrender_*` C 函数,句柄(OakVideoParams/
|
||||
OakColorTransform/OakCancelAtom/OakSubtitleParams)就地按值管理计数;
|
||||
只有真正多处重复的转换保留文件内 static 小函数(如
|
||||
fill_render_params、cancel_atom_is_cancelled)。早期的一版
|
||||
src/codec/src/adapter/ 包装类已删除。
|
||||
7. **XmlStreamWriter/Reader**:照 DEQT.md 用 oakcommon 的 C++ 类
|
||||
(src/common/src/xmlutils.h,与 oaknode/oakrender 的实践一致),未走
|
||||
C API —— 决策 7 的唯一例外,记录在案。
|
||||
|
||||
## 行为变化(相对 Qt 版)
|
||||
|
||||
- Decoder 的 `index_progress` 信号 → `std::function<void(double)>`
|
||||
回调(`set_index_progress_callback`);conform_ready/proxy_ready/
|
||||
proxy_finished 信号删除(通知归 facade/task 系统)。
|
||||
- ConformManager 无状态化:`conforming_` 列表与完成 slot 删除;
|
||||
`get_conform_state` 去掉 `decoder_id` 参数;等待语义改为同步提交后
|
||||
重查文件系统。
|
||||
- `Encoder::write_subtitle(const SubtitleBlock*)` →
|
||||
`write_subtitle(const char *text, double in_seconds, double out_seconds)`。
|
||||
注意原实现传的是 `sub_block->length()`(时长),新调用方传 out=in+length。
|
||||
- `EncodingParams::generate_matrix` 返回 `std::array<float,16>`(行主序),
|
||||
原 QMatrix4x4;`load/save` 的 QIODevice 版本变
|
||||
`load(const std::string&)`/`save_to_string()`,预设 XML 不再含声明与
|
||||
缩进(紧凑 XML,元素/属性名与顺序不变);`video_opts_` 的 XML 顺序
|
||||
由 QHash 无序变为字典序。保留了 load_v1 不赋 custom_range_ 的原 bug。
|
||||
- `PlanarFileDevice::open` 用 `std::vector<std::string>` + 类内
|
||||
`OpenMode` 枚举(k_read_only/k_write_only),FILE* 实现。
|
||||
- FFmpegDecoder 无后台 QThread(现 engine 版本已是同步 retrieve 循环)。
|
||||
- 音频 decode(C API):需要 conform 的媒体在无 task 注册方时返回
|
||||
`OAKCODEC_E_STATE`(不产生后台 conform)。
|
||||
- `oakcodec_audio_stream_info.duration_ts` 恒 0(AudioParams 不带时长)。
|
||||
|
||||
## 符号可见性
|
||||
|
||||
oakcodec 以 `-fvisibility=hidden` 编译,仅导出 `OAKCODEC_API` 标记的
|
||||
C 函数(include/codec/error.h 定义宏)。必须如此:codec 内部 adapter
|
||||
类(olive::VideoParams 等)与 oakcommon/oakrender 内同名弱符号会
|
||||
interpose(曾在 oakcommon_videoparams_init_with_time_base 内部把
|
||||
VideoParams::width() 绑进 liboakcodec 导致崩溃)。
|
||||
|
||||
## oakcommon 侧修复(随 M5 落地)
|
||||
|
||||
- `frame_to_buffer`/`buffer_to_frame` 移入 codec(oiioframebridge.h,
|
||||
内部 C++ 函数),oakcommon 的 OIIO 映射函数保留。
|
||||
- 修复 `src/common/c_api/videoparams.cpp` 的 `convert_to_olive_format`
|
||||
switch 缺 break 穿透 bug(U8 穿透到 f32,bytes_per_pixel 返回 16)。
|
||||
@@ -0,0 +1,7 @@
|
||||
target_sources(oakcodec PRIVATE
|
||||
conform.cpp
|
||||
decoder.cpp
|
||||
encoder.cpp
|
||||
frame.cpp
|
||||
proxy.cpp
|
||||
)
|
||||
@@ -0,0 +1,141 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "codec/conform.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "conformmanager.h"
|
||||
#include "decoder.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int string_out(const std::string &s, char *buf, int buf_size)
|
||||
{
|
||||
int need = static_cast<int>(s.size()) + 1;
|
||||
if (buf && buf_size > 0) {
|
||||
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
|
||||
memcpy(buf, s.data(), n);
|
||||
buf[n] = '\0';
|
||||
}
|
||||
return need;
|
||||
}
|
||||
|
||||
olive::core::AudioParams to_native_params(int sample_rate,
|
||||
uint64_t channel_layout,
|
||||
int sample_format)
|
||||
{
|
||||
return olive::core::AudioParams(
|
||||
sample_rate, channel_layout,
|
||||
static_cast<olive::core::SampleFormat::Format>(sample_format));
|
||||
}
|
||||
|
||||
olive::Decoder::CodecStream to_native_stream(const char *source_filename,
|
||||
int stream_index)
|
||||
{
|
||||
return olive::Decoder::CodecStream(
|
||||
source_filename ? source_filename : "", stream_index, nullptr);
|
||||
}
|
||||
|
||||
bool conform_args_valid(const char *cache_path, const char *source_filename)
|
||||
{
|
||||
return cache_path && *cache_path && source_filename && *source_filename;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int oakcodec_conform_create_instance(void)
|
||||
{
|
||||
olive::ConformManager::create_instance();
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_conform_destroy_instance(void)
|
||||
{
|
||||
olive::ConformManager::destroy_instance();
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_conform_get_state(const char *cache_path,
|
||||
const char *source_filename, int stream_index,
|
||||
int sample_rate, uint64_t channel_layout,
|
||||
int sample_format, int wait)
|
||||
{
|
||||
if (!conform_args_valid(cache_path, source_filename))
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!olive::ConformManager::instance())
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
olive::ConformManager::Conform c =
|
||||
olive::ConformManager::instance()->get_conform_state(
|
||||
cache_path, to_native_stream(source_filename, stream_index),
|
||||
to_native_params(sample_rate, channel_layout, sample_format),
|
||||
wait != 0);
|
||||
|
||||
switch (c.state) {
|
||||
case olive::ConformManager::k_conform_exists:
|
||||
return OAKCODEC_CONFORM_EXISTS;
|
||||
case olive::ConformManager::k_conform_generating:
|
||||
return OAKCODEC_CONFORM_GENERATING;
|
||||
case olive::ConformManager::k_conform_unavailable:
|
||||
default:
|
||||
return OAKCODEC_CONFORM_UNAVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcodec_conform_filename_count(const char *cache_path,
|
||||
const char *source_filename,
|
||||
int stream_index, int sample_rate,
|
||||
uint64_t channel_layout, int sample_format)
|
||||
{
|
||||
if (!conform_args_valid(cache_path, source_filename))
|
||||
return 0;
|
||||
|
||||
// Pure path computation: never submits work.
|
||||
return static_cast<int>(olive::ConformManager::get_conformed_filename(
|
||||
cache_path,
|
||||
to_native_stream(source_filename, stream_index),
|
||||
to_native_params(sample_rate, channel_layout,
|
||||
sample_format))
|
||||
.size());
|
||||
}
|
||||
|
||||
int oakcodec_conform_filename_at(const char *cache_path,
|
||||
const char *source_filename,
|
||||
int stream_index, int sample_rate,
|
||||
uint64_t channel_layout, int sample_format,
|
||||
int index, char *buf, int buf_size)
|
||||
{
|
||||
if (!conform_args_valid(cache_path, source_filename))
|
||||
return OAKCODEC_E_INVALID;
|
||||
|
||||
std::vector<std::string> filenames =
|
||||
olive::ConformManager::get_conformed_filename(
|
||||
cache_path, to_native_stream(source_filename, stream_index),
|
||||
to_native_params(sample_rate, channel_layout, sample_format));
|
||||
|
||||
if (index < 0 || index >= static_cast<int>(filenames.size()))
|
||||
return OAKCODEC_E_NOT_FOUND;
|
||||
return string_out(filenames[index], buf, buf_size);
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "codec/decoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "common/loopmode.h"
|
||||
#include "decoder.h"
|
||||
#include "footagedescription.h"
|
||||
#include "frame.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct ProbeBox {
|
||||
std::string decoder_name;
|
||||
olive::FootageDescription desc;
|
||||
};
|
||||
|
||||
struct DecoderBox {
|
||||
olive::DecoderPtr decoder;
|
||||
std::string last_error;
|
||||
std::string open_filename;
|
||||
int open_stream = -1;
|
||||
bool open = false;
|
||||
};
|
||||
|
||||
ProbeBox *probe_box(void *ctx)
|
||||
{
|
||||
return oakcodec::handle_impl<ProbeBox>(ctx);
|
||||
}
|
||||
|
||||
DecoderBox *decoder_box(void *ctx)
|
||||
{
|
||||
return oakcodec::handle_impl<DecoderBox>(ctx);
|
||||
}
|
||||
|
||||
thread_local std::string g_probe_error;
|
||||
|
||||
int string_out(const std::string &s, char *buf, int buf_size)
|
||||
{
|
||||
int need = static_cast<int>(s.size()) + 1;
|
||||
if (buf && buf_size > 0) {
|
||||
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
|
||||
memcpy(buf, s.data(), n);
|
||||
buf[n] = '\0';
|
||||
}
|
||||
return need;
|
||||
}
|
||||
|
||||
bool file_exists(const char *filename)
|
||||
{
|
||||
struct stat st;
|
||||
return filename && stat(filename, &st) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Probe with every available decoder, returning the first valid
|
||||
* description (and filling `name`).
|
||||
*/
|
||||
bool probe_with_any_decoder(const char *filename, std::string *name,
|
||||
olive::FootageDescription *out)
|
||||
{
|
||||
for (const olive::DecoderPtr &d :
|
||||
olive::Decoder::receive_list_of_all_decoders()) {
|
||||
olive::FootageDescription desc = d->probe(filename, nullptr);
|
||||
if (desc.is_valid()) {
|
||||
*name = desc.decoder();
|
||||
*out = desc;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void fill_video_info(const OakVideoParams &vp,
|
||||
oakcodec_video_stream_info *out)
|
||||
{
|
||||
*out = {};
|
||||
|
||||
oakcommon_videoparams_get_stream_index(vp, &out->stream_index);
|
||||
oakcommon_videoparams_get_width(vp, &out->width);
|
||||
oakcommon_videoparams_get_height(vp, &out->height);
|
||||
|
||||
int fr_num = 0, fr_den = 0;
|
||||
oakcommon_videoparams_get_frame_rate(vp, &fr_num, &fr_den);
|
||||
out->frame_rate_num = fr_num;
|
||||
out->frame_rate_den = fr_den;
|
||||
|
||||
int tb_num = 0, tb_den = 0;
|
||||
oakcommon_videoparams_get_time_base(vp, &tb_num, &tb_den);
|
||||
out->time_base_num = tb_num;
|
||||
out->time_base_den = tb_den;
|
||||
|
||||
oakcommon_videoparams_get_duration(vp, &out->duration_ts);
|
||||
oakcommon_videoparams_get_format(vp, &out->format);
|
||||
oakcommon_videoparams_get_channel_count(vp, &out->channel_count);
|
||||
oakcommon_videoparams_get_color_primaries(vp, &out->color_primaries);
|
||||
oakcommon_videoparams_get_color_transfer(vp, &out->color_trc);
|
||||
|
||||
int interlacing = OAKCOMMON_VIDEO_INTERLACE_NONE;
|
||||
oakcommon_videoparams_get_interlacing(vp, &interlacing);
|
||||
out->interlaced = interlacing != OAKCOMMON_VIDEO_INTERLACE_NONE;
|
||||
}
|
||||
|
||||
void fill_audio_info(const olive::AudioParams &ap,
|
||||
oakcodec_audio_stream_info *out)
|
||||
{
|
||||
*out = {};
|
||||
out->stream_index = ap.stream_index();
|
||||
out->sample_rate = ap.sample_rate();
|
||||
out->channel_layout = ap.channel_layout();
|
||||
out->channel_count = ap.channel_count();
|
||||
|
||||
olive::Rational tb = ap.time_base();
|
||||
out->time_base_num = tb.numerator();
|
||||
out->time_base_den = tb.denominator();
|
||||
// AudioParams carries no duration; duration_ts stays 0 (unknown).
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---- Probe ---------------------------------------------------------------- */
|
||||
|
||||
OakDecoder oakcodec_decoder_probe(const char *filename)
|
||||
{
|
||||
if (!filename || !*filename) {
|
||||
g_probe_error = "no filename given";
|
||||
return OakDecoder{};
|
||||
}
|
||||
if (!file_exists(filename)) {
|
||||
g_probe_error = std::string("file not found: ") + filename;
|
||||
return OakDecoder{};
|
||||
}
|
||||
|
||||
OakDecoder h = oakcodec::make_handle_in_place<OakDecoder, ProbeBox>();
|
||||
ProbeBox *b = probe_box(h.ctx);
|
||||
if (!b) {
|
||||
g_probe_error = "out of memory";
|
||||
return OakDecoder{};
|
||||
}
|
||||
|
||||
if (!probe_with_any_decoder(filename, &b->decoder_name, &b->desc)) {
|
||||
g_probe_error =
|
||||
std::string("no decoder recognizes this file: ") + filename;
|
||||
oakcodec_decoder_free(&h);
|
||||
return OakDecoder{};
|
||||
}
|
||||
|
||||
g_probe_error.clear();
|
||||
return h;
|
||||
}
|
||||
|
||||
int oakcodec_probe_last_error(char *buf, int buf_size)
|
||||
{
|
||||
return string_out(g_probe_error, buf, buf_size);
|
||||
}
|
||||
|
||||
int oakcodec_decoder_probe_decoder_name(OakDecoder probe, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
ProbeBox *b = probe_box(probe.ctx);
|
||||
if (!b)
|
||||
return OAKCODEC_E_INVALID;
|
||||
return string_out(b->decoder_name, buf, buf_size);
|
||||
}
|
||||
|
||||
int oakcodec_decoder_probe_video_stream_count(OakDecoder probe)
|
||||
{
|
||||
ProbeBox *b = probe_box(probe.ctx);
|
||||
if (!b)
|
||||
return 0;
|
||||
return static_cast<int>(b->desc.get_video_streams().size());
|
||||
}
|
||||
|
||||
int oakcodec_decoder_probe_audio_stream_count(OakDecoder probe)
|
||||
{
|
||||
ProbeBox *b = probe_box(probe.ctx);
|
||||
if (!b)
|
||||
return 0;
|
||||
return static_cast<int>(b->desc.get_audio_streams().size());
|
||||
}
|
||||
|
||||
int oakcodec_decoder_probe_subtitle_stream_count(OakDecoder probe)
|
||||
{
|
||||
ProbeBox *b = probe_box(probe.ctx);
|
||||
if (!b)
|
||||
return 0;
|
||||
return static_cast<int>(b->desc.get_subtitle_streams().size());
|
||||
}
|
||||
|
||||
int oakcodec_decoder_probe_get_video_stream(OakDecoder probe, int index,
|
||||
oakcodec_video_stream_info *out)
|
||||
{
|
||||
ProbeBox *b = probe_box(probe.ctx);
|
||||
if (!b || !out)
|
||||
return OAKCODEC_E_INVALID;
|
||||
const auto &streams = b->desc.get_video_streams();
|
||||
if (index < 0 || index >= static_cast<int>(streams.size()))
|
||||
return OAKCODEC_E_NOT_FOUND;
|
||||
fill_video_info(streams[static_cast<size_t>(index)], out);
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_decoder_probe_get_audio_stream(OakDecoder probe, int index,
|
||||
oakcodec_audio_stream_info *out)
|
||||
{
|
||||
ProbeBox *b = probe_box(probe.ctx);
|
||||
if (!b || !out)
|
||||
return OAKCODEC_E_INVALID;
|
||||
const auto &streams = b->desc.get_audio_streams();
|
||||
if (index < 0 || index >= static_cast<int>(streams.size()))
|
||||
return OAKCODEC_E_NOT_FOUND;
|
||||
fill_audio_info(streams[static_cast<size_t>(index)], out);
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
/* ---- Decode session -------------------------------------------------------- */
|
||||
|
||||
OakDecoder oakcodec_decoder_init(void)
|
||||
{
|
||||
return oakcodec::make_handle_in_place<OakDecoder, DecoderBox>();
|
||||
}
|
||||
|
||||
void oakcodec_decoder_free(OakDecoder *decoder)
|
||||
{
|
||||
oakcodec::free_handle(decoder);
|
||||
}
|
||||
|
||||
int oakcodec_decoder_open(OakDecoder decoder, const char *filename,
|
||||
int stream_index)
|
||||
{
|
||||
DecoderBox *b = decoder_box(decoder.ctx);
|
||||
if (!b || !filename || stream_index < 0)
|
||||
return OAKCODEC_E_INVALID;
|
||||
|
||||
if (b->open && b->decoder) {
|
||||
if (b->open_filename == filename && b->open_stream == stream_index)
|
||||
return OAKCODEC_OK; // already open on this stream
|
||||
b->decoder->close();
|
||||
b->open = false;
|
||||
}
|
||||
|
||||
if (!file_exists(filename)) {
|
||||
b->last_error = std::string("file not found: ") + filename;
|
||||
return OAKCODEC_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
std::string decoder_name;
|
||||
olive::FootageDescription desc;
|
||||
if (!probe_with_any_decoder(filename, &decoder_name, &desc)) {
|
||||
b->last_error =
|
||||
std::string("no decoder recognizes this file: ") + filename;
|
||||
return OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
b->decoder = olive::Decoder::create_from_id(decoder_name);
|
||||
if (!b->decoder) {
|
||||
b->last_error = std::string("failed to create decoder: ") + decoder_name;
|
||||
return OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
if (!b->decoder->open(
|
||||
olive::Decoder::CodecStream(filename, stream_index, nullptr))) {
|
||||
b->last_error = "failed to open stream";
|
||||
b->decoder.reset();
|
||||
return OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
b->last_error.clear();
|
||||
b->open_filename = filename;
|
||||
b->open_stream = stream_index;
|
||||
b->open = true;
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_decoder_close(OakDecoder decoder)
|
||||
{
|
||||
DecoderBox *b = decoder_box(decoder.ctx);
|
||||
if (!b)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (b->open && b->decoder) {
|
||||
b->decoder->close();
|
||||
}
|
||||
b->open = false;
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_decoder_is_open(OakDecoder decoder)
|
||||
{
|
||||
DecoderBox *b = decoder_box(decoder.ctx);
|
||||
return (b && b->open) ? 1 : 0;
|
||||
}
|
||||
|
||||
OakFrame oakcodec_decoder_decode_video(OakDecoder decoder, int numerator,
|
||||
int denominator)
|
||||
{
|
||||
DecoderBox *b = decoder_box(decoder.ctx);
|
||||
if (!b || !b->open || !b->decoder)
|
||||
return OakFrame{};
|
||||
|
||||
olive::Decoder::RetrieveVideoParams p;
|
||||
p.time = olive::Rational(numerator, denominator);
|
||||
|
||||
olive::FramePtr frame = b->decoder->retrieve_video_frame(p);
|
||||
if (!frame) {
|
||||
b->last_error = "failed to decode video frame";
|
||||
return OakFrame{};
|
||||
}
|
||||
|
||||
return oakcodec::make_handle<OakFrame>(std::move(frame));
|
||||
}
|
||||
|
||||
int oakcodec_decoder_decode_audio(OakDecoder decoder, int in_num, int in_den,
|
||||
int out_num, int out_den, int sample_rate,
|
||||
uint64_t channel_layout, float *buf,
|
||||
int buf_frames)
|
||||
{
|
||||
DecoderBox *b = decoder_box(decoder.ctx);
|
||||
if (!b || (!buf && buf_frames > 0) || buf_frames < 0)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!b->open || !b->decoder)
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
olive::AudioParams params(sample_rate, channel_layout,
|
||||
olive::core::SampleFormat::f32);
|
||||
olive::TimeRange range(olive::Rational(in_num, in_den),
|
||||
olive::Rational(out_num, out_den));
|
||||
|
||||
olive::SampleBuffer samples;
|
||||
olive::Decoder::RetrieveAudioStatus status = b->decoder->retrieve_audio(
|
||||
samples, range, params, std::string(), OAKCOMMON_LOOP_MODE_OFF,
|
||||
olive::RenderMode::k_offline);
|
||||
|
||||
if (status == olive::Decoder::k_waiting_for_conform) {
|
||||
// Interim state (pre-M8): conform tasks require a task registrar.
|
||||
b->last_error =
|
||||
"audio requires a conform, but no task submit callback is "
|
||||
"registered (see oakcodec_set_task_submit_cb)";
|
||||
return OAKCODEC_E_STATE;
|
||||
}
|
||||
if (status != olive::Decoder::k_ok || !samples.is_allocated()) {
|
||||
b->last_error = "failed to decode audio";
|
||||
return OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
int channels = samples.channel_count();
|
||||
int available = static_cast<int>(samples.sample_count());
|
||||
int frames = std::min(available, buf_frames);
|
||||
for (int c = 0; c < channels; c++) {
|
||||
const float *src = samples.data(c);
|
||||
for (int i = 0; i < frames; i++) {
|
||||
buf[static_cast<size_t>(i) * channels + c] = src[i];
|
||||
}
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
int oakcodec_decoder_last_error(OakDecoder decoder, char *buf, int buf_size)
|
||||
{
|
||||
DecoderBox *b = decoder_box(decoder.ctx);
|
||||
if (!b)
|
||||
return string_out("", buf, buf_size);
|
||||
return string_out(b->last_error, buf, buf_size);
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "codec/encoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/colortransform.h"
|
||||
#include "common/videoparams.h"
|
||||
#include "encoder.h"
|
||||
#include "frame.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr int k_rgba_channel_count = 4;
|
||||
|
||||
struct EncoderBox {
|
||||
std::unique_ptr<olive::Encoder> encoder;
|
||||
olive::EncodingParams params;
|
||||
bool open = false;
|
||||
bool flushed = false;
|
||||
};
|
||||
|
||||
EncoderBox *box(void *ctx)
|
||||
{
|
||||
return oakcodec::handle_impl<EncoderBox>(ctx);
|
||||
}
|
||||
|
||||
int string_out(const std::string &s, char *buf, int buf_size)
|
||||
{
|
||||
int need = static_cast<int>(s.size()) + 1;
|
||||
if (buf && buf_size > 0) {
|
||||
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
|
||||
memcpy(buf, s.data(), n);
|
||||
buf[n] = '\0';
|
||||
}
|
||||
return need;
|
||||
}
|
||||
|
||||
olive::EncodingParams to_native(const oakcodec_encoding_params *p)
|
||||
{
|
||||
using namespace olive;
|
||||
|
||||
EncodingParams n;
|
||||
n.set_filename(p->filename);
|
||||
n.set_format(static_cast<ExportFormat::Format>(p->format));
|
||||
|
||||
if (p->video_enabled) {
|
||||
OakVideoParams vp = oakcommon_videoparams_init_with_time_base(
|
||||
p->video_width, p->video_height, p->video_time_base_num,
|
||||
p->video_time_base_den, p->video_pixel_format,
|
||||
k_rgba_channel_count, p->video_pixel_aspect_num,
|
||||
p->video_pixel_aspect_den, p->video_interlacing, 1);
|
||||
n.enable_video(vp, static_cast<ExportCodec::Codec>(p->video_codec));
|
||||
oakcommon_videoparams_free(&vp);
|
||||
n.set_video_bit_rate(p->video_bit_rate);
|
||||
n.set_video_min_bit_rate(p->video_min_bit_rate);
|
||||
n.set_video_max_bit_rate(p->video_max_bit_rate);
|
||||
n.set_video_buffer_size(p->video_buffer_size);
|
||||
n.set_video_threads(p->video_threads);
|
||||
n.set_video_pix_fmt(p->video_pix_fmt);
|
||||
n.set_video_is_image_sequence(p->video_is_image_sequence != 0);
|
||||
n.set_video_scaling_method(
|
||||
static_cast<EncodingParams::VideoScalingMethod>(
|
||||
p->video_scaling_method));
|
||||
}
|
||||
|
||||
if (p->audio_enabled) {
|
||||
AudioParams ap(p->audio_sample_rate, p->audio_channel_layout,
|
||||
static_cast<core::SampleFormat::Format>(
|
||||
p->audio_sample_format));
|
||||
n.enable_audio(ap, static_cast<ExportCodec::Codec>(p->audio_codec));
|
||||
n.set_audio_bit_rate(p->audio_bit_rate);
|
||||
}
|
||||
|
||||
if (p->subtitles_enabled) {
|
||||
if (p->subtitles_are_sidecar) {
|
||||
n.enable_sidecar_subtitles(
|
||||
static_cast<ExportFormat::Format>(
|
||||
p->subtitles_sidecar_format),
|
||||
static_cast<ExportCodec::Codec>(p->subtitles_codec));
|
||||
} else {
|
||||
n.enable_subtitles(
|
||||
static_cast<ExportCodec::Codec>(p->subtitles_codec));
|
||||
}
|
||||
}
|
||||
|
||||
if (p->color_transform_output[0] != '\0') {
|
||||
OakColorTransform ct =
|
||||
oakcommon_colortransform_init_output(p->color_transform_output);
|
||||
n.set_color_transform(ct);
|
||||
oakcommon_colortransform_free(&ct);
|
||||
}
|
||||
|
||||
if (p->export_length_den != 0) {
|
||||
n.set_export_length(
|
||||
Rational(p->export_length_num, p->export_length_den));
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakEncoder oakcodec_encoder_init(const oakcodec_encoding_params *params)
|
||||
{
|
||||
if (!params)
|
||||
return OakEncoder{};
|
||||
|
||||
OakEncoder h = oakcodec::make_handle_in_place<OakEncoder, EncoderBox>();
|
||||
EncoderBox *b = box(h.ctx);
|
||||
if (!b)
|
||||
return OakEncoder{};
|
||||
|
||||
try {
|
||||
b->params = to_native(params);
|
||||
} catch (...) {
|
||||
oakcodec_encoder_free(&h);
|
||||
return OakEncoder{};
|
||||
}
|
||||
|
||||
if (!b->params.is_valid()) {
|
||||
oakcodec_encoder_free(&h);
|
||||
return OakEncoder{};
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void oakcodec_encoder_free(OakEncoder *encoder)
|
||||
{
|
||||
oakcodec::free_handle(encoder);
|
||||
}
|
||||
|
||||
int oakcodec_encoder_set_video_option(OakEncoder encoder, const char *key,
|
||||
const char *value)
|
||||
{
|
||||
EncoderBox *b = box(encoder.ctx);
|
||||
if (!b || !key)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (b->open)
|
||||
return OAKCODEC_E_STATE;
|
||||
b->params.set_video_option(key, value ? value : "");
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_encoder_open(OakEncoder encoder)
|
||||
{
|
||||
EncoderBox *b = box(encoder.ctx);
|
||||
if (!b)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (b->open)
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
b->encoder.reset(olive::Encoder::create_from_params(b->params));
|
||||
if (!b->encoder)
|
||||
return OAKCODEC_E_FAILED;
|
||||
|
||||
if (!b->encoder->open()) {
|
||||
return OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
b->open = true;
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_encoder_write_video(OakEncoder encoder, OakFrame frame)
|
||||
{
|
||||
EncoderBox *b = box(encoder.ctx);
|
||||
if (!b || !frame.ctx)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!b->open || b->flushed || !b->encoder)
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
// OakFrame boxes hold an olive::FramePtr (see c_api/frame.cpp).
|
||||
auto *fp = oakcodec::handle_impl<olive::FramePtr>(frame.ctx);
|
||||
if (!fp || !*fp)
|
||||
return OAKCODEC_E_INVALID;
|
||||
olive::Frame *f = fp->get();
|
||||
|
||||
return b->encoder->write_frame(*fp, f->timestamp()) ? OAKCODEC_OK
|
||||
: OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
int oakcodec_encoder_write_audio(OakEncoder encoder, const float *samples,
|
||||
int frame_count)
|
||||
{
|
||||
EncoderBox *b = box(encoder.ctx);
|
||||
if (!b || (!samples && frame_count > 0) || frame_count < 0)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!b->open || b->flushed || !b->encoder)
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
const olive::AudioParams &ap = b->params.audio_params();
|
||||
int channels = ap.channel_count();
|
||||
if (channels <= 0)
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
// Deinterleave into a planar SampleBuffer.
|
||||
olive::SampleBuffer buf(ap, static_cast<size_t>(frame_count));
|
||||
buf.allocate();
|
||||
std::vector<float> channel_data(static_cast<size_t>(frame_count));
|
||||
for (int c = 0; c < channels; c++) {
|
||||
for (int i = 0; i < frame_count; i++) {
|
||||
channel_data[i] = samples[static_cast<size_t>(i) * channels + c];
|
||||
}
|
||||
buf.set(c, channel_data.data(),
|
||||
static_cast<size_t>(frame_count));
|
||||
}
|
||||
|
||||
return b->encoder->write_audio(buf) ? OAKCODEC_OK : OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
int oakcodec_encoder_write_subtitle(OakEncoder encoder, const char *text,
|
||||
double in_seconds, double out_seconds)
|
||||
{
|
||||
EncoderBox *b = box(encoder.ctx);
|
||||
if (!b || !text)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!b->open || b->flushed || !b->encoder)
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
return b->encoder->write_subtitle(text, in_seconds, out_seconds)
|
||||
? OAKCODEC_OK
|
||||
: OAKCODEC_E_FAILED;
|
||||
}
|
||||
|
||||
int oakcodec_encoder_flush(OakEncoder encoder)
|
||||
{
|
||||
EncoderBox *b = box(encoder.ctx);
|
||||
if (!b)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!b->open)
|
||||
return OAKCODEC_E_STATE;
|
||||
if (b->flushed)
|
||||
return OAKCODEC_OK;
|
||||
|
||||
b->encoder->close();
|
||||
b->flushed = true;
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_encoder_last_error(OakEncoder encoder, char *buf, int buf_size)
|
||||
{
|
||||
EncoderBox *b = box(encoder.ctx);
|
||||
if (!b)
|
||||
return string_out("", buf, buf_size);
|
||||
return string_out(b->encoder ? b->encoder->get_error() : std::string(),
|
||||
buf, buf_size);
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "codec/frame.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "frame.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Every OakFrame box holds an olive::FramePtr: frames created here own a
|
||||
// fresh olive::Frame, decoder-produced frames alias the decoder's
|
||||
// shared_ptr. Unifying the box type keeps the addref/release thunks and
|
||||
// the impl recovery symmetric across all OakFrame handles.
|
||||
olive::Frame *impl(void *ctx)
|
||||
{
|
||||
auto *p = oakcodec::handle_impl<olive::FramePtr>(ctx);
|
||||
return p ? p->get() : nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace oakcodec
|
||||
{
|
||||
|
||||
std::atomic<int> g_alive_count{0};
|
||||
|
||||
void alive_inc()
|
||||
{
|
||||
g_alive_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void alive_dec()
|
||||
{
|
||||
g_alive_count.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace oakcodec
|
||||
|
||||
int oakcodec_debug_alive_count(void)
|
||||
{
|
||||
return oakcodec::g_alive_count.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
OakFrame oakcodec_frame_init(void)
|
||||
{
|
||||
return oakcodec::make_handle<OakFrame>(olive::Frame::create());
|
||||
}
|
||||
|
||||
OakFrame oakcodec_frame_init_with_params(OakVideoParams params)
|
||||
{
|
||||
OakFrame h = oakcodec_frame_init();
|
||||
if (h.ctx) {
|
||||
impl(h.ctx)->set_video_params(params);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
void oakcodec_frame_free(OakFrame *frame)
|
||||
{
|
||||
oakcodec::free_handle(frame);
|
||||
}
|
||||
|
||||
int oakcodec_frame_get_params(OakFrame frame, OakVideoParams *out)
|
||||
{
|
||||
if (!frame.ctx || !out)
|
||||
return OAKCODEC_E_INVALID;
|
||||
*out = impl(frame.ctx)->video_params();
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_frame_set_params(OakFrame frame, OakVideoParams params)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return OAKCODEC_E_INVALID;
|
||||
impl(frame.ctx)->set_video_params(params);
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_frame_allocate(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!impl(frame.ctx)->allocate())
|
||||
return OAKCODEC_E_STATE;
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_frame_is_allocated(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return 0;
|
||||
return impl(frame.ctx)->is_allocated() ? 1 : 0;
|
||||
}
|
||||
|
||||
void *oakcodec_frame_data(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return nullptr;
|
||||
return impl(frame.ctx)->data();
|
||||
}
|
||||
|
||||
const void *oakcodec_frame_const_data(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return nullptr;
|
||||
return impl(frame.ctx)->const_data();
|
||||
}
|
||||
|
||||
int oakcodec_frame_allocated_size(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return 0;
|
||||
return impl(frame.ctx)->allocated_size();
|
||||
}
|
||||
|
||||
int oakcodec_frame_linesize_bytes(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return 0;
|
||||
return impl(frame.ctx)->linesize_bytes();
|
||||
}
|
||||
|
||||
int oakcodec_frame_linesize_pixels(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return 0;
|
||||
return impl(frame.ctx)->linesize_pixels();
|
||||
}
|
||||
|
||||
int oakcodec_frame_width(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return 0;
|
||||
return impl(frame.ctx)->width();
|
||||
}
|
||||
|
||||
int oakcodec_frame_height(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return 0;
|
||||
return impl(frame.ctx)->height();
|
||||
}
|
||||
|
||||
int oakcodec_frame_format(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return OAKCOMMON_PIXEL_FORMAT_INVALID;
|
||||
return impl(frame.ctx)->format();
|
||||
}
|
||||
|
||||
int oakcodec_frame_channel_count(OakFrame frame)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return 0;
|
||||
return impl(frame.ctx)->channel_count();
|
||||
}
|
||||
|
||||
int oakcodec_frame_get_timestamp(OakFrame frame, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!frame.ctx || !numerator || !denominator)
|
||||
return OAKCODEC_E_INVALID;
|
||||
const olive::core::Rational &ts = impl(frame.ctx)->timestamp();
|
||||
*numerator = ts.numerator();
|
||||
*denominator = ts.denominator();
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_frame_set_timestamp(OakFrame frame, int numerator,
|
||||
int denominator)
|
||||
{
|
||||
if (!frame.ctx)
|
||||
return OAKCODEC_E_INVALID;
|
||||
impl(frame.ctx)->set_timestamp(
|
||||
olive::core::Rational(numerator, denominator));
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "codec/proxy.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "proxymanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int string_out(const std::string &s, char *buf, int buf_size)
|
||||
{
|
||||
int need = static_cast<int>(s.size()) + 1;
|
||||
if (buf && buf_size > 0) {
|
||||
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
|
||||
memcpy(buf, s.data(), n);
|
||||
buf[n] = '\0';
|
||||
}
|
||||
return need;
|
||||
}
|
||||
|
||||
olive::ProxyManager::ProxyParams to_native(const oakcodec_proxy_params *p)
|
||||
{
|
||||
olive::ProxyManager::ProxyParams n;
|
||||
if (p) {
|
||||
n.width = p->width;
|
||||
n.height = p->height;
|
||||
n.divider = p->divider;
|
||||
n.version = p->version;
|
||||
n.crf = p->crf;
|
||||
n.include_audio = p->include_audio != 0;
|
||||
n.extension = p->extension;
|
||||
n.preset = p->preset;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int oakcodec_proxy_create_instance(void)
|
||||
{
|
||||
olive::ProxyManager::create_instance();
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_proxy_destroy_instance(void)
|
||||
{
|
||||
olive::ProxyManager::destroy_instance();
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_proxy_params_default(oakcodec_proxy_params *out)
|
||||
{
|
||||
if (!out)
|
||||
return OAKCODEC_E_INVALID;
|
||||
olive::ProxyManager::ProxyParams n =
|
||||
olive::ProxyManager::proxy_params_from_config();
|
||||
*out = {};
|
||||
out->width = n.width;
|
||||
out->height = n.height;
|
||||
out->divider = n.divider;
|
||||
out->version = n.version;
|
||||
out->crf = n.crf;
|
||||
out->include_audio = n.include_audio ? 1 : 0;
|
||||
snprintf(out->extension, sizeof(out->extension), "%s",
|
||||
n.extension.c_str());
|
||||
snprintf(out->preset, sizeof(out->preset), "%s", n.preset.c_str());
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_proxy_get_state(const char *proxy_filename)
|
||||
{
|
||||
if (!proxy_filename || !*proxy_filename)
|
||||
return OAKCODEC_PROXY_STATE_MISSING;
|
||||
return static_cast<int>(
|
||||
olive::ProxyManager::get_proxy_state(proxy_filename));
|
||||
}
|
||||
|
||||
int oakcodec_proxy_state_to_string(int state, char *buf, int buf_size)
|
||||
{
|
||||
if (state < OAKCODEC_PROXY_STATE_MISSING ||
|
||||
state > OAKCODEC_PROXY_STATE_FAILED)
|
||||
return OAKCODEC_E_INVALID;
|
||||
return string_out(olive::ProxyManager::proxy_state_to_string(
|
||||
static_cast<olive::ProxyManager::ProxyState>(state)),
|
||||
buf, buf_size);
|
||||
}
|
||||
|
||||
int oakcodec_proxy_get_proxy_directory(const char *cache_path, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!cache_path)
|
||||
return OAKCODEC_E_INVALID;
|
||||
return string_out(olive::ProxyManager::get_proxy_directory(cache_path),
|
||||
buf, buf_size);
|
||||
}
|
||||
|
||||
int oakcodec_proxy_get_proxy_filename(const char *cache_path,
|
||||
const char *source_filename,
|
||||
int stream_index,
|
||||
const oakcodec_proxy_params *params,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!cache_path || !source_filename)
|
||||
return OAKCODEC_E_INVALID;
|
||||
return string_out(
|
||||
olive::ProxyManager::get_proxy_filename(
|
||||
cache_path, source_filename, stream_index, to_native(params)),
|
||||
buf, buf_size);
|
||||
}
|
||||
|
||||
int oakcodec_proxy_get_working_filename(const char *proxy_filename,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!proxy_filename)
|
||||
return OAKCODEC_E_INVALID;
|
||||
return string_out(
|
||||
olive::ProxyManager::get_working_proxy_filename(proxy_filename),
|
||||
buf, buf_size);
|
||||
}
|
||||
|
||||
int oakcodec_proxy_get_or_start(const char *cache_path,
|
||||
const char *source_filename, int stream_index,
|
||||
const oakcodec_proxy_params *params,
|
||||
oakcodec_proxy_result *out)
|
||||
{
|
||||
if (!cache_path || !source_filename || !out)
|
||||
return OAKCODEC_E_INVALID;
|
||||
if (!olive::ProxyManager::instance())
|
||||
return OAKCODEC_E_STATE;
|
||||
|
||||
olive::ProxyManager::Proxy p =
|
||||
olive::ProxyManager::instance()->get_or_start_proxy(
|
||||
cache_path, source_filename, stream_index, to_native(params));
|
||||
|
||||
out->state = static_cast<int>(p.state);
|
||||
snprintf(out->filename, sizeof(out->filename), "%s",
|
||||
p.filename.c_str());
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
int oakcodec_proxy_find_ffmpeg(const char *configured_path, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
return string_out(olive::ProxyManager::find_f_fmpeg_executable(
|
||||
configured_path ? configured_path : ""),
|
||||
buf, buf_size);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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 OAKCODEC_C_API_REFCOUNTED_H
|
||||
#define OAKCODEC_C_API_REFCOUNTED_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "codec/error.h"
|
||||
|
||||
namespace oakcodec
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Heap box behind every handle's ctx pointer.
|
||||
*
|
||||
* Same pattern as oakcommon's c_api/refcounted.h: holds the wrapped
|
||||
* object plus its atomic reference count. addref and release are emitted
|
||||
* per boxed type so that the function pointers stored in a handle always
|
||||
* run code from the DLL that created the object. Every box also
|
||||
* participates in the oakcodec_debug_alive_count() ledger.
|
||||
*/
|
||||
template <typename T> struct RefCounted {
|
||||
T impl;
|
||||
std::atomic<uint32_t> refs;
|
||||
|
||||
template <typename... Args>
|
||||
explicit RefCounted(Args &&...args)
|
||||
: impl(std::forward<Args>(args)...)
|
||||
, refs(1)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> void ref_counted_addref(void *ctx)
|
||||
{
|
||||
auto *box = static_cast<RefCounted<T> *>(ctx);
|
||||
if (box)
|
||||
box->refs.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void alive_inc();
|
||||
void alive_dec();
|
||||
|
||||
template <typename T> void ref_counted_release(void *ctx)
|
||||
{
|
||||
auto *box = static_cast<RefCounted<T> *>(ctx);
|
||||
if (box && box->refs.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
delete box;
|
||||
alive_dec();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build a by-value handle owning a freshly boxed object (count 1).
|
||||
*
|
||||
* On allocation failure the returned handle has ctx == NULL (all C API
|
||||
* functions treat that as OAKCODEC_E_INVALID and free() as a no-op).
|
||||
*/
|
||||
template <typename Handle, typename T, typename... Args>
|
||||
Handle make_handle_in_place(Args &&...args)
|
||||
{
|
||||
Handle h = {};
|
||||
try {
|
||||
h.ctx = new RefCounted<T>(std::forward<Args>(args)...);
|
||||
alive_inc();
|
||||
} catch (...) {
|
||||
h.ctx = nullptr;
|
||||
}
|
||||
h.addref = &ref_counted_addref<T>;
|
||||
h.release = &ref_counted_release<T>;
|
||||
h.abi_version = OAKCODEC_ABI_VERSION;
|
||||
return h;
|
||||
}
|
||||
|
||||
template <typename Handle, typename T> Handle make_handle(T &&value)
|
||||
{
|
||||
return make_handle_in_place<Handle, typename std::decay<T>::type>(
|
||||
std::forward<T>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recover the boxed object from a handle ctx (NULL-safe).
|
||||
*/
|
||||
template <typename T> T *handle_impl(void *ctx)
|
||||
{
|
||||
auto *box = static_cast<RefCounted<T> *>(ctx);
|
||||
return box ? &box->impl : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shared free() body: release the ctx, no-op on NULL/empty handle.
|
||||
*/
|
||||
template <typename Handle> void free_handle(Handle *h)
|
||||
{
|
||||
if (!h || !h->ctx || !h->release)
|
||||
return;
|
||||
h->release(h->ctx);
|
||||
h->ctx = nullptr;
|
||||
}
|
||||
|
||||
} // namespace oakcodec
|
||||
|
||||
#endif // OAKCODEC_C_API_REFCOUNTED_H
|
||||
@@ -0,0 +1,81 @@
|
||||
# Oak Video Editor - 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/>.
|
||||
|
||||
add_library(oakcodec SHARED
|
||||
conformmanager.cpp
|
||||
conformmanager.h
|
||||
decoder.cpp
|
||||
decoder.h
|
||||
encoder.cpp
|
||||
encoder.h
|
||||
exportcodec.cpp
|
||||
exportcodec.h
|
||||
exportformat.cpp
|
||||
exportformat.h
|
||||
footagedescription.h
|
||||
frame.cpp
|
||||
frame.h
|
||||
framemanager.cpp
|
||||
framemanager.h
|
||||
oiioframebridge.cpp
|
||||
oiioframebridge.h
|
||||
planarfiledevice.cpp
|
||||
planarfiledevice.h
|
||||
proxymanager.cpp
|
||||
proxymanager.h
|
||||
taskcallbacks.cpp
|
||||
taskcallbacks.h
|
||||
timecodemetadata.cpp
|
||||
timecodemetadata.h
|
||||
)
|
||||
add_subdirectory(ffmpeg)
|
||||
add_subdirectory(oiio)
|
||||
|
||||
# In a full-tree build the repo root is CMAKE_SOURCE_DIR; a standalone
|
||||
# build (see src/codec/standalone) sets OAK_REPO_ROOT explicitly.
|
||||
if(NOT DEFINED OAK_REPO_ROOT)
|
||||
set(OAK_REPO_ROOT ${CMAKE_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
target_include_directories(oakcodec PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${OAK_REPO_ROOT}/include
|
||||
${OAK_REPO_ROOT}/core/include
|
||||
${OAK_REPO_ROOT}/ffmpeg_bridge/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/include
|
||||
${OIIO_INCLUDE_DIRS}
|
||||
${OCIO_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# 01 §1 rule 5: only the OAKCODEC_API-marked C functions are exported;
|
||||
# codec-internal C++ classes (olive::Frame, FootageDescription, ...) must not
|
||||
# leak into the global symbol namespace where they would interpose on
|
||||
# same-named weak symbols inside oakcommon/oakrender.
|
||||
target_compile_options(oakcodec PRIVATE
|
||||
-fvisibility=hidden
|
||||
-fvisibility-inlines-hidden
|
||||
)
|
||||
|
||||
# oakcommon's C API implementation links these PUBLICly; oakcodec consumes
|
||||
# the oakcommon C ABI (and olivecore's C++ wrappers) only.
|
||||
target_link_libraries(oakcodec PUBLIC
|
||||
oakcommon
|
||||
oakrender
|
||||
olivecore
|
||||
ffmpeg_bridge
|
||||
${OCIO_LIBRARIES}
|
||||
${OIIO_LIBRARIES}
|
||||
)
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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/>.
|
||||
*/
|
||||
|
||||
#include "conformmanager.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
#include "taskcallbacks.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ConformManager *ConformManager::instance_ = nullptr;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief oakcommon C API wrapper for FileFunctions::get_unique_file_identifier
|
||||
*/
|
||||
std::string unique_file_identifier(const std::string &filename)
|
||||
{
|
||||
OakFileFunctions ff = oakcommon_filefunctions_init();
|
||||
if (!ff.ctx) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string result;
|
||||
int size = oakcommon_filefunctions_get_unique_file_identifier(
|
||||
ff, filename.c_str(), nullptr, 0);
|
||||
if (size > 0) {
|
||||
result.resize(size_t(size) - 1); // size includes the NUL
|
||||
oakcommon_filefunctions_get_unique_file_identifier(
|
||||
ff, filename.c_str(), result.data(), size);
|
||||
}
|
||||
|
||||
oakcommon_filefunctions_free(&ff);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConformManager::Conform ConformManager::get_conform_state(
|
||||
const std::string &cache_path, const Decoder::CodecStream &stream,
|
||||
const core::AudioParams ¶ms, bool wait)
|
||||
{
|
||||
// Return existing conform if exists
|
||||
std::vector<std::string> filenames =
|
||||
get_conformed_filename(cache_path, stream, params);
|
||||
if (all_conforms_exist(filenames)) {
|
||||
return { k_conform_exists, filenames };
|
||||
}
|
||||
|
||||
if (!oakcodec_task_submit_is_registered()) {
|
||||
// Interim state (pre-M8): no task system, conform cannot be generated
|
||||
return { k_conform_unavailable, std::vector<std::string>() };
|
||||
}
|
||||
|
||||
// The task owns the ".working" temporary names and the rename to the
|
||||
// final per-channel filenames on success (previously done in
|
||||
// conform_task_finished); output_filename carries the first channel's
|
||||
// final path and the task derives the siblings.
|
||||
OakCodecTaskRequest req = {};
|
||||
req.kind = OAKCODEC_TASK_CONFORM;
|
||||
req.input_filename = stream.filename().c_str();
|
||||
req.output_filename =
|
||||
filenames.empty() ? nullptr : filenames.front().c_str();
|
||||
req.stream_index = stream.stream();
|
||||
req.sample_rate = params.sample_rate();
|
||||
req.channel_layout = params.channel_layout();
|
||||
req.sample_format = int(params.format());
|
||||
|
||||
// Interim simplification: submission is synchronous - we always wait
|
||||
// for SubmitTask to return, regardless of `wait`.
|
||||
int result = SubmitTask(req);
|
||||
if (result < 0) {
|
||||
return { k_conform_unavailable, std::vector<std::string>() };
|
||||
}
|
||||
|
||||
if (all_conforms_exist(filenames)) {
|
||||
return { k_conform_exists, filenames };
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
// Synchronous wait already happened and the conform still does not
|
||||
// exist: report the wait as failed.
|
||||
return { k_conform_unavailable, std::vector<std::string>() };
|
||||
}
|
||||
|
||||
return { k_conform_generating, std::vector<std::string>() };
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
ConformManager::get_conformed_filename(const std::string &cache_path,
|
||||
const Decoder::CodecStream &stream,
|
||||
const core::AudioParams ¶ms)
|
||||
{
|
||||
std::vector<std::string> filenames(size_t(params.channel_count()));
|
||||
|
||||
const std::string base = unique_file_identifier(stream.filename()) + "-" +
|
||||
std::to_string(stream.stream()) + "." +
|
||||
std::to_string(params.sample_rate()) + "." +
|
||||
std::to_string(int(params.format())) + "." +
|
||||
std::to_string(params.channel_layout());
|
||||
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
filenames[i] = (std::filesystem::path(cache_path) /
|
||||
(base + "." + std::to_string(i) + ".pcm"))
|
||||
.string();
|
||||
}
|
||||
|
||||
return filenames;
|
||||
}
|
||||
|
||||
bool ConformManager::all_conforms_exist(const std::vector<std::string> &filenames)
|
||||
{
|
||||
std::error_code ec;
|
||||
for (const std::string &fn : filenames) {
|
||||
if (!std::filesystem::exists(fn, ec)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE 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_CONFORMMANAGER_H
|
||||
#define OAK_CONFORMMANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "decoder.h"
|
||||
#include "olive/core/render/audioparams.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Manages audio conform (pcm cache) generation
|
||||
*
|
||||
* Qt-free interim state: actual conform work is delegated to the global
|
||||
* task submit callback (include/codec/task.h). While no callback is
|
||||
* registered (pre-M8), requests report k_conform_unavailable instead of
|
||||
* starting background work.
|
||||
*
|
||||
* Behavior changes vs. the Qt version:
|
||||
* - The `conform_ready` signal is gone; completion notification is the
|
||||
* task system's / facade's business.
|
||||
* - Submission is synchronous: get_conform_state() calls the submit
|
||||
* callback inline and re-checks the filesystem afterwards. `wait`
|
||||
* only controls whether a post-submit miss is reported as
|
||||
* k_conform_unavailable (wait) or k_conform_generating (queued).
|
||||
*/
|
||||
class ConformManager {
|
||||
public:
|
||||
static void create_instance()
|
||||
{
|
||||
if (!instance_) {
|
||||
instance_ = new ConformManager();
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy_instance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
static ConformManager *instance()
|
||||
{
|
||||
return instance_;
|
||||
}
|
||||
|
||||
enum ConformState {
|
||||
k_conform_exists,
|
||||
k_conform_generating,
|
||||
k_conform_unavailable /**< No task callback registered / submit failed. */
|
||||
};
|
||||
|
||||
struct Conform {
|
||||
ConformState state;
|
||||
std::vector<std::string> filenames;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get conform state, and start conforming if no conform exists
|
||||
*
|
||||
* Stateless and thread-safe. The decoder_id parameter of the Qt
|
||||
* version was dropped: the task request addresses the source by
|
||||
* filename/stream only.
|
||||
*/
|
||||
Conform get_conform_state(const std::string &cache_path,
|
||||
const Decoder::CodecStream &stream,
|
||||
const core::AudioParams ¶ms, bool wait);
|
||||
|
||||
/**
|
||||
* @brief Get the destination filenames of an audio stream conformed to
|
||||
* a set of parameters (one per channel)
|
||||
*
|
||||
* Pure path computation: never touches the filesystem and never
|
||||
* submits work.
|
||||
*/
|
||||
static std::vector<std::string>
|
||||
get_conformed_filename(const std::string &cache_path,
|
||||
const Decoder::CodecStream &stream,
|
||||
const core::AudioParams ¶ms);
|
||||
|
||||
private:
|
||||
ConformManager() = default;
|
||||
|
||||
static ConformManager *instance_;
|
||||
|
||||
static bool all_conforms_exist(const std::vector<std::string> &filenames);
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_CONFORMMANAGER_H
|
||||
@@ -0,0 +1,458 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "decoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
#include "conformmanager.h"
|
||||
#include "ffmpeg/ffmpegdecoder.h"
|
||||
#include "oiio/oiiodecoder.h"
|
||||
#include "planarfiledevice.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief NULL/empty-handle-safe check of an oakrender cancel atom
|
||||
* (borrowed pointer, used at several retrieval entry points)
|
||||
*/
|
||||
bool cancel_atom_is_cancelled(const OakCancelAtom *cancelled)
|
||||
{
|
||||
if (!cancelled || !cancelled->ctx) {
|
||||
return false;
|
||||
}
|
||||
int c = 0;
|
||||
oakrender_cancelatom_is_cancelled(*cancelled, &c);
|
||||
return c != 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const Rational Decoder::k_any_timecode = RATIONAL_MIN;
|
||||
|
||||
Decoder::Decoder()
|
||||
: cached_texture_(nullptr)
|
||||
{
|
||||
update_last_accessed();
|
||||
}
|
||||
|
||||
Decoder::~Decoder()
|
||||
{
|
||||
oakrender_display_texture_free(cached_texture_);
|
||||
}
|
||||
|
||||
void Decoder::increment_access_time(int64_t t)
|
||||
{
|
||||
last_accessed_ += t;
|
||||
}
|
||||
|
||||
bool Decoder::open(const CodecStream &stream)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
update_last_accessed();
|
||||
|
||||
if (stream_.is_valid()) {
|
||||
// Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not.
|
||||
if (stream_ == stream) {
|
||||
return true;
|
||||
} else {
|
||||
fprintf(stderr, "Tried to open a decoder that was already open with another stream\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Stream was not open, try opening it now
|
||||
if (!stream.is_valid()) {
|
||||
// Cannot open null stream
|
||||
fprintf(stderr, "Decoder attempted to open null stream\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stream.exists()) {
|
||||
// Cannot open file that doesn't exist
|
||||
fprintf(stderr, "Decoder attempted to open file that doesn't exist\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set stream
|
||||
stream_ = stream;
|
||||
|
||||
// Try open internal
|
||||
if (open_internal()) {
|
||||
return true;
|
||||
} else {
|
||||
// Unset stream
|
||||
fprintf(stderr, "Failed to open %s stream %d\n",
|
||||
stream_.filename().c_str(), stream_.stream());
|
||||
close_internal();
|
||||
stream_.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OakRenderTexture *Decoder::retrieve_video(const RetrieveVideoParams &p)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
update_last_accessed();
|
||||
|
||||
if (!stream_.is_valid()) {
|
||||
fprintf(stderr, "Can't retrieve video on a closed decoder\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!supports_video()) {
|
||||
fprintf(stderr, "Decoder doesn't support video\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cancel_atom_is_cancelled(p.cancelled)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cached_texture_ && cached_time_ == p.time &&
|
||||
cached_divider_ == p.divider) {
|
||||
// Hand the caller its own reference; the cache keeps its own
|
||||
return oakrender_display_texture_retain(cached_texture_);
|
||||
}
|
||||
|
||||
OakRenderTexture *texture = retrieve_video_internal(p);
|
||||
oakrender_display_texture_free(cached_texture_);
|
||||
cached_texture_ = texture ? oakrender_display_texture_retain(texture) :
|
||||
nullptr;
|
||||
cached_time_ = p.time;
|
||||
cached_divider_ = p.divider;
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
FramePtr Decoder::retrieve_video_frame(const RetrieveVideoParams &p)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
update_last_accessed();
|
||||
|
||||
if (!stream_.is_valid()) {
|
||||
fprintf(stderr, "Can't retrieve video frame on a closed decoder\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!supports_video()) {
|
||||
fprintf(stderr, "Decoder doesn't support video\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cancel_atom_is_cancelled(p.cancelled)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return retrieve_video_frame_internal(p);
|
||||
}
|
||||
|
||||
Decoder::RetrieveAudioStatus
|
||||
Decoder::retrieve_audio(SampleBuffer &dest, const TimeRange &range,
|
||||
const AudioParams ¶ms,
|
||||
const std::string &cache_path, OakLoopMode loop_mode,
|
||||
RenderMode::Mode mode)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
update_last_accessed();
|
||||
|
||||
if (!stream_.is_valid()) {
|
||||
fprintf(stderr, "Can't retrieve audio on a closed decoder\n");
|
||||
return k_invalid;
|
||||
}
|
||||
|
||||
if (!supports_audio()) {
|
||||
fprintf(stderr, "Decoder doesn't support audio\n");
|
||||
return k_invalid;
|
||||
}
|
||||
|
||||
if (params.sample_rate() <= 0 || params.channel_count() <= 0) {
|
||||
fprintf(stderr, "Invalid audio parameters, skipping audio retrieve\n");
|
||||
return k_invalid;
|
||||
}
|
||||
|
||||
// Get conform state from ConformManager
|
||||
ConformManager::Conform conform =
|
||||
ConformManager::instance()->get_conform_state(
|
||||
cache_path, stream_, params, (mode == RenderMode::k_online));
|
||||
if (conform.state == ConformManager::k_conform_generating) {
|
||||
return k_waiting_for_conform;
|
||||
}
|
||||
|
||||
// See if we got the conform
|
||||
if (retrieve_audio_from_conform(dest, conform.filenames, range, loop_mode,
|
||||
params)) {
|
||||
return k_ok;
|
||||
} else {
|
||||
return k_unknown_error;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t Decoder::get_last_accessed_time()
|
||||
{
|
||||
return last_accessed_;
|
||||
}
|
||||
|
||||
void Decoder::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
update_last_accessed();
|
||||
|
||||
oakrender_display_texture_free(cached_texture_);
|
||||
cached_texture_ = nullptr;
|
||||
|
||||
if (stream_.is_valid()) {
|
||||
close_internal();
|
||||
stream_.reset();
|
||||
} else {
|
||||
fprintf(stderr, "Tried to close a decoder that wasn't open\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool Decoder::conform_audio(const std::vector<std::string> &output_filenames,
|
||||
const AudioParams ¶ms, OakCancelAtom *cancelled)
|
||||
{
|
||||
return conform_audio_internal(output_filenames, params, cancelled);
|
||||
}
|
||||
|
||||
/*
|
||||
* DECODER STATIC PUBLIC MEMBERS
|
||||
*/
|
||||
|
||||
std::vector<DecoderPtr> Decoder::receive_list_of_all_decoders()
|
||||
{
|
||||
std::vector<DecoderPtr> decoders;
|
||||
|
||||
// The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last,
|
||||
// since it supports so many formats and we presumably want to override those formats with a more specific decoder.
|
||||
decoders.push_back(std::make_shared<OIIODecoder>());
|
||||
decoders.push_back(std::make_shared<FFmpegDecoder>());
|
||||
|
||||
return decoders;
|
||||
}
|
||||
|
||||
DecoderPtr Decoder::create_from_id(const std::string &id)
|
||||
{
|
||||
if (id.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create list to iterate through
|
||||
std::vector<DecoderPtr> decoder_list = receive_list_of_all_decoders();
|
||||
|
||||
for (DecoderPtr d : decoder_list) {
|
||||
if (d->id() == id) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Decoder::signal_processing_progress(int64_t ts, int64_t duration)
|
||||
{
|
||||
if (duration != FB_NOPTS_VALUE && duration != 0) {
|
||||
if (index_progress_callback_) {
|
||||
index_progress_callback_(static_cast<double>(ts) /
|
||||
static_cast<double>(duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
Decoder::transform_image_sequence_file_name(const std::string &filename,
|
||||
const int64_t &number)
|
||||
{
|
||||
int digit_count = get_image_sequence_digit_count(filename);
|
||||
|
||||
std::filesystem::path file_path(filename);
|
||||
|
||||
// QFileInfo::completeBaseName(): filename up to the first '.'
|
||||
std::string original_basename = file_path.filename().string();
|
||||
std::string::size_type dot = original_basename.find('.');
|
||||
if (dot != std::string::npos) {
|
||||
original_basename.erase(dot);
|
||||
}
|
||||
|
||||
std::string new_basename =
|
||||
original_basename.substr(0, original_basename.size() - digit_count);
|
||||
|
||||
char number_buf[32];
|
||||
snprintf(number_buf, sizeof(number_buf), "%0*lld", digit_count,
|
||||
static_cast<long long>(number));
|
||||
new_basename += number_buf;
|
||||
|
||||
std::string new_filename = file_path.filename().string();
|
||||
std::string::size_type pos = 0;
|
||||
while ((pos = new_filename.find(original_basename, pos)) !=
|
||||
std::string::npos) {
|
||||
new_filename.replace(pos, original_basename.size(), new_basename);
|
||||
pos += new_basename.size();
|
||||
}
|
||||
|
||||
return (file_path.parent_path() / new_filename).string();
|
||||
}
|
||||
|
||||
int Decoder::get_image_sequence_digit_count(const std::string &filename)
|
||||
{
|
||||
// QFileInfo::completeBaseName(): filename up to the first '.'
|
||||
std::string basename =
|
||||
std::filesystem::path(filename).filename().string();
|
||||
std::string::size_type dot = basename.find('.');
|
||||
if (dot != std::string::npos) {
|
||||
basename.erase(dot);
|
||||
}
|
||||
|
||||
// See if basename contains a number at the end
|
||||
int digit_count = 0;
|
||||
|
||||
for (int i = int(basename.size()) - 1; i >= 0; i--) {
|
||||
if (basename[size_t(i)] >= '0' && basename[size_t(i)] <= '9') {
|
||||
digit_count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return digit_count;
|
||||
}
|
||||
|
||||
int64_t Decoder::get_image_sequence_index(const std::string &filename)
|
||||
{
|
||||
int digit_count = get_image_sequence_digit_count(filename);
|
||||
|
||||
std::string original_basename =
|
||||
std::filesystem::path(filename).filename().string();
|
||||
std::string::size_type dot = original_basename.find('.');
|
||||
if (dot != std::string::npos) {
|
||||
original_basename.erase(dot);
|
||||
}
|
||||
|
||||
std::string number_only =
|
||||
original_basename.substr(original_basename.size() - digit_count);
|
||||
|
||||
return strtoll(number_only.c_str(), nullptr, 10);
|
||||
}
|
||||
|
||||
OakRenderTexture *Decoder::retrieve_video_internal(const RetrieveVideoParams &p)
|
||||
{
|
||||
(void) p;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FramePtr Decoder::retrieve_video_frame_internal(const RetrieveVideoParams &p)
|
||||
{
|
||||
(void) p;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Decoder::conform_audio_internal(
|
||||
const std::vector<std::string> &filenames, const AudioParams ¶ms,
|
||||
OakCancelAtom *cancelled)
|
||||
{
|
||||
(void) filenames;
|
||||
(void) cancelled;
|
||||
(void) params;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Decoder::retrieve_audio_from_conform(
|
||||
SampleBuffer &sample_buffer,
|
||||
const std::vector<std::string> &conform_filenames, TimeRange range,
|
||||
OakLoopMode loop_mode, const AudioParams &input_params)
|
||||
{
|
||||
PlanarFileDevice input;
|
||||
if (input.open(conform_filenames, PlanarFileDevice::k_read_only)) {
|
||||
// Offset range by audio start offset
|
||||
range -= get_audio_start_offset();
|
||||
|
||||
int64_t read_index = input_params.time_to_bytes(range.in()) /
|
||||
input_params.channel_count();
|
||||
int64_t write_index = 0;
|
||||
|
||||
const int64_t buffer_length_in_bytes =
|
||||
sample_buffer.sample_count() *
|
||||
input_params.bytes_per_sample_per_channel();
|
||||
|
||||
while (write_index < buffer_length_in_bytes) {
|
||||
if (loop_mode == OAKCOMMON_LOOP_MODE_LOOP) {
|
||||
while (read_index >= input.size()) {
|
||||
read_index -= input.size();
|
||||
}
|
||||
|
||||
while (read_index < 0) {
|
||||
read_index += input.size();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t write_count = 0;
|
||||
|
||||
if (read_index < 0) {
|
||||
// Reading before 0, write silence here until audio data would actually start
|
||||
write_count = std::min(-read_index, buffer_length_in_bytes);
|
||||
sample_buffer.silence_bytes(write_index,
|
||||
write_index + write_count);
|
||||
} else if (read_index >= input.size()) {
|
||||
// Reading after data length, write silence until the end of the buffer
|
||||
write_count = buffer_length_in_bytes - write_index;
|
||||
sample_buffer.silence_bytes(write_index,
|
||||
write_index + write_count);
|
||||
} else {
|
||||
write_count = std::min(input.size() - read_index,
|
||||
buffer_length_in_bytes - write_index);
|
||||
input.seek(read_index);
|
||||
input.read(reinterpret_cast<char **>(
|
||||
sample_buffer.to_raw_ptrs().data()),
|
||||
write_count, write_index);
|
||||
}
|
||||
|
||||
read_index += write_count;
|
||||
write_index += write_count;
|
||||
}
|
||||
|
||||
input.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Decoder::update_last_accessed()
|
||||
{
|
||||
last_accessed_ =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_DECODER_H
|
||||
#define OAK_DECODER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/loopmode.h"
|
||||
#include "common/videoparams.h"
|
||||
#include "footagedescription.h"
|
||||
#include "frame.h"
|
||||
#include "node/block.h"
|
||||
#include "olive/core/render/audioparams.h"
|
||||
#include "olive/core/render/pixelformat.h"
|
||||
#include "olive/core/render/samplebuffer.h"
|
||||
#include "olive/core/util/rational.h"
|
||||
#include "olive/core/util/timerange.h"
|
||||
#include "render/cancelatom.h"
|
||||
#include "render/renderer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using core::AudioParams;
|
||||
using core::PixelFormat;
|
||||
using core::Rational;
|
||||
using core::SampleBuffer;
|
||||
using core::TimeRange;
|
||||
|
||||
/**
|
||||
* @brief Local replacement for render/rendermodes.h
|
||||
*
|
||||
* oakrender's C API has no render-mode counterpart. Values mirror
|
||||
* engine/render/rendermodes.h (k_offline = 0, k_online = 1).
|
||||
*/
|
||||
class RenderMode {
|
||||
public:
|
||||
enum Mode { k_offline, k_online };
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief "Don't force a color range" sentinel for
|
||||
* Decoder::RetrieveVideoParams::force_range (the actual ranges are
|
||||
* the OAKCOMMON_COLOR_RANGE_* values).
|
||||
*/
|
||||
inline constexpr int k_color_range_default = -1;
|
||||
|
||||
class Decoder;
|
||||
using DecoderPtr = std::shared_ptr<Decoder>;
|
||||
|
||||
#define DECODER_DEFAULT_DESTRUCTOR(x) \
|
||||
virtual ~x() override \
|
||||
{ \
|
||||
close_internal(); \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A decoder's is the main class for bringing external media into Olive
|
||||
*
|
||||
* Its responsibilities are to serve as
|
||||
* abstraction from codecs/decoders and provide complete frames. These frames can be video or audio data and are
|
||||
* provided as Frame objects in shared pointers to alleviate the responsibility of memory handling.
|
||||
*
|
||||
* The main function in a decoder is Retrieve() which should return complete image/audio data. A decoder should
|
||||
* alleviate all the complexities of codec compression from the rest of the application (i.e. a decoder should never
|
||||
* return a partial frame or require other parts of the system to interface directly with the codec). Often this will
|
||||
* necessitate pre-emptively caching, indexing, or even fully transcoding media before using it which can be implemented
|
||||
* through the Analyze() function.
|
||||
*
|
||||
* A decoder does NOT perform any pixel/sample format conversion. Frames should pass through the PixelService
|
||||
* to be utilized in the rest of the rendering pipeline.
|
||||
*/
|
||||
class Decoder {
|
||||
public:
|
||||
enum RetrieveState { k_ready, k_failed_to_open, k_index_unavailable };
|
||||
|
||||
Decoder();
|
||||
|
||||
virtual ~Decoder();
|
||||
|
||||
/**
|
||||
* @brief Unique decoder ID
|
||||
*/
|
||||
virtual std::string id() const = 0;
|
||||
|
||||
virtual bool supports_video()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool supports_audio()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void increment_access_time(int64_t t);
|
||||
|
||||
class CodecStream {
|
||||
public:
|
||||
CodecStream()
|
||||
: stream_(-1)
|
||||
, block_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
CodecStream(const std::string &filename, int stream,
|
||||
const OakNodeBlock *block)
|
||||
: filename_(filename)
|
||||
, stream_(stream)
|
||||
, block_(block)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return !filename_.empty() && stream_ >= 0;
|
||||
}
|
||||
|
||||
bool exists() const
|
||||
{
|
||||
std::error_code ec;
|
||||
return std::filesystem::exists(filename_, ec);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
*this = CodecStream();
|
||||
}
|
||||
|
||||
bool operator==(const CodecStream &rhs) const
|
||||
{
|
||||
return filename_ == rhs.filename_ && stream_ == rhs.stream_;
|
||||
}
|
||||
|
||||
const std::string &filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
|
||||
int stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Associated timeline block (opaque oaknode handle)
|
||||
*
|
||||
* Borrowed pointer: codec only stores/compares it, never
|
||||
* dereferences, retains, or frees it.
|
||||
*/
|
||||
const OakNodeBlock *block() const
|
||||
{
|
||||
return block_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string filename_;
|
||||
|
||||
int stream_;
|
||||
|
||||
const OakNodeBlock *block_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open stream for decoding
|
||||
*
|
||||
* This function is thread safe.
|
||||
*
|
||||
* Returns TRUE if stream could be opened successfully. Also returns TRUE if the decoder is
|
||||
* already open and the stream == the stream provided. Returns FALSE if the stream couldn't
|
||||
* be opened OR if already open and the stream is NOT the same.
|
||||
*/
|
||||
bool open(const CodecStream &stream);
|
||||
|
||||
static const Rational k_any_timecode;
|
||||
|
||||
struct RetrieveVideoParams {
|
||||
OakRenderRenderer *renderer = nullptr;
|
||||
Rational time;
|
||||
int divider = 1;
|
||||
PixelFormat maximum_format = PixelFormat::invalid;
|
||||
OakCancelAtom *cancelled = nullptr;
|
||||
int force_range = k_color_range_default;
|
||||
int src_interlacing = OAKCOMMON_VIDEO_INTERLACE_NONE;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Retrieves a video frame from footage
|
||||
*
|
||||
* This function will always return a valid frame unless a fatal error occurs (in such case,
|
||||
* nullptr will return). If the timecode is before the start of the footage, this function should
|
||||
* return the first frame. Likewise, if it is after the timecode, this function should return the
|
||||
* last frame.
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*
|
||||
* The returned texture handle is owned by the caller and must be
|
||||
* released with oakrender_display_texture_free().
|
||||
*/
|
||||
OakRenderTexture *retrieve_video(const RetrieveVideoParams &p);
|
||||
|
||||
/**
|
||||
* @brief Retrieves a decoded video frame in CPU memory.
|
||||
*
|
||||
* Used by render-process isolation to decode media in the main process and pass packed pixel
|
||||
* data to workers through shared memory.
|
||||
*/
|
||||
FramePtr retrieve_video_frame(const RetrieveVideoParams &p);
|
||||
|
||||
enum RetrieveAudioStatus {
|
||||
k_invalid = -1,
|
||||
k_ok,
|
||||
k_waiting_for_conform,
|
||||
k_unknown_error
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Retrieve audio data from footage
|
||||
*
|
||||
* This function will always return a sample buffer unless a fatal error occurs (in such case,
|
||||
* nullptr will return). The SampleBuffer should always have enough audio for the range provided.
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
RetrieveAudioStatus retrieve_audio(SampleBuffer &dest, const TimeRange &range,
|
||||
const AudioParams ¶ms,
|
||||
const std::string &cache_path,
|
||||
OakLoopMode loop_mode,
|
||||
RenderMode::Mode mode);
|
||||
|
||||
/**
|
||||
* @brief Determine the last time this decoder instance was used in any way
|
||||
*/
|
||||
int64_t get_last_accessed_time();
|
||||
|
||||
/**
|
||||
* @brief Generate a Footage object from a file
|
||||
*
|
||||
* If this decoder is able to parse this file, it will return a valid FootagePtr. Otherwise, it
|
||||
* will return nullptr.
|
||||
*
|
||||
* For sub-classes, this function should be effectively static. We can't do virtual static
|
||||
* functions in C++, but it should hold and access no state during its run.
|
||||
*
|
||||
* This function is re-entrant.
|
||||
*/
|
||||
virtual FootageDescription probe(const std::string &filename,
|
||||
OakCancelAtom *cancelled) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Closes media/deallocates memory
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Conform audio stream
|
||||
*/
|
||||
bool conform_audio(const std::vector<std::string> &output_filenames,
|
||||
const AudioParams ¶ms,
|
||||
OakCancelAtom *cancelled = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Create a Decoder instance using a Decoder ID
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A Decoder instance or nullptr if a Decoder with this ID does not exist
|
||||
*/
|
||||
static DecoderPtr create_from_id(const std::string &id);
|
||||
|
||||
static std::string
|
||||
transform_image_sequence_file_name(const std::string &filename,
|
||||
const int64_t &number);
|
||||
|
||||
static int get_image_sequence_digit_count(const std::string &filename);
|
||||
|
||||
static int64_t get_image_sequence_index(const std::string &filename);
|
||||
|
||||
static std::vector<DecoderPtr> receive_list_of_all_decoders();
|
||||
|
||||
/**
|
||||
* @brief Set a callback receiving indexing progress (0-1)
|
||||
*
|
||||
* Replaces the former index_progress Qt signal.
|
||||
*/
|
||||
void set_index_progress_callback(std::function<void(double)> callback)
|
||||
{
|
||||
index_progress_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Internal open function
|
||||
*
|
||||
* Sub-classes must override this function. Function will already be mutexed, so there is no need
|
||||
* to worry about thread safety. Also many other sanity checks will be done before this, so
|
||||
* sub-classes only need to worry about their own opening functions. It is guaranteed that the
|
||||
* decoder is not open yet and that the footage stream was from that sub-classes probe function.
|
||||
*
|
||||
* Return TRUE if everything opened successfully and the decoder is ready to work. Otherwise,
|
||||
* return FALSE. If this function returns false, Decoder will call close_internal to clean any
|
||||
* memory allocated during OpenInternal.
|
||||
*/
|
||||
virtual bool open_internal() = 0;
|
||||
|
||||
/**
|
||||
* @brief Internal close function
|
||||
*
|
||||
* Sub-classes must override this function. Function should be able to safely clear all allocated
|
||||
* memory. It may be called even if Open() didn't complete or RetrieveVideo() was never called.
|
||||
*/
|
||||
virtual void close_internal() = 0;
|
||||
|
||||
/**
|
||||
* @brief Internal frame retrieval function
|
||||
*
|
||||
* Sub-classes must override this function IF they support video. Function is already mutexed
|
||||
* so sub-classes don't need to worry about thread safety.
|
||||
*
|
||||
* The returned texture handle is owned by the caller and must be
|
||||
* released with oakrender_display_texture_free().
|
||||
*/
|
||||
virtual OakRenderTexture *
|
||||
retrieve_video_internal(const RetrieveVideoParams &p);
|
||||
|
||||
virtual FramePtr retrieve_video_frame_internal(const RetrieveVideoParams &p);
|
||||
|
||||
virtual bool
|
||||
conform_audio_internal(const std::vector<std::string> &filenames,
|
||||
const AudioParams ¶ms, OakCancelAtom *cancelled);
|
||||
|
||||
void signal_processing_progress(int64_t ts, int64_t duration);
|
||||
|
||||
/**
|
||||
* @brief Return currently open stream
|
||||
*
|
||||
* This function is NOT thread safe and should therefore only be called by thread safe functions.
|
||||
*/
|
||||
const CodecStream &stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
virtual Rational get_audio_start_offset() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void update_last_accessed();
|
||||
|
||||
bool retrieve_audio_from_conform(
|
||||
SampleBuffer &sample_buffer,
|
||||
const std::vector<std::string> &conform_filenames, TimeRange range,
|
||||
OakLoopMode loop_mode, const AudioParams ¶ms);
|
||||
|
||||
CodecStream stream_;
|
||||
|
||||
std::mutex mutex_;
|
||||
|
||||
std::atomic_int64_t last_accessed_;
|
||||
|
||||
OakRenderTexture *cached_texture_;
|
||||
Rational cached_time_;
|
||||
int cached_divider_ = 0;
|
||||
|
||||
std::function<void(double)> index_progress_callback_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DECODER_H
|
||||
@@ -0,0 +1,778 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "olive/core/util/timecodefunctions.h"
|
||||
|
||||
#include "ffmpeg/ffmpegencoder.h"
|
||||
#include "oiio/oiioencoder.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const std::regex Encoder::k_image_sequence_contains_digits("\\[[#]+\\]");
|
||||
const std::regex Encoder::k_image_sequence_remove_digits(
|
||||
"[\\-\\.\\ \\_]?\\[[#]+\\]");
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int str_to_int(const std::string &s)
|
||||
{
|
||||
return int(std::strtol(s.c_str(), nullptr, 10));
|
||||
}
|
||||
|
||||
int64_t str_to_int64(const std::string &s)
|
||||
{
|
||||
return std::strtoll(s.c_str(), nullptr, 10);
|
||||
}
|
||||
|
||||
Rational video_params_pixel_aspect_ratio(OakVideoParams vp)
|
||||
{
|
||||
int n = 0, d = 1;
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(vp, &n, &d);
|
||||
return Rational(n, d);
|
||||
}
|
||||
|
||||
Rational video_params_frame_rate_as_time_base(OakVideoParams vp)
|
||||
{
|
||||
int n = 0, d = 1;
|
||||
oakcommon_videoparams_frame_rate_as_time_base(vp, &n, &d);
|
||||
return Rational(n, d);
|
||||
}
|
||||
|
||||
std::string filefunctions_get_configuration_location()
|
||||
{
|
||||
OakFileFunctions ff = oakcommon_filefunctions_init();
|
||||
std::string result;
|
||||
if (ff.ctx) {
|
||||
int size =
|
||||
oakcommon_filefunctions_get_configuration_location(ff, nullptr, 0);
|
||||
if (size > 0) {
|
||||
result.assign(size_t(size) - 1, '\0');
|
||||
oakcommon_filefunctions_get_configuration_location(ff, result.data(),
|
||||
size);
|
||||
}
|
||||
}
|
||||
oakcommon_filefunctions_free(&ff);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Encoder::Encoder(const EncodingParams ¶ms) : params_(params) {}
|
||||
|
||||
const EncodingParams &Encoder::params() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
std::string Encoder::get_filename_for_frame(const Rational &frame)
|
||||
{
|
||||
if (params().video_is_image_sequence()) {
|
||||
// Transform!
|
||||
int64_t frame_index = core::Timecode::time_to_timestamp(
|
||||
frame, video_params_frame_rate_as_time_base(
|
||||
params().video_params()));
|
||||
int digits =
|
||||
get_image_sequence_placeholder_digit_count(params().filename());
|
||||
|
||||
char frame_index_str[32];
|
||||
snprintf(frame_index_str, sizeof(frame_index_str), "%0*lld", digits,
|
||||
static_cast<long long>(frame_index));
|
||||
|
||||
return std::regex_replace(params_.filename(),
|
||||
k_image_sequence_contains_digits,
|
||||
frame_index_str);
|
||||
} else {
|
||||
// Keep filename
|
||||
return params_.filename();
|
||||
}
|
||||
}
|
||||
|
||||
int Encoder::get_image_sequence_placeholder_digit_count(
|
||||
const std::string &filename)
|
||||
{
|
||||
std::smatch match;
|
||||
int digit_count = 0;
|
||||
if (std::regex_search(filename, match, k_image_sequence_contains_digits)) {
|
||||
size_t start = size_t(match.position(0));
|
||||
for (size_t i = start + 1; i < filename.size(); i++) {
|
||||
if (filename.at(i) == '#') {
|
||||
digit_count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return digit_count;
|
||||
}
|
||||
|
||||
bool Encoder::filename_contains_digit_placeholder(const std::string &filename)
|
||||
{
|
||||
return std::regex_search(filename, k_image_sequence_contains_digits);
|
||||
}
|
||||
|
||||
std::string Encoder::filename_remove_digit_placeholder(std::string filename)
|
||||
{
|
||||
return std::regex_replace(filename, k_image_sequence_remove_digits, "");
|
||||
}
|
||||
|
||||
EncodingParams::EncodingParams()
|
||||
: video_enabled_(false)
|
||||
, video_params_(oakcommon_videoparams_init())
|
||||
, video_bit_rate_(0)
|
||||
, video_min_bit_rate_(0)
|
||||
, video_max_bit_rate_(0)
|
||||
, video_buffer_size_(0)
|
||||
, video_threads_(0)
|
||||
, video_is_image_sequence_(false)
|
||||
, color_transform_(oakcommon_colortransform_init_output(""))
|
||||
, audio_enabled_(false)
|
||||
, audio_bit_rate_(0)
|
||||
, subtitles_enabled_(false)
|
||||
, subtitles_are_sidecar_(false)
|
||||
, video_scaling_method_(k_stretch)
|
||||
, has_custom_range_(false)
|
||||
{
|
||||
}
|
||||
|
||||
EncodingParams::EncodingParams(const EncodingParams &other)
|
||||
: filename_(other.filename_)
|
||||
, format_(other.format_)
|
||||
, video_enabled_(other.video_enabled_)
|
||||
, video_codec_(other.video_codec_)
|
||||
, video_params_(other.video_params_)
|
||||
, video_opts_(other.video_opts_)
|
||||
, video_bit_rate_(other.video_bit_rate_)
|
||||
, video_min_bit_rate_(other.video_min_bit_rate_)
|
||||
, video_max_bit_rate_(other.video_max_bit_rate_)
|
||||
, video_buffer_size_(other.video_buffer_size_)
|
||||
, video_threads_(other.video_threads_)
|
||||
, video_pix_fmt_(other.video_pix_fmt_)
|
||||
, video_is_image_sequence_(other.video_is_image_sequence_)
|
||||
, color_transform_(other.color_transform_)
|
||||
, audio_enabled_(other.audio_enabled_)
|
||||
, audio_codec_(other.audio_codec_)
|
||||
, audio_params_(other.audio_params_)
|
||||
, audio_bit_rate_(other.audio_bit_rate_)
|
||||
, subtitles_enabled_(other.subtitles_enabled_)
|
||||
, subtitles_are_sidecar_(other.subtitles_are_sidecar_)
|
||||
, subtitle_sidecar_fmt_(other.subtitle_sidecar_fmt_)
|
||||
, subtitles_codec_(other.subtitles_codec_)
|
||||
, export_length_(other.export_length_)
|
||||
, video_scaling_method_(other.video_scaling_method_)
|
||||
, has_custom_range_(other.has_custom_range_)
|
||||
, custom_range_(other.custom_range_)
|
||||
{
|
||||
if (video_params_.ctx && video_params_.addref) {
|
||||
video_params_.addref(video_params_.ctx);
|
||||
}
|
||||
if (color_transform_.ctx && color_transform_.addref) {
|
||||
color_transform_.addref(color_transform_.ctx);
|
||||
}
|
||||
}
|
||||
|
||||
EncodingParams &EncodingParams::operator=(const EncodingParams &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
// addref the incoming handles before releasing ours so that
|
||||
// self-shared handles survive the release below
|
||||
if (other.video_params_.ctx && other.video_params_.addref) {
|
||||
other.video_params_.addref(other.video_params_.ctx);
|
||||
}
|
||||
if (other.color_transform_.ctx && other.color_transform_.addref) {
|
||||
other.color_transform_.addref(other.color_transform_.ctx);
|
||||
}
|
||||
oakcommon_videoparams_free(&video_params_);
|
||||
oakcommon_colortransform_free(&color_transform_);
|
||||
|
||||
filename_ = other.filename_;
|
||||
format_ = other.format_;
|
||||
video_enabled_ = other.video_enabled_;
|
||||
video_codec_ = other.video_codec_;
|
||||
video_params_ = other.video_params_;
|
||||
video_opts_ = other.video_opts_;
|
||||
video_bit_rate_ = other.video_bit_rate_;
|
||||
video_min_bit_rate_ = other.video_min_bit_rate_;
|
||||
video_max_bit_rate_ = other.video_max_bit_rate_;
|
||||
video_buffer_size_ = other.video_buffer_size_;
|
||||
video_threads_ = other.video_threads_;
|
||||
video_pix_fmt_ = other.video_pix_fmt_;
|
||||
video_is_image_sequence_ = other.video_is_image_sequence_;
|
||||
color_transform_ = other.color_transform_;
|
||||
audio_enabled_ = other.audio_enabled_;
|
||||
audio_codec_ = other.audio_codec_;
|
||||
audio_params_ = other.audio_params_;
|
||||
audio_bit_rate_ = other.audio_bit_rate_;
|
||||
subtitles_enabled_ = other.subtitles_enabled_;
|
||||
subtitles_are_sidecar_ = other.subtitles_are_sidecar_;
|
||||
subtitle_sidecar_fmt_ = other.subtitle_sidecar_fmt_;
|
||||
subtitles_codec_ = other.subtitles_codec_;
|
||||
export_length_ = other.export_length_;
|
||||
video_scaling_method_ = other.video_scaling_method_;
|
||||
has_custom_range_ = other.has_custom_range_;
|
||||
custom_range_ = other.custom_range_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
EncodingParams::~EncodingParams()
|
||||
{
|
||||
oakcommon_videoparams_free(&video_params_);
|
||||
oakcommon_colortransform_free(&color_transform_);
|
||||
}
|
||||
|
||||
std::string EncodingParams::get_preset_path()
|
||||
{
|
||||
return (std::filesystem::path(filefunctions_get_configuration_location()) /
|
||||
"exportpresets")
|
||||
.string();
|
||||
}
|
||||
|
||||
std::vector<std::string> EncodingParams::get_list_of_presets()
|
||||
{
|
||||
std::vector<std::string> list;
|
||||
std::error_code ec;
|
||||
for (const auto &entry : std::filesystem::directory_iterator(
|
||||
get_preset_path(), ec)) {
|
||||
if (entry.is_regular_file()) {
|
||||
list.push_back(entry.path().filename().string());
|
||||
}
|
||||
}
|
||||
// QDir::entryList(QDir::Files) sorted by name by default
|
||||
std::sort(list.begin(), list.end());
|
||||
return list;
|
||||
}
|
||||
|
||||
void EncodingParams::enable_video(const OakVideoParams &video_params,
|
||||
const ExportCodec::Codec &vcodec)
|
||||
{
|
||||
if (video_params.ctx && video_params.addref) {
|
||||
video_params.addref(video_params.ctx);
|
||||
}
|
||||
oakcommon_videoparams_free(&video_params_);
|
||||
video_params_ = video_params;
|
||||
|
||||
video_enabled_ = true;
|
||||
video_codec_ = vcodec;
|
||||
}
|
||||
|
||||
void EncodingParams::set_color_transform(
|
||||
const OakColorTransform &color_transform)
|
||||
{
|
||||
if (color_transform.ctx && color_transform.addref) {
|
||||
color_transform.addref(color_transform.ctx);
|
||||
}
|
||||
oakcommon_colortransform_free(&color_transform_);
|
||||
color_transform_ = color_transform;
|
||||
}
|
||||
|
||||
void EncodingParams::enable_audio(const AudioParams &audio_params,
|
||||
const ExportCodec::Codec &acodec)
|
||||
{
|
||||
audio_enabled_ = true;
|
||||
audio_params_ = audio_params;
|
||||
audio_codec_ = acodec;
|
||||
}
|
||||
|
||||
void EncodingParams::enable_subtitles(const ExportCodec::Codec &scodec)
|
||||
{
|
||||
subtitles_enabled_ = true;
|
||||
subtitles_codec_ = scodec;
|
||||
}
|
||||
|
||||
void EncodingParams::enable_sidecar_subtitles(const ExportFormat::Format &sfmt,
|
||||
const ExportCodec::Codec &scodec)
|
||||
{
|
||||
subtitles_enabled_ = true;
|
||||
subtitles_are_sidecar_ = true;
|
||||
subtitle_sidecar_fmt_ = sfmt;
|
||||
subtitles_codec_ = scodec;
|
||||
}
|
||||
|
||||
void EncodingParams::disable_video()
|
||||
{
|
||||
video_enabled_ = false;
|
||||
}
|
||||
|
||||
void EncodingParams::disable_audio()
|
||||
{
|
||||
audio_enabled_ = false;
|
||||
}
|
||||
|
||||
void EncodingParams::disable_subtitles()
|
||||
{
|
||||
subtitles_enabled_ = false;
|
||||
}
|
||||
|
||||
bool EncodingParams::load(XmlStreamReader *reader)
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "export") {
|
||||
int version = 0;
|
||||
|
||||
for (const auto &attr : reader->attributes()) {
|
||||
if (attr.name == "version") {
|
||||
version = str_to_int(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
return load_v1(reader);
|
||||
}
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EncodingParams::load(const std::string &xml)
|
||||
{
|
||||
XmlStreamReader reader(xml);
|
||||
return load(&reader);
|
||||
}
|
||||
|
||||
std::string EncodingParams::save_to_string() const
|
||||
{
|
||||
XmlStreamWriter writer;
|
||||
save(&writer);
|
||||
return writer.output();
|
||||
}
|
||||
|
||||
void EncodingParams::save(XmlStreamWriter *writer) const
|
||||
{
|
||||
writer->write_start_element("export");
|
||||
|
||||
writer->write_attribute("version", std::to_string(k_encoder_params_version));
|
||||
|
||||
writer->write_text_element("filename", filename_);
|
||||
writer->write_text_element("format", std::to_string(format_));
|
||||
|
||||
writer->write_text_element("range", std::to_string(has_custom_range_));
|
||||
writer->write_text_element("customrangein", custom_range_.in().to_string());
|
||||
writer->write_text_element("customrangeout",
|
||||
custom_range_.out().to_string());
|
||||
|
||||
writer->write_start_element("video");
|
||||
|
||||
writer->write_attribute("enabled", std::to_string(video_enabled_));
|
||||
|
||||
if (video_enabled_) {
|
||||
int vp_width = 0, vp_height = 0, vp_format = -1, vp_divider = 1;
|
||||
oakcommon_videoparams_get_width(video_params_, &vp_width);
|
||||
oakcommon_videoparams_get_height(video_params_, &vp_height);
|
||||
oakcommon_videoparams_get_format(video_params_, &vp_format);
|
||||
oakcommon_videoparams_get_divider(video_params_, &vp_divider);
|
||||
int vp_time_base_num = 0, vp_time_base_den = 1;
|
||||
oakcommon_videoparams_get_time_base(video_params_, &vp_time_base_num,
|
||||
&vp_time_base_den);
|
||||
|
||||
writer->write_text_element("codec", std::to_string(video_codec_));
|
||||
writer->write_text_element("width", std::to_string(vp_width));
|
||||
writer->write_text_element("height", std::to_string(vp_height));
|
||||
writer->write_text_element("format", std::to_string(vp_format));
|
||||
writer->write_text_element(
|
||||
"pixelaspect",
|
||||
video_params_pixel_aspect_ratio(video_params_).to_string());
|
||||
writer->write_text_element(
|
||||
"timebase",
|
||||
Rational(vp_time_base_num, vp_time_base_den).to_string());
|
||||
writer->write_text_element("divider", std::to_string(vp_divider));
|
||||
writer->write_text_element("bitrate", std::to_string(video_bit_rate_));
|
||||
writer->write_text_element("minbitrate",
|
||||
std::to_string(video_min_bit_rate_));
|
||||
writer->write_text_element("maxbitrate",
|
||||
std::to_string(video_max_bit_rate_));
|
||||
writer->write_text_element("bufsize",
|
||||
std::to_string(video_buffer_size_));
|
||||
writer->write_text_element("threads", std::to_string(video_threads_));
|
||||
writer->write_text_element("pixfmt", video_pix_fmt_);
|
||||
writer->write_text_element("imgseq",
|
||||
std::to_string(video_is_image_sequence_));
|
||||
|
||||
std::string color_output;
|
||||
int color_output_size = oakcommon_colortransform_get_output(
|
||||
color_transform_, nullptr, 0);
|
||||
if (color_output_size > 0) {
|
||||
color_output.assign(size_t(color_output_size) - 1, '\0');
|
||||
oakcommon_colortransform_get_output(
|
||||
color_transform_, color_output.data(), color_output_size);
|
||||
}
|
||||
|
||||
writer->write_start_element("color");
|
||||
writer->write_text_element("output", color_output);
|
||||
writer->write_end_element(); // colortransform
|
||||
|
||||
writer->write_text_element("vscale",
|
||||
std::to_string(video_scaling_method_));
|
||||
|
||||
if (!video_opts_.empty()) {
|
||||
writer->write_start_element("opts");
|
||||
|
||||
for (const auto &entry : video_opts_) {
|
||||
writer->write_start_element("entry");
|
||||
|
||||
writer->write_text_element("key", entry.first);
|
||||
writer->write_text_element("value", entry.second);
|
||||
|
||||
writer->write_end_element(); // entry
|
||||
}
|
||||
|
||||
writer->write_end_element(); // opts
|
||||
}
|
||||
}
|
||||
|
||||
writer->write_end_element(); // video
|
||||
|
||||
writer->write_start_element("audio");
|
||||
|
||||
writer->write_attribute("enabled", std::to_string(audio_enabled_));
|
||||
|
||||
if (audio_enabled_) {
|
||||
writer->write_text_element("codec", std::to_string(audio_codec_));
|
||||
writer->write_text_element(
|
||||
"samplerate", std::to_string(audio_params_.sample_rate()));
|
||||
|
||||
writer->write_text_element(
|
||||
"channellayout", std::to_string(audio_params().channel_layout()));
|
||||
writer->write_text_element("format",
|
||||
audio_params_.format().to_string());
|
||||
writer->write_text_element("bitrate", std::to_string(audio_bit_rate_));
|
||||
}
|
||||
|
||||
writer->write_start_element("subtitles");
|
||||
|
||||
writer->write_attribute("enabled", std::to_string(subtitles_enabled_));
|
||||
|
||||
if (subtitles_enabled_) {
|
||||
writer->write_text_element("sidecar",
|
||||
std::to_string(subtitles_are_sidecar_));
|
||||
writer->write_text_element("sidecarformat",
|
||||
std::to_string(subtitle_sidecar_fmt_));
|
||||
|
||||
writer->write_text_element("codec", std::to_string(subtitles_codec_));
|
||||
}
|
||||
|
||||
writer->write_end_element(); // subtitles
|
||||
|
||||
writer->write_end_element(); // audio
|
||||
|
||||
writer->write_end_element(); // export
|
||||
|
||||
writer->write_end_document();
|
||||
}
|
||||
|
||||
Encoder *Encoder::create_from_id(Type id, const EncodingParams ¶ms)
|
||||
{
|
||||
switch (id) {
|
||||
case k_encoder_type_none:
|
||||
break;
|
||||
case k_encoder_type_f_fmpeg:
|
||||
return new FFmpegEncoder(params);
|
||||
case k_encoder_type_oiio:
|
||||
return new OIIOEncoder(params);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Encoder::Type Encoder::get_type_from_format(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case ExportFormat::k_format_d_nx_hd:
|
||||
case ExportFormat::k_format_matroska:
|
||||
case ExportFormat::k_format_quick_time:
|
||||
case ExportFormat::k_format_mpe_g4_video:
|
||||
case ExportFormat::k_format_mpe_g4_audio:
|
||||
case ExportFormat::k_format_wav:
|
||||
case ExportFormat::k_format_aiff:
|
||||
case ExportFormat::k_format_m_p3:
|
||||
case ExportFormat::k_format_flac:
|
||||
case ExportFormat::k_format_ogg:
|
||||
case ExportFormat::k_format_web_m:
|
||||
case ExportFormat::k_format_srt:
|
||||
return k_encoder_type_f_fmpeg;
|
||||
case ExportFormat::k_format_open_exr:
|
||||
case ExportFormat::k_format_png:
|
||||
case ExportFormat::k_format_tiff:
|
||||
return k_encoder_type_oiio;
|
||||
case ExportFormat::k_format_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return k_encoder_type_none;
|
||||
}
|
||||
|
||||
Encoder *Encoder::create_from_format(ExportFormat::Format f,
|
||||
const EncodingParams ¶ms)
|
||||
{
|
||||
return create_from_id(get_type_from_format(f), params);
|
||||
}
|
||||
|
||||
Encoder *Encoder::create_from_params(const EncodingParams ¶ms)
|
||||
{
|
||||
return create_from_format(params.format(), params);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
Encoder::get_pixel_formats_for_codec(ExportCodec::Codec c) const
|
||||
{
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
|
||||
std::vector<SampleFormat>
|
||||
Encoder::get_sample_formats_for_codec(ExportCodec::Codec c) const
|
||||
{
|
||||
return std::vector<SampleFormat>();
|
||||
}
|
||||
|
||||
std::array<float, 16>
|
||||
EncodingParams::generate_matrix(EncodingParams::VideoScalingMethod method,
|
||||
int source_width, int source_height,
|
||||
int dest_width, int dest_height)
|
||||
{
|
||||
// Identity (former default-constructed QMatrix4x4), row-major
|
||||
std::array<float, 16> preview_matrix = { 1, 0, 0, 0, //
|
||||
0, 1, 0, 0, //
|
||||
0, 0, 1, 0, //
|
||||
0, 0, 0, 1 };
|
||||
|
||||
if (method == EncodingParams::k_stretch) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
float export_ar =
|
||||
static_cast<float>(dest_width) / static_cast<float>(dest_height);
|
||||
float source_ar =
|
||||
static_cast<float>(source_width) / static_cast<float>(source_height);
|
||||
|
||||
// qFuzzyCompare(export_ar, source_ar)
|
||||
if (std::abs(export_ar - source_ar) * 100000.0f <=
|
||||
std::min(std::abs(export_ar), std::abs(source_ar))) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
if ((export_ar > source_ar) == (method == EncodingParams::k_fit)) {
|
||||
// scale(source_ar / export_ar, 1)
|
||||
preview_matrix[0] = source_ar / export_ar;
|
||||
} else {
|
||||
// scale(1, export_ar / source_ar)
|
||||
preview_matrix[5] = export_ar / source_ar;
|
||||
}
|
||||
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
bool EncodingParams::load_v1(XmlStreamReader *reader)
|
||||
{
|
||||
Rational custom_range_in, custom_range_out;
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "filename") {
|
||||
filename_ = reader->read_element_text();
|
||||
} else if (reader->name() == "format") {
|
||||
format_ = static_cast<ExportFormat::Format>(
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "range") {
|
||||
has_custom_range_ = str_to_int(reader->read_element_text());
|
||||
} else if (reader->name() == "customrangein") {
|
||||
custom_range_in =
|
||||
Rational::from_string(reader->read_element_text());
|
||||
} else if (reader->name() == "customrangeout") {
|
||||
custom_range_out =
|
||||
Rational::from_string(reader->read_element_text());
|
||||
} else if (reader->name() == "video") {
|
||||
for (const auto &attr : reader->attributes()) {
|
||||
if (attr.name == "enabled") {
|
||||
video_enabled_ = str_to_int(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "codec") {
|
||||
video_codec_ = static_cast<ExportCodec::Codec>(
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "width") {
|
||||
oakcommon_videoparams_set_width(
|
||||
video_params_,
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "height") {
|
||||
oakcommon_videoparams_set_height(
|
||||
video_params_,
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "format") {
|
||||
oakcommon_videoparams_set_format(
|
||||
video_params_,
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "pixelaspect") {
|
||||
Rational par =
|
||||
Rational::from_string(reader->read_element_text());
|
||||
oakcommon_videoparams_set_pixel_aspect_ratio(
|
||||
video_params_, par.numerator(), par.denominator());
|
||||
} else if (reader->name() == "timebase") {
|
||||
Rational tb =
|
||||
Rational::from_string(reader->read_element_text());
|
||||
oakcommon_videoparams_set_time_base(
|
||||
video_params_, tb.numerator(), tb.denominator());
|
||||
} else if (reader->name() == "divider") {
|
||||
oakcommon_videoparams_set_divider(
|
||||
video_params_,
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "bitrate") {
|
||||
video_bit_rate_ = str_to_int64(reader->read_element_text());
|
||||
} else if (reader->name() == "minbitrate") {
|
||||
video_min_bit_rate_ =
|
||||
str_to_int64(reader->read_element_text());
|
||||
} else if (reader->name() == "maxbitrate") {
|
||||
video_max_bit_rate_ =
|
||||
str_to_int64(reader->read_element_text());
|
||||
} else if (reader->name() == "bufsize") {
|
||||
video_buffer_size_ =
|
||||
str_to_int64(reader->read_element_text());
|
||||
} else if (reader->name() == "threads") {
|
||||
video_threads_ = str_to_int(reader->read_element_text());
|
||||
} else if (reader->name() == "pixfmt") {
|
||||
video_pix_fmt_ = reader->read_element_text();
|
||||
} else if (reader->name() == "imgseq") {
|
||||
video_is_image_sequence_ =
|
||||
str_to_int(reader->read_element_text());
|
||||
} else if (reader->name() == "color") {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "output") {
|
||||
OakColorTransform ct =
|
||||
oakcommon_colortransform_init_output(
|
||||
reader->read_element_text().c_str());
|
||||
oakcommon_colortransform_free(&color_transform_);
|
||||
color_transform_ = ct;
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == "vscale") {
|
||||
video_scaling_method_ = static_cast<VideoScalingMethod>(
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "opts") {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "entry") {
|
||||
std::string key, value;
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "key") {
|
||||
key = reader->read_element_text();
|
||||
} else if (reader->name() == "value") {
|
||||
value = reader->read_element_text();
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
set_video_option(key, value);
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: Resolve bug where I forgot to serialize pixel aspect ratio
|
||||
if (video_params_pixel_aspect_ratio(video_params_).isNull()) {
|
||||
oakcommon_videoparams_set_pixel_aspect_ratio(video_params_, 1,
|
||||
1);
|
||||
}
|
||||
} else if (reader->name() == "audio") {
|
||||
for (const auto &attr : reader->attributes()) {
|
||||
if (attr.name == "enabled") {
|
||||
audio_enabled_ = str_to_int(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "codec") {
|
||||
audio_codec_ = static_cast<ExportCodec::Codec>(
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "samplerate") {
|
||||
audio_params_.set_sample_rate(
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "channellayout") {
|
||||
audio_params_.set_channel_layout(
|
||||
uint64_t(str_to_int64(reader->read_element_text())));
|
||||
} else if (reader->name() == "format") {
|
||||
audio_params_.set_format(
|
||||
SampleFormat::from_string(reader->read_element_text()));
|
||||
} else if (reader->name() == "bitrate") {
|
||||
audio_bit_rate_ = str_to_int64(reader->read_element_text());
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: Resolve bug where I forgot to serialize the audio bit rate
|
||||
if (!audio_bit_rate_) {
|
||||
audio_bit_rate_ = 320000;
|
||||
}
|
||||
} else if (reader->name() == "subtitles") {
|
||||
for (const auto &attr : reader->attributes()) {
|
||||
if (attr.name == "enabled") {
|
||||
subtitles_enabled_ = str_to_int(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "sidecar") {
|
||||
subtitles_are_sidecar_ =
|
||||
str_to_int(reader->read_element_text());
|
||||
} else if (reader->name() == "sidecarformat") {
|
||||
subtitle_sidecar_fmt_ = static_cast<ExportFormat::Format>(
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else if (reader->name() == "codec") {
|
||||
subtitles_codec_ = static_cast<ExportCodec::Codec>(
|
||||
str_to_int(reader->read_element_text()));
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: custom_range_in/custom_range_out are intentionally not applied to
|
||||
// custom_range_ — this matches the original behavior (they were read but
|
||||
// never assigned).
|
||||
(void) custom_range_in;
|
||||
(void) custom_range_out;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_ENCODER_H
|
||||
#define OAK_ENCODER_H
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "olive/core/render/audioparams.h"
|
||||
#include "olive/core/render/pixelformat.h"
|
||||
#include "olive/core/render/samplebuffer.h"
|
||||
#include "olive/core/render/sampleformat.h"
|
||||
#include "olive/core/util/rational.h"
|
||||
#include "olive/core/util/timerange.h"
|
||||
|
||||
#include "common/colortransform.h"
|
||||
#include "common/videoparams.h"
|
||||
#include "exportcodec.h"
|
||||
#include "exportformat.h"
|
||||
#include "frame.h"
|
||||
#include "xmlutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using core::AudioParams;
|
||||
using core::PixelFormat;
|
||||
using core::Rational;
|
||||
using core::SampleBuffer;
|
||||
using core::SampleFormat;
|
||||
using core::TimeRange;
|
||||
|
||||
class Encoder;
|
||||
using EncoderPtr = std::shared_ptr<Encoder>;
|
||||
|
||||
/**
|
||||
* @brief Parameters for an export encode
|
||||
*
|
||||
* Holds OakVideoParams / OakColorTransform C handles directly (no adapter
|
||||
* layer). Copy constructor/assignment addref the handles, destructor
|
||||
* releases them, so EncodingParams remains safely copyable by value
|
||||
* (Encoder::params_ stores a copy).
|
||||
*/
|
||||
class EncodingParams {
|
||||
public:
|
||||
enum VideoScalingMethod { k_fit, k_stretch, k_crop };
|
||||
|
||||
EncodingParams();
|
||||
EncodingParams(const EncodingParams &other);
|
||||
EncodingParams &operator=(const EncodingParams &other);
|
||||
~EncodingParams();
|
||||
|
||||
static std::string get_preset_path();
|
||||
static std::vector<std::string> get_list_of_presets();
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return video_enabled_ || audio_enabled_ || subtitles_enabled_;
|
||||
}
|
||||
|
||||
void set_filename(const std::string &filename)
|
||||
{
|
||||
filename_ = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable video with the given parameter set
|
||||
*
|
||||
* addrefs @p video_params; the caller keeps ownership of its own
|
||||
* reference.
|
||||
*/
|
||||
void enable_video(const OakVideoParams &video_params,
|
||||
const ExportCodec::Codec &vcodec);
|
||||
void enable_audio(const AudioParams &audio_params,
|
||||
const ExportCodec::Codec &acodec);
|
||||
void enable_subtitles(const ExportCodec::Codec &scodec);
|
||||
void enable_sidecar_subtitles(const ExportFormat::Format &sfmt,
|
||||
const ExportCodec::Codec &scodec);
|
||||
|
||||
void disable_video();
|
||||
void disable_audio();
|
||||
void disable_subtitles();
|
||||
|
||||
const ExportFormat::Format &format() const
|
||||
{
|
||||
return format_;
|
||||
}
|
||||
void set_format(const ExportFormat::Format &format)
|
||||
{
|
||||
format_ = format;
|
||||
}
|
||||
|
||||
void set_video_option(const std::string &key, const std::string &value)
|
||||
{
|
||||
video_opts_[key] = value;
|
||||
}
|
||||
void set_video_bit_rate(const int64_t &rate)
|
||||
{
|
||||
video_bit_rate_ = rate;
|
||||
}
|
||||
void set_video_min_bit_rate(const int64_t &rate)
|
||||
{
|
||||
video_min_bit_rate_ = rate;
|
||||
}
|
||||
void set_video_max_bit_rate(const int64_t &rate)
|
||||
{
|
||||
video_max_bit_rate_ = rate;
|
||||
}
|
||||
void set_video_buffer_size(const int64_t &sz)
|
||||
{
|
||||
video_buffer_size_ = sz;
|
||||
}
|
||||
void set_video_threads(const int &threads)
|
||||
{
|
||||
video_threads_ = threads;
|
||||
}
|
||||
void set_video_pix_fmt(const std::string &s)
|
||||
{
|
||||
video_pix_fmt_ = s;
|
||||
}
|
||||
void set_video_is_image_sequence(bool s)
|
||||
{
|
||||
video_is_image_sequence_ = s;
|
||||
}
|
||||
/**
|
||||
* @brief Set the export color transform
|
||||
*
|
||||
* addrefs @p color_transform and releases the previously held handle.
|
||||
*/
|
||||
void set_color_transform(const OakColorTransform &color_transform);
|
||||
|
||||
const std::string &filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
|
||||
bool video_enabled() const
|
||||
{
|
||||
return video_enabled_;
|
||||
}
|
||||
const ExportCodec::Codec &video_codec() const
|
||||
{
|
||||
return video_codec_;
|
||||
}
|
||||
/**
|
||||
* @brief Borrowed video parameter handle (valid while this object lives)
|
||||
*/
|
||||
const OakVideoParams &video_params() const
|
||||
{
|
||||
return video_params_;
|
||||
}
|
||||
const std::map<std::string, std::string> &video_opts() const
|
||||
{
|
||||
return video_opts_;
|
||||
}
|
||||
std::string video_option(const std::string &key) const
|
||||
{
|
||||
auto it = video_opts_.find(key);
|
||||
return it != video_opts_.end() ? it->second : std::string();
|
||||
}
|
||||
bool has_video_opt(const std::string &key) const
|
||||
{
|
||||
return video_opts_.count(key) > 0;
|
||||
}
|
||||
const int64_t &video_bit_rate() const
|
||||
{
|
||||
return video_bit_rate_;
|
||||
}
|
||||
const int64_t &video_min_bit_rate() const
|
||||
{
|
||||
return video_min_bit_rate_;
|
||||
}
|
||||
const int64_t &video_max_bit_rate() const
|
||||
{
|
||||
return video_max_bit_rate_;
|
||||
}
|
||||
const int64_t &video_buffer_size() const
|
||||
{
|
||||
return video_buffer_size_;
|
||||
}
|
||||
const int &video_threads() const
|
||||
{
|
||||
return video_threads_;
|
||||
}
|
||||
const std::string &video_pix_fmt() const
|
||||
{
|
||||
return video_pix_fmt_;
|
||||
}
|
||||
bool video_is_image_sequence() const
|
||||
{
|
||||
return video_is_image_sequence_;
|
||||
}
|
||||
/**
|
||||
* @brief Borrowed color transform handle (valid while this object lives)
|
||||
*/
|
||||
const OakColorTransform &color_transform() const
|
||||
{
|
||||
return color_transform_;
|
||||
}
|
||||
|
||||
bool audio_enabled() const
|
||||
{
|
||||
return audio_enabled_;
|
||||
}
|
||||
const ExportCodec::Codec &audio_codec() const
|
||||
{
|
||||
return audio_codec_;
|
||||
}
|
||||
const AudioParams &audio_params() const
|
||||
{
|
||||
return audio_params_;
|
||||
}
|
||||
const int64_t &audio_bit_rate() const
|
||||
{
|
||||
return audio_bit_rate_;
|
||||
}
|
||||
|
||||
void set_audio_bit_rate(const int64_t &b)
|
||||
{
|
||||
audio_bit_rate_ = b;
|
||||
}
|
||||
|
||||
bool subtitles_enabled() const
|
||||
{
|
||||
return subtitles_enabled_;
|
||||
}
|
||||
bool subtitles_are_sidecar() const
|
||||
{
|
||||
return subtitles_are_sidecar_;
|
||||
}
|
||||
ExportFormat::Format subtitle_sidecar_fmt() const
|
||||
{
|
||||
return subtitle_sidecar_fmt_;
|
||||
}
|
||||
ExportCodec::Codec subtitles_codec() const
|
||||
{
|
||||
return subtitles_codec_;
|
||||
}
|
||||
|
||||
const Rational &get_export_length() const
|
||||
{
|
||||
return export_length_;
|
||||
}
|
||||
void set_export_length(const Rational &export_length)
|
||||
{
|
||||
export_length_ = export_length;
|
||||
}
|
||||
|
||||
bool load(const std::string &xml);
|
||||
bool load(XmlStreamReader *reader);
|
||||
|
||||
std::string save_to_string() const;
|
||||
void save(XmlStreamWriter *writer) const;
|
||||
|
||||
bool has_custom_range() const
|
||||
{
|
||||
return has_custom_range_;
|
||||
}
|
||||
const TimeRange &custom_range() const
|
||||
{
|
||||
return custom_range_;
|
||||
}
|
||||
void set_custom_range(const TimeRange &custom_range)
|
||||
{
|
||||
has_custom_range_ = true;
|
||||
custom_range_ = custom_range;
|
||||
}
|
||||
|
||||
const VideoScalingMethod &video_scaling_method() const
|
||||
{
|
||||
return video_scaling_method_;
|
||||
}
|
||||
void
|
||||
set_video_scaling_method(const VideoScalingMethod &video_scaling_method)
|
||||
{
|
||||
video_scaling_method_ = video_scaling_method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a scaling matrix for the given scaling method
|
||||
*
|
||||
* De-Qt note: formerly returned QMatrix4x4. Now returns 16 floats in
|
||||
* row-major order (m[row * 4 + column], matching QMatrix4x4's
|
||||
* operator()(row, column) layout). The result is always a diagonal
|
||||
* matrix: identity for k_stretch (or aspect-equal sources), otherwise
|
||||
* a uniform axis scale at (0,0) and (1,1).
|
||||
*/
|
||||
static std::array<float, 16>
|
||||
generate_matrix(VideoScalingMethod method, int source_width,
|
||||
int source_height, int dest_width, int dest_height);
|
||||
|
||||
private:
|
||||
static const int k_encoder_params_version = 1;
|
||||
|
||||
bool load_v1(XmlStreamReader *reader);
|
||||
|
||||
std::string filename_;
|
||||
ExportFormat::Format format_ = ExportFormat::k_format_count;
|
||||
|
||||
bool video_enabled_;
|
||||
ExportCodec::Codec video_codec_ = ExportCodec::k_codec_count;
|
||||
OakVideoParams video_params_;
|
||||
std::map<std::string, std::string> video_opts_;
|
||||
int64_t video_bit_rate_;
|
||||
int64_t video_min_bit_rate_;
|
||||
int64_t video_max_bit_rate_;
|
||||
int64_t video_buffer_size_;
|
||||
int video_threads_;
|
||||
std::string video_pix_fmt_;
|
||||
bool video_is_image_sequence_;
|
||||
OakColorTransform color_transform_;
|
||||
|
||||
bool audio_enabled_;
|
||||
ExportCodec::Codec audio_codec_ = ExportCodec::k_codec_count;
|
||||
AudioParams audio_params_;
|
||||
int64_t audio_bit_rate_;
|
||||
|
||||
bool subtitles_enabled_;
|
||||
bool subtitles_are_sidecar_;
|
||||
ExportFormat::Format subtitle_sidecar_fmt_ = ExportFormat::k_format_count;
|
||||
ExportCodec::Codec subtitles_codec_ = ExportCodec::k_codec_count;
|
||||
|
||||
Rational export_length_;
|
||||
VideoScalingMethod video_scaling_method_;
|
||||
|
||||
bool has_custom_range_;
|
||||
TimeRange custom_range_;
|
||||
};
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
Encoder(const EncodingParams ¶ms);
|
||||
|
||||
virtual ~Encoder() = default;
|
||||
|
||||
enum Type { k_encoder_type_none = -1, k_encoder_type_f_fmpeg, k_encoder_type_oiio };
|
||||
|
||||
/**
|
||||
* @brief Create a Encoder instance using a Encoder ID
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A Encoder instance or nullptr if a Decoder with this ID does not exist
|
||||
*/
|
||||
static Encoder *create_from_id(Type id, const EncodingParams ¶ms);
|
||||
|
||||
static Type get_type_from_format(ExportFormat::Format f);
|
||||
|
||||
static Encoder *create_from_format(ExportFormat::Format f,
|
||||
const EncodingParams ¶ms);
|
||||
|
||||
static Encoder *create_from_params(const EncodingParams ¶ms);
|
||||
|
||||
virtual std::vector<std::string>
|
||||
get_pixel_formats_for_codec(ExportCodec::Codec c) const;
|
||||
virtual std::vector<SampleFormat>
|
||||
get_sample_formats_for_codec(ExportCodec::Codec c) const;
|
||||
|
||||
const EncodingParams ¶ms() const;
|
||||
|
||||
virtual PixelFormat get_desired_pixel_format() const
|
||||
{
|
||||
return PixelFormat::invalid;
|
||||
}
|
||||
|
||||
const std::string &get_error() const
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
std::string get_filename_for_frame(const Rational &frame);
|
||||
|
||||
static int get_image_sequence_placeholder_digit_count(const std::string &filename);
|
||||
|
||||
static bool filename_contains_digit_placeholder(const std::string &filename);
|
||||
static std::string filename_remove_digit_placeholder(std::string filename);
|
||||
|
||||
static const std::regex k_image_sequence_contains_digits;
|
||||
static const std::regex k_image_sequence_remove_digits;
|
||||
|
||||
virtual bool open() = 0;
|
||||
|
||||
virtual bool write_frame(olive::FramePtr frame,
|
||||
olive::core::Rational time) = 0;
|
||||
virtual bool write_audio(const olive::SampleBuffer &audio) = 0;
|
||||
|
||||
/**
|
||||
* @brief Write one subtitle entry
|
||||
*
|
||||
* De-Qt note: formerly took a `const SubtitleBlock *` (an oaknode C++
|
||||
* type). Now takes the flattened text and in/out times in seconds;
|
||||
* callers extract them from the subtitle block via the oaknode C API.
|
||||
*/
|
||||
virtual bool write_subtitle(const char *text, double in_seconds,
|
||||
double out_seconds) = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
protected:
|
||||
void set_error(const std::string &err)
|
||||
{
|
||||
error_ = err;
|
||||
}
|
||||
|
||||
private:
|
||||
EncodingParams params_;
|
||||
|
||||
std::string error_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_ENCODER_H
|
||||
@@ -0,0 +1,139 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exportcodec.h"
|
||||
|
||||
extern "C" {
|
||||
}
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
std::string ExportCodec::get_codec_name(ExportCodec::Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case k_codec_d_nx_hd:
|
||||
return "DNxHD";
|
||||
case k_codec_h264:
|
||||
return "H.264";
|
||||
case k_codec_h264rgb:
|
||||
return "H.264 RGB";
|
||||
case k_codec_h265:
|
||||
return "H.265";
|
||||
case k_codec_open_exr:
|
||||
return "OpenEXR";
|
||||
case k_codec_png:
|
||||
return "PNG";
|
||||
case k_codec_pro_res:
|
||||
return "ProRes";
|
||||
case k_codec_cineform:
|
||||
return "Cineform";
|
||||
case k_codec_tiff:
|
||||
return "TIFF";
|
||||
case k_codec_m_p2:
|
||||
return "MP2";
|
||||
case k_codec_m_p3:
|
||||
return "MP3";
|
||||
case k_codec_aac:
|
||||
return "AAC";
|
||||
case k_codec_pcm:
|
||||
return "PCM (Uncompressed)";
|
||||
case k_codec_flac:
|
||||
return "FLAC";
|
||||
case k_codec_opus:
|
||||
return "Opus";
|
||||
case k_codec_vorbis:
|
||||
return "Vorbis";
|
||||
case k_codec_v_p9:
|
||||
return "VP9";
|
||||
case k_codec_a_v1:
|
||||
return "AV1";
|
||||
case k_codec_srt:
|
||||
return "SubRip SRT";
|
||||
case k_codec_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
bool ExportCodec::is_codec_a_still_image(ExportCodec::Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case k_codec_d_nx_hd:
|
||||
case k_codec_h264:
|
||||
case k_codec_h264rgb:
|
||||
case k_codec_h265:
|
||||
case k_codec_pro_res:
|
||||
case k_codec_cineform:
|
||||
case k_codec_m_p2:
|
||||
case k_codec_m_p3:
|
||||
case k_codec_aac:
|
||||
case k_codec_pcm:
|
||||
case k_codec_vorbis:
|
||||
case k_codec_opus:
|
||||
case k_codec_flac:
|
||||
case k_codec_v_p9:
|
||||
case k_codec_a_v1:
|
||||
case k_codec_srt:
|
||||
return false;
|
||||
case k_codec_open_exr:
|
||||
case k_codec_png:
|
||||
case k_codec_tiff:
|
||||
return true;
|
||||
case k_codec_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExportCodec::is_codec_lossless(Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case k_codec_pcm:
|
||||
case k_codec_flac:
|
||||
return true;
|
||||
case k_codec_d_nx_hd:
|
||||
case k_codec_h264:
|
||||
case k_codec_h264rgb:
|
||||
case k_codec_h265:
|
||||
case k_codec_pro_res:
|
||||
case k_codec_cineform:
|
||||
case k_codec_m_p2:
|
||||
case k_codec_m_p3:
|
||||
case k_codec_aac:
|
||||
case k_codec_vorbis:
|
||||
case k_codec_opus:
|
||||
case k_codec_v_p9:
|
||||
case k_codec_a_v1:
|
||||
case k_codec_srt:
|
||||
case k_codec_open_exr:
|
||||
case k_codec_png:
|
||||
case k_codec_tiff:
|
||||
case k_codec_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_EXPORTCODEC_H
|
||||
#define OAK_EXPORTCODEC_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportCodec {
|
||||
public:
|
||||
// Only append to this list (never insert) because indexes are used in serialized files
|
||||
enum Codec {
|
||||
k_codec_d_nx_hd,
|
||||
k_codec_h264,
|
||||
k_codec_h264rgb,
|
||||
k_codec_h265,
|
||||
k_codec_open_exr,
|
||||
k_codec_png,
|
||||
k_codec_pro_res,
|
||||
k_codec_cineform,
|
||||
k_codec_tiff,
|
||||
k_codec_v_p9,
|
||||
k_codec_m_p2,
|
||||
k_codec_m_p3,
|
||||
k_codec_aac,
|
||||
k_codec_pcm,
|
||||
k_codec_opus,
|
||||
k_codec_vorbis,
|
||||
k_codec_flac,
|
||||
k_codec_srt,
|
||||
k_codec_a_v1,
|
||||
|
||||
k_codec_count
|
||||
};
|
||||
|
||||
static std::string get_codec_name(Codec c);
|
||||
|
||||
static bool is_codec_a_still_image(Codec c);
|
||||
|
||||
static bool is_codec_lossless(Codec c);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTCODEC_H
|
||||
@@ -0,0 +1,249 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exportformat.h"
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
std::string ExportFormat::get_name(olive::ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case k_format_d_nx_hd:
|
||||
return "DNxHD";
|
||||
case k_format_matroska:
|
||||
return "Matroska Video";
|
||||
case k_format_mpe_g4_video:
|
||||
return "MPEG-4 Video";
|
||||
case k_format_mpe_g4_audio:
|
||||
return "MPEG-4 Audio";
|
||||
case k_format_open_exr:
|
||||
return "OpenEXR";
|
||||
case k_format_png:
|
||||
return "PNG";
|
||||
case k_format_tiff:
|
||||
return "TIFF";
|
||||
case k_format_quick_time:
|
||||
return "QuickTime";
|
||||
case k_format_wav:
|
||||
return "Wave Audio";
|
||||
case k_format_aiff:
|
||||
return "AIFF";
|
||||
case k_format_m_p3:
|
||||
return "MP3";
|
||||
case k_format_flac:
|
||||
return "FLAC";
|
||||
case k_format_ogg:
|
||||
return "Ogg";
|
||||
case k_format_web_m:
|
||||
return "WebM";
|
||||
case k_format_srt:
|
||||
return "SubRip SRT";
|
||||
|
||||
case k_format_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string ExportFormat::get_extension(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case k_format_d_nx_hd:
|
||||
return "mxf";
|
||||
case k_format_matroska:
|
||||
return "mkv";
|
||||
case k_format_mpe_g4_video:
|
||||
return "mp4";
|
||||
case k_format_mpe_g4_audio:
|
||||
return "m4a";
|
||||
case k_format_open_exr:
|
||||
return "exr";
|
||||
case k_format_png:
|
||||
return "png";
|
||||
case k_format_tiff:
|
||||
return "tiff";
|
||||
case k_format_quick_time:
|
||||
return "mov";
|
||||
case k_format_wav:
|
||||
return "wav";
|
||||
case k_format_aiff:
|
||||
return "aiff";
|
||||
case k_format_m_p3:
|
||||
return "mp3";
|
||||
case k_format_flac:
|
||||
return "flac";
|
||||
case k_format_ogg:
|
||||
return "ogg";
|
||||
case k_format_web_m:
|
||||
return "webm";
|
||||
case k_format_srt:
|
||||
return "srt";
|
||||
case k_format_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::vector<ExportCodec::Codec> ExportFormat::get_video_codecs(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case k_format_d_nx_hd:
|
||||
return { ExportCodec::k_codec_d_nx_hd };
|
||||
case k_format_matroska:
|
||||
return { ExportCodec::k_codec_h264, ExportCodec::k_codec_h264rgb,
|
||||
ExportCodec::k_codec_h265, ExportCodec::k_codec_v_p9 };
|
||||
case k_format_mpe_g4_video:
|
||||
return { ExportCodec::k_codec_h264, ExportCodec::k_codec_h264rgb,
|
||||
ExportCodec::k_codec_h265 };
|
||||
case k_format_open_exr:
|
||||
return { ExportCodec::k_codec_open_exr };
|
||||
case k_format_png:
|
||||
return { ExportCodec::k_codec_png };
|
||||
case k_format_tiff:
|
||||
return { ExportCodec::k_codec_tiff };
|
||||
case k_format_quick_time:
|
||||
return { ExportCodec::k_codec_h264, ExportCodec::k_codec_h264rgb,
|
||||
ExportCodec::k_codec_h265, ExportCodec::k_codec_pro_res,
|
||||
ExportCodec::k_codec_cineform };
|
||||
case k_format_web_m:
|
||||
return { ExportCodec::k_codec_a_v1, ExportCodec::k_codec_v_p9 };
|
||||
case k_format_ogg:
|
||||
case k_format_wav:
|
||||
case k_format_mpe_g4_audio:
|
||||
case k_format_aiff:
|
||||
case k_format_m_p3:
|
||||
case k_format_flac:
|
||||
case k_format_srt:
|
||||
case k_format_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ExportCodec::Codec> ExportFormat::get_audio_codecs(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
// Video/audio formats
|
||||
case k_format_d_nx_hd:
|
||||
return { ExportCodec::k_codec_pcm };
|
||||
case k_format_matroska:
|
||||
return { ExportCodec::k_codec_aac, ExportCodec::k_codec_m_p2,
|
||||
ExportCodec::k_codec_m_p3, ExportCodec::k_codec_pcm,
|
||||
ExportCodec::k_codec_vorbis, ExportCodec::k_codec_opus,
|
||||
ExportCodec::k_codec_flac };
|
||||
case k_format_mpe_g4_video:
|
||||
case k_format_mpe_g4_audio:
|
||||
return { ExportCodec::k_codec_aac, ExportCodec::k_codec_m_p2,
|
||||
ExportCodec::k_codec_m_p3 };
|
||||
case k_format_quick_time:
|
||||
return { ExportCodec::k_codec_aac, ExportCodec::k_codec_m_p2,
|
||||
ExportCodec::k_codec_m_p3, ExportCodec::k_codec_pcm };
|
||||
case k_format_web_m:
|
||||
return { ExportCodec::k_codec_opus, ExportCodec::k_codec_aac,
|
||||
ExportCodec::k_codec_m_p2, ExportCodec::k_codec_m_p3,
|
||||
ExportCodec::k_codec_pcm, ExportCodec::k_codec_vorbis };
|
||||
|
||||
// Audio only formats
|
||||
case k_format_wav:
|
||||
return { ExportCodec::k_codec_pcm };
|
||||
case k_format_aiff:
|
||||
return { ExportCodec::k_codec_pcm };
|
||||
case k_format_m_p3:
|
||||
return { ExportCodec::k_codec_m_p3 };
|
||||
case k_format_flac:
|
||||
return { ExportCodec::k_codec_flac };
|
||||
case k_format_ogg:
|
||||
return { ExportCodec::k_codec_opus, ExportCodec::k_codec_vorbis,
|
||||
ExportCodec::k_codec_pcm };
|
||||
|
||||
// Video only formats
|
||||
case k_format_open_exr:
|
||||
case k_format_png:
|
||||
case k_format_tiff:
|
||||
case k_format_srt:
|
||||
case k_format_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ExportCodec::Codec> ExportFormat::get_subtitle_codecs(Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case k_format_d_nx_hd:
|
||||
case k_format_mpe_g4_video:
|
||||
case k_format_mpe_g4_audio:
|
||||
case k_format_open_exr:
|
||||
case k_format_quick_time:
|
||||
case k_format_png:
|
||||
case k_format_tiff:
|
||||
case k_format_wav:
|
||||
case k_format_aiff:
|
||||
case k_format_m_p3:
|
||||
case k_format_flac:
|
||||
case k_format_ogg:
|
||||
case k_format_web_m:
|
||||
case k_format_count:
|
||||
break;
|
||||
case k_format_matroska:
|
||||
case k_format_srt:
|
||||
return { ExportCodec::k_codec_srt };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> ExportFormat::get_pixel_formats_for_codec(ExportFormat::Format f,
|
||||
ExportCodec::Codec c)
|
||||
{
|
||||
Encoder *e = Encoder::create_from_format(f, EncodingParams());
|
||||
std::vector<std::string> list;
|
||||
|
||||
if (e) {
|
||||
list = e->get_pixel_formats_for_codec(c);
|
||||
delete e;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::vector<core::SampleFormat>
|
||||
ExportFormat::get_sample_formats_for_codec(Format format, ExportCodec::Codec c)
|
||||
{
|
||||
std::vector<core::SampleFormat> f;
|
||||
Encoder *e = Encoder::create_from_format(format, EncodingParams());
|
||||
|
||||
if (e) {
|
||||
f = e->get_sample_formats_for_codec(c);
|
||||
delete e;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_EXPORTFORMAT_H
|
||||
#define OAK_EXPORTFORMAT_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "olive/core/render/sampleformat.h"
|
||||
|
||||
#include "exportcodec.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportFormat {
|
||||
public:
|
||||
// Only append to this list (never insert) because indexes are used in serialized files
|
||||
enum Format {
|
||||
k_format_d_nx_hd,
|
||||
k_format_matroska,
|
||||
k_format_mpe_g4_video,
|
||||
k_format_open_exr,
|
||||
k_format_quick_time,
|
||||
k_format_png,
|
||||
k_format_tiff,
|
||||
k_format_wav,
|
||||
k_format_aiff,
|
||||
k_format_m_p3,
|
||||
k_format_flac,
|
||||
k_format_ogg,
|
||||
k_format_web_m,
|
||||
k_format_srt,
|
||||
k_format_mpe_g4_audio,
|
||||
|
||||
k_format_count
|
||||
};
|
||||
|
||||
static std::string get_name(Format f);
|
||||
static std::string get_extension(Format f);
|
||||
static std::vector<ExportCodec::Codec>
|
||||
get_video_codecs(ExportFormat::Format f);
|
||||
static std::vector<ExportCodec::Codec>
|
||||
get_audio_codecs(ExportFormat::Format f);
|
||||
static std::vector<ExportCodec::Codec>
|
||||
get_subtitle_codecs(ExportFormat::Format f);
|
||||
|
||||
static std::vector<std::string>
|
||||
get_pixel_formats_for_codec(Format f, ExportCodec::Codec c);
|
||||
static std::vector<core::SampleFormat>
|
||||
get_sample_formats_for_codec(Format f, ExportCodec::Codec c);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTFORMAT_H
|
||||
@@ -0,0 +1,21 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
target_sources(oakcodec PRIVATE avframeptr.h ffmpegdecoder.cpp
|
||||
ffmpegdecoder.h
|
||||
ffmpegencoder.cpp
|
||||
ffmpegencoder.h
|
||||
)
|
||||
@@ -0,0 +1,139 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_CODEC_FFMPEG_AVFRAMEPTR_H
|
||||
#define OAK_CODEC_FFMPEG_AVFRAMEPTR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief C++ adapter around the ffmpeg_bridge frame handle
|
||||
*
|
||||
* Mirrors the AVFrame field access the codebase used to perform directly,
|
||||
* but every operation goes through the pure C bridge API so the editor
|
||||
* never touches FFmpeg itself. The underlying frame object always lives
|
||||
* inside the bridge library.
|
||||
*/
|
||||
class AVFrame {
|
||||
public:
|
||||
AVFrame() :
|
||||
handle_(fb_frame_alloc())
|
||||
{
|
||||
}
|
||||
|
||||
explicit AVFrame(FBFrame *handle) :
|
||||
handle_(handle)
|
||||
{
|
||||
}
|
||||
|
||||
~AVFrame()
|
||||
{
|
||||
if (handle_) {
|
||||
fb_frame_free(&handle_);
|
||||
}
|
||||
}
|
||||
|
||||
AVFrame(const AVFrame &) = delete;
|
||||
AVFrame &operator=(const AVFrame &) = delete;
|
||||
|
||||
FBFrame *handle() const { return handle_; }
|
||||
|
||||
int width() const { return fb_frame_get_width(handle_); }
|
||||
void set_width(int w) { fb_frame_set_width(handle_, w); }
|
||||
int height() const { return fb_frame_get_height(handle_); }
|
||||
void set_height(int h) { fb_frame_set_height(handle_, h); }
|
||||
int format() const { return fb_frame_get_format(handle_); }
|
||||
void set_format(int f) { fb_frame_set_format(handle_, f); }
|
||||
int64_t pts() const { return fb_frame_get_pts(handle_); }
|
||||
void set_pts(int64_t p) { fb_frame_set_pts(handle_, p); }
|
||||
int64_t best_effort_timestamp() const
|
||||
{
|
||||
return fb_frame_get_best_effort_timestamp(handle_);
|
||||
}
|
||||
int nb_samples() const { return fb_frame_get_nb_samples(handle_); }
|
||||
void set_nb_samples(int n) { fb_frame_set_nb_samples(handle_, n); }
|
||||
int sample_rate() const { return fb_frame_get_sample_rate(handle_); }
|
||||
void set_sample_rate(int r) { fb_frame_set_sample_rate(handle_, r); }
|
||||
int color_range() const { return fb_frame_get_color_range(handle_); }
|
||||
void set_color_range(int r) { fb_frame_set_color_range(handle_, r); }
|
||||
int colorspace() const { return fb_frame_get_colorspace(handle_); }
|
||||
void set_colorspace(int cs) { fb_frame_set_colorspace(handle_, cs); }
|
||||
uint64_t channel_layout_mask() const
|
||||
{
|
||||
return fb_frame_get_channel_layout_mask(handle_);
|
||||
}
|
||||
void set_channel_layout_mask(uint64_t m)
|
||||
{
|
||||
fb_frame_set_channel_layout_mask(handle_, m);
|
||||
}
|
||||
|
||||
bool is_hw() const { return fb_frame_is_hw(handle_) != 0; }
|
||||
int hw_transfer_data(const AVFrame *src)
|
||||
{
|
||||
return fb_frame_hw_transfer_data(handle_, src->handle_);
|
||||
}
|
||||
int get_buffer(int align) { return fb_frame_get_buffer(handle_, align); }
|
||||
int make_writable() { return fb_frame_make_writable(handle_); }
|
||||
|
||||
uint8_t *data(int plane) { return fb_frame_get_data(handle_, plane); }
|
||||
const uint8_t *data(int plane) const
|
||||
{
|
||||
return fb_frame_get_data_const(handle_, plane);
|
||||
}
|
||||
void set_data(int plane, uint8_t *d)
|
||||
{
|
||||
fb_frame_set_data(handle_, plane, d);
|
||||
}
|
||||
int linesize(int plane) const
|
||||
{
|
||||
return fb_frame_get_linesize(handle_, plane);
|
||||
}
|
||||
void set_linesize(int plane, int l)
|
||||
{
|
||||
fb_frame_set_linesize(handle_, plane, l);
|
||||
}
|
||||
|
||||
private:
|
||||
FBFrame *handle_;
|
||||
};
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr(FBFrame *f)
|
||||
{
|
||||
return std::make_shared<AVFrame>(f);
|
||||
}
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr()
|
||||
{
|
||||
return std::make_shared<AVFrame>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CODEC_FFMPEG_AVFRAMEPTR_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_FFMPEGDECODER_H
|
||||
#define OAK_FFMPEGDECODER_H
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
#include "decoder.h"
|
||||
#include "ffmpeg/avframeptr.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A Decoder derivative that uses the ffmpeg_bridge library as an Olive decoder
|
||||
*
|
||||
* All media access goes through the pure C API of the ffmpeg_bridge shared
|
||||
* library; this class never sees an FFmpeg structure or function.
|
||||
*/
|
||||
class FFmpegDecoder : public Decoder {
|
||||
public:
|
||||
// Constructor
|
||||
FFmpegDecoder();
|
||||
|
||||
// Destructor
|
||||
DECODER_DEFAULT_DESTRUCTOR(FFmpegDecoder)
|
||||
|
||||
virtual std::string id() const override;
|
||||
|
||||
virtual bool supports_video() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual bool supports_audio() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual FootageDescription probe(const std::string &filename,
|
||||
OakCancelAtom *cancelled) const override;
|
||||
|
||||
protected:
|
||||
virtual bool open_internal() override;
|
||||
virtual OakRenderTexture *
|
||||
retrieve_video_internal(const RetrieveVideoParams &p) override;
|
||||
virtual FramePtr
|
||||
retrieve_video_frame_internal(const RetrieveVideoParams &p) override;
|
||||
virtual bool
|
||||
conform_audio_internal(const std::vector<std::string> &filenames,
|
||||
const AudioParams ¶ms,
|
||||
OakCancelAtom *cancelled) override;
|
||||
virtual void close_internal() override;
|
||||
|
||||
virtual Rational get_audio_start_offset() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Handle a bridge error code
|
||||
*
|
||||
* Uses the bridge API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
|
||||
* function also automatically closes the Decoder.
|
||||
*
|
||||
* @param error_code
|
||||
*/
|
||||
static std::string f_fmpeg_error(int error_code);
|
||||
|
||||
void free_scaler();
|
||||
|
||||
AVFramePtr transfer_hardware_frame(AVFramePtr f);
|
||||
|
||||
static PixelFormat get_native_pixel_format(int pix_fmt);
|
||||
static int get_native_channel_count(int pix_fmt);
|
||||
|
||||
static bool is_pixel_format_glsl_compatible(int f);
|
||||
|
||||
AVFramePtr get_frame_from_cache(const int64_t &t) const;
|
||||
|
||||
void clear_frame_cache();
|
||||
|
||||
AVFramePtr pre_process_frame(AVFramePtr f, const RetrieveVideoParams &p);
|
||||
|
||||
OakRenderTexture *process_frame_into_texture(AVFramePtr f,
|
||||
const RetrieveVideoParams &p,
|
||||
const AVFramePtr original);
|
||||
|
||||
AVFramePtr retrieve_frame(const Rational &time, OakCancelAtom *cancelled);
|
||||
|
||||
void remove_first_frame();
|
||||
|
||||
static int maximum_queue_size();
|
||||
|
||||
FBScaler *scaler_;
|
||||
int scaler_src_width_;
|
||||
int scaler_src_height_;
|
||||
int scaler_src_format_;
|
||||
int scaler_dst_width_;
|
||||
int scaler_dst_height_;
|
||||
int scaler_dst_format_;
|
||||
int scaler_colrange_;
|
||||
int scaler_colspace_;
|
||||
|
||||
FBPacket *working_packet_;
|
||||
|
||||
int64_t second_ts_;
|
||||
|
||||
std::list<AVFramePtr> cached_frames_;
|
||||
|
||||
bool cache_at_zero_;
|
||||
bool cache_at_eof_;
|
||||
|
||||
FBDecoder *instance_;
|
||||
|
||||
// Stream parameters cached on open (the stream object itself lives
|
||||
// inside the bridge library)
|
||||
Rational stream_time_base_;
|
||||
int64_t stream_start_time_;
|
||||
int64_t stream_duration_;
|
||||
int64_t format_start_time_;
|
||||
int input_sample_format_;
|
||||
int input_sample_rate_;
|
||||
uint64_t input_channel_layout_mask_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FFMPEGDECODER_H
|
||||
@@ -0,0 +1,547 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ffmpegencoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "common/subtitleparams.h"
|
||||
#include "common/videoparams.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string to_lower(const std::string &s)
|
||||
{
|
||||
std::string r = s;
|
||||
std::transform(r.begin(), r.end(), r.begin(),
|
||||
[](unsigned char c) { return char(std::tolower(c)); });
|
||||
return r;
|
||||
}
|
||||
|
||||
bool contains(const std::string &haystack, const std::string &needle)
|
||||
{
|
||||
return haystack.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
Rational video_params_pixel_aspect_ratio(OakVideoParams vp)
|
||||
{
|
||||
int n = 0, d = 1;
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(vp, &n, &d);
|
||||
return Rational(n, d);
|
||||
}
|
||||
|
||||
Rational video_params_frame_rate_as_time_base(OakVideoParams vp)
|
||||
{
|
||||
int n = 0, d = 1;
|
||||
oakcommon_videoparams_frame_rate_as_time_base(vp, &n, &d);
|
||||
return Rational(n, d);
|
||||
}
|
||||
|
||||
std::string colortransform_get_output(OakColorTransform ct)
|
||||
{
|
||||
std::string result;
|
||||
int size = oakcommon_colortransform_get_output(ct, nullptr, 0);
|
||||
if (size > 0) {
|
||||
result.assign(size_t(size) - 1, '\0');
|
||||
oakcommon_colortransform_get_output(ct, result.data(), size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms)
|
||||
: Encoder(params)
|
||||
, encoder_(nullptr)
|
||||
, open_(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::get_color_tags_for_colorspace(const std::string &colorspace,
|
||||
int *primaries, int *trc,
|
||||
int *matrix)
|
||||
{
|
||||
const std::string name = to_lower(colorspace);
|
||||
|
||||
if (contains(name, "pq") || contains(name, "2084")) {
|
||||
*primaries = fb_color_primaries_bt2020;
|
||||
*trc = fb_color_trc_pq;
|
||||
*matrix = fb_col_spc_b_t2020_ncl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contains(name, "hlg")) {
|
||||
*primaries = fb_color_primaries_bt2020;
|
||||
*trc = fb_color_trc_hlg;
|
||||
*matrix = fb_col_spc_b_t2020_ncl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contains(name, "2020")) {
|
||||
*primaries = fb_color_primaries_bt2020;
|
||||
*trc = fb_color_trc_bt709;
|
||||
*matrix = fb_col_spc_b_t2020_ncl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contains(name, "p3")) {
|
||||
*primaries = fb_color_primaries_smpte432;
|
||||
*trc = fb_color_trc_srgb;
|
||||
*matrix = fb_col_spc_b_t709;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contains(name, "srgb")) {
|
||||
*primaries = fb_color_primaries_bt709;
|
||||
*trc = fb_color_trc_srgb;
|
||||
*matrix = fb_col_spc_b_t709;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contains(name, "pal")) {
|
||||
*primaries = fb_color_primaries_bt470bg;
|
||||
*trc = fb_color_trc_gamma28;
|
||||
*matrix = fb_col_spc_b_t470_bg;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contains(name, "ntsc")) {
|
||||
*primaries = fb_color_primaries_smpte170m;
|
||||
*trc = fb_color_trc_smpte170m;
|
||||
*matrix = fb_col_spc_smpt_e170_m;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contains(name, "1886") || contains(name, "709")) {
|
||||
*primaries = fb_color_primaries_bt709;
|
||||
*trc = fb_color_trc_bt709;
|
||||
*matrix = fb_col_spc_b_t709;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
FFmpegEncoder::get_pixel_formats_for_codec(ExportCodec::Codec c) const
|
||||
{
|
||||
std::vector<std::string> pix_fmts;
|
||||
|
||||
int bridge_codec = export_codec_to_bridge(c);
|
||||
if (bridge_codec != fb_codec_none) {
|
||||
int count =
|
||||
fb_encoder_codec_get_pixel_formats(bridge_codec, nullptr, 0);
|
||||
if (count > 0) {
|
||||
std::vector<const char *> names(static_cast<size_t>(count));
|
||||
fb_encoder_codec_get_pixel_formats(bridge_codec, names.data(),
|
||||
count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
pix_fmts.push_back(names[size_t(i)] ? names[size_t(i)] : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pix_fmts;
|
||||
}
|
||||
|
||||
std::vector<SampleFormat>
|
||||
FFmpegEncoder::get_sample_formats_for_codec(ExportCodec::Codec c) const
|
||||
{
|
||||
std::vector<SampleFormat> f;
|
||||
|
||||
if (c == ExportCodec::k_codec_pcm) {
|
||||
// FFmpeg lists these as separate codecs so we need custom functionality here
|
||||
// We list signed 16 first because ExportDialog will always use the first element by default
|
||||
// (because first element is the "default" in FFmpeg)
|
||||
f = { SampleFormat::s16, SampleFormat::u8, SampleFormat::s32,
|
||||
SampleFormat::s64, SampleFormat::f32, SampleFormat::f64 };
|
||||
} else {
|
||||
int bridge_codec = export_codec_to_bridge(c);
|
||||
if (bridge_codec != fb_codec_none) {
|
||||
int count =
|
||||
fb_encoder_codec_get_sample_formats(bridge_codec, nullptr, 0);
|
||||
if (count > 0) {
|
||||
std::vector<int> fmts(static_cast<size_t>(count));
|
||||
fb_encoder_codec_get_sample_formats(bridge_codec, fmts.data(),
|
||||
count);
|
||||
for (int fmt : fmts) {
|
||||
int native = -1;
|
||||
oakcommon_ffmpegutils_get_native_sample_format(fmt,
|
||||
&native);
|
||||
if (native != SampleFormat::invalid) {
|
||||
f.push_back(
|
||||
SampleFormat(static_cast<SampleFormat::Format>(
|
||||
native)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::open()
|
||||
{
|
||||
if (open_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FBEncoderConfig config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.filename = params().filename().c_str();
|
||||
|
||||
// Storage keeping C strings alive until fb_encoder_create deep-copies them
|
||||
std::string pix_fmt_str;
|
||||
std::string subtitle_header;
|
||||
std::vector<std::string> opt_key_storage;
|
||||
std::vector<std::string> opt_value_storage;
|
||||
std::vector<const char *> opt_keys;
|
||||
std::vector<const char *> opt_values;
|
||||
|
||||
// Set up video if it's enabled
|
||||
if (params().video_enabled()) {
|
||||
const OakVideoParams &vp = params().video_params();
|
||||
|
||||
config.video_enabled = 1;
|
||||
config.video_codec = export_codec_to_bridge(params().video_codec());
|
||||
oakcommon_videoparams_get_width(vp, &config.video_width);
|
||||
oakcommon_videoparams_get_height(vp, &config.video_height);
|
||||
Rational pixel_aspect = video_params_pixel_aspect_ratio(vp);
|
||||
config.video_pixel_aspect_num = pixel_aspect.numerator();
|
||||
config.video_pixel_aspect_den = pixel_aspect.denominator();
|
||||
Rational time_base = video_params_frame_rate_as_time_base(vp);
|
||||
config.video_time_base_num = time_base.numerator();
|
||||
config.video_time_base_den = time_base.denominator();
|
||||
int frame_rate_num = 0, frame_rate_den = 1;
|
||||
oakcommon_videoparams_get_frame_rate(vp, &frame_rate_num,
|
||||
&frame_rate_den);
|
||||
config.video_frame_rate_num = frame_rate_num;
|
||||
config.video_frame_rate_den = frame_rate_den;
|
||||
|
||||
pix_fmt_str = params().video_pix_fmt();
|
||||
config.video_pix_fmt = pix_fmt_str.c_str();
|
||||
|
||||
// This is the format we will expect frames received in Write() to be in
|
||||
int native_pixel_fmt = -1;
|
||||
oakcommon_videoparams_get_format(vp, &native_pixel_fmt);
|
||||
|
||||
// This is the format we will need to convert the frame to for the bridge to understand it
|
||||
int compatible_fmt = -1;
|
||||
oakcommon_ffmpegutils_get_compatible_pixel_format(native_pixel_fmt,
|
||||
&compatible_fmt);
|
||||
video_conversion_fmt_ =
|
||||
PixelFormat(static_cast<PixelFormat::Format>(compatible_fmt));
|
||||
|
||||
// These are the equivalent pixel formats as bridge pixel formats
|
||||
int src_alpha_pix_fmt = fb_pix_fmt_none;
|
||||
int src_noalpha_pix_fmt = fb_pix_fmt_none;
|
||||
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
|
||||
compatible_fmt, OAKCOMMON_RGBA_CHANNEL_COUNT, &src_alpha_pix_fmt);
|
||||
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
|
||||
compatible_fmt, OAKCOMMON_RGB_CHANNEL_COUNT, &src_noalpha_pix_fmt);
|
||||
|
||||
if (src_alpha_pix_fmt == fb_pix_fmt_none ||
|
||||
src_noalpha_pix_fmt == fb_pix_fmt_none) {
|
||||
set_error("Failed to find suitable pixel format for this buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
config.video_src_pix_fmt = src_alpha_pix_fmt;
|
||||
|
||||
int color_range = OAKCOMMON_COLOR_RANGE_LIMITED;
|
||||
oakcommon_videoparams_get_color_range(vp, &color_range);
|
||||
config.video_color_range = color_range == OAKCOMMON_COLOR_RANGE_FULL ?
|
||||
fb_color_range_jpeg :
|
||||
fb_color_range_mpeg;
|
||||
|
||||
int interlacing = OAKCOMMON_VIDEO_INTERLACE_NONE;
|
||||
oakcommon_videoparams_get_interlacing(vp, &interlacing);
|
||||
switch (interlacing) {
|
||||
case OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST:
|
||||
config.video_field_order = fb_field_order_tt;
|
||||
break;
|
||||
case OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST:
|
||||
config.video_field_order = fb_field_order_bb;
|
||||
break;
|
||||
default:
|
||||
config.video_field_order = fb_field_order_progressive;
|
||||
break;
|
||||
}
|
||||
|
||||
config.video_bit_rate = params().video_bit_rate();
|
||||
config.video_min_bit_rate = params().video_min_bit_rate();
|
||||
config.video_max_bit_rate = params().video_max_bit_rate();
|
||||
config.video_buffer_size = params().video_buffer_size();
|
||||
config.video_threads = params().video_threads();
|
||||
|
||||
const std::string color_output =
|
||||
colortransform_get_output(params().color_transform());
|
||||
config.video_color_srgb =
|
||||
contains(to_lower(color_output), "srgb") ? 1 : 0;
|
||||
|
||||
// Derive explicit nclc tags (HDR etc.) from the export colorspace;
|
||||
// the bridge falls back to the legacy sRGB/Rec.709 logic when these
|
||||
// are unspecified
|
||||
int color_primaries = fb_color_primaries_unspec;
|
||||
int color_trc = fb_color_trc_unspec;
|
||||
int color_matrix = fb_col_spc_unspec;
|
||||
get_color_tags_for_colorspace(color_output, &color_primaries,
|
||||
&color_trc, &color_matrix);
|
||||
config.video_color_primaries = color_primaries;
|
||||
config.video_color_trc = color_trc;
|
||||
config.video_colorspace = color_matrix;
|
||||
|
||||
// Custom options (skip Olive-internal keys)
|
||||
for (const auto &opt : params().video_opts()) {
|
||||
if (opt.first.compare(0, 4, "ove_") != 0) {
|
||||
opt_key_storage.push_back(opt.first);
|
||||
opt_value_storage.push_back(opt.second);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < opt_key_storage.size(); i++) {
|
||||
opt_keys.push_back(opt_key_storage[i].c_str());
|
||||
opt_values.push_back(opt_value_storage[i].c_str());
|
||||
}
|
||||
config.video_opt_keys = opt_keys.data();
|
||||
config.video_opt_values = opt_values.data();
|
||||
config.video_opt_count = int(opt_keys.size());
|
||||
}
|
||||
|
||||
// Set up audio if it's enabled
|
||||
if (params().audio_enabled()) {
|
||||
config.audio_enabled = 1;
|
||||
config.audio_codec = export_codec_to_bridge(params().audio_codec());
|
||||
config.audio_sample_rate = params().audio_params().sample_rate();
|
||||
config.audio_channel_layout_mask =
|
||||
params().audio_params().channel_layout();
|
||||
int audio_sample_fmt = -1;
|
||||
oakcommon_ffmpegutils_get_ffmpeg_sample_format(
|
||||
static_cast<int>(params().audio_params().format()),
|
||||
&audio_sample_fmt);
|
||||
config.audio_sample_format = audio_sample_fmt;
|
||||
config.audio_bit_rate = params().audio_bit_rate();
|
||||
}
|
||||
|
||||
// Set up subtitles if they're enabled
|
||||
if (params().subtitles_enabled()) {
|
||||
config.subtitles_enabled = 1;
|
||||
config.subtitle_codec = export_codec_to_bridge(params().subtitles_codec());
|
||||
int header_size =
|
||||
oakcommon_subtitleparams_generate_ass_header(nullptr, 0);
|
||||
if (header_size > 0) {
|
||||
subtitle_header.assign(size_t(header_size) - 1, '\0');
|
||||
oakcommon_subtitleparams_generate_ass_header(
|
||||
subtitle_header.data(), header_size);
|
||||
}
|
||||
config.subtitle_header =
|
||||
reinterpret_cast<const uint8_t *>(subtitle_header.data());
|
||||
config.subtitle_header_size = int(subtitle_header.size());
|
||||
}
|
||||
|
||||
encoder_ = fb_encoder_create(&config);
|
||||
if (!encoder_) {
|
||||
set_error("Failed to create encoder");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fb_encoder_open(encoder_) != 0) {
|
||||
set_error_from_bridge();
|
||||
fb_encoder_free(&encoder_);
|
||||
return false;
|
||||
}
|
||||
|
||||
open_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::write_frame(FramePtr frame, Rational time)
|
||||
{
|
||||
// The render worker pool finishes tickets without a result when no
|
||||
// worker is available (or the worker crashed); a null frame must fail
|
||||
// the encode cleanly instead of crashing the export task.
|
||||
if (!frame) {
|
||||
fprintf(stderr,
|
||||
"FFmpegEncoder::write_frame called with null frame\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We may need to convert this frame to a frame that the bridge will understand
|
||||
if (frame->format() != static_cast<int>(video_conversion_fmt_)) {
|
||||
frame = frame->convert(static_cast<int>(video_conversion_fmt_));
|
||||
}
|
||||
|
||||
int src_pix_fmt = fb_pix_fmt_none;
|
||||
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
|
||||
frame->format(), frame->channel_count(), &src_pix_fmt);
|
||||
|
||||
int r = fb_encoder_write_video_frame(
|
||||
encoder_, frame->width(), frame->height(), src_pix_fmt,
|
||||
reinterpret_cast<const uint8_t *>(frame->data()),
|
||||
frame->linesize_bytes(), time.to_double());
|
||||
if (r != 0) {
|
||||
set_error_from_bridge();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::write_audio(const SampleBuffer &audio)
|
||||
{
|
||||
if (!audio.is_allocated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const AudioParams &audio_params = audio.audio_params().is_valid() ?
|
||||
audio.audio_params() :
|
||||
params().audio_params();
|
||||
|
||||
std::vector<const uint8_t *> channel_data(
|
||||
size_t(audio.audio_params().channel_count()));
|
||||
for (size_t i = 0; i < channel_data.size(); i++) {
|
||||
channel_data[i] =
|
||||
reinterpret_cast<const uint8_t *>(audio.data(int(i)));
|
||||
}
|
||||
|
||||
int sample_fmt = -1;
|
||||
oakcommon_ffmpegutils_get_ffmpeg_sample_format(
|
||||
static_cast<int>(audio.audio_params().format()), &sample_fmt);
|
||||
|
||||
int r = fb_encoder_write_audio(
|
||||
encoder_, channel_data.data(),
|
||||
audio.audio_params().channel_count(), sample_fmt,
|
||||
audio_params.sample_rate(), int64_t(audio_params.channel_layout()),
|
||||
int64_t(audio.sample_count()));
|
||||
if (r != 0) {
|
||||
set_error_from_bridge();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::write_audio_data(const AudioParams &audio_params,
|
||||
const uint8_t **data,
|
||||
int input_sample_count)
|
||||
{
|
||||
int sample_fmt = -1;
|
||||
oakcommon_ffmpegutils_get_ffmpeg_sample_format(
|
||||
static_cast<int>(audio_params.format()), &sample_fmt);
|
||||
|
||||
int r = fb_encoder_write_audio(
|
||||
encoder_, data, audio_params.channel_count(), sample_fmt,
|
||||
audio_params.sample_rate(), int64_t(audio_params.channel_layout()),
|
||||
input_sample_count);
|
||||
if (r != 0) {
|
||||
set_error_from_bridge();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::write_subtitle(const char *text, double in_seconds,
|
||||
double out_seconds)
|
||||
{
|
||||
int r = fb_encoder_write_subtitle(encoder_, text, in_seconds, out_seconds);
|
||||
if (r != 0) {
|
||||
set_error_from_bridge();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FFmpegEncoder::close()
|
||||
{
|
||||
if (encoder_) {
|
||||
// Flushes encoders, writes the trailer, and frees everything
|
||||
fb_encoder_free(&encoder_);
|
||||
}
|
||||
|
||||
open_ = false;
|
||||
}
|
||||
|
||||
void FFmpegEncoder::set_error_from_bridge()
|
||||
{
|
||||
const char *err = fb_encoder_get_error(encoder_);
|
||||
set_error(err ? err : "");
|
||||
}
|
||||
|
||||
int FFmpegEncoder::export_codec_to_bridge(ExportCodec::Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case ExportCodec::k_codec_h264:
|
||||
return fb_codec_h264;
|
||||
case ExportCodec::k_codec_h264rgb:
|
||||
return fb_codec_h264_rgb;
|
||||
case ExportCodec::k_codec_d_nx_hd:
|
||||
return fb_codec_dnxhd;
|
||||
case ExportCodec::k_codec_pro_res:
|
||||
return fb_codec_prores;
|
||||
case ExportCodec::k_codec_cineform:
|
||||
return fb_codec_cineform;
|
||||
case ExportCodec::k_codec_h265:
|
||||
return fb_codec_h265;
|
||||
case ExportCodec::k_codec_v_p9:
|
||||
return fb_codec_v_p9;
|
||||
case ExportCodec::k_codec_a_v1:
|
||||
return fb_codec_a_v1;
|
||||
case ExportCodec::k_codec_open_exr:
|
||||
return fb_codec_openexr;
|
||||
case ExportCodec::k_codec_png:
|
||||
return fb_codec_png;
|
||||
case ExportCodec::k_codec_tiff:
|
||||
return fb_codec_tiff;
|
||||
case ExportCodec::k_codec_m_p2:
|
||||
return fb_codec_m_p2;
|
||||
case ExportCodec::k_codec_m_p3:
|
||||
return fb_codec_m_p3;
|
||||
case ExportCodec::k_codec_aac:
|
||||
return fb_codec_aac;
|
||||
case ExportCodec::k_codec_pcm:
|
||||
return fb_codec_pcm;
|
||||
case ExportCodec::k_codec_flac:
|
||||
return fb_codec_flac;
|
||||
case ExportCodec::k_codec_opus:
|
||||
return fb_codec_opus;
|
||||
case ExportCodec::k_codec_vorbis:
|
||||
return fb_codec_vorbis;
|
||||
case ExportCodec::k_codec_srt:
|
||||
return fb_codec_srt;
|
||||
case ExportCodec::k_codec_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return fb_codec_none;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_FFMPEGENCODER_H
|
||||
#define OAK_FFMPEGENCODER_H
|
||||
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief An Encoder derivative that uses the ffmpeg_bridge library for encoding
|
||||
*
|
||||
* All encoding work happens inside the ffmpeg_bridge shared library through
|
||||
* its pure C API; this class only translates EncodingParams into a bridge
|
||||
* configuration and forwards calls.
|
||||
*/
|
||||
class FFmpegEncoder : public Encoder {
|
||||
public:
|
||||
FFmpegEncoder(const EncodingParams ¶ms);
|
||||
|
||||
virtual std::vector<std::string>
|
||||
get_pixel_formats_for_codec(ExportCodec::Codec c) const override;
|
||||
|
||||
virtual std::vector<SampleFormat>
|
||||
get_sample_formats_for_codec(ExportCodec::Codec c) const override;
|
||||
|
||||
virtual bool open() override;
|
||||
|
||||
virtual bool write_frame(olive::FramePtr frame,
|
||||
olive::core::Rational time) override;
|
||||
|
||||
virtual bool write_audio(const olive::SampleBuffer &audio) override;
|
||||
|
||||
bool write_audio_data(const AudioParams &audio_params, const uint8_t **data,
|
||||
int input_sample_count);
|
||||
|
||||
virtual bool write_subtitle(const char *text, double in_seconds,
|
||||
double out_seconds) override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
virtual PixelFormat get_desired_pixel_format() const override
|
||||
{
|
||||
return video_conversion_fmt_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derives nclc color tags from an output colorspace name
|
||||
*
|
||||
* Extracted for testability. Returns true when the name maps to
|
||||
* explicit tags (PQ/HLG/BT.2020, sRGB, P3, Rec.601, Rec.709); returns
|
||||
* false for unknown names, in which case the bridge's legacy
|
||||
* Rec.709/sRGB inference applies.
|
||||
*/
|
||||
static bool get_color_tags_for_colorspace(const std::string &colorspace,
|
||||
int *primaries, int *trc,
|
||||
int *matrix);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Copy the last error message from the bridge into the encoder error state
|
||||
*/
|
||||
void set_error_from_bridge();
|
||||
|
||||
static int export_codec_to_bridge(ExportCodec::Codec c);
|
||||
|
||||
FBEncoder *encoder_;
|
||||
|
||||
PixelFormat video_conversion_fmt_;
|
||||
|
||||
bool open_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FFMPEGENCODER_H
|
||||
@@ -0,0 +1,291 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_CODEC_FOOTAGEDESCRIPTION_H
|
||||
#define OAK_CODEC_FOOTAGEDESCRIPTION_H
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/subtitleparams.h"
|
||||
#include "common/videoparams.h"
|
||||
#include "olive/core/render/audioparams.h"
|
||||
#include "olive/core/util/rational.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using core::AudioParams;
|
||||
using core::Rational;
|
||||
|
||||
/**
|
||||
* @brief Codec-internal replacement for the former
|
||||
* node/project/footage/footagedescription.h
|
||||
*
|
||||
* Value type describing the streams a Decoder::probe() found in a file.
|
||||
* oaknode has no C API counterpart, so codec keeps its own copy. Video and
|
||||
* subtitle streams are stored as oakcommon by-value handles; the class
|
||||
* addrefs on insert/copy and releases on destruction. The original's
|
||||
* Track::Type mapping (get_type_of_stream) and XML load/save (probe cache)
|
||||
* belonged to the oaknode-facing side and are intentionally not reproduced
|
||||
* here; consumers can use stream_is_video/audio/subtitle.
|
||||
*/
|
||||
class FootageDescription {
|
||||
public:
|
||||
FootageDescription(const std::string &decoder = std::string())
|
||||
: decoder_(decoder)
|
||||
, total_stream_count_(0)
|
||||
, has_source_start_time_(false)
|
||||
{
|
||||
}
|
||||
|
||||
FootageDescription(const FootageDescription &other)
|
||||
: decoder_(other.decoder_)
|
||||
, video_streams_(other.video_streams_)
|
||||
, audio_streams_(other.audio_streams_)
|
||||
, subtitle_streams_(other.subtitle_streams_)
|
||||
, total_stream_count_(other.total_stream_count_)
|
||||
, source_start_time_(other.source_start_time_)
|
||||
, source_start_time_source_(other.source_start_time_source_)
|
||||
, has_source_start_time_(other.has_source_start_time_)
|
||||
{
|
||||
for (const OakVideoParams &h : video_streams_) {
|
||||
if (h.ctx) {
|
||||
h.addref(h.ctx);
|
||||
}
|
||||
}
|
||||
for (const OakSubtitleParams &h : subtitle_streams_) {
|
||||
if (h.ctx) {
|
||||
h.addref(h.ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FootageDescription &operator=(const FootageDescription &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
release_streams();
|
||||
decoder_ = other.decoder_;
|
||||
video_streams_ = other.video_streams_;
|
||||
audio_streams_ = other.audio_streams_;
|
||||
subtitle_streams_ = other.subtitle_streams_;
|
||||
total_stream_count_ = other.total_stream_count_;
|
||||
source_start_time_ = other.source_start_time_;
|
||||
source_start_time_source_ = other.source_start_time_source_;
|
||||
has_source_start_time_ = other.has_source_start_time_;
|
||||
for (const OakVideoParams &h : video_streams_) {
|
||||
if (h.ctx) {
|
||||
h.addref(h.ctx);
|
||||
}
|
||||
}
|
||||
for (const OakSubtitleParams &h : subtitle_streams_) {
|
||||
if (h.ctx) {
|
||||
h.addref(h.ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~FootageDescription()
|
||||
{
|
||||
release_streams();
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return !decoder_.empty() &&
|
||||
(!video_streams_.empty() || !audio_streams_.empty() ||
|
||||
!subtitle_streams_.empty());
|
||||
}
|
||||
|
||||
const std::string &decoder() const
|
||||
{
|
||||
return decoder_;
|
||||
}
|
||||
|
||||
void add_video_stream(const OakVideoParams &video_params)
|
||||
{
|
||||
assert(!has_stream_index(stream_index_of(video_params)));
|
||||
|
||||
if (video_params.ctx) {
|
||||
video_params.addref(video_params.ctx);
|
||||
}
|
||||
video_streams_.push_back(video_params);
|
||||
}
|
||||
|
||||
void add_audio_stream(const AudioParams &audio_params)
|
||||
{
|
||||
assert(!has_stream_index(audio_params.stream_index()));
|
||||
|
||||
audio_streams_.push_back(audio_params);
|
||||
}
|
||||
|
||||
void add_subtitle_stream(const OakSubtitleParams &sub_params)
|
||||
{
|
||||
assert(!has_stream_index(stream_index_of(sub_params)));
|
||||
|
||||
if (sub_params.ctx) {
|
||||
sub_params.addref(sub_params.ctx);
|
||||
}
|
||||
subtitle_streams_.push_back(sub_params);
|
||||
}
|
||||
|
||||
bool stream_is_video(int index) const
|
||||
{
|
||||
for (const OakVideoParams &vp : video_streams_) {
|
||||
if (stream_index_of(vp) == index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool stream_is_audio(int index) const
|
||||
{
|
||||
for (const AudioParams &ap : audio_streams_) {
|
||||
if (ap.stream_index() == index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool stream_is_subtitle(int index) const
|
||||
{
|
||||
for (const OakSubtitleParams &sp : subtitle_streams_) {
|
||||
if (stream_index_of(sp) == index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_stream_index(int index) const
|
||||
{
|
||||
return stream_is_video(index) || stream_is_audio(index) ||
|
||||
stream_is_subtitle(index);
|
||||
}
|
||||
|
||||
int get_stream_count() const
|
||||
{
|
||||
return total_stream_count_;
|
||||
}
|
||||
void set_stream_count(int s)
|
||||
{
|
||||
total_stream_count_ = s;
|
||||
}
|
||||
|
||||
void set_source_start_time(const Rational &time, const std::string &source)
|
||||
{
|
||||
source_start_time_ = time;
|
||||
source_start_time_source_ = source;
|
||||
has_source_start_time_ = true;
|
||||
}
|
||||
|
||||
bool has_source_start_time() const
|
||||
{
|
||||
return has_source_start_time_;
|
||||
}
|
||||
|
||||
const Rational &source_start_time() const
|
||||
{
|
||||
return source_start_time_;
|
||||
}
|
||||
|
||||
const std::string &source_start_time_source() const
|
||||
{
|
||||
return source_start_time_source_;
|
||||
}
|
||||
|
||||
const std::vector<OakVideoParams> &get_video_streams() const
|
||||
{
|
||||
return video_streams_;
|
||||
}
|
||||
|
||||
const std::vector<AudioParams> &get_audio_streams() const
|
||||
{
|
||||
return audio_streams_;
|
||||
}
|
||||
std::vector<AudioParams> &get_audio_streams()
|
||||
{
|
||||
return audio_streams_;
|
||||
}
|
||||
|
||||
const std::vector<OakSubtitleParams> &get_subtitle_streams() const
|
||||
{
|
||||
return subtitle_streams_;
|
||||
}
|
||||
|
||||
private:
|
||||
static int stream_index_of(const OakVideoParams ¶ms)
|
||||
{
|
||||
int index = -1;
|
||||
oakcommon_videoparams_get_stream_index(params, &index);
|
||||
return index;
|
||||
}
|
||||
|
||||
static int stream_index_of(const OakSubtitleParams ¶ms)
|
||||
{
|
||||
int index = -1;
|
||||
oakcommon_subtitleparams_get_stream_index(params, &index);
|
||||
return index;
|
||||
}
|
||||
|
||||
void release_streams()
|
||||
{
|
||||
for (OakVideoParams &h : video_streams_) {
|
||||
if (h.ctx) {
|
||||
h.release(h.ctx);
|
||||
}
|
||||
}
|
||||
video_streams_.clear();
|
||||
for (OakSubtitleParams &h : subtitle_streams_) {
|
||||
if (h.ctx) {
|
||||
h.release(h.ctx);
|
||||
}
|
||||
}
|
||||
subtitle_streams_.clear();
|
||||
}
|
||||
|
||||
std::string decoder_;
|
||||
|
||||
std::vector<OakVideoParams> video_streams_;
|
||||
|
||||
std::vector<AudioParams> audio_streams_;
|
||||
|
||||
std::vector<OakSubtitleParams> subtitle_streams_;
|
||||
|
||||
int total_stream_count_;
|
||||
|
||||
Rational source_start_time_;
|
||||
|
||||
std::string source_start_time_source_;
|
||||
|
||||
bool has_source_start_time_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CODEC_FOOTAGEDESCRIPTION_H
|
||||
@@ -0,0 +1,237 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "frame.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
|
||||
#include "common/oiioutils.h"
|
||||
#include "framemanager.h"
|
||||
#include "oiioframebridge.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
Frame::Frame()
|
||||
: params_(oakcommon_videoparams_init())
|
||||
, data_(nullptr)
|
||||
, data_size_(0)
|
||||
, timestamp_(0)
|
||||
, linesize_(0)
|
||||
, linesize_pixels_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Frame::~Frame()
|
||||
{
|
||||
destroy();
|
||||
|
||||
if (params_.ctx && params_.release) {
|
||||
params_.release(params_.ctx);
|
||||
params_.ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
FramePtr Frame::create()
|
||||
{
|
||||
return std::make_shared<Frame>();
|
||||
}
|
||||
|
||||
OakVideoParams Frame::video_params() const
|
||||
{
|
||||
OakVideoParams copy = params_;
|
||||
if (copy.ctx && copy.addref) {
|
||||
copy.addref(copy.ctx);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
void Frame::set_video_params(const OakVideoParams ¶ms)
|
||||
{
|
||||
if (params_.ctx && params_.release) {
|
||||
params_.release(params_.ctx);
|
||||
}
|
||||
|
||||
params_ = params;
|
||||
|
||||
if (params_.ctx && params_.addref) {
|
||||
params_.addref(params_.ctx);
|
||||
}
|
||||
|
||||
linesize_ = generate_linesize_bytes(width(), format(), channel_count());
|
||||
|
||||
int bpp = bytes_per_pixel();
|
||||
linesize_pixels_ = bpp > 0 ? linesize_ / bpp : 0;
|
||||
}
|
||||
|
||||
FramePtr Frame::interlace(FramePtr top, FramePtr bottom)
|
||||
{
|
||||
OakVideoParams top_params = top->video_params();
|
||||
OakVideoParams bottom_params = bottom->video_params();
|
||||
|
||||
int equal = 0;
|
||||
oakcommon_videoparams_equals(top_params, bottom_params, &equal);
|
||||
|
||||
oakcommon_videoparams_free(&bottom_params);
|
||||
|
||||
if (!equal) {
|
||||
fprintf(stderr,
|
||||
"Tried to interlace two frames that had incompatible parameters\n");
|
||||
oakcommon_videoparams_free(&top_params);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FramePtr interlaced = Frame::create();
|
||||
interlaced->set_video_params(top_params);
|
||||
oakcommon_videoparams_free(&top_params);
|
||||
interlaced->allocate();
|
||||
|
||||
int linesize = interlaced->linesize_bytes();
|
||||
|
||||
for (int i = 0; i < interlaced->height(); i++) {
|
||||
FramePtr which = (i % 2 == 0) ? top : bottom;
|
||||
|
||||
memcpy(interlaced->data() + i * linesize,
|
||||
which->const_data() + i * linesize, linesize);
|
||||
}
|
||||
|
||||
return interlaced;
|
||||
}
|
||||
|
||||
int Frame::generate_linesize_bytes(int width, int format, int channel_count)
|
||||
{
|
||||
// Align to 32 bytes (not sure if this is necessary?)
|
||||
int bytes_per_pixel = oakcommon_videoparams_static_get_bytes_per_pixel(
|
||||
static_cast<OakPixelFormat>(format), channel_count);
|
||||
return bytes_per_pixel * ((width + 31) & ~31);
|
||||
}
|
||||
|
||||
core::Color Frame::get_pixel(int x, int y) const
|
||||
{
|
||||
if (!contains_pixel(x, y)) {
|
||||
return core::Color();
|
||||
}
|
||||
|
||||
int byte_offset = y * linesize_bytes() + x * bytes_per_pixel();
|
||||
|
||||
return core::Color(reinterpret_cast<const char *>(data_ + byte_offset),
|
||||
core_format(), channel_count());
|
||||
}
|
||||
|
||||
bool Frame::contains_pixel(int x, int y) const
|
||||
{
|
||||
return (is_allocated() && x >= 0 && x < width() && y >= 0 && y < height());
|
||||
}
|
||||
|
||||
void Frame::set_pixel(int x, int y, const core::Color &c)
|
||||
{
|
||||
if (!contains_pixel(x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int byte_offset = y * linesize_bytes() + x * bytes_per_pixel();
|
||||
|
||||
c.to_data(reinterpret_cast<char *>(data_ + byte_offset), core_format(),
|
||||
channel_count());
|
||||
}
|
||||
|
||||
bool Frame::allocate()
|
||||
{
|
||||
// Assume this frame is intended to be a video frame
|
||||
int is_valid = 0;
|
||||
oakcommon_videoparams_get_is_valid(params_, &is_valid);
|
||||
if (!is_valid) {
|
||||
std::cerr << "Tried to allocate a frame with invalid parameters";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_allocated()) {
|
||||
// Already allocated
|
||||
return true;
|
||||
}
|
||||
|
||||
data_size_ = linesize_ * height();
|
||||
data_ = FrameManager::allocate(data_size_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Frame::destroy()
|
||||
{
|
||||
if (is_allocated()) {
|
||||
FrameManager::deallocate(data_size_, data_);
|
||||
|
||||
data_size_ = 0;
|
||||
data_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief OIIO base type for a native pixel format, via the oakcommon C ABI
|
||||
*
|
||||
* The OakOIIOUtils object is stateless; a process-lifetime handle is kept
|
||||
* here to avoid re-boxing it on every conversion.
|
||||
*/
|
||||
static OIIO::TypeDesc::BASETYPE oiio_base_type_for_format(int format)
|
||||
{
|
||||
static OakOIIOUtils utils = oakcommon_oiioutils_init();
|
||||
|
||||
int base_type = 0; // OIIO::TypeDesc::UNKNOWN
|
||||
oakcommon_oiioutils_get_oiio_base_type_from_format(utils, format,
|
||||
&base_type);
|
||||
return static_cast<OIIO::TypeDesc::BASETYPE>(base_type);
|
||||
}
|
||||
|
||||
FramePtr Frame::convert(int format) const
|
||||
{
|
||||
// Create new params with destination format
|
||||
OakVideoParams params = video_params();
|
||||
oakcommon_videoparams_set_format(params, format);
|
||||
|
||||
// Create new frame
|
||||
FramePtr converted = Frame::create();
|
||||
converted->set_video_params(params);
|
||||
oakcommon_videoparams_free(¶ms);
|
||||
converted->set_timestamp(timestamp_);
|
||||
converted->allocate();
|
||||
|
||||
// Do the conversion through OIIO for convenience
|
||||
OIIO::ImageBuf src(OIIO::ImageSpec(width(), height(), channel_count(),
|
||||
oiio_base_type_for_format(this->format())));
|
||||
|
||||
oiio_frame_to_buffer(const_data(), linesize_bytes(), &src);
|
||||
|
||||
OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(),
|
||||
channel_count(),
|
||||
oiio_base_type_for_format(format)));
|
||||
|
||||
if (dst.copy_pixels(src)) {
|
||||
oiio_buffer_to_frame(&dst, converted->data(),
|
||||
converted->linesize_bytes());
|
||||
return converted;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_FRAME_H
|
||||
#define OAK_FRAME_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <olive/core/render/pixelformat.h>
|
||||
#include <olive/core/util/color.h>
|
||||
#include <olive/core/util/rational.h>
|
||||
|
||||
#include "common/videoparams.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class Frame;
|
||||
using FramePtr = std::shared_ptr<Frame>;
|
||||
|
||||
/**
|
||||
* @brief Video frame data or audio sample data from a Decoder
|
||||
*
|
||||
* Qt-free (M5). The parameter set is held as an OakVideoParams handle
|
||||
* (oakcommon C ABI); every access goes through the oakcommon_videoparams_*
|
||||
* functions. Pixel formats cross the boundary as OakPixelFormat int codes
|
||||
* (numerically identical to olive::core::PixelFormat::Format).
|
||||
*/
|
||||
class Frame {
|
||||
public:
|
||||
Frame();
|
||||
|
||||
~Frame();
|
||||
|
||||
Frame(const Frame &) = delete;
|
||||
|
||||
static FramePtr create();
|
||||
|
||||
/**
|
||||
* @brief Return a copy of the frame's parameter handle
|
||||
*
|
||||
* The returned handle has had its reference count incremented; the
|
||||
* caller must release it (oakcommon_videoparams_free()).
|
||||
*/
|
||||
OakVideoParams video_params() const;
|
||||
void set_video_params(const OakVideoParams ¶ms);
|
||||
|
||||
static FramePtr interlace(FramePtr top, FramePtr bottom);
|
||||
|
||||
static int generate_linesize_bytes(int width, int format,
|
||||
int channel_count);
|
||||
|
||||
int linesize_pixels() const
|
||||
{
|
||||
return linesize_pixels_;
|
||||
}
|
||||
|
||||
int linesize_bytes() const
|
||||
{
|
||||
return linesize_;
|
||||
}
|
||||
|
||||
int width() const
|
||||
{
|
||||
int width = 0;
|
||||
oakcommon_videoparams_get_effective_width(params_, &width);
|
||||
return width;
|
||||
}
|
||||
|
||||
int height() const
|
||||
{
|
||||
int height = 0;
|
||||
oakcommon_videoparams_get_effective_height(params_, &height);
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pixel format as an OakPixelFormat value
|
||||
* (== olive::core::PixelFormat::Format ordinal).
|
||||
*/
|
||||
int format() const
|
||||
{
|
||||
int format = OAKCOMMON_PIXEL_FORMAT_INVALID;
|
||||
oakcommon_videoparams_get_format(params_, &format);
|
||||
return format;
|
||||
}
|
||||
|
||||
int channel_count() const
|
||||
{
|
||||
int count = 0;
|
||||
oakcommon_videoparams_get_channel_count(params_, &count);
|
||||
return count;
|
||||
}
|
||||
|
||||
core::Color get_pixel(int x, int y) const;
|
||||
bool contains_pixel(int x, int y) const;
|
||||
void set_pixel(int x, int y, const core::Color &c);
|
||||
|
||||
/**
|
||||
* @brief Get frame's timestamp.
|
||||
*
|
||||
* This timestamp is always a Rational that will equate to the time in seconds.
|
||||
*/
|
||||
const core::Rational ×tamp() const
|
||||
{
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
void set_timestamp(const core::Rational ×tamp)
|
||||
{
|
||||
timestamp_ = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the data buffer of this frame
|
||||
*/
|
||||
char *data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the const data buffer of this frame
|
||||
*/
|
||||
const char *const_data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate memory buffer to store data based on parameters
|
||||
*
|
||||
* For video frames, the width(), height(), and format() must be set for this function to work.
|
||||
*
|
||||
* If a memory buffer has been previously allocated without destroying, this function will destroy it.
|
||||
*/
|
||||
bool allocate();
|
||||
|
||||
/**
|
||||
* @brief Return whether the frame is allocated or not
|
||||
*/
|
||||
bool is_allocated() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy a memory buffer allocated with allocate()
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* @brief Returns the size of the array returned in data() in bytes
|
||||
*
|
||||
* Returns 0 if nothing is allocated.
|
||||
*/
|
||||
int allocated_size() const
|
||||
{
|
||||
return data_size_;
|
||||
}
|
||||
|
||||
FramePtr convert(int format) const;
|
||||
|
||||
private:
|
||||
int bytes_per_pixel() const
|
||||
{
|
||||
int bytes = 0;
|
||||
oakcommon_videoparams_get_bytes_per_pixel(params_, &bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
core::PixelFormat core_format() const
|
||||
{
|
||||
return core::PixelFormat(
|
||||
static_cast<core::PixelFormat::Format>(format()));
|
||||
}
|
||||
|
||||
OakVideoParams params_;
|
||||
|
||||
char *data_;
|
||||
int data_size_;
|
||||
|
||||
core::Rational timestamp_;
|
||||
|
||||
int linesize_;
|
||||
|
||||
int linesize_pixels_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // OAK_FRAME_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
target_sources(oakcodec PRIVATE
|
||||
oiiodecoder.cpp
|
||||
oiiodecoder.h
|
||||
oiioencoder.cpp
|
||||
oiioencoder.h
|
||||
)
|
||||
@@ -0,0 +1,400 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "oiiodecoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
#include "common/oiioutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
std::vector<std::string> OIIODecoder::supported_formats;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Thin wrappers over the oakcommon_oiioutils_* C API (replacing the former
|
||||
// OIIOUtils C++ class). The handle is stateless, so it is created and
|
||||
// released per call.
|
||||
|
||||
int pix_format_from_oiio_basetype(int base_type)
|
||||
{
|
||||
int out = OAKCOMMON_PIXEL_FORMAT_INVALID;
|
||||
OakOIIOUtils utils = oakcommon_oiioutils_init();
|
||||
oakcommon_oiioutils_get_format_from_oiio_basetype(utils, base_type, &out);
|
||||
oakcommon_oiioutils_free(&utils);
|
||||
return out;
|
||||
}
|
||||
|
||||
int oiio_base_type_from_pix_format(int pixel_format)
|
||||
{
|
||||
int out = 0; // OIIO::TypeDesc::UNKNOWN
|
||||
OakOIIOUtils utils = oakcommon_oiioutils_init();
|
||||
oakcommon_oiioutils_get_oiio_base_type_from_format(utils, pixel_format,
|
||||
&out);
|
||||
oakcommon_oiioutils_free(&utils);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Flatten an OakVideoParams handle into the oakrender POD
|
||||
* (needed at every texture creation point)
|
||||
*/
|
||||
void fill_render_params(const OakVideoParams ¶ms,
|
||||
oakrender_video_params *out)
|
||||
{
|
||||
*out = oakrender_video_params{};
|
||||
oakcommon_videoparams_get_width(params, &out->width);
|
||||
oakcommon_videoparams_get_height(params, &out->height);
|
||||
oakcommon_videoparams_get_time_base(params, &out->time_base_num,
|
||||
&out->time_base_den);
|
||||
oakcommon_videoparams_get_format(params, &out->format);
|
||||
oakcommon_videoparams_get_pixel_aspect_ratio(
|
||||
params, &out->pixel_aspect_num, &out->pixel_aspect_den);
|
||||
oakcommon_videoparams_get_interlacing(params, &out->interlacing);
|
||||
oakcommon_videoparams_get_color_range(params, &out->color_range);
|
||||
oakcommon_videoparams_get_divider(params, &out->divider);
|
||||
oakcommon_videoparams_get_video_type(params, &out->video_type);
|
||||
oakcommon_videoparams_get_premultiplied_alpha(params,
|
||||
&out->premultiplied_alpha);
|
||||
}
|
||||
|
||||
std::vector<std::string> split_string(const std::string &s, char delimiter)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
std::string::size_type start = 0;
|
||||
while (true) {
|
||||
std::string::size_type pos = s.find(delimiter, start);
|
||||
if (pos == std::string::npos) {
|
||||
out.push_back(s.substr(start));
|
||||
break;
|
||||
}
|
||||
out.push_back(s.substr(start, pos - start));
|
||||
start = pos + 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string to_lower(const std::string &s)
|
||||
{
|
||||
std::string out = s;
|
||||
std::transform(out.begin(), out.end(), out.begin(),
|
||||
[](unsigned char c) { return char(std::tolower(c)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OIIODecoder::OIIODecoder()
|
||||
: image_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
std::string OIIODecoder::id() const
|
||||
{
|
||||
return "oiio";
|
||||
}
|
||||
|
||||
FootageDescription OIIODecoder::probe(const std::string &filename,
|
||||
OakCancelAtom *cancelled) const
|
||||
{
|
||||
(void) cancelled;
|
||||
|
||||
FootageDescription desc(id());
|
||||
|
||||
// Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying
|
||||
// to open a file that it can't if it's given one
|
||||
if (!file_type_is_supported(filename)) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
auto in = OIIO::ImageInput::open(filename);
|
||||
|
||||
if (!in) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
// Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle
|
||||
// it better
|
||||
if (!strcmp(in->format_name(), "FFmpeg movie")) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
bool stream_enabled = true;
|
||||
|
||||
int i;
|
||||
for (i = 0; in->seek_subimage(i, 0); i++) {
|
||||
OIIO::ImageSpec spec = in->spec();
|
||||
|
||||
OakVideoParams video_params = get_video_params_from_image_spec(spec);
|
||||
|
||||
oakcommon_videoparams_set_stream_index(video_params, i);
|
||||
|
||||
if (i > 1) {
|
||||
// This is a multilayer image and this image might have an offset
|
||||
OIIO::ImageSpec root_spec = in->spec(0);
|
||||
|
||||
float norm_x = spec.x + float(spec.width) * 0.5f -
|
||||
float(root_spec.width) * 0.5f;
|
||||
float norm_y = spec.y + float(spec.height) * 0.5f -
|
||||
float(root_spec.height) * 0.5f;
|
||||
|
||||
oakcommon_videoparams_set_x(video_params, norm_x);
|
||||
oakcommon_videoparams_set_y(video_params, norm_y);
|
||||
}
|
||||
|
||||
// By default, only enable the first subimage (presumably the combined image). Later we will
|
||||
// ask the user if they want to enable the layers instead.
|
||||
oakcommon_videoparams_set_enabled(video_params, stream_enabled ? 1 : 0);
|
||||
stream_enabled = false;
|
||||
|
||||
// OIIO automatically premultiplies alpha
|
||||
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
|
||||
// likely reduces the fidelity?
|
||||
oakcommon_videoparams_set_premultiplied_alpha(video_params, 1);
|
||||
|
||||
desc.add_video_stream(video_params);
|
||||
oakcommon_videoparams_free(&video_params);
|
||||
}
|
||||
|
||||
desc.set_stream_count(i);
|
||||
|
||||
// If we're here, we have a successful image open
|
||||
in->close();
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
bool OIIODecoder::open_internal()
|
||||
{
|
||||
// If we can open the filename provided, assume everything is working
|
||||
return open_image_handler(stream().filename(), stream().stream());
|
||||
}
|
||||
|
||||
OakRenderTexture *
|
||||
OIIODecoder::retrieve_video_internal(const RetrieveVideoParams &p)
|
||||
{
|
||||
FramePtr frame = retrieve_video_frame_internal(p);
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OakVideoParams frame_params = frame->video_params(); // addref'd copy
|
||||
oakrender_video_params rvp;
|
||||
fill_render_params(frame_params, &rvp);
|
||||
oakcommon_videoparams_free(&frame_params);
|
||||
|
||||
// Frame linesize is already in bytes, which is what the C API expects
|
||||
return oakrender_display_texture_create(p.renderer, &rvp, frame->data(),
|
||||
frame->linesize_bytes());
|
||||
}
|
||||
|
||||
FramePtr OIIODecoder::retrieve_video_frame_internal(const RetrieveVideoParams &p)
|
||||
{
|
||||
OakVideoParams vp = get_video_params_from_image_spec(image_->spec());
|
||||
oakcommon_videoparams_set_divider(vp, p.divider);
|
||||
|
||||
if (!buffer_.is_allocated() || last_params_.divider != p.divider) {
|
||||
last_params_ = p;
|
||||
|
||||
buffer_.destroy();
|
||||
buffer_.set_video_params(vp); // Frame addrefs the handle
|
||||
buffer_.allocate();
|
||||
|
||||
if (p.divider == 1) {
|
||||
// Just upload straight to the buffer
|
||||
image_->read_image(0, 0, 0, -1, oiio_pix_fmt_, buffer_.data());
|
||||
} else {
|
||||
OIIO::ImageBuf buf(image_->spec());
|
||||
image_->read_image(0, 0, 0, -1, image_->spec().format,
|
||||
buf.localpixels(), buf.pixel_stride(),
|
||||
buf.scanline_stride(), buf.z_stride());
|
||||
|
||||
// Roughly downsample image for divider (for some reason OIIO::ImageBufAlgo::resample failed here)
|
||||
int px_sz = 0;
|
||||
oakcommon_videoparams_get_bytes_per_pixel(vp, &px_sz);
|
||||
for (int dst_y = 0; dst_y < buffer_.height(); dst_y++) {
|
||||
int src_y = dst_y * buf.spec().height / buffer_.height();
|
||||
|
||||
for (int dst_x = 0; dst_x < buffer_.width(); dst_x++) {
|
||||
int src_x = dst_x * buf.spec().width / buffer_.width();
|
||||
memcpy(buffer_.data() + buffer_.linesize_bytes() * dst_y +
|
||||
px_sz * dst_x,
|
||||
static_cast<uint8_t *>(buf.localpixels()) +
|
||||
buf.scanline_stride() * src_y + px_sz * src_x,
|
||||
px_sz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int format = OAKCOMMON_PIXEL_FORMAT_INVALID;
|
||||
oakcommon_videoparams_get_format(vp, &format);
|
||||
oakcommon_videoparams_free(&vp);
|
||||
|
||||
// Force F32 output for all still images
|
||||
if (format != PixelFormat::f32) {
|
||||
FramePtr f32_frame = buffer_.convert(PixelFormat::f32);
|
||||
if (f32_frame) {
|
||||
f32_frame->set_timestamp(p.time);
|
||||
return f32_frame;
|
||||
}
|
||||
}
|
||||
|
||||
FramePtr frame = Frame::create();
|
||||
OakVideoParams buffer_params = buffer_.video_params(); // addref'd copy
|
||||
frame->set_video_params(buffer_params);
|
||||
oakcommon_videoparams_free(&buffer_params);
|
||||
frame->set_timestamp(p.time);
|
||||
if (!frame->allocate()) {
|
||||
return nullptr;
|
||||
}
|
||||
memcpy(frame->data(), buffer_.const_data(),
|
||||
size_t(buffer_.allocated_size()));
|
||||
return frame;
|
||||
}
|
||||
|
||||
void OIIODecoder::close_internal()
|
||||
{
|
||||
close_image_handle();
|
||||
}
|
||||
|
||||
bool OIIODecoder::file_type_is_supported(const std::string &fn)
|
||||
{
|
||||
// We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
|
||||
// will segfault entirely if given unexpected data (an MPEG-4 for instance). To workaround this issue, we use OIIO's
|
||||
// "extension_list" attribute and match it with the extension of the file.
|
||||
|
||||
// Check if we've created the supported formats list, create it if not
|
||||
if (supported_formats.empty()) {
|
||||
std::vector<std::string> extension_list =
|
||||
split_string(OIIO::get_string_attribute("extension_list"), ';');
|
||||
|
||||
// The format of "extension_list" is "format:ext", we want to separate it into a simple list of extensions
|
||||
for (const std::string &ext : extension_list) {
|
||||
std::vector<std::string> format_and_ext = split_string(ext, ':');
|
||||
|
||||
if (format_and_ext.size() >= 2) {
|
||||
std::vector<std::string> exts =
|
||||
split_string(format_and_ext.at(1), ',');
|
||||
supported_formats.insert(supported_formats.end(),
|
||||
exts.begin(), exts.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// QFileInfo::suffix(): extension after the last '.', case-insensitive match
|
||||
std::string suffix = std::filesystem::path(fn).extension().string();
|
||||
if (!suffix.empty() && suffix.front() == '.') {
|
||||
suffix.erase(0, 1);
|
||||
}
|
||||
suffix = to_lower(suffix);
|
||||
|
||||
for (const std::string &supported : supported_formats) {
|
||||
if (to_lower(supported) == suffix) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OIIODecoder::open_image_handler(const std::string &fn, int subimage)
|
||||
{
|
||||
image_ = OIIO::ImageInput::open(fn);
|
||||
|
||||
if (!image_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!image_->seek_subimage(subimage, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we can work with this pixel format
|
||||
const OIIO::ImageSpec &spec = image_->spec();
|
||||
|
||||
// We use RGBA frames because that tends to be the native format of GPUs
|
||||
pix_fmt_ = static_cast<PixelFormat::Format>(
|
||||
pix_format_from_oiio_basetype(spec.format.basetype));
|
||||
|
||||
if (pix_fmt_ == PixelFormat::invalid) {
|
||||
fprintf(stderr, "Failed to convert OIIO::ImageDesc to native pixel format\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
oiio_pix_fmt_ =
|
||||
static_cast<OIIO::TypeDesc::BASETYPE>(
|
||||
oiio_base_type_from_pix_format(pix_fmt_));
|
||||
|
||||
if (oiio_pix_fmt_ == OIIO::TypeDesc::UNKNOWN) {
|
||||
fprintf(stderr, "Failed to determine appropriate OIIO basetype from native format\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OIIODecoder::close_image_handle()
|
||||
{
|
||||
if (image_) {
|
||||
image_->close();
|
||||
image_ = nullptr;
|
||||
}
|
||||
|
||||
buffer_.destroy();
|
||||
}
|
||||
|
||||
OakVideoParams
|
||||
OIIODecoder::get_video_params_from_image_spec(const OIIO::ImageSpec &spec)
|
||||
{
|
||||
OakVideoParams video_params = oakcommon_videoparams_init();
|
||||
|
||||
oakcommon_videoparams_set_width(video_params, spec.width);
|
||||
oakcommon_videoparams_set_height(video_params, spec.height);
|
||||
oakcommon_videoparams_set_format(
|
||||
video_params, pix_format_from_oiio_basetype(spec.format.basetype));
|
||||
oakcommon_videoparams_set_channel_count(video_params, spec.nchannels);
|
||||
|
||||
int par_num = 1, par_den = 1;
|
||||
{
|
||||
OakOIIOUtils utils = oakcommon_oiioutils_init();
|
||||
oakcommon_oiioutils_get_pixel_aspect_ratio(
|
||||
utils, spec.get_float_attribute("PixelAspectRatio", 1.0f),
|
||||
&par_num, &par_den);
|
||||
oakcommon_oiioutils_free(&utils);
|
||||
}
|
||||
oakcommon_videoparams_set_pixel_aspect_ratio(video_params, par_num,
|
||||
par_den);
|
||||
|
||||
oakcommon_videoparams_set_video_type(video_params,
|
||||
OAKCOMMON_VIDEO_TYPE_STILL);
|
||||
|
||||
return video_params;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_OIIODECODER_H
|
||||
#define OAK_OIIODECODER_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <OpenImageIO/imageio.h>
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
|
||||
#include "decoder.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OIIODecoder : public Decoder {
|
||||
public:
|
||||
OIIODecoder();
|
||||
|
||||
DECODER_DEFAULT_DESTRUCTOR(OIIODecoder)
|
||||
|
||||
virtual std::string id() const override;
|
||||
|
||||
virtual bool supports_video() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual FootageDescription probe(const std::string &filename,
|
||||
OakCancelAtom *cancelled) const override;
|
||||
|
||||
protected:
|
||||
virtual bool open_internal() override;
|
||||
virtual OakRenderTexture *
|
||||
retrieve_video_internal(const RetrieveVideoParams &p) override;
|
||||
virtual FramePtr
|
||||
retrieve_video_frame_internal(const RetrieveVideoParams &p) override;
|
||||
virtual void close_internal() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<OIIO::ImageInput> image_;
|
||||
|
||||
static bool file_type_is_supported(const std::string &fn);
|
||||
|
||||
bool open_image_handler(const std::string &fn, int subimage);
|
||||
|
||||
void close_image_handle();
|
||||
|
||||
static OakVideoParams
|
||||
get_video_params_from_image_spec(const OIIO::ImageSpec &spec);
|
||||
|
||||
PixelFormat pix_fmt_;
|
||||
OIIO::TypeDesc::BASETYPE oiio_pix_fmt_;
|
||||
|
||||
Frame buffer_;
|
||||
RetrieveVideoParams last_params_;
|
||||
|
||||
static std::vector<std::string> supported_formats;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_OIIODECODER_H
|
||||
@@ -0,0 +1,92 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "oiioencoder.h"
|
||||
|
||||
#include <OpenImageIO/imageio.h>
|
||||
|
||||
#include "common/oiioutils.h"
|
||||
|
||||
OIIO_NAMESPACE_USING
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
OIIOEncoder::OIIOEncoder(const EncodingParams ¶ms) : Encoder(params) {}
|
||||
|
||||
bool OIIOEncoder::open()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::write_frame(FramePtr frame, Rational time)
|
||||
{
|
||||
std::string filename = get_filename_for_frame(time);
|
||||
|
||||
auto output = OIIO::ImageOutput::create(filename);
|
||||
if (!output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int base_type = 0; // OIIO::TypeDesc::UNKNOWN
|
||||
OakOIIOUtils oiio_utils = oakcommon_oiioutils_init();
|
||||
oakcommon_oiioutils_get_oiio_base_type_from_format(
|
||||
oiio_utils, frame->format(), &base_type);
|
||||
oakcommon_oiioutils_free(&oiio_utils);
|
||||
|
||||
OIIO::TypeDesc type(static_cast<OIIO::TypeDesc::BASETYPE>(base_type));
|
||||
OIIO::ImageSpec spec(frame->width(), frame->height(),
|
||||
frame->channel_count(), type);
|
||||
|
||||
if (!output->open(filename, spec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!output->write_image(type, frame->data(), OIIO::AutoStride,
|
||||
frame->linesize_bytes())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!output->close()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::write_audio(const SampleBuffer &audio)
|
||||
{
|
||||
// Do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::write_subtitle(const char *text, double in_seconds,
|
||||
double out_seconds)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void OIIOEncoder::close()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_OIIOENCODER_H
|
||||
#define OAK_OIIOENCODER_H
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OIIOEncoder : public Encoder {
|
||||
public:
|
||||
OIIOEncoder(const EncodingParams ¶ms);
|
||||
|
||||
virtual bool open() override;
|
||||
|
||||
virtual bool write_frame(olive::FramePtr frame,
|
||||
olive::core::Rational time) override;
|
||||
virtual bool write_audio(const SampleBuffer &audio) override;
|
||||
virtual bool write_subtitle(const char *text, double in_seconds,
|
||||
double out_seconds) override;
|
||||
|
||||
virtual void close() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_OIIOENCODER_H
|
||||
@@ -0,0 +1,40 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "oiioframebridge.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
void oiio_frame_to_buffer(const void *data, int64_t linesize_bytes,
|
||||
OIIO::ImageBuf *buf)
|
||||
{
|
||||
buf->set_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
|
||||
static_cast<OIIO::stride_t>(linesize_bytes));
|
||||
}
|
||||
|
||||
void oiio_buffer_to_frame(OIIO::ImageBuf *buf, void *data,
|
||||
int64_t linesize_bytes)
|
||||
{
|
||||
buf->get_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
|
||||
static_cast<OIIO::stride_t>(linesize_bytes));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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_OIIOFRAMEBRIDGE_H
|
||||
#define OAK_OIIOFRAMEBRIDGE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Copies raw pixel data into an OIIO image buffer
|
||||
*
|
||||
* Moved from oakcommon's OIIOUtils (M5): these two helpers are only used
|
||||
* by codec (Frame::convert() and the OIIO decoder/encoder), so they live
|
||||
* here as internal C++ functions and no longer cross the oakcommon
|
||||
* boundary. `format`/`nb_channels` describe the raw buffer and are only
|
||||
* needed by callers to have set up `buf`'s spec correctly beforehand;
|
||||
* the copy itself goes by the buffer's spec.
|
||||
*/
|
||||
void oiio_frame_to_buffer(const void *data, int64_t linesize_bytes,
|
||||
OIIO::ImageBuf *buf);
|
||||
|
||||
/**
|
||||
* @brief Copies an OIIO image buffer's pixels into raw memory
|
||||
*/
|
||||
void oiio_buffer_to_frame(OIIO::ImageBuf *buf, void *data,
|
||||
int64_t linesize_bytes);
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_OIIOFRAMEBRIDGE_H
|
||||
@@ -0,0 +1,125 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "planarfiledevice.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
PlanarFileDevice::PlanarFileDevice() = default;
|
||||
|
||||
PlanarFileDevice::~PlanarFileDevice()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool PlanarFileDevice::open(const std::vector<std::string> &filenames,
|
||||
OpenMode mode)
|
||||
{
|
||||
if (isOpen()) {
|
||||
// Already open
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *mode_str = (mode == k_read_only) ? "rb" : "wb";
|
||||
|
||||
files_.resize(filenames.size(), nullptr);
|
||||
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
files_[i] = std::fopen(filenames.at(i).c_str(), mode_str);
|
||||
if (!files_[i]) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t PlanarFileDevice::read(char **data, int64_t bytes_per_channel,
|
||||
int64_t offset)
|
||||
{
|
||||
int64_t ret = -1;
|
||||
|
||||
if (isOpen()) {
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
// Kind of clunky but should be largely fine
|
||||
ret = int64_t(std::fread(data[i] + offset, 1,
|
||||
size_t(bytes_per_channel), files_[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t PlanarFileDevice::write(const char **data, int64_t bytes_per_channel,
|
||||
int64_t offset)
|
||||
{
|
||||
int64_t ret = -1;
|
||||
|
||||
if (isOpen()) {
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
// Kind of clunky but should be largely fine
|
||||
ret = int64_t(std::fwrite(data[i] + offset, 1,
|
||||
size_t(bytes_per_channel), files_[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t PlanarFileDevice::size() const
|
||||
{
|
||||
if (isOpen()) {
|
||||
struct stat st;
|
||||
if (fstat(fileno(files_.front()), &st) == 0) {
|
||||
return int64_t(st.st_size);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool PlanarFileDevice::seek(int64_t pos)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
ret = (std::fseek(files_[i], pos, SEEK_SET) == 0) && ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PlanarFileDevice::close()
|
||||
{
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
std::FILE *f = files_.at(i);
|
||||
if (f) {
|
||||
std::fclose(f);
|
||||
}
|
||||
}
|
||||
files_.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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_PLANARFILEDEVICE_H
|
||||
#define OAK_PLANARFILEDEVICE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Reads/writes interleaved planar channel files
|
||||
*
|
||||
* De-Qt replacement for the QFile-based version; now a thin wrapper over
|
||||
* std::FILE. Not copyable; closes all files on destruction.
|
||||
*/
|
||||
class PlanarFileDevice {
|
||||
public:
|
||||
/**
|
||||
* @brief Open mode (replaces QIODevice::OpenMode)
|
||||
*/
|
||||
enum OpenMode { k_read_only, k_write_only };
|
||||
|
||||
PlanarFileDevice();
|
||||
|
||||
~PlanarFileDevice();
|
||||
|
||||
PlanarFileDevice(const PlanarFileDevice &) = delete;
|
||||
PlanarFileDevice &operator=(const PlanarFileDevice &) = delete;
|
||||
|
||||
bool isOpen() const
|
||||
{
|
||||
return !files_.empty();
|
||||
}
|
||||
|
||||
bool open(const std::vector<std::string> &filenames, OpenMode mode);
|
||||
|
||||
int64_t read(char **data, int64_t bytes_per_channel, int64_t offset = 0);
|
||||
|
||||
int64_t write(const char **data, int64_t bytes_per_channel,
|
||||
int64_t offset = 0);
|
||||
|
||||
int64_t size() const;
|
||||
|
||||
bool seek(int64_t pos);
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
std::vector<std::FILE *> files_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_PLANARFILEDEVICE_H
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Oak Video Editor - 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/>.
|
||||
*/
|
||||
|
||||
#include "proxymanager.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
#include "taskcallbacks.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ProxyManager *ProxyManager::instance_ = nullptr;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief oakcommon C API wrapper for FileFunctions::get_unique_file_identifier
|
||||
*/
|
||||
std::string unique_file_identifier(const std::string &filename)
|
||||
{
|
||||
OakFileFunctions ff = oakcommon_filefunctions_init();
|
||||
if (!ff.ctx) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string result;
|
||||
int size = oakcommon_filefunctions_get_unique_file_identifier(
|
||||
ff, filename.c_str(), nullptr, 0);
|
||||
if (size > 0) {
|
||||
result.resize(size_t(size) - 1); // size includes the NUL
|
||||
oakcommon_filefunctions_get_unique_file_identifier(
|
||||
ff, filename.c_str(), result.data(), size);
|
||||
}
|
||||
|
||||
oakcommon_filefunctions_free(&ff);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief oakcommon C API wrapper for FileFunctions::get_application_path
|
||||
*/
|
||||
std::string application_path()
|
||||
{
|
||||
OakFileFunctions ff = oakcommon_filefunctions_init();
|
||||
if (!ff.ctx) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string result;
|
||||
int size = oakcommon_filefunctions_get_application_path(ff, nullptr, 0);
|
||||
if (size > 0) {
|
||||
result.resize(size_t(size) - 1);
|
||||
oakcommon_filefunctions_get_application_path(ff, result.data(), size);
|
||||
}
|
||||
|
||||
oakcommon_filefunctions_free(&ff);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_executable_file(const std::filesystem::path &p)
|
||||
{
|
||||
std::error_code ec;
|
||||
return std::filesystem::is_regular_file(p, ec) &&
|
||||
::access(p.c_str(), X_OK) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string ProxyManager::get_proxy_directory(const std::string &cache_path)
|
||||
{
|
||||
return (std::filesystem::path(cache_path) / "proxy").string();
|
||||
}
|
||||
|
||||
std::string ProxyManager::get_proxy_filename(const std::string &cache_path,
|
||||
const std::string &source_filename,
|
||||
int stream_index,
|
||||
const ProxyParams ¶ms)
|
||||
{
|
||||
const std::string proxy_dir = get_proxy_directory(cache_path);
|
||||
const std::string extension =
|
||||
params.extension.empty() ? "mp4" : params.extension;
|
||||
|
||||
// Divider mode scales relative to the source, so the tag names the
|
||||
// divider rather than an absolute target size
|
||||
std::string size_tag;
|
||||
if (params.divider > 1) {
|
||||
size_tag = "div" + std::to_string(params.divider);
|
||||
} else {
|
||||
size_tag = std::to_string(params.width) + "x" +
|
||||
std::to_string(params.height);
|
||||
}
|
||||
|
||||
const std::string filename = unique_file_identifier(source_filename) + "-" +
|
||||
std::to_string(stream_index) + "." + size_tag +
|
||||
".v" + std::to_string(params.version) + ".a" +
|
||||
(params.include_audio ? "1" : "0") + "." +
|
||||
extension;
|
||||
|
||||
return (std::filesystem::path(proxy_dir) / filename).string();
|
||||
}
|
||||
|
||||
std::string ProxyManager::get_working_proxy_filename(const std::string &proxy_filename)
|
||||
{
|
||||
// Append a recognizable suffix while keeping a standard container extension
|
||||
// so ffmpeg can infer the output format.
|
||||
return proxy_filename + ".working.mp4";
|
||||
}
|
||||
|
||||
ProxyManager::ProxyState
|
||||
ProxyManager::get_proxy_state(const std::string &proxy_filename)
|
||||
{
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(proxy_filename, ec)) {
|
||||
return k_proxy_ready;
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(get_working_proxy_filename(proxy_filename), ec)) {
|
||||
return k_proxy_generating;
|
||||
}
|
||||
|
||||
return k_proxy_missing;
|
||||
}
|
||||
|
||||
std::string ProxyManager::proxy_state_to_string(ProxyState state)
|
||||
{
|
||||
switch (state) {
|
||||
case k_proxy_missing:
|
||||
return "missing";
|
||||
case k_proxy_generating:
|
||||
return "generating";
|
||||
case k_proxy_ready:
|
||||
return "ready";
|
||||
case k_proxy_failed:
|
||||
return "failed";
|
||||
}
|
||||
|
||||
return "missing";
|
||||
}
|
||||
|
||||
ProxyManager::ProxyState
|
||||
ProxyManager::proxy_state_from_string(const std::string &state)
|
||||
{
|
||||
if (state == "generating") {
|
||||
return k_proxy_generating;
|
||||
}
|
||||
|
||||
if (state == "ready") {
|
||||
return k_proxy_ready;
|
||||
}
|
||||
|
||||
if (state == "failed") {
|
||||
return k_proxy_failed;
|
||||
}
|
||||
|
||||
return k_proxy_missing;
|
||||
}
|
||||
|
||||
bool ProxyManager::proxy_filename_has_audio(const std::string &proxy_filename)
|
||||
{
|
||||
return std::filesystem::path(proxy_filename)
|
||||
.filename()
|
||||
.string()
|
||||
.find(".a1.") != std::string::npos;
|
||||
}
|
||||
|
||||
ProxyManager::ProxyParams ProxyManager::proxy_params_from_config()
|
||||
{
|
||||
// Interim state: the Qt config store (OAK_CONFIG ProxyWidth/ProxyHeight/
|
||||
// ProxyDivider/ProxyCRF/ProxyPreset/ProxyIncludeAudio) is not split yet,
|
||||
// so the compiled-in defaults apply.
|
||||
return ProxyParams();
|
||||
}
|
||||
|
||||
std::string ProxyManager::find_f_fmpeg_executable(const std::string &configured_path)
|
||||
{
|
||||
// An explicitly configured path takes precedence if it is usable
|
||||
if (!configured_path.empty()) {
|
||||
if (is_executable_file(configured_path)) {
|
||||
return std::filesystem::absolute(configured_path).string();
|
||||
}
|
||||
|
||||
fprintf(stderr, "Configured ffmpeg path is not a valid executable: %s\n",
|
||||
configured_path.c_str());
|
||||
}
|
||||
|
||||
// Fall back to searching the system PATH
|
||||
if (const char *path_env = std::getenv("PATH")) {
|
||||
std::string paths = path_env;
|
||||
size_t pos = 0;
|
||||
while (pos <= paths.size()) {
|
||||
size_t colon = paths.find(':', pos);
|
||||
std::string dir = paths.substr(
|
||||
pos, colon == std::string::npos ? colon : colon - pos);
|
||||
if (!dir.empty()) {
|
||||
std::filesystem::path candidate = std::filesystem::path(dir) / "ffmpeg";
|
||||
if (is_executable_file(candidate)) {
|
||||
return candidate.string();
|
||||
}
|
||||
}
|
||||
if (colon == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
pos = colon + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, try common install locations (PATH on GUI-launched apps,
|
||||
// particularly on macOS, often lacks these)
|
||||
std::vector<std::string> candidates;
|
||||
const std::string app_path = application_path();
|
||||
if (!app_path.empty()) {
|
||||
candidates.push_back(app_path + "/ffmpeg");
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
candidates.push_back("/opt/homebrew/bin/ffmpeg");
|
||||
candidates.push_back("/usr/local/bin/ffmpeg");
|
||||
#endif
|
||||
candidates.push_back("/usr/bin/ffmpeg");
|
||||
candidates.push_back("/usr/local/bin/ffmpeg");
|
||||
|
||||
for (const std::string &candidate : candidates) {
|
||||
if (is_executable_file(candidate)) {
|
||||
return std::filesystem::absolute(candidate).string();
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
ProxyManager::Proxy
|
||||
ProxyManager::get_or_start_proxy(const std::string &cache_path,
|
||||
const std::string &source_filename, int stream_index,
|
||||
const ProxyParams ¶ms)
|
||||
{
|
||||
const std::string filename =
|
||||
get_proxy_filename(cache_path, source_filename, stream_index, params);
|
||||
const ProxyState file_state = get_proxy_state(filename);
|
||||
if (file_state == k_proxy_ready) {
|
||||
return { k_proxy_ready, filename };
|
||||
}
|
||||
|
||||
if (!oakcodec_task_submit_is_registered()) {
|
||||
// Interim state (pre-M8): no task system, proxy cannot be generated
|
||||
return { k_proxy_missing, filename };
|
||||
}
|
||||
|
||||
if (file_state == k_proxy_generating) {
|
||||
// Stale working file from an interrupted run
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(get_working_proxy_filename(filename), ec);
|
||||
}
|
||||
|
||||
// The task owns the ".working.mp4" temporary name and the rename to the
|
||||
// final filename on success (previously done in proxy_task_finished).
|
||||
OakCodecTaskRequest req = {};
|
||||
req.kind = OAKCODEC_TASK_PROXY;
|
||||
req.input_filename = source_filename.c_str();
|
||||
req.output_filename = filename.c_str();
|
||||
req.stream_index = stream_index;
|
||||
if (params.divider <= 1) {
|
||||
req.proxy_width = params.width;
|
||||
req.proxy_height = params.height;
|
||||
}
|
||||
|
||||
// Interim simplification: submission is synchronous.
|
||||
int result = SubmitTask(req);
|
||||
if (result < 0) {
|
||||
return { k_proxy_failed, filename };
|
||||
}
|
||||
|
||||
if (get_proxy_state(filename) == k_proxy_ready) {
|
||||
return { k_proxy_ready, filename };
|
||||
}
|
||||
|
||||
return { k_proxy_generating, filename };
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Oak Video Editor - 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_PROXYMANAGER_H
|
||||
#define OAK_PROXYMANAGER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Manages proxy (lower-res stand-in) generation
|
||||
*
|
||||
* Qt-free interim state: actual proxy transcodes are delegated to the
|
||||
* global task submit callback (include/codec/task.h). While no callback
|
||||
* is registered (pre-M8), get_or_start_proxy() reports the proxy as
|
||||
* missing instead of starting background work.
|
||||
*
|
||||
* Behavior changes vs. the Qt version:
|
||||
* - The `proxy_ready`/`proxy_finished` signals are gone; completion
|
||||
* notification is the task system's / facade's business.
|
||||
* - Submission is synchronous: get_or_start_proxy() calls the submit
|
||||
* callback inline and re-derives the state from the filesystem.
|
||||
* - proxy_params_from_config() returns compiled-in defaults until the
|
||||
* config milestone wires a real store.
|
||||
*/
|
||||
class ProxyManager {
|
||||
public:
|
||||
static void create_instance()
|
||||
{
|
||||
if (!instance_) {
|
||||
instance_ = new ProxyManager();
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy_instance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
static ProxyManager *instance()
|
||||
{
|
||||
return instance_;
|
||||
}
|
||||
|
||||
enum ProxyState {
|
||||
k_proxy_missing,
|
||||
k_proxy_generating,
|
||||
k_proxy_ready,
|
||||
k_proxy_failed
|
||||
};
|
||||
|
||||
struct ProxyParams {
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
/**
|
||||
* @brief Source resolution divider (1 = use absolute width/height,
|
||||
* 2/4/8 = fraction of the source resolution)
|
||||
*/
|
||||
int divider = 1;
|
||||
int version = 1;
|
||||
std::string extension = "mp4";
|
||||
int crf = 23;
|
||||
std::string preset = "veryfast";
|
||||
bool include_audio = true;
|
||||
};
|
||||
|
||||
struct Proxy {
|
||||
ProxyState state = k_proxy_missing;
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
static std::string get_proxy_directory(const std::string &cache_path);
|
||||
|
||||
static std::string get_proxy_filename(const std::string &cache_path,
|
||||
const std::string &source_filename,
|
||||
int stream_index,
|
||||
const ProxyParams ¶ms);
|
||||
|
||||
static std::string get_working_proxy_filename(const std::string &proxy_filename);
|
||||
|
||||
static ProxyState get_proxy_state(const std::string &proxy_filename);
|
||||
|
||||
static std::string proxy_state_to_string(ProxyState state);
|
||||
|
||||
static ProxyState proxy_state_from_string(const std::string &state);
|
||||
|
||||
/**
|
||||
* @brief Returns true if a proxy filename generated by GetProxyFilename()
|
||||
* indicates the proxy contains audio streams
|
||||
*/
|
||||
static bool proxy_filename_has_audio(const std::string &proxy_filename);
|
||||
|
||||
/**
|
||||
* @brief Builds proxy parameters from the global application config
|
||||
*
|
||||
* Interim state: returns the compiled-in defaults (1280x720, divider 1,
|
||||
* mp4, crf 23, veryfast, audio included); the config milestone wires
|
||||
* the real store.
|
||||
*/
|
||||
static ProxyParams proxy_params_from_config();
|
||||
|
||||
/**
|
||||
* @brief Locates an ffmpeg executable for proxy generation
|
||||
*
|
||||
* Resolution order: the explicitly configured path (if non-empty and an
|
||||
* existing executable file), then the system PATH, then common
|
||||
* platform-specific install locations. Returns an empty string if no
|
||||
* executable could be found.
|
||||
*/
|
||||
static std::string find_f_fmpeg_executable(const std::string &configured_path);
|
||||
|
||||
Proxy get_or_start_proxy(const std::string &cache_path,
|
||||
const std::string &source_filename, int stream_index,
|
||||
const ProxyParams ¶ms);
|
||||
|
||||
private:
|
||||
ProxyManager() = default;
|
||||
|
||||
static ProxyManager *instance_;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_PROXYMANAGER_H
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
*/
|
||||
|
||||
#include "taskcallbacks.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::mutex g_task_cb_mutex;
|
||||
oakcodec_task_submit_fn g_task_cb = nullptr;
|
||||
void *g_task_cb_userdata = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
void oakcodec_set_task_submit_cb(oakcodec_task_submit_fn cb, void *userdata)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_task_cb_mutex);
|
||||
g_task_cb = cb;
|
||||
g_task_cb_userdata = userdata;
|
||||
}
|
||||
|
||||
int oakcodec_task_submit_is_registered(void)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_task_cb_mutex);
|
||||
return g_task_cb != nullptr ? 1 : 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
int SubmitTask(const OakCodecTaskRequest &req)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_task_cb_mutex);
|
||||
if (!g_task_cb) {
|
||||
return OAKCODEC_E_STATE;
|
||||
}
|
||||
return g_task_cb(&req, g_task_cb_userdata);
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
|
||||
Oak Video Editor - 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_CODEC_TASKCALLBACKS_H
|
||||
#define OAK_CODEC_TASKCALLBACKS_H
|
||||
|
||||
#include "codec/task.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief C++-side convenience wrapper around the registered submit callback
|
||||
*
|
||||
* Returns the callback's return value, or OAKCODEC_E_STATE when no
|
||||
* callback is registered (interim pre-M8 state).
|
||||
*/
|
||||
int SubmitTask(const OakCodecTaskRequest &req);
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_CODEC_TASKCALLBACKS_H
|
||||
@@ -0,0 +1,111 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "timecodemetadata.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
|
||||
#include "olive/core/util/timecodefunctions.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string trimmed(const std::string &s)
|
||||
{
|
||||
const char *ws = " \t\n\r\f\v";
|
||||
size_t begin = s.find_first_not_of(ws);
|
||||
if (begin == std::string::npos) {
|
||||
return std::string();
|
||||
}
|
||||
size_t end = s.find_last_not_of(ws);
|
||||
return s.substr(begin, end - begin + 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TimecodeMetadata::SourceTime
|
||||
TimecodeMetadata::from_timecode_string(const std::string &timecode,
|
||||
const core::Rational &timebase)
|
||||
{
|
||||
SourceTime result;
|
||||
const std::string trimmed_tc = trimmed(timecode);
|
||||
if (trimmed_tc.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const core::Timecode::Display display =
|
||||
trimmed_tc.find(';') != std::string::npos ?
|
||||
core::Timecode::k_timecode_drop_frame :
|
||||
core::Timecode::k_timecode_non_drop_frame;
|
||||
result.time =
|
||||
core::Timecode::timecode_to_time(trimmed_tc, timebase, display, &ok);
|
||||
result.valid = ok;
|
||||
if (ok) {
|
||||
result.source = "timecode";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TimecodeMetadata::SourceTime
|
||||
TimecodeMetadata::from_bwf_time_reference(const std::string &time_reference,
|
||||
int sample_rate)
|
||||
{
|
||||
SourceTime result;
|
||||
if (sample_rate <= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const std::string trimmed_ref = trimmed(time_reference);
|
||||
char *end = nullptr;
|
||||
const unsigned long long samples =
|
||||
std::strtoull(trimmed_ref.c_str(), &end, 10);
|
||||
ok = end != trimmed_ref.c_str() && *end == '\0';
|
||||
if (!ok) {
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned long long numerator = samples;
|
||||
unsigned long long denominator = static_cast<unsigned long long>(sample_rate);
|
||||
const unsigned long long divisor = std::gcd(numerator, denominator);
|
||||
numerator /= divisor;
|
||||
denominator /= divisor;
|
||||
|
||||
const unsigned long long rational_limit =
|
||||
static_cast<unsigned long long>(std::numeric_limits<int>::max());
|
||||
if (numerator <= rational_limit && denominator <= rational_limit) {
|
||||
result.time = core::Rational(static_cast<int>(numerator),
|
||||
static_cast<int>(denominator));
|
||||
} else {
|
||||
result.time = core::Rational::from_double(
|
||||
static_cast<double>(samples) / static_cast<double>(sample_rate));
|
||||
}
|
||||
result.source = "bwf_time_reference";
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/***
|
||||
|
||||
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_TIMECODEMETADATA_H
|
||||
#define OAK_TIMECODEMETADATA_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "olive/core/util/rational.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class TimecodeMetadata {
|
||||
public:
|
||||
struct SourceTime {
|
||||
core::Rational time;
|
||||
std::string source;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static SourceTime from_timecode_string(const std::string &timecode,
|
||||
const core::Rational &timebase);
|
||||
|
||||
static SourceTime from_bwf_time_reference(const std::string &time_reference,
|
||||
int sample_rate);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_TIMECODEMETADATA_H
|
||||
@@ -0,0 +1,155 @@
|
||||
# Oak Video Editor - 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/>.
|
||||
|
||||
# Standalone build driver for the oakcodec module (M5). Mirrors
|
||||
# src/render/standalone: oakcodec links oakrender (CancelAtom/texture C
|
||||
# ABI), oakcommon, olivecore and ffmpeg_bridge; references into the
|
||||
# not-yet-split task/config modules are interim no-ops (task submit
|
||||
# callback registry, compiled-in proxy defaults), and oakrender's own
|
||||
# dangling symbols resolve via -undefined dynamic_lookup (macOS).
|
||||
#
|
||||
# Usage (macOS/Homebrew):
|
||||
# cmake -S src/codec/standalone -B build-oakcodec
|
||||
# cmake --build build-oakcodec -j
|
||||
# ctest --test-dir build-oakcodec
|
||||
|
||||
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
|
||||
|
||||
project(oakcodec-standalone LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
get_filename_component(OAK_REPO_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${OAK_REPO_ROOT}/cmake")
|
||||
if(EXISTS "/opt/homebrew")
|
||||
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
|
||||
endif()
|
||||
|
||||
find_package(EXPAT REQUIRED)
|
||||
find_package(OpenColorIO CONFIG REQUIRED)
|
||||
find_package(OpenImageIO CONFIG REQUIRED)
|
||||
|
||||
set(OCIO_LIBRARIES OpenColorIO::OpenColorIO)
|
||||
set(OCIO_INCLUDE_DIRS "")
|
||||
set(OIIO_LIBRARIES OpenImageIO::OpenImageIO)
|
||||
set(OIIO_INCLUDE_DIRS "")
|
||||
|
||||
# In-repo libraries, built from source (same set src/render/standalone
|
||||
# assembles, because oakcodec links oakrender):
|
||||
# - olivecore (core/): oakcore_* C ABI and olive::core C++ utils
|
||||
# - ffmpeg_bridge: fb_* C ABI (the only FFmpeg access codec performs)
|
||||
# - oakundo / oakcommon / oaknode: oakrender's own dependencies
|
||||
# - oakrender: CancelAtom + texture C ABI consumed by codec
|
||||
set(OLIVECORE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(CMAKE_DISABLE_FIND_PACKAGE_OpenTimelineIO ON)
|
||||
|
||||
add_subdirectory(${OAK_REPO_ROOT}/core ${CMAKE_BINARY_DIR}/core)
|
||||
target_include_directories(olivecore PUBLIC ${OAK_REPO_ROOT}/third_party/openfx/include)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/ffmpeg_bridge ${CMAKE_BINARY_DIR}/ffmpeg_bridge)
|
||||
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/undo ${CMAKE_BINARY_DIR}/undo)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/common ${CMAKE_BINARY_DIR}/common)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/node ${CMAKE_BINARY_DIR}/node)
|
||||
set(BUILD_TESTS ON)
|
||||
|
||||
# oaknode needs its transition stubs when built in this tree (see
|
||||
# src/render/standalone/CMakeLists.txt).
|
||||
target_include_directories(oaknode BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
)
|
||||
target_include_directories(oaknode PUBLIC
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
/opt/homebrew/include
|
||||
/opt/homebrew/include/Imath
|
||||
)
|
||||
target_link_options(oaknode PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/src ${CMAKE_BINARY_DIR}/render)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/c_api ${CMAKE_BINARY_DIR}/render_c_api)
|
||||
|
||||
# Transition stub dirs must precede everything else: src/render/transition
|
||||
# first, then src/node/transition (shared stubs).
|
||||
target_include_directories(oakrender BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
)
|
||||
|
||||
target_include_directories(oakrender PUBLIC
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
/opt/homebrew/include
|
||||
/opt/homebrew/include/Imath
|
||||
/opt/homebrew/include/OpenEXR
|
||||
)
|
||||
|
||||
# Vulkan headers (Homebrew keg-only vulkan-headers).
|
||||
if(NOT EXISTS "/opt/homebrew/include/vulkan/vulkan.h")
|
||||
execute_process(COMMAND brew --prefix vulkan-headers
|
||||
OUTPUT_VARIABLE VULKAN_HEADERS_PREFIX
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
if(VULKAN_HEADERS_PREFIX AND EXISTS "${VULKAN_HEADERS_PREFIX}/include/vulkan/vulkan.h")
|
||||
target_include_directories(oakrender PUBLIC "${VULKAN_HEADERS_PREFIX}/include")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Symbols of the not-yet-split engine modules dangle by design. The
|
||||
# backend libraries resolve most symbols from liboakrender at load time
|
||||
# and dangle the same way.
|
||||
foreach(t oakrender oakgl oakgl2 oakvulkan)
|
||||
if(TARGET ${t})
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
target_link_libraries(oakrender PRIVATE
|
||||
oaknode
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
ffmpeg_bridge
|
||||
${OCIO_LIBRARIES}
|
||||
${OIIO_LIBRARIES}
|
||||
"-framework OpenGL"
|
||||
"-framework CoreVideo"
|
||||
"-framework Metal"
|
||||
"-framework QuartzCore"
|
||||
)
|
||||
|
||||
# oakcodec itself. Its own dangling references (none expected beyond what
|
||||
# the linked libraries already dangle) resolve the same way.
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/codec/src ${CMAKE_BINARY_DIR}/codec)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/codec/c_api ${CMAKE_BINARY_DIR}/codec_c_api)
|
||||
|
||||
target_link_options(oakcodec PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
|
||||
# Tests (oakcodec-gtest).
|
||||
if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/codec/tests ${CMAKE_BINARY_DIR}/codec_tests)
|
||||
endif()
|
||||
@@ -0,0 +1,84 @@
|
||||
# Oak Video Editor - 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/>.
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
# In a full-tree build the repo root is CMAKE_SOURCE_DIR; a standalone
|
||||
# build (see src/codec/standalone) sets OAK_REPO_ROOT explicitly.
|
||||
if(NOT DEFINED OAK_REPO_ROOT)
|
||||
set(OAK_REPO_ROOT ${CMAKE_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
add_executable(oakcodec-gtest
|
||||
frame_test.cpp
|
||||
decoder_test.cpp
|
||||
encoder_test.cpp
|
||||
task_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(oakcodec-gtest PRIVATE
|
||||
oakcodec
|
||||
oakrender
|
||||
oaknode
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
# liboakrender/liboaknode dangle OFX host symbols (-undefined
|
||||
# dynamic_lookup); force-load the host support archive into the test
|
||||
# process so dyld finds them in the flat namespace at startup. Mirrors
|
||||
# src/render/tests/CMakeLists.txt.
|
||||
if(NOT DEFINED OAKRENDER_OFX_HOST_ARCHIVE)
|
||||
find_library(OAKRENDER_OFX_HOST_ARCHIVE NAMES OfxHost
|
||||
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport)
|
||||
endif()
|
||||
if(NOT OAKRENDER_OFX_HOST_ARCHIVE)
|
||||
message(FATAL_ERROR
|
||||
"libOfxHost.a not found; run the full-tree build once or set "
|
||||
"OAKRENDER_OFX_HOST_ARCHIVE")
|
||||
endif()
|
||||
target_link_options(oakcodec-gtest PRIVATE
|
||||
"-Wl,-force_load,${OAKRENDER_OFX_HOST_ARCHIVE}")
|
||||
|
||||
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
|
||||
# dynamic_lookup; the test binary links the inert shim from
|
||||
# src/node/standalone instead.
|
||||
target_sources(oakcodec-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/src/node/standalone/oakengine_ipc_shim.cpp)
|
||||
target_include_directories(oakcodec-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
)
|
||||
|
||||
# include/ must win over the render/node transition dirs that leak in
|
||||
# through oaknode's PUBLIC includes: they carry codec/*.h stubs that would
|
||||
# otherwise shadow the real oakcodec public headers. -iquote is searched
|
||||
# before every -I for quoted includes.
|
||||
target_compile_options(oakcodec-gtest PRIVATE
|
||||
"-iquote" "${OAK_REPO_ROOT}/include"
|
||||
)
|
||||
|
||||
# tests/demo.mp4 lives at the repo's shared tests directory.
|
||||
target_compile_definitions(oakcodec-gtest PRIVATE
|
||||
OAKCODEC_TEST_DATA_DIR="${OAK_REPO_ROOT}/tests")
|
||||
|
||||
gtest_discover_tests(oakcodec-gtest
|
||||
DISCOVERY_MODE PRE_TEST
|
||||
PROPERTIES ENVIRONMENT
|
||||
"OCIO=${OAK_REPO_ROOT}/engine/render/ocioconf/config.ocio")
|
||||
@@ -0,0 +1,180 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "codec/decoder.h"
|
||||
|
||||
#ifndef OAKCODEC_TEST_DATA_DIR
|
||||
#define OAKCODEC_TEST_DATA_DIR "tests"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string demo_path()
|
||||
{
|
||||
return std::string(OAKCODEC_TEST_DATA_DIR) + "/demo.mp4";
|
||||
}
|
||||
|
||||
bool demo_exists()
|
||||
{
|
||||
FILE *f = fopen(demo_path().c_str(), "rb");
|
||||
if (f) {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OakCodecDecoder, ProbeDemoMp4)
|
||||
{
|
||||
if (!demo_exists()) {
|
||||
GTEST_SKIP() << "tests/demo.mp4 not available";
|
||||
}
|
||||
|
||||
int before = oakcodec_debug_alive_count();
|
||||
|
||||
OakDecoder probe = oakcodec_decoder_probe(demo_path().c_str());
|
||||
ASSERT_NE(probe.ctx, nullptr);
|
||||
|
||||
char name[64] = {};
|
||||
EXPECT_GT(oakcodec_decoder_probe_decoder_name(probe, name, sizeof(name)),
|
||||
0);
|
||||
EXPECT_STRNE(name, "");
|
||||
|
||||
int video_count = oakcodec_decoder_probe_video_stream_count(probe);
|
||||
EXPECT_GE(video_count, 1);
|
||||
|
||||
if (video_count >= 1) {
|
||||
oakcodec_video_stream_info info = {};
|
||||
ASSERT_EQ(oakcodec_decoder_probe_get_video_stream(probe, 0, &info),
|
||||
OAKCODEC_OK);
|
||||
EXPECT_GT(info.width, 0);
|
||||
EXPECT_GT(info.height, 0);
|
||||
EXPECT_GT(info.time_base_den, 0);
|
||||
|
||||
// Out-of-range index
|
||||
EXPECT_EQ(oakcodec_decoder_probe_get_video_stream(
|
||||
probe, video_count, &info),
|
||||
OAKCODEC_E_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Audio stream enumeration must not crash (count may be 0)
|
||||
int audio_count = oakcodec_decoder_probe_audio_stream_count(probe);
|
||||
EXPECT_GE(audio_count, 0);
|
||||
if (audio_count >= 1) {
|
||||
oakcodec_audio_stream_info ainfo = {};
|
||||
ASSERT_EQ(oakcodec_decoder_probe_get_audio_stream(probe, 0, &ainfo),
|
||||
OAKCODEC_OK);
|
||||
EXPECT_GT(ainfo.sample_rate, 0);
|
||||
}
|
||||
|
||||
oakcodec_decoder_free(&probe);
|
||||
EXPECT_EQ(probe.ctx, nullptr);
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before);
|
||||
}
|
||||
|
||||
TEST(OakCodecDecoder, ProbeMissingFile)
|
||||
{
|
||||
OakDecoder probe =
|
||||
oakcodec_decoder_probe("/nonexistent/path/to/file.mp4");
|
||||
EXPECT_EQ(probe.ctx, nullptr);
|
||||
|
||||
char err[256] = {};
|
||||
EXPECT_GT(oakcodec_probe_last_error(err, sizeof(err)), 1);
|
||||
EXPECT_STRNE(err, "");
|
||||
|
||||
oakcodec_decoder_free(&probe); // no-op
|
||||
}
|
||||
|
||||
TEST(OakCodecDecoder, OpenAndDecodeFirstFrame)
|
||||
{
|
||||
if (!demo_exists()) {
|
||||
GTEST_SKIP() << "tests/demo.mp4 not available";
|
||||
}
|
||||
|
||||
// Find the first video stream index via a probe.
|
||||
OakDecoder probe = oakcodec_decoder_probe(demo_path().c_str());
|
||||
ASSERT_NE(probe.ctx, nullptr);
|
||||
ASSERT_GE(oakcodec_decoder_probe_video_stream_count(probe), 1);
|
||||
oakcodec_video_stream_info info = {};
|
||||
ASSERT_EQ(oakcodec_decoder_probe_get_video_stream(probe, 0, &info),
|
||||
OAKCODEC_OK);
|
||||
int stream_index = info.stream_index;
|
||||
oakcodec_decoder_free(&probe);
|
||||
|
||||
int before = oakcodec_debug_alive_count();
|
||||
|
||||
OakDecoder d = oakcodec_decoder_init();
|
||||
ASSERT_NE(d.ctx, nullptr);
|
||||
EXPECT_EQ(oakcodec_decoder_is_open(d), 0);
|
||||
|
||||
ASSERT_EQ(oakcodec_decoder_open(d, demo_path().c_str(), stream_index),
|
||||
OAKCODEC_OK);
|
||||
EXPECT_EQ(oakcodec_decoder_is_open(d), 1);
|
||||
|
||||
OakFrame frame = oakcodec_decoder_decode_video(d, 0, 1);
|
||||
ASSERT_NE(frame.ctx, nullptr);
|
||||
EXPECT_EQ(oakcodec_frame_is_allocated(frame), 1);
|
||||
EXPECT_NE(oakcodec_frame_const_data(frame), nullptr);
|
||||
EXPECT_GT(oakcodec_frame_width(frame), 0);
|
||||
EXPECT_GT(oakcodec_frame_height(frame), 0);
|
||||
|
||||
oakcodec_frame_free(&frame);
|
||||
|
||||
EXPECT_EQ(oakcodec_decoder_close(d), OAKCODEC_OK);
|
||||
EXPECT_EQ(oakcodec_decoder_is_open(d), 0);
|
||||
|
||||
oakcodec_decoder_free(&d);
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before);
|
||||
}
|
||||
|
||||
TEST(OakCodecDecoder, OpenMissingFile)
|
||||
{
|
||||
OakDecoder d = oakcodec_decoder_init();
|
||||
ASSERT_NE(d.ctx, nullptr);
|
||||
|
||||
int rc = oakcodec_decoder_open(d, "/nonexistent/video.mp4", 0);
|
||||
EXPECT_EQ(rc, OAKCODEC_E_NOT_FOUND);
|
||||
EXPECT_EQ(oakcodec_decoder_is_open(d), 0);
|
||||
|
||||
char err[256] = {};
|
||||
EXPECT_GT(oakcodec_decoder_last_error(d, err, sizeof(err)), 1);
|
||||
EXPECT_STRNE(err, "");
|
||||
|
||||
oakcodec_decoder_free(&d);
|
||||
}
|
||||
|
||||
TEST(OakCodecDecoder, EmptyHandleSemantics)
|
||||
{
|
||||
OakDecoder empty = {};
|
||||
EXPECT_EQ(oakcodec_decoder_is_open(empty), 0);
|
||||
EXPECT_EQ(oakcodec_decoder_probe_video_stream_count(empty), 0);
|
||||
OakFrame f = oakcodec_decoder_decode_video(empty, 0, 1);
|
||||
EXPECT_EQ(f.ctx, nullptr);
|
||||
oakcodec_decoder_free(nullptr);
|
||||
oakcodec_decoder_free(&empty);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "codec/decoder.h"
|
||||
#include "codec/encoder.h"
|
||||
|
||||
// Format/codec values mirror oakengine/encoding.h (olive::ExportFormat /
|
||||
// olive::ExportCodec).
|
||||
#define TEST_FORMAT_MPEG4 2
|
||||
#define TEST_CODEC_H264 1
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string temp_mp4_path()
|
||||
{
|
||||
std::string p = std::string("/tmp/oakcodec_encoder_test.mp4");
|
||||
remove(p.c_str());
|
||||
return p;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OakCodecEncoder, EncodeMp4RoundTrip)
|
||||
{
|
||||
std::string path = temp_mp4_path();
|
||||
|
||||
int before = oakcodec_debug_alive_count();
|
||||
|
||||
oakcodec_encoding_params params = {};
|
||||
snprintf(params.filename, sizeof(params.filename), "%s", path.c_str());
|
||||
params.format = TEST_FORMAT_MPEG4;
|
||||
params.video_enabled = 1;
|
||||
params.video_codec = TEST_CODEC_H264;
|
||||
params.video_width = 64;
|
||||
params.video_height = 64;
|
||||
params.video_time_base_num = 1;
|
||||
params.video_time_base_den = 25;
|
||||
params.video_pixel_format = OAKCOMMON_PIXEL_FORMAT_U8;
|
||||
params.video_interlacing = OAKCODEC_INTERLACE_NONE;
|
||||
params.video_pixel_aspect_num = 1;
|
||||
params.video_pixel_aspect_den = 1;
|
||||
params.video_bit_rate = 200000;
|
||||
snprintf(params.video_pix_fmt, sizeof(params.video_pix_fmt), "yuv420p");
|
||||
|
||||
OakEncoder enc = oakcodec_encoder_init(¶ms);
|
||||
ASSERT_NE(enc.ctx, nullptr);
|
||||
|
||||
if (oakcodec_encoder_open(enc) != OAKCODEC_OK) {
|
||||
char err[512] = {};
|
||||
oakcodec_encoder_last_error(enc, err, sizeof(err));
|
||||
oakcodec_encoder_free(&enc);
|
||||
GTEST_SKIP() << "encoder not available in this environment: " << err;
|
||||
}
|
||||
|
||||
// Write 10 solid frames.
|
||||
OakVideoParams vp = oakcommon_videoparams_init_with_time_base(
|
||||
64, 64, 1, 25, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
|
||||
OakFrame frame = oakcodec_frame_init_with_params(vp);
|
||||
oakcommon_videoparams_free(&vp);
|
||||
ASSERT_NE(frame.ctx, nullptr);
|
||||
ASSERT_EQ(oakcodec_frame_allocate(frame), OAKCODEC_OK);
|
||||
|
||||
int linesize = oakcodec_frame_linesize_bytes(frame);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
memset(oakcodec_frame_data(frame), 16 + i * 10,
|
||||
static_cast<size_t>(linesize) * 64);
|
||||
ASSERT_EQ(oakcodec_frame_set_timestamp(frame, i, 25), OAKCODEC_OK);
|
||||
ASSERT_EQ(oakcodec_encoder_write_video(enc, frame), OAKCODEC_OK);
|
||||
}
|
||||
|
||||
EXPECT_EQ(oakcodec_encoder_flush(enc), OAKCODEC_OK);
|
||||
// Writing after flush is a state error.
|
||||
EXPECT_EQ(oakcodec_encoder_write_video(enc, frame), OAKCODEC_E_STATE);
|
||||
|
||||
oakcodec_frame_free(&frame);
|
||||
oakcodec_encoder_free(&enc);
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before);
|
||||
|
||||
// Round-trip: the decoder must open the file and decode a frame.
|
||||
OakDecoder probe = oakcodec_decoder_probe(path.c_str());
|
||||
ASSERT_NE(probe.ctx, nullptr);
|
||||
ASSERT_GE(oakcodec_decoder_probe_video_stream_count(probe), 1);
|
||||
oakcodec_video_stream_info info = {};
|
||||
ASSERT_EQ(oakcodec_decoder_probe_get_video_stream(probe, 0, &info),
|
||||
OAKCODEC_OK);
|
||||
EXPECT_EQ(info.width, 64);
|
||||
EXPECT_EQ(info.height, 64);
|
||||
int stream_index = info.stream_index;
|
||||
oakcodec_decoder_free(&probe);
|
||||
|
||||
OakDecoder dec = oakcodec_decoder_init();
|
||||
ASSERT_NE(dec.ctx, nullptr);
|
||||
ASSERT_EQ(oakcodec_decoder_open(dec, path.c_str(), stream_index),
|
||||
OAKCODEC_OK);
|
||||
OakFrame decoded = oakcodec_decoder_decode_video(dec, 0, 1);
|
||||
ASSERT_NE(decoded.ctx, nullptr);
|
||||
EXPECT_NE(oakcodec_frame_const_data(decoded), nullptr);
|
||||
oakcodec_frame_free(&decoded);
|
||||
oakcodec_decoder_free(&dec);
|
||||
|
||||
remove(path.c_str());
|
||||
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before);
|
||||
}
|
||||
|
||||
TEST(OakCodecEncoder, EmptyHandleSemantics)
|
||||
{
|
||||
OakEncoder empty = {};
|
||||
EXPECT_EQ(oakcodec_encoder_open(empty), OAKCODEC_E_INVALID);
|
||||
EXPECT_EQ(oakcodec_encoder_flush(empty), OAKCODEC_E_INVALID);
|
||||
oakcodec_encoder_free(nullptr);
|
||||
oakcodec_encoder_free(&empty);
|
||||
|
||||
// An all-disabled params struct is invalid -> empty handle.
|
||||
oakcodec_encoding_params params = {};
|
||||
OakEncoder enc = oakcodec_encoder_init(¶ms);
|
||||
EXPECT_EQ(enc.ctx, nullptr);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "codec/frame.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
OakVideoParams make_params(int width, int height)
|
||||
{
|
||||
return oakcommon_videoparams_init_with_time_base(
|
||||
width, height, 1001, 30000, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OakCodecFrame, InitAndFree)
|
||||
{
|
||||
int before = oakcodec_debug_alive_count();
|
||||
|
||||
OakFrame f = oakcodec_frame_init();
|
||||
ASSERT_NE(f.ctx, nullptr);
|
||||
EXPECT_EQ(f.abi_version, OAKCODEC_ABI_VERSION);
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before + 1);
|
||||
|
||||
oakcodec_frame_free(&f);
|
||||
EXPECT_EQ(f.ctx, nullptr);
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before);
|
||||
|
||||
// NULL / empty no-ops
|
||||
oakcodec_frame_free(nullptr);
|
||||
oakcodec_frame_free(&f);
|
||||
}
|
||||
|
||||
TEST(OakCodecFrame, ParamsRoundTrip)
|
||||
{
|
||||
OakFrame f = oakcodec_frame_init();
|
||||
ASSERT_NE(f.ctx, nullptr);
|
||||
|
||||
OakVideoParams p = make_params(320, 240);
|
||||
ASSERT_EQ(oakcodec_frame_set_params(f, p), OAKCODEC_OK);
|
||||
oakcommon_videoparams_free(&p);
|
||||
|
||||
OakVideoParams out = {};
|
||||
ASSERT_EQ(oakcodec_frame_get_params(f, &out), OAKCODEC_OK);
|
||||
|
||||
int w = 0, h = 0, fmt = -2, ch = 0, tb_num = 0, tb_den = 0;
|
||||
oakcommon_videoparams_get_width(out, &w);
|
||||
oakcommon_videoparams_get_height(out, &h);
|
||||
oakcommon_videoparams_get_format(out, &fmt);
|
||||
oakcommon_videoparams_get_channel_count(out, &ch);
|
||||
oakcommon_videoparams_get_time_base(out, &tb_num, &tb_den);
|
||||
oakcommon_videoparams_free(&out);
|
||||
|
||||
EXPECT_EQ(w, 320);
|
||||
EXPECT_EQ(h, 240);
|
||||
EXPECT_EQ(fmt, OAKCOMMON_PIXEL_FORMAT_U8);
|
||||
EXPECT_EQ(ch, 4);
|
||||
EXPECT_EQ(tb_num, 1001);
|
||||
EXPECT_EQ(tb_den, 30000);
|
||||
|
||||
oakcodec_frame_free(&f);
|
||||
}
|
||||
|
||||
TEST(OakCodecFrame, AllocateDataLinesize)
|
||||
{
|
||||
OakVideoParams p = make_params(64, 48);
|
||||
OakFrame f = oakcodec_frame_init_with_params(p);
|
||||
oakcommon_videoparams_free(&p);
|
||||
ASSERT_NE(f.ctx, nullptr);
|
||||
|
||||
EXPECT_EQ(oakcodec_frame_is_allocated(f), 0);
|
||||
EXPECT_EQ(oakcodec_frame_data(f), nullptr);
|
||||
|
||||
ASSERT_EQ(oakcodec_frame_allocate(f), OAKCODEC_OK);
|
||||
EXPECT_EQ(oakcodec_frame_is_allocated(f), 1);
|
||||
ASSERT_NE(oakcodec_frame_data(f), nullptr);
|
||||
EXPECT_EQ(oakcodec_frame_const_data(f), oakcodec_frame_data(f));
|
||||
|
||||
// u8 rgba = 4 bytes/px, width 64 aligned to 32 -> 64 * 4
|
||||
EXPECT_EQ(oakcodec_frame_linesize_bytes(f), 64 * 4);
|
||||
EXPECT_EQ(oakcodec_frame_linesize_pixels(f), 64);
|
||||
EXPECT_EQ(oakcodec_frame_allocated_size(f), 64 * 4 * 48);
|
||||
EXPECT_EQ(oakcodec_frame_width(f), 64);
|
||||
EXPECT_EQ(oakcodec_frame_height(f), 48);
|
||||
EXPECT_EQ(oakcodec_frame_format(f), OAKCOMMON_PIXEL_FORMAT_U8);
|
||||
EXPECT_EQ(oakcodec_frame_channel_count(f), 4);
|
||||
|
||||
// Allocating again is a successful no-op
|
||||
EXPECT_EQ(oakcodec_frame_allocate(f), OAKCODEC_OK);
|
||||
|
||||
oakcodec_frame_free(&f);
|
||||
}
|
||||
|
||||
TEST(OakCodecFrame, RefCounting)
|
||||
{
|
||||
int before = oakcodec_debug_alive_count();
|
||||
|
||||
OakFrame f = oakcodec_frame_init();
|
||||
ASSERT_NE(f.ctx, nullptr);
|
||||
|
||||
// Copy the struct and addref: two references, one object.
|
||||
OakFrame copy = f;
|
||||
copy.addref(copy.ctx);
|
||||
|
||||
// Release the copy; object stays alive.
|
||||
copy.release(copy.ctx);
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before + 1);
|
||||
EXPECT_EQ(oakcodec_frame_width(f), 0); // still valid, default params
|
||||
|
||||
oakcodec_frame_free(&f);
|
||||
EXPECT_EQ(oakcodec_debug_alive_count(), before);
|
||||
}
|
||||
|
||||
TEST(OakCodecFrame, Timestamp)
|
||||
{
|
||||
OakFrame f = oakcodec_frame_init();
|
||||
ASSERT_NE(f.ctx, nullptr);
|
||||
|
||||
ASSERT_EQ(oakcodec_frame_set_timestamp(f, 1001, 30000), OAKCODEC_OK);
|
||||
int num = 0, den = 0;
|
||||
ASSERT_EQ(oakcodec_frame_get_timestamp(f, &num, &den), OAKCODEC_OK);
|
||||
EXPECT_EQ(num, 1001);
|
||||
EXPECT_EQ(den, 30000);
|
||||
|
||||
oakcodec_frame_free(&f);
|
||||
}
|
||||
|
||||
TEST(OakCodecFrame, EmptyHandleSemantics)
|
||||
{
|
||||
OakFrame empty = {};
|
||||
EXPECT_EQ(oakcodec_frame_get_params(empty, nullptr), OAKCODEC_E_INVALID);
|
||||
EXPECT_EQ(oakcodec_frame_allocate(empty), OAKCODEC_E_INVALID);
|
||||
EXPECT_EQ(oakcodec_frame_is_allocated(empty), 0);
|
||||
EXPECT_EQ(oakcodec_frame_data(empty), nullptr);
|
||||
EXPECT_EQ(oakcodec_frame_width(empty), 0);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "codec/conform.h"
|
||||
#include "codec/proxy.h"
|
||||
#include "codec/task.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct SubmitLog {
|
||||
int calls = 0;
|
||||
OakCodecTaskRequest last = {};
|
||||
std::string input;
|
||||
std::string output;
|
||||
};
|
||||
|
||||
int recording_submit(const OakCodecTaskRequest *req, void *userdata)
|
||||
{
|
||||
auto *log = static_cast<SubmitLog *>(userdata);
|
||||
log->calls++;
|
||||
log->last = *req;
|
||||
log->input = req->input_filename ? req->input_filename : "";
|
||||
log->output = req->output_filename ? req->output_filename : "";
|
||||
// Accept the task but do no work (files never appear).
|
||||
return OAKCODEC_OK;
|
||||
}
|
||||
|
||||
struct TaskRegistrarGuard {
|
||||
TaskRegistrarGuard() { oakcodec_set_task_submit_cb(nullptr, nullptr); }
|
||||
~TaskRegistrarGuard() { oakcodec_set_task_submit_cb(nullptr, nullptr); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OakCodecTask, RegistryRoundTrip)
|
||||
{
|
||||
TaskRegistrarGuard guard;
|
||||
|
||||
EXPECT_EQ(oakcodec_task_submit_is_registered(), 0);
|
||||
|
||||
SubmitLog log;
|
||||
oakcodec_set_task_submit_cb(&recording_submit, &log);
|
||||
EXPECT_EQ(oakcodec_task_submit_is_registered(), 1);
|
||||
|
||||
oakcodec_set_task_submit_cb(nullptr, nullptr);
|
||||
EXPECT_EQ(oakcodec_task_submit_is_registered(), 0);
|
||||
}
|
||||
|
||||
TEST(OakCodecConform, UnregisteredReportsUnavailable)
|
||||
{
|
||||
TaskRegistrarGuard guard;
|
||||
|
||||
ASSERT_EQ(oakcodec_conform_create_instance(), OAKCODEC_OK);
|
||||
|
||||
int state = oakcodec_conform_get_state("/tmp/oakcodec_conform_test",
|
||||
"some_video.mp4", 1, 48000, 0x3, 4,
|
||||
1);
|
||||
EXPECT_EQ(state, OAKCODEC_CONFORM_UNAVAILABLE);
|
||||
|
||||
// Filename computation is still deterministic without a registrar.
|
||||
int count = oakcodec_conform_filename_count(
|
||||
"/tmp/oakcodec_conform_test", "some_video.mp4", 1, 48000, 0x3, 4);
|
||||
EXPECT_GE(count, 1); // stereo layout -> 2 channels
|
||||
if (count >= 1) {
|
||||
char buf[1024] = {};
|
||||
EXPECT_GT(oakcodec_conform_filename_at(
|
||||
"/tmp/oakcodec_conform_test", "some_video.mp4", 1,
|
||||
48000, 0x3, 4, 0, buf, sizeof(buf)),
|
||||
1);
|
||||
EXPECT_STRNE(buf, "");
|
||||
EXPECT_EQ(oakcodec_conform_filename_at(
|
||||
"/tmp/oakcodec_conform_test", "some_video.mp4", 1,
|
||||
48000, 0x3, 4, count, buf, sizeof(buf)),
|
||||
OAKCODEC_E_NOT_FOUND);
|
||||
}
|
||||
|
||||
oakcodec_conform_destroy_instance();
|
||||
}
|
||||
|
||||
TEST(OakCodecConform, RegisteredSubmitIsInvoked)
|
||||
{
|
||||
TaskRegistrarGuard guard;
|
||||
SubmitLog log;
|
||||
oakcodec_set_task_submit_cb(&recording_submit, &log);
|
||||
|
||||
ASSERT_EQ(oakcodec_conform_create_instance(), OAKCODEC_OK);
|
||||
|
||||
// wait=0: the (no-op) task was "queued" -> GENERATING.
|
||||
int state = oakcodec_conform_get_state("/tmp/oakcodec_conform_test",
|
||||
"some_video.mp4", 1, 48000, 0x3, 4,
|
||||
0);
|
||||
EXPECT_EQ(state, OAKCODEC_CONFORM_GENERATING);
|
||||
EXPECT_EQ(log.calls, 1);
|
||||
EXPECT_EQ(log.last.kind, OAKCODEC_TASK_CONFORM);
|
||||
EXPECT_EQ(log.last.stream_index, 1);
|
||||
EXPECT_EQ(log.last.sample_rate, 48000);
|
||||
|
||||
// wait=1: post-submit miss -> UNAVAILABLE.
|
||||
state = oakcodec_conform_get_state("/tmp/oakcodec_conform_test",
|
||||
"some_video.mp4", 1, 48000, 0x3, 4, 1);
|
||||
EXPECT_EQ(state, OAKCODEC_CONFORM_UNAVAILABLE);
|
||||
|
||||
oakcodec_conform_destroy_instance();
|
||||
}
|
||||
|
||||
TEST(OakCodecProxy, MissingAndStateStrings)
|
||||
{
|
||||
TaskRegistrarGuard guard;
|
||||
|
||||
ASSERT_EQ(oakcodec_proxy_create_instance(), OAKCODEC_OK);
|
||||
|
||||
EXPECT_EQ(oakcodec_proxy_get_state(nullptr), OAKCODEC_PROXY_STATE_MISSING);
|
||||
EXPECT_EQ(oakcodec_proxy_get_state("/nonexistent/proxy.mp4"),
|
||||
OAKCODEC_PROXY_STATE_MISSING);
|
||||
|
||||
char buf[64] = {};
|
||||
EXPECT_GT(oakcodec_proxy_state_to_string(OAKCODEC_PROXY_STATE_READY, buf,
|
||||
sizeof(buf)),
|
||||
1);
|
||||
EXPECT_EQ(oakcodec_proxy_state_to_string(99, buf, sizeof(buf)),
|
||||
OAKCODEC_E_INVALID);
|
||||
|
||||
oakcodec_proxy_params params = {};
|
||||
ASSERT_EQ(oakcodec_proxy_params_default(¶ms), OAKCODEC_OK);
|
||||
EXPECT_GT(params.width, 0);
|
||||
EXPECT_STRNE(params.extension, "");
|
||||
|
||||
oakcodec_proxy_result result = {};
|
||||
ASSERT_EQ(oakcodec_proxy_get_or_start("/tmp/oakcodec_proxy_test",
|
||||
"some_video.mp4", 0, ¶ms, &result),
|
||||
OAKCODEC_OK);
|
||||
// No registrar: stays missing, filename is still computed.
|
||||
EXPECT_EQ(result.state, OAKCODEC_PROXY_STATE_MISSING);
|
||||
EXPECT_STRNE(result.filename, "");
|
||||
|
||||
oakcodec_proxy_destroy_instance();
|
||||
}
|
||||
|
||||
TEST(OakCodecProxy, RegisteredSubmitIsInvoked)
|
||||
{
|
||||
TaskRegistrarGuard guard;
|
||||
SubmitLog log;
|
||||
oakcodec_set_task_submit_cb(&recording_submit, &log);
|
||||
|
||||
ASSERT_EQ(oakcodec_proxy_create_instance(), OAKCODEC_OK);
|
||||
|
||||
oakcodec_proxy_params params = {};
|
||||
ASSERT_EQ(oakcodec_proxy_params_default(¶ms), OAKCODEC_OK);
|
||||
|
||||
oakcodec_proxy_result result = {};
|
||||
ASSERT_EQ(oakcodec_proxy_get_or_start("/tmp/oakcodec_proxy_test",
|
||||
"some_video.mp4", 2, ¶ms, &result),
|
||||
OAKCODEC_OK);
|
||||
EXPECT_EQ(log.calls, 1);
|
||||
EXPECT_EQ(log.last.kind, OAKCODEC_TASK_PROXY);
|
||||
EXPECT_EQ(log.last.stream_index, 2);
|
||||
// Task accepted but produced nothing -> generating.
|
||||
EXPECT_EQ(result.state, OAKCODEC_PROXY_STATE_GENERATING);
|
||||
|
||||
oakcodec_proxy_destroy_instance();
|
||||
}
|
||||
@@ -23,10 +23,15 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/colortransform.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
struct OakCommonColorTransform {
|
||||
olive::ColorTransform impl;
|
||||
};
|
||||
/**
|
||||
* @brief Recover the boxed olive::ColorTransform from a handle (NULL-safe).
|
||||
*/
|
||||
static olive::ColorTransform *ct(OakColorTransform transform)
|
||||
{
|
||||
return oakcommon::handle_impl<olive::ColorTransform>(transform.ctx);
|
||||
}
|
||||
|
||||
static int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
{
|
||||
@@ -36,89 +41,116 @@ static int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
return needed;
|
||||
}
|
||||
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_output(
|
||||
OakColorTransform oakcommon_colortransform_init_output(
|
||||
const char *output)
|
||||
{
|
||||
OakColorTransform h = {};
|
||||
if (!output)
|
||||
return nullptr;
|
||||
return h;
|
||||
try {
|
||||
return new OakCommonColorTransform{
|
||||
olive::ColorTransform(std::string(output))};
|
||||
return oakcommon::make_handle<OakColorTransform>(
|
||||
olive::ColorTransform(std::string(output)));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakColorTransform empty = {};
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_display(
|
||||
OakColorTransform oakcommon_colortransform_init_display(
|
||||
const char *display, const char *view, const char *look)
|
||||
{
|
||||
OakColorTransform h = {};
|
||||
if (!display || !view || !look)
|
||||
return nullptr;
|
||||
return h;
|
||||
try {
|
||||
return new OakCommonColorTransform{olive::ColorTransform(
|
||||
std::string(display), std::string(view), std::string(look))};
|
||||
return oakcommon::make_handle<OakColorTransform>(
|
||||
olive::ColorTransform(std::string(display), std::string(view),
|
||||
std::string(look)));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakColorTransform empty = {};
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_colortransform_free(OakCommonColorTransform *transform)
|
||||
OakColorTransform oakcommon_colortransform_init_from_native(
|
||||
const olive::ColorTransform *src)
|
||||
{
|
||||
delete transform;
|
||||
if (!src) {
|
||||
OakColorTransform h = {};
|
||||
return h;
|
||||
}
|
||||
try {
|
||||
return oakcommon::make_handle<OakColorTransform>(
|
||||
olive::ColorTransform(*src));
|
||||
} catch (...) {
|
||||
OakColorTransform h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
|
||||
const olive::ColorTransform *oakcommon_colortransform_get_native(
|
||||
OakColorTransform transform)
|
||||
{
|
||||
return ct(transform);
|
||||
}
|
||||
|
||||
void oakcommon_colortransform_free(OakColorTransform *transform)
|
||||
{
|
||||
oakcommon::free_handle(transform);
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_is_display(OakColorTransform transform,
|
||||
int *is_display)
|
||||
{
|
||||
if (!transform || !is_display)
|
||||
if (!ct(transform) || !is_display)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*is_display = transform->impl.is_display() ? 1 : 0;
|
||||
*is_display = ct(transform)->is_display() ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_display(OakColorTransform transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
if (!ct(transform))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.display(), buf, buf_size);
|
||||
return copy_string(ct(transform)->display(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_output(OakColorTransform transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
if (!ct(transform))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.output(), buf, buf_size);
|
||||
return copy_string(ct(transform)->output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_view(OakColorTransform transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
if (!ct(transform))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.view(), buf, buf_size);
|
||||
return copy_string(ct(transform)->view(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_look(OakCommonColorTransform *transform,
|
||||
int oakcommon_colortransform_get_look(OakColorTransform transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
if (!ct(transform))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.look(), buf, buf_size);
|
||||
return copy_string(ct(transform)->look(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
|
||||
@@ -26,43 +26,79 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../src/commandlineparser.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
struct OakCommonCommandLineParser {
|
||||
CommandLineParser impl;
|
||||
};
|
||||
namespace
|
||||
{
|
||||
|
||||
struct OakCommonCommandLineOption {
|
||||
/**
|
||||
* @brief State boxed behind an option handle's ctx pointer.
|
||||
*
|
||||
* The option pointer is borrowed: the option itself is owned by the
|
||||
* parser, so releasing the box never destroys it.
|
||||
*/
|
||||
struct OptionState {
|
||||
CommandLineParser::Option *option;
|
||||
};
|
||||
|
||||
struct OakCommonCommandLinePositionalArgument {
|
||||
/**
|
||||
* @brief State boxed behind a positional-argument handle's ctx pointer.
|
||||
*
|
||||
* The argument pointer is borrowed: the argument itself is owned by the
|
||||
* parser, so releasing the box never destroys it.
|
||||
*/
|
||||
struct PositionalArgumentState {
|
||||
CommandLineParser::PositionalArgument *argument;
|
||||
};
|
||||
|
||||
OakCommonCommandLineParser *oakcommon_commandlineparser_init(void)
|
||||
CommandLineParser *clp(OakCommandLineParser parser)
|
||||
{
|
||||
return oakcommon::handle_impl<CommandLineParser>(parser.ctx);
|
||||
}
|
||||
|
||||
CommandLineParser::Option *clo(OakCommandLineOption option)
|
||||
{
|
||||
OptionState *state =
|
||||
oakcommon::handle_impl<OptionState>(option.ctx);
|
||||
return state ? state->option : nullptr;
|
||||
}
|
||||
|
||||
CommandLineParser::PositionalArgument *clpa(
|
||||
OakCommandLinePositionalArgument argument)
|
||||
{
|
||||
PositionalArgumentState *state =
|
||||
oakcommon::handle_impl<PositionalArgumentState>(argument.ctx);
|
||||
return state ? state->argument : nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCommandLineParser oakcommon_commandlineparser_init(void)
|
||||
{
|
||||
try {
|
||||
return new (std::nothrow) OakCommonCommandLineParser();
|
||||
return oakcommon::make_handle_in_place<OakCommandLineParser,
|
||||
CommandLineParser>();
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
OakCommandLineParser h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_commandlineparser_free(OakCommonCommandLineParser *parser)
|
||||
void oakcommon_commandlineparser_free(OakCommandLineParser *parser)
|
||||
{
|
||||
delete parser;
|
||||
oakcommon::free_handle(parser);
|
||||
}
|
||||
|
||||
int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
|
||||
int oakcommon_commandlineparser_set_app_info(OakCommandLineParser parser,
|
||||
const char *name,
|
||||
const char *version)
|
||||
{
|
||||
if (!parser || !name) {
|
||||
if (!clp(parser) || !name) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
parser->impl.set_app_info(name, version ? version : "");
|
||||
clp(parser)->set_app_info(name, version ? version : "");
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
@@ -70,11 +106,11 @@ int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
|
||||
}
|
||||
|
||||
int oakcommon_commandlineparser_add_option(
|
||||
OakCommonCommandLineParser *parser, const char *const *names, int name_count,
|
||||
OakCommandLineParser parser, const char *const *names, int name_count,
|
||||
const char *description, int takes_arg, const char *arg_placeholder,
|
||||
int hidden, OakCommonCommandLineOption **out_option)
|
||||
int hidden, OakCommandLineOption *out_option)
|
||||
{
|
||||
if (!parser || !names || name_count <= 0) {
|
||||
if (!clp(parser) || !names || name_count <= 0) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -88,18 +124,16 @@ int oakcommon_commandlineparser_add_option(
|
||||
strings.emplace_back(names[i]);
|
||||
}
|
||||
|
||||
const CommandLineParser::Option *option = parser->impl.add_option(
|
||||
const CommandLineParser::Option *option = clp(parser)->add_option(
|
||||
strings, description ? description : "", takes_arg != 0,
|
||||
arg_placeholder ? arg_placeholder : "", hidden != 0);
|
||||
|
||||
if (out_option) {
|
||||
auto *handle =
|
||||
new (std::nothrow) OakCommonCommandLineOption();
|
||||
if (!handle) {
|
||||
*out_option = oakcommon::make_handle<OakCommandLineOption>(
|
||||
OptionState{const_cast<CommandLineParser::Option *>(option)});
|
||||
if (!out_option->ctx) {
|
||||
return OAKCOMMON_E_NOMEM;
|
||||
}
|
||||
handle->option = const_cast<CommandLineParser::Option *>(option);
|
||||
*out_option = handle;
|
||||
}
|
||||
|
||||
return OAKCOMMON_OK;
|
||||
@@ -109,28 +143,28 @@ int oakcommon_commandlineparser_add_option(
|
||||
}
|
||||
|
||||
int oakcommon_commandlineparser_add_positional_argument(
|
||||
OakCommonCommandLineParser *parser, const char *name,
|
||||
OakCommandLineParser parser, const char *name,
|
||||
const char *description, int required,
|
||||
OakCommonCommandLinePositionalArgument **out_argument)
|
||||
OakCommandLinePositionalArgument *out_argument)
|
||||
{
|
||||
if (!parser || !name) {
|
||||
if (!clp(parser) || !name) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const CommandLineParser::PositionalArgument *argument =
|
||||
parser->impl.add_positional_argument(
|
||||
clp(parser)->add_positional_argument(
|
||||
name, description ? description : "", required != 0);
|
||||
|
||||
if (out_argument) {
|
||||
auto *handle =
|
||||
new (std::nothrow) OakCommonCommandLinePositionalArgument();
|
||||
if (!handle) {
|
||||
*out_argument =
|
||||
oakcommon::make_handle<OakCommandLinePositionalArgument>(
|
||||
PositionalArgumentState{
|
||||
const_cast<CommandLineParser::PositionalArgument *>(
|
||||
argument)});
|
||||
if (!out_argument->ctx) {
|
||||
return OAKCOMMON_E_NOMEM;
|
||||
}
|
||||
handle->argument =
|
||||
const_cast<CommandLineParser::PositionalArgument *>(argument);
|
||||
*out_argument = handle;
|
||||
}
|
||||
|
||||
return OAKCOMMON_OK;
|
||||
@@ -139,10 +173,10 @@ int oakcommon_commandlineparser_add_positional_argument(
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
|
||||
int oakcommon_commandlineparser_process(OakCommandLineParser parser,
|
||||
const char *const *argv, int argc)
|
||||
{
|
||||
if (!parser || !argv || argc < 0) {
|
||||
if (!clp(parser) || !argv || argc < 0) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -153,37 +187,37 @@ int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
|
||||
args.emplace_back(argv[i] ? argv[i] : "");
|
||||
}
|
||||
|
||||
parser->impl.process(args);
|
||||
clp(parser)->process(args);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
|
||||
int oakcommon_commandlineparser_print_help(OakCommandLineParser parser,
|
||||
const char *filename)
|
||||
{
|
||||
if (!parser || !filename) {
|
||||
if (!clp(parser) || !filename) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
parser->impl.print_help(filename);
|
||||
clp(parser)->print_help(filename);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_commandlineoption_is_set(OakCommonCommandLineOption *option,
|
||||
int oakcommon_commandlineoption_is_set(OakCommandLineOption option,
|
||||
bool *is_set)
|
||||
{
|
||||
if (!option || !option->option || !is_set) {
|
||||
if (!clo(option) || !is_set) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*is_set = option->option->is_set();
|
||||
*is_set = clo(option)->is_set();
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
@@ -212,29 +246,29 @@ static int copy_setting(const std::string &value, char *buf, int buf_size)
|
||||
return required;
|
||||
}
|
||||
|
||||
int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
|
||||
int oakcommon_commandlineoption_get_setting(OakCommandLineOption option,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!option || !option->option) {
|
||||
if (!clo(option)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_setting(option->option->get_setting(), buf, buf_size);
|
||||
return copy_setting(clo(option)->get_setting(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
|
||||
int oakcommon_commandlineoption_set_setting(OakCommandLineOption option,
|
||||
const char *value)
|
||||
{
|
||||
if (!option || !option->option || !value) {
|
||||
if (!clo(option) || !value) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
option->option->set_setting(value);
|
||||
clo(option)->set_setting(value);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
@@ -242,41 +276,41 @@ int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
|
||||
}
|
||||
|
||||
int oakcommon_commandlinepositionalargument_get_setting(
|
||||
OakCommonCommandLinePositionalArgument *argument, char *buf, int buf_size)
|
||||
OakCommandLinePositionalArgument argument, char *buf, int buf_size)
|
||||
{
|
||||
if (!argument || !argument->argument) {
|
||||
if (!clpa(argument)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_setting(argument->argument->get_setting(), buf, buf_size);
|
||||
return copy_setting(clpa(argument)->get_setting(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_commandlinepositionalargument_set_setting(
|
||||
OakCommonCommandLinePositionalArgument *argument, const char *value)
|
||||
OakCommandLinePositionalArgument argument, const char *value)
|
||||
{
|
||||
if (!argument || !argument->argument || !value) {
|
||||
if (!clpa(argument) || !value) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
argument->argument->set_setting(value);
|
||||
clpa(argument)->set_setting(value);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option)
|
||||
void oakcommon_commandlineoption_free(OakCommandLineOption *option)
|
||||
{
|
||||
delete option;
|
||||
oakcommon::free_handle(option);
|
||||
}
|
||||
|
||||
void oakcommon_commandlinepositionalargument_free(
|
||||
OakCommonCommandLinePositionalArgument *argument)
|
||||
OakCommandLinePositionalArgument *argument)
|
||||
{
|
||||
delete argument;
|
||||
oakcommon::free_handle(argument);
|
||||
}
|
||||
|
||||
@@ -22,25 +22,47 @@
|
||||
|
||||
#include "../src/current.h"
|
||||
|
||||
struct OakCommonCurrent {
|
||||
Current *current;
|
||||
};
|
||||
|
||||
OakCommonCurrent *oakcommon_current_instance(void)
|
||||
namespace
|
||||
{
|
||||
static OakCommonCurrent handle = { &Current::get_instance() };
|
||||
return &handle;
|
||||
|
||||
/**
|
||||
* @brief No-op addref/release for the singleton: it is never destroyed.
|
||||
*/
|
||||
void singleton_noop(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
}
|
||||
|
||||
void oakcommon_current_free(OakCommonCurrent *self)
|
||||
/**
|
||||
* @brief Recover the Current singleton from a handle (NULL-safe).
|
||||
*/
|
||||
Current *current_of(OakCurrent self)
|
||||
{
|
||||
// No-op: the handle wraps a process-wide singleton.
|
||||
return static_cast<Current *>(self.ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCurrent oakcommon_current_instance(void)
|
||||
{
|
||||
OakCurrent h = {};
|
||||
h.ctx = &Current::get_instance();
|
||||
h.addref = &singleton_noop;
|
||||
h.release = &singleton_noop;
|
||||
h.abi_version = OAKCOMMON_ABI_VERSION;
|
||||
return h;
|
||||
}
|
||||
|
||||
void oakcommon_current_free(OakCurrent *self)
|
||||
{
|
||||
// No-op: the handle wraps a process-wide singleton whose release()
|
||||
// intentionally never destroys anything.
|
||||
(void)self;
|
||||
}
|
||||
|
||||
static int current_set(Current *current,
|
||||
void (Current::*set_fn)(std::shared_ptr<void>),
|
||||
void *obj, OakCommonDestroyFn destroy)
|
||||
void *obj, OakDestroyFn destroy)
|
||||
{
|
||||
try {
|
||||
std::shared_ptr<void> value;
|
||||
@@ -70,78 +92,78 @@ static int current_get(Current *current,
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_current_set_video_params(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy)
|
||||
int oakcommon_current_set_video_params(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy)
|
||||
{
|
||||
if (!self)
|
||||
if (!current_of(self))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_set(self->current, &Current::set_current_video_params,
|
||||
return current_set(current_of(self), &Current::set_current_video_params,
|
||||
obj, destroy);
|
||||
}
|
||||
|
||||
int oakcommon_current_set_audio_params(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy)
|
||||
int oakcommon_current_set_audio_params(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy)
|
||||
{
|
||||
if (!self)
|
||||
if (!current_of(self))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_set(self->current, &Current::set_current_audio_params,
|
||||
return current_set(current_of(self), &Current::set_current_audio_params,
|
||||
obj, destroy);
|
||||
}
|
||||
|
||||
int oakcommon_current_set_plugin_host(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy)
|
||||
int oakcommon_current_set_plugin_host(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy)
|
||||
{
|
||||
if (!self)
|
||||
if (!current_of(self))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_set(self->current, &Current::set_plugin_host, obj,
|
||||
return current_set(current_of(self), &Current::set_plugin_host, obj,
|
||||
destroy);
|
||||
}
|
||||
|
||||
int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
|
||||
OakCommonDestroyFn destroy)
|
||||
int oakcommon_current_set_plugin_cache(OakCurrent self, void *obj,
|
||||
OakDestroyFn destroy)
|
||||
{
|
||||
if (!self)
|
||||
if (!current_of(self))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_set(self->current, &Current::set_plugin_cache, obj,
|
||||
return current_set(current_of(self), &Current::set_plugin_cache, obj,
|
||||
destroy);
|
||||
}
|
||||
|
||||
int oakcommon_current_get_video_params(OakCommonCurrent *self, void **out)
|
||||
int oakcommon_current_get_video_params(OakCurrent self, void **out)
|
||||
{
|
||||
if (!self || !out)
|
||||
if (!current_of(self) || !out)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_get(self->current, &Current::current_video_params,
|
||||
return current_get(current_of(self), &Current::current_video_params,
|
||||
out);
|
||||
}
|
||||
|
||||
int oakcommon_current_get_audio_params(OakCommonCurrent *self, void **out)
|
||||
int oakcommon_current_get_audio_params(OakCurrent self, void **out)
|
||||
{
|
||||
if (!self || !out)
|
||||
if (!current_of(self) || !out)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_get(self->current, &Current::current_audio_params,
|
||||
return current_get(current_of(self), &Current::current_audio_params,
|
||||
out);
|
||||
}
|
||||
|
||||
int oakcommon_current_get_plugin_host(OakCommonCurrent *self, void **out)
|
||||
int oakcommon_current_get_plugin_host(OakCurrent self, void **out)
|
||||
{
|
||||
if (!self || !out)
|
||||
if (!current_of(self) || !out)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_get(self->current, &Current::plugin_host, out);
|
||||
return current_get(current_of(self), &Current::plugin_host, out);
|
||||
}
|
||||
|
||||
int oakcommon_current_get_plugin_cache(OakCommonCurrent *self, void **out)
|
||||
int oakcommon_current_get_plugin_cache(OakCurrent self, void **out)
|
||||
{
|
||||
if (!self || !out)
|
||||
if (!current_of(self) || !out)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return current_get(self->current, &Current::plugin_cache, out);
|
||||
return current_get(current_of(self), &Current::plugin_cache, out);
|
||||
}
|
||||
|
||||
int oakcommon_current_is_interactive(OakCommonCurrent *self, int *out)
|
||||
int oakcommon_current_is_interactive(OakCurrent self, int *out)
|
||||
{
|
||||
if (!self || !out)
|
||||
if (!current_of(self) || !out)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
*out = self->current->interactive() ? 1 : 0;
|
||||
*out = current_of(self)->interactive() ? 1 : 0;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <vector>
|
||||
|
||||
#include "../src/debug.h"
|
||||
|
||||
int oakcommon_debug_log(int level, const char *msg)
|
||||
@@ -43,3 +46,68 @@ int oakcommon_debug_level_name(int level, char *buf, int buf_size)
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_log(int level, const char *fmt, ...)
|
||||
{
|
||||
if (!fmt)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
va_list sizing;
|
||||
va_copy(sizing, args);
|
||||
int needed = vsnprintf(nullptr, 0, fmt, sizing);
|
||||
va_end(sizing);
|
||||
|
||||
if (needed < 0) {
|
||||
va_end(args);
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
|
||||
std::string msg;
|
||||
try {
|
||||
// Dynamically sized: arbitrary message length, no truncation,
|
||||
// no fixed stack buffer.
|
||||
std::vector<char> buf(static_cast<size_t>(needed) + 1);
|
||||
vsnprintf(buf.data(), buf.size(), fmt, args);
|
||||
msg.assign(buf.data(), static_cast<size_t>(needed));
|
||||
} catch (...) {
|
||||
va_end(args);
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
try {
|
||||
olive::log_message(level, msg);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_log_set_level(int level)
|
||||
{
|
||||
if (level < OAKCOMMON_DEBUG_DEBUG || level > OAKCOMMON_DEBUG_FATAL)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
|
||||
try {
|
||||
olive::set_log_level(static_cast<olive::DebugLevel>(level));
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_log_get_level(int *out_level)
|
||||
{
|
||||
if (!out_level)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
|
||||
try {
|
||||
*out_level = static_cast<int>(olive::get_log_level());
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
@@ -24,11 +24,21 @@
|
||||
#include <string>
|
||||
|
||||
#include "../src/filefunctions.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
struct OakCommonFileFunctions {
|
||||
int unused; /**< Stateless family; handle kept for API uniformity. */
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Stateless family; the boxed object is empty and only exists so
|
||||
* the handle has something to reference-count.
|
||||
*/
|
||||
struct FileFunctionsState {
|
||||
int unused;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -54,25 +64,27 @@ bool is_valid_string_out(const char *buf, int buf_size)
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCommonFileFunctions *oakcommon_filefunctions_init(void)
|
||||
OakFileFunctions oakcommon_filefunctions_init(void)
|
||||
{
|
||||
try {
|
||||
return new OakCommonFileFunctions{0};
|
||||
return oakcommon::make_handle<OakFileFunctions>(
|
||||
FileFunctionsState{0});
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakFileFunctions h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_filefunctions_free(OakCommonFileFunctions *self)
|
||||
void oakcommon_filefunctions_free(OakFileFunctions *self)
|
||||
{
|
||||
delete self;
|
||||
oakcommon::free_handle(self);
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_get_unique_file_identifier(
|
||||
OakCommonFileFunctions *self, const char *filename, char *buf,
|
||||
OakFileFunctions self, const char *filename, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (self == nullptr || filename == nullptr ||
|
||||
if (self.ctx == nullptr || filename == nullptr ||
|
||||
!is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
@@ -87,9 +99,9 @@ int oakcommon_filefunctions_get_unique_file_identifier(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_get_configuration_location(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size)
|
||||
OakFileFunctions self, char *buf, int buf_size)
|
||||
{
|
||||
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -103,9 +115,9 @@ int oakcommon_filefunctions_get_configuration_location(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_get_application_path(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size)
|
||||
OakFileFunctions self, char *buf, int buf_size)
|
||||
{
|
||||
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -118,9 +130,9 @@ int oakcommon_filefunctions_get_application_path(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_get_temp_file_path(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size)
|
||||
OakFileFunctions self, char *buf, int buf_size)
|
||||
{
|
||||
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -133,9 +145,9 @@ int oakcommon_filefunctions_get_temp_file_path(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_get_auto_recovery_root(
|
||||
OakCommonFileFunctions *self, char *buf, int buf_size)
|
||||
OakFileFunctions self, char *buf, int buf_size)
|
||||
{
|
||||
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -148,10 +160,10 @@ int oakcommon_filefunctions_get_auto_recovery_root(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_can_copy_directory_without_overwriting(
|
||||
OakCommonFileFunctions *self, const char *source, const char *dest,
|
||||
OakFileFunctions self, const char *source, const char *dest,
|
||||
int *out)
|
||||
{
|
||||
if (self == nullptr || source == nullptr || dest == nullptr ||
|
||||
if (self.ctx == nullptr || source == nullptr || dest == nullptr ||
|
||||
out == nullptr) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
@@ -167,11 +179,11 @@ int oakcommon_filefunctions_can_copy_directory_without_overwriting(
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
|
||||
int oakcommon_filefunctions_copy_directory(OakFileFunctions self,
|
||||
const char *source,
|
||||
const char *dest, int overwrite)
|
||||
{
|
||||
if (self == nullptr || source == nullptr || dest == nullptr) {
|
||||
if (self.ctx == nullptr || source == nullptr || dest == nullptr) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -184,10 +196,10 @@ int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_directory_is_valid(
|
||||
OakCommonFileFunctions *self, const char *dir,
|
||||
OakFileFunctions self, const char *dir,
|
||||
int try_to_create_if_not_exists, int *out)
|
||||
{
|
||||
if (self == nullptr || dir == nullptr || out == nullptr) {
|
||||
if (self.ctx == nullptr || dir == nullptr || out == nullptr) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -203,10 +215,10 @@ int oakcommon_filefunctions_directory_is_valid(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_ensure_filename_extension(
|
||||
OakCommonFileFunctions *self, const char *filename,
|
||||
OakFileFunctions self, const char *filename,
|
||||
const char *extension, char *buf, int buf_size)
|
||||
{
|
||||
if (self == nullptr || filename == nullptr || extension == nullptr ||
|
||||
if (self.ctx == nullptr || filename == nullptr || extension == nullptr ||
|
||||
!is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
@@ -222,10 +234,10 @@ int oakcommon_filefunctions_ensure_filename_extension(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_read_file_as_string(
|
||||
OakCommonFileFunctions *self, const char *filename, char *buf,
|
||||
OakFileFunctions self, const char *filename, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (self == nullptr || filename == nullptr ||
|
||||
if (self.ctx == nullptr || filename == nullptr ||
|
||||
!is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
@@ -240,10 +252,10 @@ int oakcommon_filefunctions_read_file_as_string(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_get_safe_temporary_filename(
|
||||
OakCommonFileFunctions *self, const char *original, char *buf,
|
||||
OakFileFunctions self, const char *original, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (self == nullptr || original == nullptr ||
|
||||
if (self.ctx == nullptr || original == nullptr ||
|
||||
!is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
@@ -258,10 +270,10 @@ int oakcommon_filefunctions_get_safe_temporary_filename(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_rename_file_allow_overwrite(
|
||||
OakCommonFileFunctions *self, const char *from, const char *to,
|
||||
OakFileFunctions self, const char *from, const char *to,
|
||||
int *out)
|
||||
{
|
||||
if (self == nullptr || from == nullptr || to == nullptr ||
|
||||
if (self.ctx == nullptr || from == nullptr || to == nullptr ||
|
||||
out == nullptr) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
@@ -276,10 +288,10 @@ int oakcommon_filefunctions_rename_file_allow_overwrite(
|
||||
}
|
||||
|
||||
int oakcommon_filefunctions_get_formatted_executable_for_platform(
|
||||
OakCommonFileFunctions *self, const char *unformatted, char *buf,
|
||||
OakFileFunctions self, const char *unformatted, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (self == nullptr || unformatted == nullptr ||
|
||||
if (self.ctx == nullptr || unformatted == nullptr ||
|
||||
!is_valid_string_out(buf, buf_size)) {
|
||||
return OAKCOMMON_E_INVALID;
|
||||
}
|
||||
|
||||
@@ -23,30 +23,42 @@
|
||||
#include <new>
|
||||
|
||||
#include "../src/ocioutils.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
struct OakCommonOCIOUtils {
|
||||
int unused; /**< Stateless; only the address matters. */
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Stateless family; the boxed object is empty and only exists so
|
||||
* the handle has something to reference-count.
|
||||
*/
|
||||
struct OCIOUtilsState {
|
||||
int unused;
|
||||
};
|
||||
|
||||
OakCommonOCIOUtils *oakcommon_ocioutils_init(void)
|
||||
} // namespace
|
||||
|
||||
OakOCIOUtils oakcommon_ocioutils_init(void)
|
||||
{
|
||||
try {
|
||||
return new (std::nothrow) OakCommonOCIOUtils{};
|
||||
return oakcommon::make_handle<OakOCIOUtils>(
|
||||
OCIOUtilsState{0});
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
OakOCIOUtils h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_ocioutils_free(OakCommonOCIOUtils *self)
|
||||
void oakcommon_ocioutils_free(OakOCIOUtils *self)
|
||||
{
|
||||
delete self;
|
||||
oakcommon::free_handle(self);
|
||||
}
|
||||
|
||||
int oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
|
||||
OakCommonOCIOUtils *self, int pixel_format, int *out_bit_depth)
|
||||
OakOCIOUtils self, int pixel_format, int *out_bit_depth)
|
||||
{
|
||||
try {
|
||||
if (self == NULL || out_bit_depth == NULL)
|
||||
if (self.ctx == NULL || out_bit_depth == NULL)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
if (pixel_format < OAKCOMMON_PIXEL_FORMAT_INVALID ||
|
||||
pixel_format >= OAKCOMMON_PIXEL_FORMAT_COUNT)
|
||||
|
||||
@@ -23,30 +23,42 @@
|
||||
#include <new>
|
||||
|
||||
#include "../src/oiioutils.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
struct OakCommonOIIOUtils {
|
||||
int unused; /**< Stateless; only the address matters. */
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Stateless family; the boxed object is empty and only exists so
|
||||
* the handle has something to reference-count.
|
||||
*/
|
||||
struct OIIOUtilsState {
|
||||
int unused;
|
||||
};
|
||||
|
||||
OakCommonOIIOUtils *oakcommon_oiioutils_init(void)
|
||||
} // namespace
|
||||
|
||||
OakOIIOUtils oakcommon_oiioutils_init(void)
|
||||
{
|
||||
try {
|
||||
return new (std::nothrow) OakCommonOIIOUtils{};
|
||||
return oakcommon::make_handle<OakOIIOUtils>(
|
||||
OIIOUtilsState{0});
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
OakOIIOUtils h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_oiioutils_free(OakCommonOIIOUtils *self)
|
||||
void oakcommon_oiioutils_free(OakOIIOUtils *self)
|
||||
{
|
||||
delete self;
|
||||
oakcommon::free_handle(self);
|
||||
}
|
||||
|
||||
int oakcommon_oiioutils_get_oiio_base_type_from_format(
|
||||
OakCommonOIIOUtils *self, int pixel_format, int *out_base_type)
|
||||
OakOIIOUtils self, int pixel_format, int *out_base_type)
|
||||
{
|
||||
try {
|
||||
if (self == NULL || out_base_type == NULL)
|
||||
if (self.ctx == NULL || out_base_type == NULL)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
if (pixel_format < OAKCOMMON_PIXEL_FORMAT_INVALID ||
|
||||
pixel_format >= OAKCOMMON_PIXEL_FORMAT_COUNT)
|
||||
@@ -63,10 +75,10 @@ int oakcommon_oiioutils_get_oiio_base_type_from_format(
|
||||
}
|
||||
|
||||
int oakcommon_oiioutils_get_format_from_oiio_basetype(
|
||||
OakCommonOIIOUtils *self, int base_type, int *out_pixel_format)
|
||||
OakOIIOUtils self, int base_type, int *out_pixel_format)
|
||||
{
|
||||
try {
|
||||
if (self == NULL || out_pixel_format == NULL)
|
||||
if (self.ctx == NULL || out_pixel_format == NULL)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
if (base_type < 0 || base_type >= OIIO::TypeDesc::LASTBASE)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
@@ -82,11 +94,11 @@ int oakcommon_oiioutils_get_format_from_oiio_basetype(
|
||||
}
|
||||
|
||||
int oakcommon_oiioutils_get_pixel_aspect_ratio(
|
||||
OakCommonOIIOUtils *self, double pixel_aspect_ratio, int *out_numerator,
|
||||
OakOIIOUtils self, double pixel_aspect_ratio, int *out_numerator,
|
||||
int *out_denominator)
|
||||
{
|
||||
try {
|
||||
if (self == NULL || out_numerator == NULL || out_denominator == NULL)
|
||||
if (self.ctx == NULL || out_numerator == NULL || out_denominator == NULL)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
|
||||
olive::core::Rational par =
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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 OAKCOMMON_C_API_REFCOUNTED_H
|
||||
#define OAKCOMMON_C_API_REFCOUNTED_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "common/handle.h"
|
||||
|
||||
namespace oakcommon
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Heap box behind every handle's ctx pointer.
|
||||
*
|
||||
* Holds the wrapped object plus its atomic reference count. addref and
|
||||
* release are emitted per boxed type so that the function pointers stored
|
||||
* in a handle always run code from the DLL that created the object.
|
||||
*/
|
||||
template <typename T> struct RefCounted {
|
||||
T impl;
|
||||
std::atomic<uint32_t> refs;
|
||||
|
||||
template <typename... Args>
|
||||
explicit RefCounted(Args &&...args)
|
||||
: impl(std::forward<Args>(args)...)
|
||||
, refs(1)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Handle addref thunk: atomically increments the count.
|
||||
*/
|
||||
template <typename T> void ref_counted_addref(void *ctx)
|
||||
{
|
||||
auto *box = static_cast<RefCounted<T> *>(ctx);
|
||||
if (box)
|
||||
box->refs.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle release thunk: decrements the count, destroys at zero.
|
||||
*/
|
||||
template <typename T> void ref_counted_release(void *ctx)
|
||||
{
|
||||
auto *box = static_cast<RefCounted<T> *>(ctx);
|
||||
if (box && box->refs.fetch_sub(1, std::memory_order_acq_rel) == 1)
|
||||
delete box;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build a by-value handle owning a freshly boxed object (count 1).
|
||||
*
|
||||
* The object is constructed in place inside the box, so non-movable
|
||||
* types are supported. On allocation failure the returned handle has
|
||||
* ctx == NULL (all C API functions treat that as OAKCOMMON_E_INVALID
|
||||
* and free() as a no-op).
|
||||
*/
|
||||
template <typename Handle, typename T, typename... Args>
|
||||
Handle make_handle_in_place(Args &&...args)
|
||||
{
|
||||
Handle h = {};
|
||||
try {
|
||||
h.ctx = new RefCounted<T>(std::forward<Args>(args)...);
|
||||
} catch (...) {
|
||||
h.ctx = nullptr;
|
||||
}
|
||||
h.addref = &ref_counted_addref<T>;
|
||||
h.release = &ref_counted_release<T>;
|
||||
h.abi_version = OAKCOMMON_ABI_VERSION;
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build a by-value handle from an existing object (copied/moved
|
||||
* into the box, reference count 1).
|
||||
*/
|
||||
template <typename Handle, typename T>
|
||||
Handle make_handle(T &&value)
|
||||
{
|
||||
return make_handle_in_place<Handle, typename std::decay<T>::type>(
|
||||
std::forward<T>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recover the boxed object from a handle ctx (NULL-safe).
|
||||
*/
|
||||
template <typename T> T *handle_impl(void *ctx)
|
||||
{
|
||||
auto *box = static_cast<RefCounted<T> *>(ctx);
|
||||
return box ? &box->impl : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shared free() body: release the ctx, no-op on NULL/empty handle.
|
||||
*
|
||||
* Clears ctx afterwards so a double free through the same (copied)
|
||||
* struct is caught by the caller's own bookkeeping, not by us.
|
||||
*/
|
||||
template <typename Handle> void free_handle(Handle *h)
|
||||
{
|
||||
if (!h || !h->ctx || !h->release)
|
||||
return;
|
||||
h->release(h->ctx);
|
||||
h->ctx = nullptr;
|
||||
}
|
||||
|
||||
} // namespace oakcommon
|
||||
|
||||
#endif // OAKCOMMON_C_API_REFCOUNTED_H
|
||||
@@ -23,14 +23,19 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/subtitleparams.h"
|
||||
|
||||
struct OakCommonSubtitleParams {
|
||||
olive::SubtitleParams impl;
|
||||
};
|
||||
#include "refcounted.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Recover the boxed olive::SubtitleParams from a handle (NULL-safe).
|
||||
*/
|
||||
olive::SubtitleParams *sp(OakSubtitleParams params)
|
||||
{
|
||||
return oakcommon::handle_impl<olive::SubtitleParams>(params.ctx);
|
||||
}
|
||||
|
||||
int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
{
|
||||
int needed = (int)value.size() + 1;
|
||||
@@ -41,92 +46,110 @@ int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCommonSubtitleParams *oakcommon_subtitleparams_init(void)
|
||||
OakSubtitleParams oakcommon_subtitleparams_init(void)
|
||||
{
|
||||
try {
|
||||
return new OakCommonSubtitleParams{olive::SubtitleParams()};
|
||||
return oakcommon::make_handle<OakSubtitleParams>(
|
||||
olive::SubtitleParams());
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakSubtitleParams h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_subtitleparams_free(OakCommonSubtitleParams *params)
|
||||
OakSubtitleParams oakcommon_subtitleparams_init_from_native(
|
||||
const olive::SubtitleParams *src)
|
||||
{
|
||||
delete params;
|
||||
if (!src) {
|
||||
OakSubtitleParams h = {};
|
||||
return h;
|
||||
}
|
||||
try {
|
||||
return oakcommon::make_handle<OakSubtitleParams>(
|
||||
olive::SubtitleParams(*src));
|
||||
} catch (...) {
|
||||
OakSubtitleParams h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_subtitleparams_free(OakSubtitleParams *params)
|
||||
{
|
||||
oakcommon::free_handle(params);
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_stream_index(
|
||||
OakCommonSubtitleParams *params, int *index)
|
||||
OakSubtitleParams params, int *index)
|
||||
{
|
||||
if (!params || !index)
|
||||
if (!sp(params) || !index)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*index = params->impl.stream_index();
|
||||
*index = sp(params)->stream_index();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_set_stream_index(
|
||||
OakCommonSubtitleParams *params, int index)
|
||||
OakSubtitleParams params, int index)
|
||||
{
|
||||
if (!params)
|
||||
if (!sp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_stream_index(index);
|
||||
sp(params)->set_stream_index(index);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_enabled(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_get_enabled(OakSubtitleParams params,
|
||||
int *enabled)
|
||||
{
|
||||
if (!params || !enabled)
|
||||
if (!sp(params) || !enabled)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*enabled = params->impl.enabled() ? 1 : 0;
|
||||
*enabled = sp(params)->enabled() ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_set_enabled(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_set_enabled(OakSubtitleParams params,
|
||||
int enabled)
|
||||
{
|
||||
if (!params)
|
||||
if (!sp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_enabled(enabled != 0);
|
||||
sp(params)->set_enabled(enabled != 0);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_is_valid(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_is_valid(OakSubtitleParams params,
|
||||
int *is_valid)
|
||||
{
|
||||
if (!params || !is_valid)
|
||||
if (!sp(params) || !is_valid)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*is_valid = params->impl.is_valid() ? 1 : 0;
|
||||
*is_valid = sp(params)->is_valid() ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_count(OakCommonSubtitleParams *params, int *count)
|
||||
int oakcommon_subtitleparams_count(OakSubtitleParams params, int *count)
|
||||
{
|
||||
if (!params || !count)
|
||||
if (!sp(params) || !count)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*count = (int)params->impl.size();
|
||||
*count = (int)sp(params)->size();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_duration(OakSubtitleParams params,
|
||||
int *numerator, int *denominator)
|
||||
{
|
||||
if (!params || !numerator || !denominator)
|
||||
if (!sp(params) || !numerator || !denominator)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
olive::core::Rational d = params->impl.duration();
|
||||
olive::core::Rational d = sp(params)->duration();
|
||||
*numerator = d.numerator();
|
||||
*denominator = d.denominator();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_add_subtitle(OakSubtitleParams params,
|
||||
int in_num, int in_den, int out_num,
|
||||
int out_den, const char *text)
|
||||
{
|
||||
if (!params || !text)
|
||||
if (!sp(params) || !text)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
params->impl.push_back(olive::Subtitle(
|
||||
sp(params)->push_back(olive::Subtitle(
|
||||
olive::core::TimeRange(olive::core::Rational(in_num, in_den),
|
||||
olive::core::Rational(out_num, out_den)),
|
||||
text));
|
||||
@@ -136,23 +159,23 @@ int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params)
|
||||
int oakcommon_subtitleparams_clear(OakSubtitleParams params)
|
||||
{
|
||||
if (!params)
|
||||
if (!sp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.clear();
|
||||
sp(params)->clear();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_get_subtitle(OakSubtitleParams params,
|
||||
int index, int *in_num, int *in_den,
|
||||
int *out_num, int *out_den)
|
||||
{
|
||||
if (!params || !in_num || !in_den || !out_num || !out_den)
|
||||
if (!sp(params) || !in_num || !in_den || !out_num || !out_den)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
if (index < 0 || index >= (int)params->impl.size())
|
||||
if (index < 0 || index >= (int)sp(params)->size())
|
||||
return OAKCOMMON_E_NOT_FOUND;
|
||||
const olive::Subtitle &s = params->impl.at(index);
|
||||
const olive::Subtitle &s = sp(params)->at(index);
|
||||
olive::core::Rational in = s.time().in();
|
||||
olive::core::Rational out = s.time().out();
|
||||
*in_num = in.numerator();
|
||||
@@ -162,16 +185,16 @@ int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_subtitle_text(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_get_subtitle_text(OakSubtitleParams params,
|
||||
int index, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
if (!sp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
if (index < 0 || index >= (int)params->impl.size())
|
||||
if (index < 0 || index >= (int)sp(params)->size())
|
||||
return OAKCOMMON_E_NOT_FOUND;
|
||||
try {
|
||||
return copy_string(params->impl.at(index).text(), buf, buf_size);
|
||||
return copy_string(sp(params)->at(index).text(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
@@ -187,10 +210,10 @@ int oakcommon_subtitleparams_generate_ass_header(char *buf, int buf_size)
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_load_xml(OakSubtitleParams params,
|
||||
const char *xml)
|
||||
{
|
||||
if (!params || !xml)
|
||||
if (!sp(params) || !xml)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamReader reader(xml);
|
||||
@@ -199,22 +222,22 @@ int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
|
||||
// Position on the root element; load() consumes its children.
|
||||
if (!olive::xml_read_next_start_element(&reader))
|
||||
return OAKCOMMON_E_FAILED;
|
||||
params->impl.load(&reader);
|
||||
sp(params)->load(&reader);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_save_xml(OakCommonSubtitleParams *params,
|
||||
int oakcommon_subtitleparams_save_xml(OakSubtitleParams params,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
if (!sp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamWriter writer;
|
||||
writer.write_start_element("subtitleparams");
|
||||
params->impl.save(&writer);
|
||||
sp(params)->save(&writer);
|
||||
writer.write_end_element();
|
||||
return copy_string(writer.output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
|
||||
+200
-132
@@ -23,10 +23,20 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/videoparams.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
struct OakCommonVideoParams {
|
||||
olive::VideoParams impl;
|
||||
};
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Recover the boxed olive::VideoParams from a handle (NULL-safe).
|
||||
*/
|
||||
olive::VideoParams *vp(OakVideoParams params)
|
||||
{
|
||||
return oakcommon::handle_impl<olive::VideoParams>(params.ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -51,317 +61,345 @@ int get_rational(const olive::core::Rational &r, int *numerator,
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCommonVideoParams *oakcommon_videoparams_init(void)
|
||||
OakVideoParams oakcommon_videoparams_init(void)
|
||||
{
|
||||
try {
|
||||
return new OakCommonVideoParams{olive::VideoParams()};
|
||||
return oakcommon::make_handle<OakVideoParams>(
|
||||
olive::VideoParams());
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakVideoParams h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_basic(
|
||||
OakVideoParams oakcommon_videoparams_init_basic(
|
||||
int width, int height, int pixel_format, int nb_channels,
|
||||
int pixel_aspect_num, int pixel_aspect_den, int interlacing, int divider)
|
||||
{
|
||||
try {
|
||||
return new OakCommonVideoParams{olive::VideoParams(
|
||||
width, height,
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format),
|
||||
nb_channels,
|
||||
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(interlacing),
|
||||
divider)};
|
||||
return oakcommon::make_handle<OakVideoParams>(
|
||||
olive::VideoParams(
|
||||
width, height,
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format),
|
||||
nb_channels,
|
||||
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(interlacing),
|
||||
divider));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakVideoParams h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_with_time_base(
|
||||
OakVideoParams oakcommon_videoparams_init_with_time_base(
|
||||
int width, int height, int time_base_num, int time_base_den,
|
||||
int pixel_format, int nb_channels, int pixel_aspect_num,
|
||||
int pixel_aspect_den, int interlacing, int divider)
|
||||
{
|
||||
try {
|
||||
return new OakCommonVideoParams{olive::VideoParams(
|
||||
width, height,
|
||||
olive::core::Rational(time_base_num, time_base_den),
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format),
|
||||
nb_channels,
|
||||
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(interlacing),
|
||||
divider)};
|
||||
return oakcommon::make_handle<OakVideoParams>(
|
||||
olive::VideoParams(
|
||||
width, height,
|
||||
olive::core::Rational(time_base_num, time_base_den),
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format),
|
||||
nb_channels,
|
||||
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(interlacing),
|
||||
divider));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakVideoParams h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_videoparams_free(OakCommonVideoParams *params)
|
||||
OakVideoParams oakcommon_videoparams_init_from_native(
|
||||
const olive::VideoParams *src)
|
||||
{
|
||||
delete params;
|
||||
if (!src) {
|
||||
OakVideoParams h = {};
|
||||
return h;
|
||||
}
|
||||
try {
|
||||
return oakcommon::make_handle<OakVideoParams>(
|
||||
olive::VideoParams(*src));
|
||||
} catch (...) {
|
||||
OakVideoParams h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
const olive::VideoParams *oakcommon_videoparams_get_native(
|
||||
OakVideoParams params)
|
||||
{
|
||||
return vp(params);
|
||||
}
|
||||
|
||||
void oakcommon_videoparams_free(OakVideoParams *params)
|
||||
{
|
||||
oakcommon::free_handle(params);
|
||||
}
|
||||
|
||||
#define OAKCOMMON_VIDEOPARAMS_INT_GETTER(name, expr) \
|
||||
int oakcommon_videoparams_get_##name(OakCommonVideoParams *params, \
|
||||
int oakcommon_videoparams_get_##name(OakVideoParams params, \
|
||||
int *out) \
|
||||
{ \
|
||||
if (!params || !out) \
|
||||
if (!vp(params) || !out) \
|
||||
return OAKCOMMON_E_INVALID; \
|
||||
*out = (expr); \
|
||||
return OAKCOMMON_OK; \
|
||||
}
|
||||
|
||||
#define OAKCOMMON_VIDEOPARAMS_INT_SETTER(name, stmt) \
|
||||
int oakcommon_videoparams_set_##name(OakCommonVideoParams *params, \
|
||||
int oakcommon_videoparams_set_##name(OakVideoParams params, \
|
||||
int value) \
|
||||
{ \
|
||||
if (!params) \
|
||||
if (!vp(params)) \
|
||||
return OAKCOMMON_E_INVALID; \
|
||||
stmt; \
|
||||
return OAKCOMMON_OK; \
|
||||
}
|
||||
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(width, params->impl.width())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(width, params->impl.set_width(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(height, params->impl.height())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(height, params->impl.set_height(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(depth, params->impl.depth())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(depth, params->impl.set_depth(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_3d, params->impl.is_3d() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(format, static_cast<int>(params->impl.format()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(width, vp(params)->width())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(width, vp(params)->set_width(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(height, vp(params)->height())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(height, vp(params)->set_height(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(depth, vp(params)->depth())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(depth, vp(params)->set_depth(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_3d, vp(params)->is_3d() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(format, static_cast<int>(vp(params)->format()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
format, params->impl.set_format(
|
||||
format, vp(params)->set_format(
|
||||
static_cast<olive::core::PixelFormat::Format>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(channel_count, params->impl.channel_count())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(channel_count, vp(params)->channel_count())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(channel_count,
|
||||
params->impl.set_channel_count(value))
|
||||
vp(params)->set_channel_count(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(interlacing,
|
||||
static_cast<int>(params->impl.interlacing()))
|
||||
static_cast<int>(vp(params)->interlacing()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
interlacing,
|
||||
params->impl.set_interlacing(
|
||||
vp(params)->set_interlacing(
|
||||
static_cast<olive::VideoParams::Interlacing>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(divider, params->impl.divider())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(divider, params->impl.set_divider(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(enabled, params->impl.enabled() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(enabled, params->impl.set_enabled(value != 0))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(stream_index, params->impl.stream_index())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(divider, vp(params)->divider())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(divider, vp(params)->set_divider(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(enabled, vp(params)->enabled() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(enabled, vp(params)->set_enabled(value != 0))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(stream_index, vp(params)->stream_index())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(stream_index,
|
||||
params->impl.set_stream_index(value))
|
||||
vp(params)->set_stream_index(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(video_type,
|
||||
static_cast<int>(params->impl.video_type()))
|
||||
static_cast<int>(vp(params)->video_type()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
video_type,
|
||||
params->impl.set_video_type(static_cast<olive::VideoParams::Type>(value)))
|
||||
vp(params)->set_video_type(static_cast<olive::VideoParams::Type>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(premultiplied_alpha,
|
||||
params->impl.premultiplied_alpha() ? 1 : 0)
|
||||
vp(params)->premultiplied_alpha() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(premultiplied_alpha,
|
||||
params->impl.set_premultiplied_alpha(
|
||||
vp(params)->set_premultiplied_alpha(
|
||||
value != 0))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_range,
|
||||
static_cast<int>(params->impl.color_range()))
|
||||
static_cast<int>(vp(params)->color_range()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
color_range, params->impl.set_color_range(
|
||||
color_range, vp(params)->set_color_range(
|
||||
static_cast<olive::VideoParams::ColorRange>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_primaries,
|
||||
params->impl.color_primaries())
|
||||
vp(params)->color_primaries())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_primaries,
|
||||
params->impl.set_color_primaries(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_transfer, params->impl.color_transfer())
|
||||
vp(params)->set_color_primaries(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_transfer, vp(params)->color_transfer())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_transfer,
|
||||
params->impl.set_color_transfer(value))
|
||||
vp(params)->set_color_transfer(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(square_pixel_width,
|
||||
params->impl.square_pixel_width())
|
||||
vp(params)->square_pixel_width())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_width,
|
||||
params->impl.effective_width())
|
||||
vp(params)->effective_width())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_height,
|
||||
params->impl.effective_height())
|
||||
vp(params)->effective_height())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_depth,
|
||||
params->impl.effective_depth())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_valid, params->impl.is_valid() ? 1 : 0)
|
||||
vp(params)->effective_depth())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_valid, vp(params)->is_valid() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_channel,
|
||||
params->impl.get_bytes_per_channel())
|
||||
vp(params)->get_bytes_per_channel())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_pixel,
|
||||
params->impl.get_bytes_per_pixel())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(buffer_size, params->impl.get_buffer_size())
|
||||
vp(params)->get_bytes_per_pixel())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(buffer_size, vp(params)->get_buffer_size())
|
||||
|
||||
int oakcommon_videoparams_get_x(OakCommonVideoParams *params, float *x)
|
||||
int oakcommon_videoparams_get_x(OakVideoParams params, float *x)
|
||||
{
|
||||
if (!params || !x)
|
||||
if (!vp(params) || !x)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*x = params->impl.x();
|
||||
*x = vp(params)->x();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_x(OakCommonVideoParams *params, float x)
|
||||
int oakcommon_videoparams_set_x(OakVideoParams params, float x)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_x(x);
|
||||
vp(params)->set_x(x);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_y(OakCommonVideoParams *params, float *y)
|
||||
int oakcommon_videoparams_get_y(OakVideoParams params, float *y)
|
||||
{
|
||||
if (!params || !y)
|
||||
if (!vp(params) || !y)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*y = params->impl.y();
|
||||
*y = vp(params)->y();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_y(OakCommonVideoParams *params, float y)
|
||||
int oakcommon_videoparams_set_y(OakVideoParams params, float y)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_y(y);
|
||||
vp(params)->set_y(y);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_start_time(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_start_time(OakVideoParams params,
|
||||
int64_t *start_time)
|
||||
{
|
||||
if (!params || !start_time)
|
||||
if (!vp(params) || !start_time)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*start_time = params->impl.start_time();
|
||||
*start_time = vp(params)->start_time();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_start_time(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_start_time(OakVideoParams params,
|
||||
int64_t start_time)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_start_time(start_time);
|
||||
vp(params)->set_start_time(start_time);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_duration(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_duration(OakVideoParams params,
|
||||
int64_t *duration)
|
||||
{
|
||||
if (!params || !duration)
|
||||
if (!vp(params) || !duration)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*duration = params->impl.duration();
|
||||
*duration = vp(params)->duration();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_duration(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_duration(OakVideoParams params,
|
||||
int64_t duration)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_duration(duration);
|
||||
vp(params)->set_duration(duration);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_time_base(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_time_base(OakVideoParams params,
|
||||
int *numerator, int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.time_base(), numerator, denominator);
|
||||
return get_rational(vp(params)->time_base(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_time_base(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_time_base(OakVideoParams params,
|
||||
int numerator, int denominator)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_time_base(olive::core::Rational(numerator, denominator));
|
||||
vp(params)->set_time_base(olive::core::Rational(numerator, denominator));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_frame_rate(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_frame_rate(OakVideoParams params,
|
||||
int *numerator, int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.frame_rate(), numerator, denominator);
|
||||
return get_rational(vp(params)->frame_rate(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_frame_rate(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_frame_rate(OakVideoParams params,
|
||||
int numerator, int denominator)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_frame_rate(olive::core::Rational(numerator, denominator));
|
||||
vp(params)->set_frame_rate(olive::core::Rational(numerator, denominator));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_frame_rate_as_time_base(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_frame_rate_as_time_base(OakVideoParams params,
|
||||
int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.frame_rate_as_time_base(), numerator,
|
||||
return get_rational(vp(params)->frame_rate_as_time_base(), numerator,
|
||||
denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_pixel_aspect_ratio(OakVideoParams params,
|
||||
int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.pixel_aspect_ratio(), numerator,
|
||||
return get_rational(vp(params)->pixel_aspect_ratio(), numerator,
|
||||
denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_pixel_aspect_ratio(OakVideoParams params,
|
||||
int numerator, int denominator)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_pixel_aspect_ratio(
|
||||
vp(params)->set_pixel_aspect_ratio(
|
||||
olive::core::Rational(numerator, denominator));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_colorspace(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_get_colorspace(OakVideoParams params,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(params->impl.colorspace(), buf, buf_size);
|
||||
return copy_string(vp(params)->colorspace(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_colorspace(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_set_colorspace(OakVideoParams params,
|
||||
const char *colorspace)
|
||||
{
|
||||
if (!params || !colorspace)
|
||||
if (!vp(params) || !colorspace)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_colorspace(colorspace);
|
||||
vp(params)->set_colorspace(colorspace);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_time_in_timebase_units(
|
||||
OakCommonVideoParams *params, int time_num, int time_den,
|
||||
OakVideoParams params, int time_num, int time_den,
|
||||
int64_t *timestamp)
|
||||
{
|
||||
if (!params || !timestamp)
|
||||
if (!vp(params) || !timestamp)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*timestamp = params->impl.get_time_in_timebase_units(
|
||||
*timestamp = vp(params)->get_time_in_timebase_units(
|
||||
olive::core::Rational(time_num, time_den));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_equals(OakCommonVideoParams *params,
|
||||
OakCommonVideoParams *other, int *equal)
|
||||
int oakcommon_videoparams_equals(OakVideoParams params,
|
||||
OakVideoParams other, int *equal)
|
||||
{
|
||||
if (!params || !other || !equal)
|
||||
if (!vp(params) || !vp(other) || !equal)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*equal = (params->impl == other->impl) ? 1 : 0;
|
||||
*equal = (*vp(params) == *vp(other)) ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
|
||||
int oakcommon_videoparams_load_xml(OakVideoParams params,
|
||||
const char *xml)
|
||||
{
|
||||
if (!params || !xml)
|
||||
if (!vp(params) || !xml)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamReader reader(xml);
|
||||
@@ -370,22 +408,22 @@ int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
|
||||
// Position on the root element; load() consumes its children.
|
||||
if (!olive::xml_read_next_start_element(&reader))
|
||||
return OAKCOMMON_E_FAILED;
|
||||
params->impl.load(&reader);
|
||||
vp(params)->load(&reader);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_save_xml(OakCommonVideoParams *params, char *buf,
|
||||
int oakcommon_videoparams_save_xml(OakVideoParams params, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
if (!vp(params))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamWriter writer;
|
||||
writer.write_start_element("videoparams");
|
||||
params->impl.save(&writer);
|
||||
vp(params)->save(&writer);
|
||||
writer.write_end_element();
|
||||
return copy_string(writer.output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
@@ -476,3 +514,33 @@ int oakcommon_videoparams_frame_rate_to_string(int numerator, int denominator,
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
static olive::PixelFormat convert_to_olive_format(OakPixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case OAKCOMMON_PIXEL_FORMAT_INVALID:
|
||||
return olive::PixelFormat::invalid;
|
||||
case OAKCOMMON_PIXEL_FORMAT_COUNT:
|
||||
return olive::PixelFormat::count;
|
||||
case OAKCOMMON_PIXEL_FORMAT_U8:
|
||||
return olive::PixelFormat::u8;
|
||||
case OAKCOMMON_PIXEL_FORMAT_U10:
|
||||
return olive::PixelFormat::u10;
|
||||
case OAKCOMMON_PIXEL_FORMAT_U16:
|
||||
return olive::PixelFormat::u16;
|
||||
case OAKCOMMON_PIXEL_FORMAT_F16:
|
||||
return olive::PixelFormat::f16;
|
||||
case OAKCOMMON_PIXEL_FORMAT_F32:
|
||||
return olive::PixelFormat::f32;
|
||||
}
|
||||
return olive::PixelFormat::invalid;
|
||||
}
|
||||
int oakcommon_videoparams_static_get_bytes_per_channel(OakPixelFormat format)
|
||||
{
|
||||
return olive::VideoParams::get_bytes_per_channel(convert_to_olive_format(format));
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_static_get_bytes_per_pixel(OakPixelFormat format, int channels)
|
||||
{
|
||||
return olive::VideoParams::get_bytes_per_pixel(convert_to_olive_format(format), channels);
|
||||
}
|
||||
|
||||
@@ -24,24 +24,40 @@
|
||||
#include <new>
|
||||
|
||||
#include "../src/xmlutils.h"
|
||||
#include "refcounted.h"
|
||||
|
||||
struct OakCommonXmlReader {
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Reader state boxed behind the handle's ctx pointer.
|
||||
*/
|
||||
struct XmlReaderState {
|
||||
olive::XmlStreamReader reader;
|
||||
std::string cached_text;
|
||||
bool has_cached_text = false;
|
||||
|
||||
explicit OakCommonXmlReader(const char *data)
|
||||
explicit XmlReaderState(const char *data)
|
||||
: reader(data)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct OakCommonXmlWriter {
|
||||
olive::XmlStreamWriter writer;
|
||||
};
|
||||
|
||||
namespace
|
||||
/**
|
||||
* @brief Recover the boxed reader state from a handle (NULL-safe).
|
||||
*/
|
||||
XmlReaderState *xr(OakXmlReader reader)
|
||||
{
|
||||
return oakcommon::handle_impl<XmlReaderState>(reader.ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recover the boxed writer from a handle (NULL-safe).
|
||||
*/
|
||||
olive::XmlStreamWriter *xw(OakXmlWriter writer)
|
||||
{
|
||||
return oakcommon::handle_impl<olive::XmlStreamWriter>(writer.ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy @p value into the two-stage string buffer.
|
||||
@@ -62,99 +78,102 @@ int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
|
||||
extern "C" {
|
||||
|
||||
OakCommonXmlReader *oakcommon_xml_reader_init(const char *data)
|
||||
OakXmlReader oakcommon_xml_reader_init(const char *data)
|
||||
{
|
||||
OakXmlReader h = {};
|
||||
if (!data)
|
||||
return nullptr;
|
||||
return h;
|
||||
try {
|
||||
return new (std::nothrow) OakCommonXmlReader(data);
|
||||
return oakcommon::make_handle<OakXmlReader>(
|
||||
XmlReaderState(data));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakXmlReader empty = {};
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_xml_reader_free(OakCommonXmlReader *reader)
|
||||
void oakcommon_xml_reader_free(OakXmlReader *reader)
|
||||
{
|
||||
delete reader;
|
||||
oakcommon::free_handle(reader);
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_read_next_start_element(OakXmlReader reader,
|
||||
int *found)
|
||||
{
|
||||
if (!reader || !found)
|
||||
if (!xr(reader) || !found)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
reader->has_cached_text = false;
|
||||
*found = olive::xml_read_next_start_element(&reader->reader) ? 1 : 0;
|
||||
xr(reader)->has_cached_text = false;
|
||||
*found = olive::xml_read_next_start_element(&xr(reader)->reader) ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
|
||||
int oakcommon_xml_reader_name(OakXmlReader reader, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!reader)
|
||||
if (!xr(reader))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(reader->reader.name(), buf, buf_size);
|
||||
return copy_string(xr(reader)->reader.name(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_read_element_text(OakXmlReader reader,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!reader)
|
||||
if (!xr(reader))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
// read_element_text() consumes the stream, so cache the result to
|
||||
// keep the two-stage (size query then copy) buffer convention working.
|
||||
if (!reader->has_cached_text) {
|
||||
reader->cached_text = reader->reader.read_element_text();
|
||||
reader->has_cached_text = true;
|
||||
if (!xr(reader)->has_cached_text) {
|
||||
xr(reader)->cached_text = xr(reader)->reader.read_element_text();
|
||||
xr(reader)->has_cached_text = true;
|
||||
}
|
||||
return copy_string(reader->cached_text, buf, buf_size);
|
||||
return copy_string(xr(reader)->cached_text, buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_skip_current_element(OakCommonXmlReader *reader)
|
||||
int oakcommon_xml_reader_skip_current_element(OakXmlReader reader)
|
||||
{
|
||||
if (!reader)
|
||||
if (!xr(reader))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
reader->has_cached_text = false;
|
||||
reader->reader.skip_current_element();
|
||||
xr(reader)->has_cached_text = false;
|
||||
xr(reader)->reader.skip_current_element();
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_attribute_count(OakXmlReader reader,
|
||||
int *count)
|
||||
{
|
||||
if (!reader || !count)
|
||||
if (!xr(reader) || !count)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
*count = static_cast<int>(reader->reader.attributes().size());
|
||||
*count = static_cast<int>(xr(reader)->reader.attributes().size());
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
|
||||
int oakcommon_xml_reader_attribute_name(OakXmlReader reader, int index,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!reader)
|
||||
if (!xr(reader))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
const auto &attrs = reader->reader.attributes();
|
||||
const auto &attrs = xr(reader)->reader.attributes();
|
||||
if (index < 0 || index >= static_cast<int>(attrs.size()))
|
||||
return OAKCOMMON_E_NOT_FOUND;
|
||||
return copy_string(attrs[index].name, buf, buf_size);
|
||||
@@ -163,13 +182,13 @@ int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_attribute_value(OakXmlReader reader,
|
||||
int index, char *buf, int buf_size)
|
||||
{
|
||||
if (!reader)
|
||||
if (!xr(reader))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
const auto &attrs = reader->reader.attributes();
|
||||
const auto &attrs = xr(reader)->reader.attributes();
|
||||
if (index < 0 || index >= static_cast<int>(attrs.size()))
|
||||
return OAKCOMMON_E_NOT_FOUND;
|
||||
return copy_string(attrs[index].value, buf, buf_size);
|
||||
@@ -178,117 +197,119 @@ int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_reader_has_error(OakCommonXmlReader *reader,
|
||||
int oakcommon_xml_reader_has_error(OakXmlReader reader,
|
||||
int *has_error)
|
||||
{
|
||||
if (!reader || !has_error)
|
||||
if (!xr(reader) || !has_error)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
*has_error = reader->reader.has_error() ? 1 : 0;
|
||||
*has_error = xr(reader)->reader.has_error() ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakCommonXmlWriter *oakcommon_xml_writer_init(void)
|
||||
OakXmlWriter oakcommon_xml_writer_init(void)
|
||||
{
|
||||
try {
|
||||
return new (std::nothrow) OakCommonXmlWriter();
|
||||
return oakcommon::make_handle<OakXmlWriter>(
|
||||
olive::XmlStreamWriter());
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
OakXmlWriter h = {};
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_xml_writer_free(OakCommonXmlWriter *writer)
|
||||
void oakcommon_xml_writer_free(OakXmlWriter *writer)
|
||||
{
|
||||
delete writer;
|
||||
oakcommon::free_handle(writer);
|
||||
}
|
||||
|
||||
int oakcommon_xml_writer_write_start_element(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_start_element(OakXmlWriter writer,
|
||||
const char *name)
|
||||
{
|
||||
if (!writer || !name)
|
||||
if (!xw(writer) || !name)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
writer->writer.write_start_element(name);
|
||||
xw(writer)->write_start_element(name);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_writer_write_attribute(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_attribute(OakXmlWriter writer,
|
||||
const char *name, const char *value)
|
||||
{
|
||||
if (!writer || !name || !value)
|
||||
if (!xw(writer) || !name || !value)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
writer->writer.write_attribute(name, value);
|
||||
xw(writer)->write_attribute(name, value);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_writer_write_characters(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_characters(OakXmlWriter writer,
|
||||
const char *text)
|
||||
{
|
||||
if (!writer || !text)
|
||||
if (!xw(writer) || !text)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
writer->writer.write_characters(text);
|
||||
xw(writer)->write_characters(text);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_writer_write_text_element(OakCommonXmlWriter *writer,
|
||||
int oakcommon_xml_writer_write_text_element(OakXmlWriter writer,
|
||||
const char *name,
|
||||
const char *text)
|
||||
{
|
||||
if (!writer || !name || !text)
|
||||
if (!xw(writer) || !name || !text)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
writer->writer.write_text_element(name, text);
|
||||
xw(writer)->write_text_element(name, text);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_writer_write_end_element(OakCommonXmlWriter *writer)
|
||||
int oakcommon_xml_writer_write_end_element(OakXmlWriter writer)
|
||||
{
|
||||
if (!writer)
|
||||
if (!xw(writer))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
writer->writer.write_end_element();
|
||||
xw(writer)->write_end_element();
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer)
|
||||
int oakcommon_xml_writer_write_end_document(OakXmlWriter writer)
|
||||
{
|
||||
if (!writer)
|
||||
if (!xw(writer))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
writer->writer.write_end_document();
|
||||
xw(writer)->write_end_document();
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_xml_writer_output(OakCommonXmlWriter *writer, char *buf,
|
||||
int oakcommon_xml_writer_output(OakXmlWriter writer, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!writer)
|
||||
if (!xw(writer))
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(writer->writer.output(), buf, buf_size);
|
||||
return copy_string(xw(writer)->output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,39 @@
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Minimum level emitted by log_message(); default k_debug_info.
|
||||
*/
|
||||
std::atomic<int> g_log_level{ k_debug_info };
|
||||
|
||||
/**
|
||||
* @brief Guards g_log_sink (std::function is not atomic).
|
||||
*/
|
||||
std::mutex g_sink_mutex;
|
||||
LogSink g_log_sink;
|
||||
|
||||
/**
|
||||
* @brief Default sink: stderr + flush, same as debug_handler().
|
||||
*/
|
||||
void stderr_sink(const std::string &line)
|
||||
{
|
||||
fputs(line.c_str(), stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int debug_level_name(int level, char *buf, int buf_size)
|
||||
{
|
||||
const char *name;
|
||||
@@ -58,16 +85,82 @@ int debug_level_name(int level, char *buf, int buf_size)
|
||||
return needed;
|
||||
}
|
||||
|
||||
void debug_handler(int level, const char *msg)
|
||||
std::string format_log_line(int level, const std::string &msg)
|
||||
{
|
||||
char level_name[16];
|
||||
|
||||
debug_level_name(level, level_name, sizeof(level_name));
|
||||
fprintf(stderr, "[%s] %s\n", level_name, msg ? msg : "");
|
||||
|
||||
// Always flush so debug messages appear immediately, even on
|
||||
// platforms that buffer stderr.
|
||||
fflush(stderr);
|
||||
std::string line;
|
||||
line.reserve(strlen(level_name) + msg.size() + 4);
|
||||
line += '[';
|
||||
line += level_name;
|
||||
line += "] ";
|
||||
line += msg;
|
||||
line += '\n';
|
||||
return line;
|
||||
}
|
||||
|
||||
void debug_handler(int level, const char *msg)
|
||||
{
|
||||
stderr_sink(format_log_line(level, msg ? msg : ""));
|
||||
}
|
||||
|
||||
void set_log_sink(LogSink sink)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sink_mutex);
|
||||
g_log_sink = std::move(sink);
|
||||
}
|
||||
|
||||
void set_log_level(DebugLevel level)
|
||||
{
|
||||
if (level < k_debug_debug || level > k_debug_fatal)
|
||||
return;
|
||||
g_log_level.store(static_cast<int>(level), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
DebugLevel get_log_level()
|
||||
{
|
||||
return static_cast<DebugLevel>(g_log_level.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
void log_message(int level, const std::string &msg)
|
||||
{
|
||||
if (level < g_log_level.load(std::memory_order_relaxed))
|
||||
return;
|
||||
|
||||
std::string line = format_log_line(level, msg);
|
||||
|
||||
LogSink sink;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sink_mutex);
|
||||
sink = g_log_sink;
|
||||
}
|
||||
// Invoke outside the lock so a sink may log re-entrantly.
|
||||
if (sink)
|
||||
sink(line);
|
||||
else
|
||||
stderr_sink(line);
|
||||
}
|
||||
|
||||
void log_debug(const std::string &msg)
|
||||
{
|
||||
log_message(k_debug_debug, msg);
|
||||
}
|
||||
|
||||
void log_info(const std::string &msg)
|
||||
{
|
||||
log_message(k_debug_info, msg);
|
||||
}
|
||||
|
||||
void log_warning(const std::string &msg)
|
||||
{
|
||||
log_message(k_debug_warning, msg);
|
||||
}
|
||||
|
||||
void log_critical(const std::string &msg)
|
||||
{
|
||||
log_message(k_debug_error, msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+61
-1
@@ -21,6 +21,9 @@
|
||||
#ifndef OAK_DEBUG_H
|
||||
#define OAK_DEBUG_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
@@ -28,7 +31,8 @@ namespace olive
|
||||
* @brief Severity levels for debug output.
|
||||
*
|
||||
* Replaces Qt's QtMsgType now that the debug handler no longer depends
|
||||
* on QDebug.
|
||||
* on QDebug. Values are ordered by ascending severity so that a simple
|
||||
* `level < threshold` comparison implements level filtering.
|
||||
*/
|
||||
enum DebugLevel {
|
||||
k_debug_debug,
|
||||
@@ -56,9 +60,65 @@ int debug_level_name(int level, char *buf, int buf_size);
|
||||
* De-Qt replacement for the old Qt message handler: qDebug() output is
|
||||
* replaced by fprintf(stderr). A NULL message is treated as an empty
|
||||
* string. Lines are always flushed so messages appear immediately.
|
||||
*
|
||||
* This is the unfiltered low-level writer; use log_message() (or the
|
||||
* log_debug()/log_info()/... helpers) for level-filtered logging.
|
||||
*/
|
||||
void debug_handler(int level, const char *msg);
|
||||
|
||||
/**
|
||||
* @brief Format a log line as "[LEVEL] message\n".
|
||||
*/
|
||||
std::string format_log_line(int level, const std::string &msg);
|
||||
|
||||
/**
|
||||
* @brief Destination for filtered log lines.
|
||||
*
|
||||
* Receives the fully formatted line ("[LEVEL] message\n"). The default
|
||||
* sink writes to stderr and flushes, matching debug_handler().
|
||||
*/
|
||||
using LogSink = std::function<void(const std::string &line)>;
|
||||
|
||||
/**
|
||||
* @brief Install a custom log sink (e.g. for tests or log files).
|
||||
*
|
||||
* Passing an empty LogSink restores the default stderr sink.
|
||||
* Thread-safe; the sink is invoked without the internal lock held.
|
||||
*/
|
||||
void set_log_sink(LogSink sink);
|
||||
|
||||
/**
|
||||
* @brief Set the minimum level emitted by log_message().
|
||||
*
|
||||
* Messages with a lower level are dropped. The default is
|
||||
* k_debug_info. Values outside the DebugLevel range are ignored.
|
||||
* Thread-safe (atomic store).
|
||||
*/
|
||||
void set_log_level(DebugLevel level);
|
||||
|
||||
/**
|
||||
* @brief The current minimum level emitted by log_message().
|
||||
*/
|
||||
DebugLevel get_log_level();
|
||||
|
||||
/**
|
||||
* @brief Emit a message if `level` passes the current level filter.
|
||||
*
|
||||
* The formatted line ("[LEVEL] message\n") is handed to the installed
|
||||
* sink. Messages below the level set with set_log_level() are dropped.
|
||||
*/
|
||||
void log_message(int level, const std::string &msg);
|
||||
|
||||
/**
|
||||
* @brief Level-filtered convenience wrappers, replacing qDebug(),
|
||||
* qInfo(), qWarning() and qCritical(). Callers compose the message
|
||||
* themselves (plain std::string concatenation).
|
||||
*/
|
||||
void log_debug(const std::string &msg);
|
||||
void log_info(const std::string &msg);
|
||||
void log_warning(const std::string &msg);
|
||||
void log_critical(const std::string &msg);
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DEBUG_H
|
||||
|
||||
@@ -88,17 +88,3 @@ OIIOUtils::get_pixel_aspect_ratio_from_oiio(const OIIO::ImageSpec &spec)
|
||||
return olive::core::Rational::from_double(
|
||||
spec.get_float_attribute("PixelAspectRatio", 1));
|
||||
}
|
||||
|
||||
void OIIOUtils::frame_to_buffer(const void *data, int64_t linesize_bytes,
|
||||
OIIO::ImageBuf *buf)
|
||||
{
|
||||
buf->set_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
|
||||
static_cast<OIIO::stride_t>(linesize_bytes));
|
||||
}
|
||||
|
||||
void OIIOUtils::buffer_to_frame(OIIO::ImageBuf *buf, void *data,
|
||||
int64_t linesize_bytes)
|
||||
{
|
||||
buf->get_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
|
||||
static_cast<OIIO::stride_t>(linesize_bytes));
|
||||
}
|
||||
|
||||
@@ -35,8 +35,9 @@
|
||||
* Qt-free reimplementation of the former olive::OIIOUtils. The reverse
|
||||
* dependencies on codec/frame.h and render/videoparams.h (which pull in
|
||||
* Qt) were replaced with the Qt-free olive/core/render/pixelformat.h and
|
||||
* olive/core/util/rational.h. The Frame-based helpers were flattened to
|
||||
* raw data pointer + linesize, which is all they ever used from Frame.
|
||||
* olive/core/util/rational.h. The Frame-based helpers (frame_to_buffer /
|
||||
* buffer_to_frame) moved to oakcodec in M5
|
||||
* (src/codec/src/oiioframebridge.h) — they are only used by codec.
|
||||
*/
|
||||
class OIIOUtils {
|
||||
public:
|
||||
@@ -65,24 +66,6 @@ public:
|
||||
*/
|
||||
static olive::core::Rational
|
||||
get_pixel_aspect_ratio_from_oiio(const OIIO::ImageSpec &spec);
|
||||
|
||||
/**
|
||||
* @brief Copies raw pixel data into an OIIO image buffer
|
||||
*
|
||||
* Flattened form of the former Frame-based frame_to_buffer(); pass
|
||||
* Frame::const_data() and Frame::linesize_bytes() at the call site.
|
||||
*/
|
||||
static void frame_to_buffer(const void *data, int64_t linesize_bytes,
|
||||
OIIO::ImageBuf *buf);
|
||||
|
||||
/**
|
||||
* @brief Copies an OIIO image buffer's pixels into raw memory
|
||||
*
|
||||
* Flattened form of the former Frame-based buffer_to_frame(); pass
|
||||
* Frame::data() and Frame::linesize_bytes() at the call site.
|
||||
*/
|
||||
static void buffer_to_frame(OIIO::ImageBuf *buf, void *data,
|
||||
int64_t linesize_bytes);
|
||||
};
|
||||
|
||||
#endif // OAK_OIIOUTILS_H
|
||||
|
||||
@@ -26,6 +26,8 @@ add_executable(oakcommon-gtest
|
||||
dropworkflowbehavior_test.cpp
|
||||
ffmpegutils_test.cpp
|
||||
filefunctions_test.cpp
|
||||
handle_test.cpp
|
||||
log_test.cpp
|
||||
memorypool_test.cpp
|
||||
miscutils_test.cpp
|
||||
ocioutils_test.cpp
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string read_string(int (*fn)(OakCommonColorTransform *, char *, int),
|
||||
OakCommonColorTransform *t)
|
||||
std::string read_string(int (*fn)(OakColorTransform, char *, int),
|
||||
OakColorTransform t)
|
||||
{
|
||||
int needed = fn(t, nullptr, 0);
|
||||
EXPECT_GT(needed, 0);
|
||||
@@ -43,9 +43,8 @@ std::string read_string(int (*fn)(OakCommonColorTransform *, char *, int),
|
||||
|
||||
TEST(CommonColorTransformCApi, InitOutput)
|
||||
{
|
||||
OakCommonColorTransform *t =
|
||||
oakcommon_colortransform_init_output("sRGB");
|
||||
ASSERT_NE(t, nullptr);
|
||||
OakColorTransform t = oakcommon_colortransform_init_output("sRGB");
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
int is_display = -1;
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
|
||||
@@ -54,19 +53,19 @@ TEST(CommonColorTransformCApi, InitOutput)
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_output, t), "sRGB");
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_display, t), "sRGB");
|
||||
|
||||
oakcommon_colortransform_free(t);
|
||||
oakcommon_colortransform_free(&t);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, InitOutputNullString)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_colortransform_init_output(nullptr), nullptr);
|
||||
EXPECT_EQ(oakcommon_colortransform_init_output(nullptr).ctx,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, InitDisplay)
|
||||
{
|
||||
OakCommonColorTransform *t =
|
||||
oakcommon_colortransform_init_display("sRGB", "Studio", "None");
|
||||
ASSERT_NE(t, nullptr);
|
||||
OakColorTransform t = oakcommon_colortransform_init_display("sRGB", "Studio", "None");
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
int is_display = 0;
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
|
||||
@@ -76,16 +75,16 @@ TEST(CommonColorTransformCApi, InitDisplay)
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_view, t), "Studio");
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_look, t), "None");
|
||||
|
||||
oakcommon_colortransform_free(t);
|
||||
oakcommon_colortransform_free(&t);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, InitDisplayNullString)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display(nullptr, "v", "l"),
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display(nullptr, "v", "l").ctx,
|
||||
nullptr);
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display("d", nullptr, "l"),
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display("d", nullptr, "l").ctx,
|
||||
nullptr);
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display("d", "v", nullptr),
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display("d", "v", nullptr).ctx,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
@@ -98,24 +97,23 @@ TEST(CommonColorTransformCApi, NullHandleErrors)
|
||||
{
|
||||
int i = 0;
|
||||
char buf[16];
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(nullptr, &i),
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(OakColorTransform{}, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_display(nullptr, buf, sizeof(buf)),
|
||||
EXPECT_EQ(oakcommon_colortransform_get_display(OakColorTransform{}, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_output(nullptr, buf, sizeof(buf)),
|
||||
EXPECT_EQ(oakcommon_colortransform_get_output(OakColorTransform{}, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_view(nullptr, buf, sizeof(buf)),
|
||||
EXPECT_EQ(oakcommon_colortransform_get_view(OakColorTransform{}, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_look(nullptr, buf, sizeof(buf)),
|
||||
EXPECT_EQ(oakcommon_colortransform_get_look(OakColorTransform{}, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, NullOutParam)
|
||||
{
|
||||
OakCommonColorTransform *t =
|
||||
oakcommon_colortransform_init_output("sRGB");
|
||||
ASSERT_NE(t, nullptr);
|
||||
OakColorTransform t = oakcommon_colortransform_init_output("sRGB");
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(t, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
oakcommon_colortransform_free(t);
|
||||
oakcommon_colortransform_free(&t);
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
TEST(CommandLineParser, InitFree)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, FreeNull)
|
||||
@@ -41,14 +41,13 @@ TEST(CommandLineParser, FreeNull)
|
||||
|
||||
TEST(CommandLineParser, AddOptionInvalidArgs)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
|
||||
const char *names[] = { "h", "-help" };
|
||||
OakCommonCommandLineOption *option = nullptr;
|
||||
OakCommandLineOption option = {};
|
||||
|
||||
EXPECT_EQ(oakcommon_commandlineparser_add_option(
|
||||
nullptr, names, 2, "desc", 0, nullptr, 0, &option),
|
||||
EXPECT_EQ(oakcommon_commandlineparser_add_option(OakCommandLineParser{}, names, 2, "desc", 0, nullptr, 0, &option),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_commandlineparser_add_option(
|
||||
parser, nullptr, 2, "desc", 0, nullptr, 0, &option),
|
||||
@@ -57,28 +56,28 @@ TEST(CommandLineParser, AddOptionInvalidArgs)
|
||||
parser, names, 0, "desc", 0, nullptr, 0, &option),
|
||||
OAKCOMMON_E_INVALID);
|
||||
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, ProcessOptionHitAndMiss)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
|
||||
const char *names[] = { "h", "-help" };
|
||||
OakCommonCommandLineOption *hit = nullptr;
|
||||
OakCommonCommandLineOption *miss = nullptr;
|
||||
OakCommandLineOption hit = {};
|
||||
OakCommandLineOption miss = {};
|
||||
|
||||
ASSERT_EQ(oakcommon_commandlineparser_add_option(
|
||||
parser, names, 2, "Show help", 0, nullptr, 0, &hit),
|
||||
OAKCOMMON_OK);
|
||||
ASSERT_NE(hit, nullptr);
|
||||
ASSERT_NE(hit.ctx, nullptr);
|
||||
|
||||
const char *other_names[] = { "v", "-version" };
|
||||
ASSERT_EQ(oakcommon_commandlineparser_add_option(
|
||||
parser, other_names, 2, "Show version", 0, nullptr, 0, &miss),
|
||||
OAKCOMMON_OK);
|
||||
ASSERT_NE(miss, nullptr);
|
||||
ASSERT_NE(miss.ctx, nullptr);
|
||||
|
||||
const char *argv[] = { "oak", "-h" };
|
||||
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 2),
|
||||
@@ -92,18 +91,18 @@ TEST(CommandLineParser, ProcessOptionHitAndMiss)
|
||||
ASSERT_EQ(oakcommon_commandlineoption_is_set(miss, &is_set), OAKCOMMON_OK);
|
||||
EXPECT_FALSE(is_set);
|
||||
|
||||
oakcommon_commandlineoption_free(hit);
|
||||
oakcommon_commandlineoption_free(miss);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlineoption_free(&hit);
|
||||
oakcommon_commandlineoption_free(&miss);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, ProcessMatchesSecondAliasCaseInsensitive)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
|
||||
const char *names[] = { "h", "-help" };
|
||||
OakCommonCommandLineOption *option = nullptr;
|
||||
OakCommandLineOption option = {};
|
||||
ASSERT_EQ(oakcommon_commandlineparser_add_option(
|
||||
parser, names, 2, "Show help", 0, nullptr, 0, &option),
|
||||
OAKCOMMON_OK);
|
||||
@@ -118,17 +117,17 @@ TEST(CommandLineParser, ProcessMatchesSecondAliasCaseInsensitive)
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_TRUE(is_set);
|
||||
|
||||
oakcommon_commandlineoption_free(option);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlineoption_free(&option);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, OptionTakesArg)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
|
||||
const char *names[] = { "e", "-export" };
|
||||
OakCommonCommandLineOption *option = nullptr;
|
||||
OakCommandLineOption option = {};
|
||||
ASSERT_EQ(oakcommon_commandlineparser_add_option(
|
||||
parser, names, 2, "Export", 1, "filename", 0, &option),
|
||||
OAKCOMMON_OK);
|
||||
@@ -153,20 +152,20 @@ TEST(CommandLineParser, OptionTakesArg)
|
||||
required);
|
||||
EXPECT_STREQ(buf.data(), "/tmp/out.mp4");
|
||||
|
||||
oakcommon_commandlineoption_free(option);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlineoption_free(&option);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, PositionalArgument)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
|
||||
OakCommonCommandLinePositionalArgument *arg = nullptr;
|
||||
OakCommandLinePositionalArgument arg = {};
|
||||
ASSERT_EQ(oakcommon_commandlineparser_add_positional_argument(
|
||||
parser, "project", "Project file", 0, &arg),
|
||||
OAKCOMMON_OK);
|
||||
ASSERT_NE(arg, nullptr);
|
||||
ASSERT_NE(arg.ctx, nullptr);
|
||||
|
||||
const char *argv[] = { "oak", "/tmp/project.ove" };
|
||||
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 2),
|
||||
@@ -182,20 +181,20 @@ TEST(CommandLineParser, PositionalArgument)
|
||||
required);
|
||||
EXPECT_STREQ(buf.data(), "/tmp/project.ove");
|
||||
|
||||
oakcommon_commandlinepositionalargument_free(arg);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlinepositionalargument_free(&arg);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, PositionalArgumentSetGetSetting)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
|
||||
OakCommonCommandLinePositionalArgument *arg = nullptr;
|
||||
OakCommandLinePositionalArgument arg = {};
|
||||
ASSERT_EQ(oakcommon_commandlineparser_add_positional_argument(
|
||||
parser, "project", "Project file", 0, &arg),
|
||||
OAKCOMMON_OK);
|
||||
ASSERT_NE(arg, nullptr);
|
||||
ASSERT_NE(arg.ctx, nullptr);
|
||||
|
||||
ASSERT_EQ(oakcommon_commandlinepositionalargument_set_setting(arg,
|
||||
"hello.ove"),
|
||||
@@ -209,28 +208,28 @@ TEST(CommandLineParser, PositionalArgumentSetGetSetting)
|
||||
EXPECT_STREQ(buf, "hello.ove");
|
||||
|
||||
// Error paths
|
||||
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(nullptr,
|
||||
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(OakCommandLinePositionalArgument{},
|
||||
"x"),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(arg,
|
||||
nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_commandlinepositionalargument_get_setting(nullptr,
|
||||
EXPECT_EQ(oakcommon_commandlinepositionalargument_get_setting(OakCommandLinePositionalArgument{},
|
||||
buf,
|
||||
sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
|
||||
oakcommon_commandlinepositionalargument_free(arg);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlinepositionalargument_free(&arg);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, TwoStageStringGetterSmallBuffer)
|
||||
{
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
|
||||
const char *names[] = { "e" };
|
||||
OakCommonCommandLineOption *option = nullptr;
|
||||
OakCommandLineOption option = {};
|
||||
ASSERT_EQ(oakcommon_commandlineparser_add_option(
|
||||
parser, names, 1, "Export", 1, "filename", 0, &option),
|
||||
OAKCOMMON_OK);
|
||||
@@ -249,27 +248,27 @@ TEST(CommandLineParser, TwoStageStringGetterSmallBuffer)
|
||||
EXPECT_EQ(oakcommon_commandlineoption_get_setting(option, nullptr, 0), 7);
|
||||
|
||||
// Error paths
|
||||
EXPECT_EQ(oakcommon_commandlineoption_get_setting(nullptr, buf,
|
||||
EXPECT_EQ(oakcommon_commandlineoption_get_setting(OakCommandLineOption{}, buf,
|
||||
sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_commandlineoption_is_set(option, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
bool dummy = false;
|
||||
EXPECT_EQ(oakcommon_commandlineoption_is_set(nullptr, &dummy),
|
||||
EXPECT_EQ(oakcommon_commandlineoption_is_set(OakCommandLineOption{}, &dummy),
|
||||
OAKCOMMON_E_INVALID);
|
||||
|
||||
oakcommon_commandlineoption_free(option);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlineoption_free(&option);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
TEST(CommandLineParser, ProcessInvalidArgs)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_commandlineparser_process(nullptr, nullptr, 0),
|
||||
EXPECT_EQ(oakcommon_commandlineparser_process(OakCommandLineParser{}, nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
|
||||
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser, nullptr);
|
||||
OakCommandLineParser parser = oakcommon_commandlineparser_init();
|
||||
ASSERT_NE(parser.ctx, nullptr);
|
||||
EXPECT_EQ(oakcommon_commandlineparser_process(parser, nullptr, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
oakcommon_commandlineparser_free(parser);
|
||||
oakcommon_commandlineparser_free(&parser);
|
||||
}
|
||||
|
||||
@@ -24,25 +24,29 @@
|
||||
|
||||
#include "common/current.h"
|
||||
|
||||
TEST(OakCommonCurrent, InstanceIsSingleton)
|
||||
TEST(OakCurrent, InstanceIsSingleton)
|
||||
{
|
||||
OakCommonCurrent *a = oakcommon_current_instance();
|
||||
OakCommonCurrent *b = oakcommon_current_instance();
|
||||
OakCurrent a = oakcommon_current_instance();
|
||||
OakCurrent b = oakcommon_current_instance();
|
||||
|
||||
ASSERT_NE(a, nullptr);
|
||||
EXPECT_EQ(a, b);
|
||||
ASSERT_NE(a.ctx, nullptr);
|
||||
EXPECT_EQ(a.ctx, b.ctx);
|
||||
EXPECT_EQ(a.abi_version, OAKCOMMON_ABI_VERSION);
|
||||
}
|
||||
|
||||
TEST(OakCommonCurrent, FreeNullIsNoOp)
|
||||
TEST(OakCurrent, FreeNullIsNoOp)
|
||||
{
|
||||
oakcommon_current_free(nullptr);
|
||||
oakcommon_current_free(oakcommon_current_instance());
|
||||
OakCurrent c = oakcommon_current_instance();
|
||||
oakcommon_current_free(&c);
|
||||
// Releasing the singleton handle never destroys the object.
|
||||
EXPECT_NE(c.ctx, nullptr);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(OakCommonCurrent, VideoParamsSetGetRoundTrip)
|
||||
TEST(OakCurrent, VideoParamsSetGetRoundTrip)
|
||||
{
|
||||
OakCommonCurrent *c = oakcommon_current_instance();
|
||||
OakCurrent c = oakcommon_current_instance();
|
||||
int *params = static_cast<int *>(malloc(sizeof(int)));
|
||||
ASSERT_NE(params, nullptr);
|
||||
*params = 42;
|
||||
@@ -62,9 +66,9 @@ TEST(OakCommonCurrent, VideoParamsSetGetRoundTrip)
|
||||
EXPECT_EQ(out, nullptr);
|
||||
}
|
||||
|
||||
TEST(OakCommonCurrent, AudioParamsSetGetRoundTrip)
|
||||
TEST(OakCurrent, AudioParamsSetGetRoundTrip)
|
||||
{
|
||||
OakCommonCurrent *c = oakcommon_current_instance();
|
||||
OakCurrent c = oakcommon_current_instance();
|
||||
int value = 7; // non-owning storage, no destroy callback
|
||||
|
||||
ASSERT_EQ(oakcommon_current_set_audio_params(c, &value, nullptr),
|
||||
@@ -78,9 +82,9 @@ TEST(OakCommonCurrent, AudioParamsSetGetRoundTrip)
|
||||
OAKCOMMON_OK);
|
||||
}
|
||||
|
||||
TEST(OakCommonCurrent, PluginHostAndCacheRoundTrip)
|
||||
TEST(OakCurrent, PluginHostAndCacheRoundTrip)
|
||||
{
|
||||
OakCommonCurrent *c = oakcommon_current_instance();
|
||||
OakCurrent c = oakcommon_current_instance();
|
||||
int host = 1, cache = 2;
|
||||
|
||||
ASSERT_EQ(oakcommon_current_set_plugin_host(c, &host, nullptr),
|
||||
@@ -100,31 +104,29 @@ TEST(OakCommonCurrent, PluginHostAndCacheRoundTrip)
|
||||
OAKCOMMON_OK);
|
||||
}
|
||||
|
||||
TEST(OakCommonCurrent, NullHandleAndOutArgs)
|
||||
TEST(OakCurrent, NullHandleAndOutArgs)
|
||||
{
|
||||
void *out = nullptr;
|
||||
int flag = 0;
|
||||
|
||||
EXPECT_EQ(oakcommon_current_set_video_params(nullptr, &flag, nullptr),
|
||||
EXPECT_EQ(oakcommon_current_set_video_params(OakCurrent{}, &flag, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_current_get_video_params(nullptr, &out),
|
||||
EXPECT_EQ(oakcommon_current_get_video_params(OakCurrent{}, &out),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_current_get_video_params(
|
||||
oakcommon_current_instance(), nullptr),
|
||||
OakCurrent c = oakcommon_current_instance();
|
||||
EXPECT_EQ(oakcommon_current_get_video_params(c, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_current_is_interactive(nullptr, &flag),
|
||||
EXPECT_EQ(oakcommon_current_is_interactive(OakCurrent{}, &flag),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_current_is_interactive(
|
||||
oakcommon_current_instance(), nullptr),
|
||||
EXPECT_EQ(oakcommon_current_is_interactive(c, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(OakCommonCurrent, IsInteractive)
|
||||
TEST(OakCurrent, IsInteractive)
|
||||
{
|
||||
int flag = 0;
|
||||
|
||||
ASSERT_EQ(oakcommon_current_is_interactive(
|
||||
oakcommon_current_instance(), &flag),
|
||||
OAKCOMMON_OK);
|
||||
OakCurrent c = oakcommon_current_instance();
|
||||
ASSERT_EQ(oakcommon_current_is_interactive(c, &flag), OAKCOMMON_OK);
|
||||
EXPECT_EQ(flag, 1);
|
||||
}
|
||||
|
||||
@@ -22,25 +22,25 @@
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
TEST(OakCommonDebug, LogValidMessage)
|
||||
TEST(OakDebug, LogValidMessage)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_debug_log(OAKCOMMON_DEBUG_WARNING, "hello"),
|
||||
OAKCOMMON_OK);
|
||||
}
|
||||
|
||||
TEST(OakCommonDebug, LogNullMessage)
|
||||
TEST(OakDebug, LogNullMessage)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_debug_log(OAKCOMMON_DEBUG_WARNING, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(OakCommonDebug, LogOutOfRangeLevel)
|
||||
TEST(OakDebug, LogOutOfRangeLevel)
|
||||
{
|
||||
// Out-of-range levels are tolerated and print as UNKNOWN.
|
||||
EXPECT_EQ(oakcommon_debug_log(999, "odd level"), OAKCOMMON_OK);
|
||||
}
|
||||
|
||||
TEST(OakCommonDebug, LevelNameRoundTrip)
|
||||
TEST(OakDebug, LevelNameRoundTrip)
|
||||
{
|
||||
char buf[16];
|
||||
|
||||
@@ -51,14 +51,14 @@ TEST(OakCommonDebug, LevelNameRoundTrip)
|
||||
EXPECT_STREQ(buf, "WARNING");
|
||||
}
|
||||
|
||||
TEST(OakCommonDebug, LevelNameQuerySize)
|
||||
TEST(OakDebug, LevelNameQuerySize)
|
||||
{
|
||||
int needed = oakcommon_debug_level_name(OAKCOMMON_DEBUG_DEBUG,
|
||||
nullptr, 0);
|
||||
EXPECT_EQ(needed, 6); // "DEBUG" + NUL
|
||||
}
|
||||
|
||||
TEST(OakCommonDebug, LevelNameUnknownLevel)
|
||||
TEST(OakDebug, LevelNameUnknownLevel)
|
||||
{
|
||||
char buf[16];
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user