core: turn olivecore into liboakcore.so with a pure C ABI

liboakcore is now a shared library that exposes only a C ABI:
- every value class (Rational, TimeRange, Color, Bezier, AudioParams,
  SampleBuffer) and the free-function groups (StringUtils, fraction
  utils, Timecode) is wrapped in an opaque-handle C API under
  core/include/olive/core/oakcore/ (init/copy/free + self-first
  functions), implemented in core/src/capi/
- consumers keep the original C++ API unchanged through same-name
  wrapper classes that hold the handle and forward across the C
  boundary; original implementations moved to core/src/oliveimpl
  (namespace olive::core::internal) and are hidden from export
- TimeRangeList/TimeRangeListFrameIterator are reimplemented inline
  over the wrapper (iterators/containers don't cross C ABI)
- generic Value container stays internal (unused by consumers) and is
  no longer part of the public umbrella header
- hidden visibility + OAKCORE_BUILD export macro; nm shows zero
  olive::* symbols exported
- install into the platform's standard libdir (GNUInstallDirs);
  Windows DLLs next to the executables, macOS into the app bundle
- TimelineWorkArea::in/out/length now return by value: the wrapped
  TimeRange getters return values, and forwarding them through const
  references dangled (found via RenderWorkerFootageTest crash)
- tests: 9 new pure C ABI test executables (oakcore_*_test) covering
  every public C function; 4 stale legacy core tests removed (they
  targeted a long-renamed API and were never built due to a malformed
  option() that also kept OLIVECORE_BUILD_TESTS off)
- CI/CD: oakcore.dll staged for NSIS, liboakcore.so added to the
  AppImage validation list, build-tree DLL copies on Windows
This commit is contained in:
2026-07-19 23:49:40 +08:00
parent 52625f2b98
commit 4fc8b80d7e
69 changed files with 7994 additions and 717 deletions
+2 -1
View File
@@ -33,6 +33,7 @@
#include "util/tests.h"
#include "util/timecodefunctions.h"
#include "util/timerange.h"
#include "util/value.h"
// util/value.h is deliberately not part of the public API: the generic
// Value container is unused by consumers and stays internal to the library
#endif // OAK_LIBOLIVECORE_H
@@ -0,0 +1,157 @@
/***
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 OAKCORE_AUDIOPARAMS_H
#define OAKCORE_AUDIOPARAMS_H
#include <stdint.h>
#include "export.h"
#include "rational.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file audioparams.h
* @brief C ABI for the audio stream parameter value type
*
* Opaque handle + free functions. All returned OakAudioParams* and
* OakRational* are owned by the caller and must be released with
* oakcore_audioparams_free() / oakcore_rational_free() respectively.
*
* Sample formats cross the boundary as plain ints carrying the
* olive::core::SampleFormat::Format enum values (render/sampleformat.h);
* channel layouts are plain 64-bit masks (render/channellayout.h).
*/
typedef struct OakAudioParams OakAudioParams;
/**
* Creates parameters for the given sample rate, channel layout mask and
* sample format. The timebase is initialized to 1/sample_rate.
*/
OAKCORE_API OakAudioParams *oakcore_audioparams_create(int sample_rate,
uint64_t channel_layout,
int format);
/**
* Creates default (invalid) parameters: sample_rate=0, empty channel
* layout, invalid format.
*/
OAKCORE_API OakAudioParams *oakcore_audioparams_create_invalid(void);
OAKCORE_API OakAudioParams *oakcore_audioparams_copy(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_free(OakAudioParams *self);
OAKCORE_API int oakcore_audioparams_sample_rate(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_set_sample_rate(OakAudioParams *self,
int sample_rate);
OAKCORE_API uint64_t oakcore_audioparams_channel_layout(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_set_channel_layout(OakAudioParams *self,
uint64_t mask);
OAKCORE_API OakRational *oakcore_audioparams_time_base(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_set_time_base(OakAudioParams *self,
const OakRational *timebase);
/**
* Returns a new owned handle to Rational(1, sample_rate).
*/
OAKCORE_API OakRational *oakcore_audioparams_sample_rate_as_time_base(
const OakAudioParams *self);
OAKCORE_API int oakcore_audioparams_format(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_set_format(OakAudioParams *self, int format);
OAKCORE_API int oakcore_audioparams_enabled(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_set_enabled(OakAudioParams *self, int enabled);
OAKCORE_API int oakcore_audioparams_stream_index(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_set_stream_index(OakAudioParams *self,
int stream_index);
OAKCORE_API int64_t oakcore_audioparams_duration(const OakAudioParams *self);
OAKCORE_API void oakcore_audioparams_set_duration(OakAudioParams *self,
int64_t duration);
/**
* Time/byte/sample conversions. All of these require valid parameters
* (oakcore_audioparams_is_valid()); calling them on invalid parameters is
* an error (the library asserts in debug builds).
*/
OAKCORE_API int64_t oakcore_audioparams_time_to_bytes(const OakAudioParams *self,
double time);
OAKCORE_API int64_t oakcore_audioparams_time_to_bytes_rational(
const OakAudioParams *self, const OakRational *time);
OAKCORE_API int64_t oakcore_audioparams_time_to_bytes_per_channel(
const OakAudioParams *self, double time);
OAKCORE_API int64_t oakcore_audioparams_time_to_bytes_per_channel_rational(
const OakAudioParams *self, const OakRational *time);
OAKCORE_API int64_t oakcore_audioparams_time_to_samples(const OakAudioParams *self,
double time);
OAKCORE_API int64_t oakcore_audioparams_time_to_samples_rational(
const OakAudioParams *self, const OakRational *time);
OAKCORE_API int64_t oakcore_audioparams_samples_to_bytes(const OakAudioParams *self,
int64_t samples);
OAKCORE_API int64_t oakcore_audioparams_samples_to_bytes_per_channel(
const OakAudioParams *self, int64_t samples);
OAKCORE_API OakRational *oakcore_audioparams_samples_to_time(
const OakAudioParams *self, int64_t samples);
OAKCORE_API int64_t oakcore_audioparams_bytes_to_samples(const OakAudioParams *self,
int64_t bytes);
OAKCORE_API OakRational *oakcore_audioparams_bytes_to_time(
const OakAudioParams *self, int64_t bytes);
OAKCORE_API OakRational *oakcore_audioparams_bytes_per_channel_to_time(
const OakAudioParams *self, int64_t bytes);
OAKCORE_API int oakcore_audioparams_channel_count(const OakAudioParams *self);
OAKCORE_API int oakcore_audioparams_bytes_per_sample_per_channel(
const OakAudioParams *self);
OAKCORE_API int oakcore_audioparams_bits_per_sample(const OakAudioParams *self);
OAKCORE_API int oakcore_audioparams_is_valid(const OakAudioParams *self);
/**
* Equality like operator==: 1 when equal, 0 otherwise. Compares format,
* sample rate, timebase and channel layout (not the footage parameters).
*/
OAKCORE_API int oakcore_audioparams_equals(const OakAudioParams *self,
const OakAudioParams *other);
/**
* Enumerates AudioParams::k_supported_channel_layouts. Out-of-range indices
* return 0.
*/
OAKCORE_API int oakcore_audioparams_supported_channel_layout_count(void);
OAKCORE_API uint64_t oakcore_audioparams_supported_channel_layout_at(int index);
/**
* Enumerates AudioParams::k_supported_sample_rates. Out-of-range indices
* return 0.
*/
OAKCORE_API int oakcore_audioparams_supported_sample_rate_count(void);
OAKCORE_API int oakcore_audioparams_supported_sample_rate_at(int index);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_AUDIOPARAMS_H */
+74
View File
@@ -0,0 +1,74 @@
/***
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 OAKCORE_BEZIER_H
#define OAKCORE_BEZIER_H
#include "export.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file bezier.h
* @brief C ABI for the keyframe easing bezier curve value type
*
* Opaque handle + free functions. All returned OakBezier* are owned by
* the caller and must be released with oakcore_bezier_free().
*/
typedef struct OakBezier OakBezier;
OAKCORE_API OakBezier *oakcore_bezier_create(void);
OAKCORE_API OakBezier *oakcore_bezier_create_xy(double x, double y);
OAKCORE_API OakBezier *oakcore_bezier_create_full(double x, double y,
double cp1_x, double cp1_y,
double cp2_x, double cp2_y);
OAKCORE_API OakBezier *oakcore_bezier_copy(const OakBezier *self);
OAKCORE_API void oakcore_bezier_free(OakBezier *self);
OAKCORE_API double oakcore_bezier_x(const OakBezier *self);
OAKCORE_API double oakcore_bezier_y(const OakBezier *self);
OAKCORE_API double oakcore_bezier_cp1_x(const OakBezier *self);
OAKCORE_API double oakcore_bezier_cp1_y(const OakBezier *self);
OAKCORE_API double oakcore_bezier_cp2_x(const OakBezier *self);
OAKCORE_API double oakcore_bezier_cp2_y(const OakBezier *self);
OAKCORE_API void oakcore_bezier_set_x(OakBezier *self, double x);
OAKCORE_API void oakcore_bezier_set_y(OakBezier *self, double y);
OAKCORE_API void oakcore_bezier_set_cp1_x(OakBezier *self, double cp1_x);
OAKCORE_API void oakcore_bezier_set_cp1_y(OakBezier *self, double cp1_y);
OAKCORE_API void oakcore_bezier_set_cp2_x(OakBezier *self, double cp2_x);
OAKCORE_API void oakcore_bezier_set_cp2_y(OakBezier *self, double cp2_y);
OAKCORE_API double oakcore_bezier_quadratic_xto_t(double x, double a, double b,
double c);
OAKCORE_API double oakcore_bezier_quadratic_tto_y(double a, double b, double c,
double t);
OAKCORE_API double oakcore_bezier_cubic_xto_t(double x, double a, double b,
double c, double d);
OAKCORE_API double oakcore_bezier_cubic_tto_y(double a, double b, double c,
double d, double t);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_BEZIER_H */
+123
View File
@@ -0,0 +1,123 @@
/***
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 OAKCORE_COLOR_H
#define OAKCORE_COLOR_H
#include "export.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file color.h
* @brief C ABI for the high precision RGBA color value type
*
* Opaque handle + free functions. All returned OakColor* are owned by the
* caller and must be released with oakcore_color_free().
*
* A color is always 4 float channels in red/green/blue/alpha order. Channel
* values are unbounded: values outside [0, 1] are valid (HDR).
*
* Functions taking a `format` argument expect the same values as
* PixelFormat::Format (render/pixelformat.h): invalid = -1, u8 = 0, u10 = 1,
* u16 = 2, f16 = 3, f32 = 4.
*/
typedef struct OakColor OakColor;
OAKCORE_API OakColor *oakcore_color_create(void);
OAKCORE_API OakColor *oakcore_color_create_rgba(float r, float g, float b,
float a);
OAKCORE_API OakColor *oakcore_color_copy(const OakColor *self);
OAKCORE_API void oakcore_color_free(OakColor *self);
/**
* Creates a color from hue/saturation/value.
*
* Hue expects a value between 0.0 and 360.0. Saturation and value expect a
* value between 0.0 and 1.0.
*/
OAKCORE_API OakColor *oakcore_color_from_hsv(float h, float s, float v);
/**
* Creates a color from raw pixel data in the given pixel format. At most 4
* channels are read; missing channels default to 0.
*/
OAKCORE_API OakColor *oakcore_color_from_data(const char *data, int format,
int nb_channels);
OAKCORE_API float oakcore_color_red(const OakColor *self);
OAKCORE_API float oakcore_color_green(const OakColor *self);
OAKCORE_API float oakcore_color_blue(const OakColor *self);
OAKCORE_API float oakcore_color_alpha(const OakColor *self);
OAKCORE_API void oakcore_color_set_red(OakColor *self, float red);
OAKCORE_API void oakcore_color_set_green(OakColor *self, float green);
OAKCORE_API void oakcore_color_set_blue(OakColor *self, float blue);
OAKCORE_API void oakcore_color_set_alpha(OakColor *self, float alpha);
OAKCORE_API void oakcore_color_to_hsv(const OakColor *self, float *hue,
float *sat, float *val);
OAKCORE_API float oakcore_color_hsv_hue(const OakColor *self);
OAKCORE_API float oakcore_color_hsv_saturation(const OakColor *self);
OAKCORE_API float oakcore_color_value(const OakColor *self);
OAKCORE_API void oakcore_color_to_hsl(const OakColor *self, float *hue,
float *sat, float *lightness);
OAKCORE_API float oakcore_color_hsl_hue(const OakColor *self);
OAKCORE_API float oakcore_color_hsl_saturation(const OakColor *self);
OAKCORE_API float oakcore_color_lightness(const OakColor *self);
/**
* Borrowed pointer to the 4 float channels (rgba order), valid until
* oakcore_color_free().
*/
OAKCORE_API float *oakcore_color_data(OakColor *self);
OAKCORE_API const float *oakcore_color_const_data(const OakColor *self);
/**
* Writes the color as raw pixel data in the given pixel format. At most 4
* channels are written; out must have room for nb_channels * bytes-per-channel
* (u10 always packs to 4 bytes).
*/
OAKCORE_API void oakcore_color_to_data(const OakColor *self, char *out,
int format, int nb_channels);
/**
* Super rough luminance value mostly used for UI (determining whether to
* overlay with black or white text).
*/
OAKCORE_API float oakcore_color_get_rough_luminance(const OakColor *self);
OAKCORE_API void oakcore_color_add_assign(OakColor *self,
const OakColor *other);
OAKCORE_API void oakcore_color_sub_assign(OakColor *self,
const OakColor *other);
OAKCORE_API void oakcore_color_add_scalar_assign(OakColor *self, float value);
OAKCORE_API void oakcore_color_sub_scalar_assign(OakColor *self, float value);
OAKCORE_API void oakcore_color_mul_scalar_assign(OakColor *self, float value);
OAKCORE_API void oakcore_color_div_scalar_assign(OakColor *self, float value);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_COLOR_H */
+45
View File
@@ -0,0 +1,45 @@
/***
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 OAKCORE_EXPORT_H
#define OAKCORE_EXPORT_H
/**
* @file export.h
* @brief Symbol visibility macros for liboakcore
*
* liboakcore exposes a pure C ABI: every public function is declared with
* OAKCORE_API and everything else is hidden. No C++ symbols cross the
* library boundary.
*/
#if defined(_WIN32) || defined(__CYGWIN__)
#ifdef OAKCORE_BUILD
#define OAKCORE_API __declspec(dllexport)
#else
#define OAKCORE_API __declspec(dllimport)
#endif
#elif defined(__GNUC__) || defined(__clang__)
#define OAKCORE_API __attribute__((visibility("default")))
#else
#define OAKCORE_API
#endif
#endif /* OAKCORE_EXPORT_H */
@@ -0,0 +1,88 @@
/***
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 OAKCORE_FRACTIONUTILS_H
#define OAKCORE_FRACTIONUTILS_H
#include <stdint.h>
#include "export.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file fractionutils.h
* @brief C ABI for the fraction utility functions
*
* Pure integer helpers: there is no object and therefore no opaque handle.
* In/out values are passed through pointer out-parameters.
*/
/**
* @brief Rounding modes for oakcore_fractionutils_rescale_rnd()
*
* Mirrors the FFmpeg AVRounding modes: NEAR_INF rounds to the nearest value
* with halfway cases rounded away from zero, UP rounds toward positive
* infinity.
*/
typedef enum OakFractionRounding {
OAK_FRACTION_ROUNDING_NEAR_INF = 0,
OAK_FRACTION_ROUNDING_UP = 1
} OakFractionRounding;
/**
* @brief Reduces the fraction *num / *den in place so that both fit within max
*
* Divides out the greatest common divisor and, if the values still do not
* fit within max, finds the closest approximation using continued fractions.
*
* A zero denominator is preserved (with the numerator set to zero).
* num and den must not be NULL.
*/
OAKCORE_API void oakcore_fractionutils_reduce_fraction(int64_t *num,
int64_t *den,
int64_t max);
/**
* @brief Three-way comparison of the fractions an/ad and bn/bd
*
* Returns -1 if a < b, 0 if a == b, 1 if a > b, and INT_MIN when the
* comparison is meaningless (degenerate zero-denominator fractions).
*/
OAKCORE_API int oakcore_fractionutils_compare_fractions(int an, int ad,
int bn, int bd);
/**
* @brief Rescales a by the fraction b/c: returns a * b / c
*
* The intermediate product is computed with 128-bit arithmetic where
* available so that no precision is lost for large timestamps.
*/
OAKCORE_API int64_t oakcore_fractionutils_rescale_rnd(int64_t a, int64_t b,
int64_t c,
OakFractionRounding rnd);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_FRACTIONUTILS_H */
@@ -0,0 +1,87 @@
/***
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 OAKCORE_RATIONAL_H
#define OAKCORE_RATIONAL_H
#include "export.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file rational.h
* @brief C ABI for the rational number value type
*
* Opaque handle + free functions. All returned OakRational* are owned by
* the caller and must be released with oakcore_rational_free().
*/
typedef struct OakRational OakRational;
OAKCORE_API OakRational *oakcore_rational_create(int numerator);
OAKCORE_API OakRational *oakcore_rational_create_nd(int numerator,
int denominator);
OAKCORE_API OakRational *oakcore_rational_create_nan(void);
OAKCORE_API OakRational *oakcore_rational_copy(const OakRational *self);
OAKCORE_API void oakcore_rational_free(OakRational *self);
OAKCORE_API int oakcore_rational_numerator(const OakRational *self);
OAKCORE_API int oakcore_rational_denominator(const OakRational *self);
OAKCORE_API double oakcore_rational_to_double(const OakRational *self);
/**
* Writes "num/den" into buf (NUL-terminated when buf_size > 0).
* Returns the number of characters that would have been written excluding
* the NUL, so buf == NULL or a too-small buffer can be used to query the
* required size.
*/
OAKCORE_API int oakcore_rational_to_string(const OakRational *self, char *buf,
int buf_size);
OAKCORE_API OakRational *oakcore_rational_from_double(double value, int *ok);
OAKCORE_API OakRational *oakcore_rational_from_string(const char *str, int *ok);
OAKCORE_API int oakcore_rational_is_null(const OakRational *self);
OAKCORE_API int oakcore_rational_is_nan(const OakRational *self);
OAKCORE_API OakRational *oakcore_rational_flipped(const OakRational *self);
OAKCORE_API void oakcore_rational_flip(OakRational *self);
OAKCORE_API void oakcore_rational_add_assign(OakRational *self,
const OakRational *other);
OAKCORE_API void oakcore_rational_sub_assign(OakRational *self,
const OakRational *other);
OAKCORE_API void oakcore_rational_mul_assign(OakRational *self,
const OakRational *other);
OAKCORE_API void oakcore_rational_div_assign(OakRational *self,
const OakRational *other);
/**
* Three-way comparison like compare_fractions: -1, 0 or 1.
*/
OAKCORE_API int oakcore_rational_compare(const OakRational *self,
const OakRational *other);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_RATIONAL_H */
@@ -0,0 +1,149 @@
/***
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 OAKCORE_SAMPLEBUFFER_H
#define OAKCORE_SAMPLEBUFFER_H
#include <stddef.h>
#include "export.h"
#include "rational.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file samplebuffer.h
* @brief C ABI for the planar audio sample buffer
*
* Opaque handle + free functions. Every returned OakSampleBuffer or
* OakAudioParams handle is owned by the caller and must be released with
* oakcore_samplebuffer_free() or oakcore_audioparams_free() respectively.
* Samples are always stored planar (one float array per channel).
*/
typedef struct OakSampleBuffer OakSampleBuffer;
typedef struct OakAudioParams OakAudioParams;
OAKCORE_API OakSampleBuffer *oakcore_samplebuffer_create(void);
OAKCORE_API OakSampleBuffer *oakcore_samplebuffer_create_length(
const OakAudioParams *params, const OakRational *length);
OAKCORE_API OakSampleBuffer *oakcore_samplebuffer_create_samples(
const OakAudioParams *params, size_t samples_per_channel);
OAKCORE_API OakSampleBuffer *oakcore_samplebuffer_copy(
const OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_free(OakSampleBuffer *self);
OAKCORE_API OakSampleBuffer *oakcore_samplebuffer_rip_channel(
const OakSampleBuffer *self, int channel);
/**
* Copies the samples of one channel into out. Returns the total number of
* floats in the channel (equal to the sample count), so out == NULL (or a
* too-small buffer) can be used to query the required size; at most out_size
* floats are written.
*/
OAKCORE_API int oakcore_samplebuffer_rip_channel_vector(
const OakSampleBuffer *self, int channel, float *out, int out_size);
/**
* Returns a copy of the buffer's audio parameters as a new owned handle
* (release with oakcore_audioparams_free()).
*/
OAKCORE_API OakAudioParams *oakcore_samplebuffer_audio_params(
const OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_set_audio_params(
OakSampleBuffer *self, const OakAudioParams *params);
OAKCORE_API size_t oakcore_samplebuffer_sample_count(
const OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_set_sample_count(OakSampleBuffer *self,
size_t sample_count);
OAKCORE_API void oakcore_samplebuffer_set_sample_count_length(
OakSampleBuffer *self, const OakRational *length);
/**
* Borrowed pointer to the samples of one channel; it becomes invalid when the
* handle is destroyed or reallocated and must not be freed by the caller.
* Returns NULL when the buffer is not allocated or the channel is out of
* range.
*/
OAKCORE_API float *oakcore_samplebuffer_data(OakSampleBuffer *self,
int channel);
/**
* Fills out with one borrowed sample pointer per channel (see
* oakcore_samplebuffer_data()). out must have room for
* oakcore_samplebuffer_channel_count() entries.
*/
OAKCORE_API void oakcore_samplebuffer_to_raw_ptrs(OakSampleBuffer *self,
float **out);
OAKCORE_API int oakcore_samplebuffer_channel_count(const OakSampleBuffer *self);
OAKCORE_API int oakcore_samplebuffer_is_allocated(const OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_allocate(OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_destroy(OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_reverse(OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_speed(OakSampleBuffer *self,
double speed);
OAKCORE_API void oakcore_samplebuffer_transform_volume(OakSampleBuffer *self,
float f);
OAKCORE_API void oakcore_samplebuffer_transform_volume_for_channel(
OakSampleBuffer *self, int channel, float volume);
OAKCORE_API void oakcore_samplebuffer_transform_volume_to(
float f, const OakSampleBuffer *input, OakSampleBuffer *output);
OAKCORE_API void oakcore_samplebuffer_transform_volume_for_channel_to(
int channel, float volume, const OakSampleBuffer *input,
OakSampleBuffer *output);
OAKCORE_API void oakcore_samplebuffer_transform_volume_for_sample(
OakSampleBuffer *self, size_t sample_index, float volume);
OAKCORE_API void oakcore_samplebuffer_transform_volume_for_sample_on_channel(
OakSampleBuffer *self, size_t sample_index, int channel, float volume);
OAKCORE_API void oakcore_samplebuffer_clamp(OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_silence(OakSampleBuffer *self);
OAKCORE_API void oakcore_samplebuffer_silence_range(OakSampleBuffer *self,
size_t start_sample,
size_t end_sample);
OAKCORE_API void oakcore_samplebuffer_silence_bytes(OakSampleBuffer *self,
size_t start_byte,
size_t end_byte);
OAKCORE_API void oakcore_samplebuffer_set(OakSampleBuffer *self, int channel,
const float *data,
size_t sample_offset,
size_t sample_length);
/**
* Copies channel "from" of other into channel "to" of self; from == -1 uses
* the same index as to.
*/
OAKCORE_API void oakcore_samplebuffer_fast_set(OakSampleBuffer *self,
const OakSampleBuffer *other,
int to, int from);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_SAMPLEBUFFER_H */
@@ -0,0 +1,94 @@
/***
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 OAKCORE_STRINGUTILS_H
#define OAKCORE_STRINGUTILS_H
#include <stdarg.h>
#include "export.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file stringutils.h
* @brief C ABI for the string utility functions
*
* StringUtils consists of static functions only, so there are no objects
* and no opaque handle. Functions returning string lists hand back a
* caller-owned char** that must be released with
* oakcore_stringutils_free_string_array().
*/
/**
* Splits s on separator and returns a newly allocated array of newly
* allocated NUL-terminated strings; *count receives the element count.
* Release the result with oakcore_stringutils_free_string_array().
* A NULL or empty s yields one empty string, mirroring the C++
* implementation. Returns NULL only on allocation failure.
*/
OAKCORE_API char **oakcore_stringutils_split(const char *s, char separator,
int *count);
/**
* Splits s wherever the regular expression pattern matches (std::regex
* ECMAScript syntax). Same ownership and NULL semantics as
* oakcore_stringutils_split().
*/
OAKCORE_API char **oakcore_stringutils_split_regex(const char *s,
const char *pattern,
int *count);
/**
* Releases an array returned by oakcore_stringutils_split() or
* oakcore_stringutils_split_regex(). Safe to call with arr == NULL.
*/
OAKCORE_API void oakcore_stringutils_free_string_array(char **arr, int count);
/**
* Parses an int from s in the given base (usually 10, or 16 for hex).
* Returns the parsed value, or 0 on parser error. ok is an optional output
* parameter set to 1 on success and 0 on failure.
*/
OAKCORE_API int oakcore_stringutils_to_int(const char *s, int base, int *ok);
/**
* Formats a string with vsnprintf semantics: writes into buf
* (NUL-terminated when buf_size > 0) and returns the number of characters
* that would have been written excluding the NUL, so buf == NULL or a
* too-small buffer can be used to query the required size.
*/
OAKCORE_API int oakcore_stringutils_format(char *buf, int buf_size,
const char *fmt, ...);
/**
* va_list form of oakcore_stringutils_format(), for forwarding from other
* variadic functions.
*/
OAKCORE_API int oakcore_stringutils_format_v(char *buf, int buf_size,
const char *fmt, va_list args);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_STRINGUTILS_H */
@@ -0,0 +1,145 @@
/***
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 OAKCORE_TIMECODEFUNCTIONS_H
#define OAKCORE_TIMECODEFUNCTIONS_H
#include <stdint.h>
#include "export.h"
#include "rational.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file timecodefunctions.h
* @brief C ABI for the time/timecode/timestamp conversion functions
*
* Free functions only (the wrapped class is all-static, so there is no
* object handle of its own). Times and timebases are passed as borrowed
* OakRational handles; every returned OakRational* is owned by the caller
* and must be released with oakcore_rational_free().
*
* Terminology used throughout:
*
* `time` - time in seconds presented in a Rational form
* `timebase` - the base time unit of an audio/video stream in seconds
* `timestamp` - an integer representation of a time in timebase units
* (in many cases is used like a frame number)
* `timecode` - a user-friendly string representation of a time according to OakTimecodeDisplay
*/
/**
* @brief User-friendly timecode display modes
*
* Values must stay in sync with the wrapped class's Display enum.
*/
typedef enum OakTimecodeDisplay {
OAK_TIMECODE_DISPLAY_DROP_FRAME = 0,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME = 1,
OAK_TIMECODE_DISPLAY_SECONDS = 2,
OAK_TIMECODE_DISPLAY_FRAMES = 3,
OAK_TIMECODE_DISPLAY_MILLISECONDS = 4
} OakTimecodeDisplay;
/**
* @brief Rounding modes for time/timestamp conversions
*
* Values must stay in sync with the wrapped class's Rounding enum.
*/
typedef enum OakTimecodeRounding {
OAK_TIMECODE_ROUNDING_CEIL = 0,
OAK_TIMECODE_ROUNDING_FLOOR = 1,
OAK_TIMECODE_ROUNDING_ROUND = 2
} OakTimecodeRounding;
/**
* @brief Convert a time (according to a Rational timebase) to a user-friendly string representation
*
* Writes the timecode into buf (NUL-terminated when buf_size > 0).
* Returns the number of characters that would have been written excluding
* the NUL, so buf == NULL or a too-small buffer can be used to query the
* required size.
*/
OAKCORE_API int oakcore_timecode_time_to_timecode(const OakRational *time,
const OakRational *timebase,
OakTimecodeDisplay display,
int show_plus_if_positive,
char *buf, int buf_size);
/**
* @brief Convert a user-friendly timecode string to a time in seconds
*
* Returns a new owned OakRational (free with oakcore_rational_free()).
* ok is set to 1 on success and 0 on failure (may be NULL).
*/
OAKCORE_API OakRational *oakcore_timecode_timecode_to_time(const char *timecode,
const OakRational *timebase,
OakTimecodeDisplay display,
int *ok);
/**
* @brief Convert a millisecond count to an "HH:MM:SS" string
*
* Same buf/buf_size convention as oakcore_timecode_time_to_timecode().
*/
OAKCORE_API int oakcore_timecode_time_to_string(int64_t ms, char *buf,
int buf_size);
/**
* @brief Snap a time to the nearest timestamp boundary of a timebase
*
* Returns a new owned OakRational (free with oakcore_rational_free()).
*/
OAKCORE_API OakRational *oakcore_timecode_snap_time_to_timebase(const OakRational *time,
const OakRational *timebase,
OakTimecodeRounding rounding);
OAKCORE_API int64_t oakcore_timecode_time_to_timestamp(const OakRational *time,
const OakRational *timebase,
OakTimecodeRounding rounding);
OAKCORE_API int64_t oakcore_timecode_time_to_timestamp_d(double time,
const OakRational *timebase,
OakTimecodeRounding rounding);
OAKCORE_API int64_t oakcore_timecode_rescale_timestamp(int64_t ts,
const OakRational *source,
const OakRational *dest);
OAKCORE_API int64_t oakcore_timecode_rescale_timestamp_ceil(int64_t ts,
const OakRational *source,
const OakRational *dest);
/**
* @brief Convert a timestamp in timebase units back to a time in seconds
*
* Returns a new owned OakRational (free with oakcore_rational_free()).
*/
OAKCORE_API OakRational *oakcore_timecode_timestamp_to_time(int64_t timestamp,
const OakRational *timebase);
OAKCORE_API int oakcore_timecode_timebase_is_drop_frame(const OakRational *timebase);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_TIMECODEFUNCTIONS_H */
+110
View File
@@ -0,0 +1,110 @@
/***
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 OAKCORE_TIMERANGE_H
#define OAKCORE_TIMERANGE_H
#include "export.h"
#include "rational.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file timerange.h
* @brief C ABI for the time range value type
*
* Opaque handle + free functions. All returned OakTimeRange and OakRational
* pointers are owned by the caller and must be released with
* oakcore_timerange_free() and oakcore_rational_free() respectively.
*/
typedef struct OakTimeRange OakTimeRange;
OAKCORE_API OakTimeRange *oakcore_timerange_create(void);
OAKCORE_API OakTimeRange *oakcore_timerange_create_io(const OakRational *in,
const OakRational *out);
OAKCORE_API OakTimeRange *oakcore_timerange_copy(const OakTimeRange *self);
OAKCORE_API void oakcore_timerange_free(OakTimeRange *self);
OAKCORE_API OakRational *oakcore_timerange_in(const OakTimeRange *self);
OAKCORE_API OakRational *oakcore_timerange_out(const OakTimeRange *self);
OAKCORE_API OakRational *oakcore_timerange_length(const OakTimeRange *self);
OAKCORE_API void oakcore_timerange_set_in(OakTimeRange *self,
const OakRational *in);
OAKCORE_API void oakcore_timerange_set_out(OakTimeRange *self,
const OakRational *out);
OAKCORE_API void oakcore_timerange_set_range(OakTimeRange *self,
const OakRational *in,
const OakRational *out);
OAKCORE_API int oakcore_timerange_equal(const OakTimeRange *self,
const OakTimeRange *other);
OAKCORE_API int oakcore_timerange_overlaps_with(const OakTimeRange *self,
const OakTimeRange *other,
int in_inclusive,
int out_inclusive);
OAKCORE_API int oakcore_timerange_contains_range(const OakTimeRange *self,
const OakTimeRange *other,
int in_inclusive,
int out_inclusive);
OAKCORE_API int oakcore_timerange_contains_time(const OakTimeRange *self,
const OakRational *time);
OAKCORE_API OakTimeRange *oakcore_timerange_combined(
const OakTimeRange *self, const OakTimeRange *other);
OAKCORE_API OakTimeRange *oakcore_timerange_combine(const OakTimeRange *a,
const OakTimeRange *b);
OAKCORE_API OakTimeRange *oakcore_timerange_intersected(
const OakTimeRange *self, const OakTimeRange *other);
OAKCORE_API OakTimeRange *oakcore_timerange_intersect(const OakTimeRange *a,
const OakTimeRange *b);
OAKCORE_API OakTimeRange *oakcore_timerange_add(const OakTimeRange *self,
const OakRational *rhs);
OAKCORE_API OakTimeRange *oakcore_timerange_sub(const OakTimeRange *self,
const OakRational *rhs);
OAKCORE_API void oakcore_timerange_add_assign(OakTimeRange *self,
const OakRational *rhs);
OAKCORE_API void oakcore_timerange_sub_assign(OakTimeRange *self,
const OakRational *rhs);
/**
* Splits the range into chunks of chunk_size (the first and last chunk are
* clamped to the range bounds) and writes a newly allocated owned handle per
* chunk into out_ranges. Returns the total number of chunks, so
* out_ranges == NULL (or a too-small array) can be used to query the
* required size; oakcore_timerange_split_count() is the direct equivalent
* of that query.
*/
OAKCORE_API int oakcore_timerange_split_count(const OakTimeRange *self,
int chunk_size);
OAKCORE_API int oakcore_timerange_split(const OakTimeRange *self,
int chunk_size,
OakTimeRange **out_ranges,
int out_size);
#ifdef __cplusplus
}
#endif
#endif /* OAKCORE_TIMERANGE_H */
+192 -79
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -21,11 +21,12 @@
#ifndef OAK_LIBOLIVECORE_AUDIOPARAMS_H
#define OAK_LIBOLIVECORE_AUDIOPARAMS_H
#include <cstring>
#include <assert.h>
#include <cstdint>
#include <vector>
#include "olive/core/oakcore/audioparams.h"
#include "channellayout.h"
#include "sampleformat.h"
#include "../util/rational.h"
@@ -36,9 +37,9 @@ namespace olive::core
/**
* @brief Audio parameters class managing audio stream configuration
*
* Channel layouts are stored as plain 64-bit masks (see channellayout.h).
* Because the mask is a simple value type, AudioParams has value semantics
* and can be copied freely.
* Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* opaque OakAudioParams handle and forwards every call across the C boundary.
* The public API is unchanged from the original implementation.
*/
class AudioParams {
public:
@@ -47,12 +48,8 @@ public:
* sample_rate=0, channel_layout empty, format=INVALID
*/
AudioParams()
: sample_rate_(0)
, channel_layout_mask_(0)
, channel_count_(0)
, format_(SampleFormat::invalid)
: handle_(oakcore_audioparams_create_invalid())
{
set_default_footage_parameters();
}
/**
@@ -63,31 +60,61 @@ public:
*/
AudioParams(const int &sample_rate, uint64_t channel_layout,
const SampleFormat &format)
: sample_rate_(sample_rate)
, channel_layout_mask_(channel_layout)
, channel_count_(0)
, format_(format)
: handle_(oakcore_audioparams_create(sample_rate, channel_layout, format))
{
set_default_footage_parameters();
timebase_ = sample_rate_as_time_base();
calculate_channel_count();
}
AudioParams(const AudioParams &rhs)
: handle_(oakcore_audioparams_copy(rhs.handle_))
{
}
AudioParams(AudioParams &&rhs) noexcept
: handle_(rhs.handle_)
{
rhs.handle_ = nullptr;
}
~AudioParams()
{
oakcore_audioparams_free(handle_);
}
AudioParams &operator=(const AudioParams &rhs)
{
if (this != &rhs) {
oakcore_audioparams_free(handle_);
handle_ = oakcore_audioparams_copy(rhs.handle_);
}
return *this;
}
AudioParams &operator=(AudioParams &&rhs) noexcept
{
if (this != &rhs) {
oakcore_audioparams_free(handle_);
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
}
return *this;
}
int sample_rate() const
{
return sample_rate_;
return oakcore_audioparams_sample_rate(handle_);
}
void set_sample_rate(int sample_rate)
{
sample_rate_ = sample_rate;
oakcore_audioparams_set_sample_rate(handle_, sample_rate);
}
/**
* @brief Channel layout as a 64-bit mask (0 if unspecified)
*/
const uint64_t &channel_layout() const
uint64_t channel_layout() const
{
return channel_layout_mask_;
return oakcore_audioparams_channel_layout(handle_);
}
/**
@@ -96,122 +123,208 @@ public:
*/
void set_channel_layout(uint64_t mask)
{
channel_layout_mask_ = mask;
calculate_channel_count();
oakcore_audioparams_set_channel_layout(handle_, mask);
}
Rational time_base() const
{
return timebase_;
return Rational::from_handle(oakcore_audioparams_time_base(handle_));
}
void set_time_base(const Rational &timebase)
{
timebase_ = timebase;
oakcore_audioparams_set_time_base(handle_, timebase.handle());
}
Rational sample_rate_as_time_base() const
{
return Rational(1, sample_rate());
return Rational::from_handle(
oakcore_audioparams_sample_rate_as_time_base(handle_));
}
SampleFormat format() const
{
return format_;
return SampleFormat(
static_cast<SampleFormat::Format>(oakcore_audioparams_format(handle_)));
}
void set_format(SampleFormat format)
{
format_ = format;
oakcore_audioparams_set_format(handle_, format);
}
bool enabled() const
{
return enabled_;
return oakcore_audioparams_enabled(handle_) != 0;
}
void set_enabled(bool e)
{
enabled_ = e;
oakcore_audioparams_set_enabled(handle_, e ? 1 : 0);
}
int stream_index() const
{
return stream_index_;
return oakcore_audioparams_stream_index(handle_);
}
void set_stream_index(int s)
{
stream_index_ = s;
oakcore_audioparams_set_stream_index(handle_, s);
}
int64_t duration() const
{
return duration_;
return oakcore_audioparams_duration(handle_);
}
void set_duration(int64_t duration)
{
duration_ = duration;
oakcore_audioparams_set_duration(handle_, duration);
}
int64_t time_to_bytes(const double &time) const;
int64_t time_to_bytes(const Rational &time) const;
int64_t time_to_bytes_per_channel(const double &time) const;
int64_t time_to_bytes_per_channel(const Rational &time) const;
int64_t time_to_samples(const double &time) const;
int64_t time_to_samples(const Rational &time) const;
int64_t samples_to_bytes(const int64_t &samples) const;
int64_t samples_to_bytes_per_channel(const int64_t &samples) const;
Rational samples_to_time(const int64_t &samples) const;
int64_t bytes_to_samples(const int64_t &bytes) const;
Rational bytes_to_time(const int64_t &bytes) const;
Rational bytes_per_channel_to_time(const int64_t &bytes) const;
int channel_count() const;
int bytes_per_sample_per_channel() const;
int bits_per_sample() const;
bool is_valid() const;
int64_t time_to_bytes(const double &time) const
{
return oakcore_audioparams_time_to_bytes(handle_, time);
}
bool operator==(const AudioParams &other) const;
bool operator!=(const AudioParams &other) const;
int64_t time_to_bytes(const Rational &time) const
{
return oakcore_audioparams_time_to_bytes_rational(handle_, time.handle());
}
int64_t time_to_bytes_per_channel(const double &time) const
{
return oakcore_audioparams_time_to_bytes_per_channel(handle_, time);
}
int64_t time_to_bytes_per_channel(const Rational &time) const
{
return oakcore_audioparams_time_to_bytes_per_channel_rational(handle_,
time.handle());
}
int64_t time_to_samples(const double &time) const
{
return oakcore_audioparams_time_to_samples(handle_, time);
}
int64_t time_to_samples(const Rational &time) const
{
return oakcore_audioparams_time_to_samples_rational(handle_, time.handle());
}
int64_t samples_to_bytes(const int64_t &samples) const
{
return oakcore_audioparams_samples_to_bytes(handle_, samples);
}
int64_t samples_to_bytes_per_channel(const int64_t &samples) const
{
return oakcore_audioparams_samples_to_bytes_per_channel(handle_, samples);
}
Rational samples_to_time(const int64_t &samples) const
{
return Rational::from_handle(
oakcore_audioparams_samples_to_time(handle_, samples));
}
int64_t bytes_to_samples(const int64_t &bytes) const
{
return oakcore_audioparams_bytes_to_samples(handle_, bytes);
}
Rational bytes_to_time(const int64_t &bytes) const
{
return Rational::from_handle(
oakcore_audioparams_bytes_to_time(handle_, bytes));
}
Rational bytes_per_channel_to_time(const int64_t &bytes) const
{
return Rational::from_handle(
oakcore_audioparams_bytes_per_channel_to_time(handle_, bytes));
}
int channel_count() const
{
return oakcore_audioparams_channel_count(handle_);
}
int bytes_per_sample_per_channel() const
{
return oakcore_audioparams_bytes_per_sample_per_channel(handle_);
}
int bits_per_sample() const
{
return oakcore_audioparams_bits_per_sample(handle_);
}
bool is_valid() const
{
return oakcore_audioparams_is_valid(handle_) != 0;
}
bool operator==(const AudioParams &other) const
{
return oakcore_audioparams_equals(handle_, other.handle_) != 0;
}
bool operator!=(const AudioParams &other) const
{
return !(*this == other);
}
static const std::vector<uint64_t> k_supported_channel_layouts;
static const std::vector<int> k_supported_sample_rates;
private:
void set_default_footage_parameters()
/**
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
*/
OakAudioParams *handle() const
{
enabled_ = true;
stream_index_ = 0;
duration_ = 0;
return handle_;
}
/**
* @brief Updates channel_count_ from the current channel_layout_mask_
* Called after any channel layout modification.
* @brief Wraps an owned C handle (takes ownership)
*/
void calculate_channel_count();
static AudioParams from_handle(OakAudioParams *handle)
{
return AudioParams(handle);
}
int sample_rate_; ///< Audio sample rate in Hz (e.g., 48000)
private:
explicit AudioParams(OakAudioParams *handle)
: handle_(handle)
{
}
/**
* @brief Channel layout mask (0 if unspecified)
*
* Plain value type mirroring FFmpeg's AV_CH_LAYOUT_* masks; no dynamic
* memory is involved, so copies are trivially safe.
*/
uint64_t channel_layout_mask_;
int channel_count_; ///< Cached channel count from layout
SampleFormat format_; ///< Audio sample format
// Footage-specific parameters (serialized with footage metadata)
int enabled_; // Using int instead of bool fixes GCC 11 stringop-overflow issue (byte alignment)
int stream_index_; ///< Index in the source file's stream list
int64_t duration_; ///< Stream duration in timebase units
Rational timebase_; ///< Timebase for this audio stream
OakAudioParams *handle_;
};
inline const std::vector<uint64_t> AudioParams::k_supported_channel_layouts = [] {
std::vector<uint64_t> v;
const int n = oakcore_audioparams_supported_channel_layout_count();
v.reserve(size_t(n));
for (int i = 0; i < n; i++) {
v.push_back(oakcore_audioparams_supported_channel_layout_at(i));
}
return v;
}();
inline const std::vector<int> AudioParams::k_supported_sample_rates = [] {
std::vector<int> v;
const int n = oakcore_audioparams_supported_sample_rate_count();
v.reserve(size_t(n));
for (int i = 0; i < n; i++) {
v.push_back(oakcore_audioparams_supported_sample_rate_at(i));
}
return v;
}();
}
#endif // OAK_LIBOLIVECORE_AUDIOPARAMS_H
+214 -51
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -22,11 +22,11 @@
#ifndef OAK_LIBOLIVECORE_SAMPLEBUFFER_H
#define OAK_LIBOLIVECORE_SAMPLEBUFFER_H
#include <memory>
#include <vector>
#include "audioparams.h"
#include "../util/rational.h"
#include "olive/core/oakcore/samplebuffer.h"
#include "olive/core/render/audioparams.h"
#include "olive/core/util/rational.h"
namespace olive::core
{
@@ -34,102 +34,265 @@ namespace olive::core
/**
* @brief A buffer of audio samples
*
* Audio samples in this structure are always stored in PLANAR (separated by channel). This is done to simplify audio
* rendering code. This replaces the old system of using QByteArrays (containing packed audio) and while SampleBuffer
* replaces many of those in the rendering/processing side of things, QByteArrays are currently still in use for
* playback, including reading to and from the cache.
* Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* opaque OakSampleBuffer handle and forwards every call across the C boundary.
* The public API is unchanged from the original implementation, except that
* the getters return AudioParams/size_t by value instead of by const
* reference.
*
* Audio samples in this structure are always stored in PLANAR (separated by
* channel). This is done to simplify audio rendering code. This replaces the
* old system of using QByteArrays (containing packed audio) and while
* SampleBuffer replaces many of those in the rendering/processing side of
* things, QByteArrays are currently still in use for playback, including
* reading to and from the cache.
*/
class SampleBuffer {
public:
SampleBuffer();
SampleBuffer(const AudioParams &audio_params, const Rational &length);
SampleBuffer(const AudioParams &audio_params, size_t samples_per_channel);
SampleBuffer rip_channel(int channel) const;
std::vector<float> rip_channel_vector(int channel) const;
const AudioParams &audio_params() const;
void set_audio_params(const AudioParams &params);
const size_t &sample_count() const
SampleBuffer()
: handle_(oakcore_samplebuffer_create())
{
return sample_count_per_channel_;
}
void set_sample_count(const size_t &sample_count);
SampleBuffer(const AudioParams &audio_params, const Rational &length)
: handle_(oakcore_samplebuffer_create_length(audio_params.handle(),
length.handle()))
{
}
SampleBuffer(const AudioParams &audio_params, size_t samples_per_channel)
: handle_(oakcore_samplebuffer_create_samples(audio_params.handle(),
samples_per_channel))
{
}
SampleBuffer(const SampleBuffer &rhs)
: handle_(oakcore_samplebuffer_copy(rhs.handle_))
{
}
SampleBuffer(SampleBuffer &&rhs) noexcept
: handle_(rhs.handle_)
{
rhs.handle_ = nullptr;
}
~SampleBuffer()
{
oakcore_samplebuffer_free(handle_);
}
SampleBuffer &operator=(const SampleBuffer &rhs)
{
if (this != &rhs) {
oakcore_samplebuffer_free(handle_);
handle_ = oakcore_samplebuffer_copy(rhs.handle_);
}
return *this;
}
SampleBuffer &operator=(SampleBuffer &&rhs) noexcept
{
if (this != &rhs) {
oakcore_samplebuffer_free(handle_);
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
}
return *this;
}
SampleBuffer rip_channel(int channel) const
{
return from_handle(oakcore_samplebuffer_rip_channel(handle_, channel));
}
std::vector<float> rip_channel_vector(int channel) const
{
const int size = oakcore_samplebuffer_rip_channel_vector(
handle_, channel, nullptr, 0);
std::vector<float> v(static_cast<size_t>(size));
if (size > 0) {
oakcore_samplebuffer_rip_channel_vector(handle_, channel, v.data(),
size);
}
return v;
}
AudioParams audio_params() const
{
return AudioParams::from_handle(
oakcore_samplebuffer_audio_params(handle_));
}
void set_audio_params(const AudioParams &params)
{
oakcore_samplebuffer_set_audio_params(handle_, params.handle());
}
size_t sample_count() const
{
return oakcore_samplebuffer_sample_count(handle_);
}
void set_sample_count(const size_t &sample_count)
{
oakcore_samplebuffer_set_sample_count(handle_, sample_count);
}
void set_sample_count(const Rational &length)
{
set_sample_count(audio_params_.time_to_samples(length));
oakcore_samplebuffer_set_sample_count_length(handle_,
length.handle());
}
float *data(int channel)
{
return data_[channel].data();
return oakcore_samplebuffer_data(handle_, channel);
}
const float *data(int channel) const
{
return data_.at(channel).data();
return oakcore_samplebuffer_data(handle_, channel);
}
std::vector<float *> to_raw_ptrs()
{
std::vector<float *> r(data_.size());
for (size_t i = 0; i < r.size(); i++) {
r[i] = data_[i].data();
std::vector<float *> r(static_cast<size_t>(channel_count()));
if (!r.empty()) {
oakcore_samplebuffer_to_raw_ptrs(handle_, r.data());
}
return r;
}
int channel_count() const
{
return data_.size();
return oakcore_samplebuffer_channel_count(handle_);
}
bool is_allocated() const
{
return !data_.empty();
return oakcore_samplebuffer_is_allocated(handle_) != 0;
}
void allocate();
void destroy();
void reverse();
void speed(double speed);
void allocate()
{
oakcore_samplebuffer_allocate(handle_);
}
void destroy()
{
oakcore_samplebuffer_destroy(handle_);
}
void reverse()
{
oakcore_samplebuffer_reverse(handle_);
}
void speed(double speed)
{
oakcore_samplebuffer_speed(handle_, speed);
}
void transform_volume(float f)
{
oakcore_samplebuffer_transform_volume(handle_, f);
}
void transform_volume_for_channel(int channel, float volume)
{
oakcore_samplebuffer_transform_volume_for_channel(handle_, channel,
volume);
}
void transform_volume(float f);
void transform_volume_for_channel(int channel, float volume);
static void transform_volume(float f, const SampleBuffer *input,
SampleBuffer *output);
SampleBuffer *output)
{
oakcore_samplebuffer_transform_volume_to(f, input->handle_,
output->handle_);
}
static void transform_volume_for_channel(int channel, float volume,
const SampleBuffer *input,
SampleBuffer *output);
SampleBuffer *output)
{
oakcore_samplebuffer_transform_volume_for_channel_to(
channel, volume, input->handle_, output->handle_);
}
void transform_volume_for_sample(size_t sample_index, float volume)
{
oakcore_samplebuffer_transform_volume_for_sample(handle_, sample_index,
volume);
}
void transform_volume_for_sample(size_t sample_index, float volume);
void transform_volume_for_sample_on_channel(size_t sample_index,
int channel, float volume);
int channel, float volume)
{
oakcore_samplebuffer_transform_volume_for_sample_on_channel(
handle_, sample_index, channel, volume);
}
void clamp();
void clamp()
{
oakcore_samplebuffer_clamp(handle_);
}
void silence();
void silence(size_t start_sample, size_t end_sample);
void silence_bytes(size_t start_byte, size_t end_byte);
void silence()
{
oakcore_samplebuffer_silence(handle_);
}
void silence(size_t start_sample, size_t end_sample)
{
oakcore_samplebuffer_silence_range(handle_, start_sample, end_sample);
}
void silence_bytes(size_t start_byte, size_t end_byte)
{
oakcore_samplebuffer_silence_bytes(handle_, start_byte, end_byte);
}
void set(int channel, const float *data, size_t sample_offset,
size_t sample_length);
size_t sample_length)
{
oakcore_samplebuffer_set(handle_, channel, data, sample_offset,
sample_length);
}
void set(int channel, const float *data, size_t sample_length)
{
set(channel, data, 0, sample_length);
}
void fast_set(const SampleBuffer &other, int to, int from = -1);
void fast_set(const SampleBuffer &other, int to, int from = -1)
{
oakcore_samplebuffer_fast_set(handle_, other.handle_, to, from);
}
/**
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
*/
OakSampleBuffer *handle() const
{
return handle_;
}
/**
* @brief Wraps an owned C handle (takes ownership)
*/
static SampleBuffer from_handle(OakSampleBuffer *handle)
{
return SampleBuffer(handle);
}
private:
void clamp_channel(int channel);
explicit SampleBuffer(OakSampleBuffer *handle)
: handle_(handle)
{
}
AudioParams audio_params_;
size_t sample_count_per_channel_;
std::vector<std::vector<float>> data_;
OakSampleBuffer *handle_;
};
}
+123 -43
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -24,113 +24,193 @@
#include <Imath/ImathVec.h>
#include "olive/core/oakcore/bezier.h"
namespace olive::core
{
/**
* @brief Keyframe easing bezier curve value type
*
* Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* opaque OakBezier handle and forwards every call across the C boundary.
* The public API is unchanged from the original implementation.
*/
class Bezier {
public:
Bezier();
Bezier(double x, double y);
Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x,
double cp2_y);
Bezier()
: handle_(oakcore_bezier_create())
{
}
const double &x() const
Bezier(double x, double y)
: handle_(oakcore_bezier_create_xy(x, y))
{
return x_;
}
const double &y() const
Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x,
double cp2_y)
: handle_(
oakcore_bezier_create_full(x, y, cp1_x, cp1_y, cp2_x, cp2_y))
{
return y_;
}
const double &cp1_x() const
Bezier(const Bezier &rhs)
: handle_(oakcore_bezier_copy(rhs.handle_))
{
return cp1_x_;
}
const double &cp1_y() const
Bezier(Bezier &&rhs) noexcept
: handle_(rhs.handle_)
{
return cp1_y_;
rhs.handle_ = nullptr;
}
const double &cp2_x() const
~Bezier()
{
return cp2_x_;
oakcore_bezier_free(handle_);
}
const double &cp2_y() const
Bezier &operator=(const Bezier &rhs)
{
return cp2_y_;
if (this != &rhs) {
oakcore_bezier_free(handle_);
handle_ = oakcore_bezier_copy(rhs.handle_);
}
return *this;
}
Bezier &operator=(Bezier &&rhs) noexcept
{
if (this != &rhs) {
oakcore_bezier_free(handle_);
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
}
return *this;
}
double x() const
{
return oakcore_bezier_x(handle_);
}
double y() const
{
return oakcore_bezier_y(handle_);
}
double cp1_x() const
{
return oakcore_bezier_cp1_x(handle_);
}
double cp1_y() const
{
return oakcore_bezier_cp1_y(handle_);
}
double cp2_x() const
{
return oakcore_bezier_cp2_x(handle_);
}
double cp2_y() const
{
return oakcore_bezier_cp2_y(handle_);
}
Imath::V2d to_vec() const
{
return Imath::V2d(x_, y_);
return Imath::V2d(x(), y());
}
Imath::V2d control_point_1_to_vec() const
{
return Imath::V2d(cp1_x_, cp1_y_);
return Imath::V2d(cp1_x(), cp1_y());
}
Imath::V2d control_point_2_to_vec() const
{
return Imath::V2d(cp2_x_, cp2_y_);
return Imath::V2d(cp2_x(), cp2_y());
}
void set_x(const double &x)
{
x_ = x;
oakcore_bezier_set_x(handle_, x);
}
void set_y(const double &y)
{
y_ = y;
oakcore_bezier_set_y(handle_, y);
}
void set_cp1_x(const double &cp1_x)
{
cp1_x_ = cp1_x;
oakcore_bezier_set_cp1_x(handle_, cp1_x);
}
void set_cp1_y(const double &cp1_y)
{
cp1_y_ = cp1_y;
oakcore_bezier_set_cp1_y(handle_, cp1_y);
}
void set_cp2_x(const double &cp2_x)
{
cp2_x_ = cp2_x;
oakcore_bezier_set_cp2_x(handle_, cp2_x);
}
void set_cp2_y(const double &cp2_y)
{
cp2_y_ = cp2_y;
oakcore_bezier_set_cp2_y(handle_, cp2_y);
}
static double quadratic_xto_t(double x, double a, double b, double c);
static double quadratic_xto_t(double x, double a, double b, double c)
{
return oakcore_bezier_quadratic_xto_t(x, a, b, c);
}
static double quadratic_tto_y(double a, double b, double c, double t);
static double quadratic_tto_y(double a, double b, double c, double t)
{
return oakcore_bezier_quadratic_tto_y(a, b, c, t);
}
static double quadratic_xto_y(double x, const Imath::V2d &a,
const Imath::V2d &b, const Imath::V2d &c)
const Imath::V2d &b, const Imath::V2d &c)
{
return quadratic_tto_y(a.y, b.y, c.y, quadratic_xto_t(x, a.x, b.x, c.x));
}
static double cubic_xto_t(double x, double a, double b, double c, double d);
static double cubic_xto_t(double x, double a, double b, double c, double d)
{
return oakcore_bezier_cubic_xto_t(x, a, b, c, d);
}
static double cubic_tto_y(double a, double b, double c, double d, double t);
static double cubic_tto_y(double a, double b, double c, double d, double t)
{
return oakcore_bezier_cubic_tto_y(a, b, c, d, t);
}
static double cubic_xto_y(double x, const Imath::V2d &a, const Imath::V2d &b,
const Imath::V2d &c, const Imath::V2d &d)
const Imath::V2d &c, const Imath::V2d &d)
{
return cubic_tto_y(a.y, b.y, c.y, d.y, cubic_xto_t(x, a.x, b.x, c.x, d.x));
return cubic_tto_y(a.y, b.y, c.y, d.y,
cubic_xto_t(x, a.x, b.x, c.x, d.x));
}
/**
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
*/
OakBezier *handle() const
{
return handle_;
}
/**
* @brief Wraps an owned C handle (takes ownership)
*/
static Bezier from_handle(OakBezier *handle)
{
return Bezier(handle);
}
private:
static double calculate_t_from_x(bool cubic, double x, double a, double b,
double c, double d);
explicit Bezier(OakBezier *handle)
: handle_(handle)
{
}
double x_;
double y_;
double cp1_x_;
double cp1_y_;
double cp2_x_;
double cp2_y_;
OakBezier *handle_;
};
}
+173 -49
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -22,6 +22,7 @@
#ifndef OAK_LIBOLIVECORE_COLOR_H
#define OAK_LIBOLIVECORE_COLOR_H
#include "olive/core/oakcore/color.h"
#include "../render/pixelformat.h"
namespace olive::core
@@ -29,6 +30,10 @@ namespace olive::core
/**
* @brief High precision 32-bit DataType based RGBA color value
*
* Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* opaque OakColor handle and forwards every call across the C boundary.
* The public API is unchanged from the original implementation.
*/
class Color {
public:
@@ -36,22 +41,55 @@ public:
static constexpr unsigned int rgba = 4;
Color()
: handle_(oakcore_color_create())
{
for (unsigned int i = 0; i < rgba; i++) {
data_[i] = 0.0;
}
}
Color(const DataType &r, const DataType &g, const DataType &b,
const DataType &a = 1.0f)
: handle_(oakcore_color_create_rgba(r, g, b, a))
{
data_[0] = r;
data_[1] = g;
data_[2] = b;
data_[3] = a;
}
Color(const char *data, const PixelFormat &format, int ch_layout);
Color(const char *data, const PixelFormat &format, int ch_layout)
: handle_(oakcore_color_from_data(data, format, ch_layout))
{
}
Color(const Color &rhs)
: handle_(oakcore_color_copy(rhs.handle_))
{
}
Color(Color &&rhs) noexcept
: handle_(rhs.handle_)
{
rhs.handle_ = nullptr;
}
~Color()
{
oakcore_color_free(handle_);
}
Color &operator=(const Color &rhs)
{
if (this != &rhs) {
oakcore_color_free(handle_);
handle_ = oakcore_color_copy(rhs.handle_);
}
return *this;
}
Color &operator=(Color &&rhs) noexcept
{
if (this != &rhs) {
oakcore_color_free(handle_);
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
}
return *this;
}
/**
* @brief Creates a Color struct from hue/saturation/value
@@ -59,78 +97,143 @@ public:
* Hue expects a value between 0.0 and 360.0. Saturation and Value expect a value between 0.0 and 1.0.
*/
static Color from_hsv(const DataType &h, const DataType &s,
const DataType &v);
const DataType &red() const
const DataType &v)
{
return data_[0];
}
const DataType &green() const
{
return data_[1];
}
const DataType &blue() const
{
return data_[2];
}
const DataType &alpha() const
{
return data_[3];
return from_handle(oakcore_color_from_hsv(h, s, v));
}
void to_hsv(DataType *hue, DataType *sat, DataType *val) const;
DataType hsv_hue() const;
DataType hsv_saturation() const;
DataType value() const;
DataType red() const
{
return oakcore_color_red(handle_);
}
DataType green() const
{
return oakcore_color_green(handle_);
}
DataType blue() const
{
return oakcore_color_blue(handle_);
}
DataType alpha() const
{
return oakcore_color_alpha(handle_);
}
void to_hsl(DataType *hue, DataType *sat, DataType *lightness) const;
DataType hsl_hue() const;
DataType hsl_saturation() const;
DataType lightness() const;
void to_hsv(DataType *hue, DataType *sat, DataType *val) const
{
oakcore_color_to_hsv(handle_, hue, sat, val);
}
DataType hsv_hue() const
{
return oakcore_color_hsv_hue(handle_);
}
DataType hsv_saturation() const
{
return oakcore_color_hsv_saturation(handle_);
}
DataType value() const
{
return oakcore_color_value(handle_);
}
void to_hsl(DataType *hue, DataType *sat, DataType *lightness) const
{
oakcore_color_to_hsl(handle_, hue, sat, lightness);
}
DataType hsl_hue() const
{
return oakcore_color_hsl_hue(handle_);
}
DataType hsl_saturation() const
{
return oakcore_color_hsl_saturation(handle_);
}
DataType lightness() const
{
return oakcore_color_lightness(handle_);
}
void set_red(const DataType &red)
{
data_[0] = red;
oakcore_color_set_red(handle_, red);
}
void set_green(const DataType &green)
{
data_[1] = green;
oakcore_color_set_green(handle_, green);
}
void set_blue(const DataType &blue)
{
data_[2] = blue;
oakcore_color_set_blue(handle_, blue);
}
void set_alpha(const DataType &alpha)
{
data_[3] = alpha;
oakcore_color_set_alpha(handle_, alpha);
}
DataType *data()
{
return data_;
return oakcore_color_data(handle_);
}
const DataType *data() const
{
return data_;
return oakcore_color_const_data(handle_);
}
void to_data(char *out, const PixelFormat &format,
unsigned int nb_channels) const;
unsigned int nb_channels) const
{
oakcore_color_to_data(handle_, out, format, int(nb_channels));
}
static Color from_data(const char *in, const PixelFormat &format,
unsigned int nb_channels);
unsigned int nb_channels)
{
return from_handle(oakcore_color_from_data(in, format, int(nb_channels)));
}
// Suuuuper rough luminance value mostly used for UI (determining whether to overlay with black
// or white text)
DataType get_rough_luminance() const;
DataType get_rough_luminance() const
{
return oakcore_color_get_rough_luminance(handle_);
}
// Assignment math operators
Color &operator+=(const Color &rhs);
Color &operator-=(const Color &rhs);
Color &operator+=(const DataType &rhs);
Color &operator-=(const DataType &rhs);
Color &operator*=(const DataType &rhs);
Color &operator/=(const DataType &rhs);
Color &operator+=(const Color &rhs)
{
oakcore_color_add_assign(handle_, rhs.handle_);
return *this;
}
Color &operator-=(const Color &rhs)
{
oakcore_color_sub_assign(handle_, rhs.handle_);
return *this;
}
Color &operator+=(const DataType &rhs)
{
oakcore_color_add_scalar_assign(handle_, rhs);
return *this;
}
Color &operator-=(const DataType &rhs)
{
oakcore_color_sub_scalar_assign(handle_, rhs);
return *this;
}
Color &operator*=(const DataType &rhs)
{
oakcore_color_mul_scalar_assign(handle_, rhs);
return *this;
}
Color &operator/=(const DataType &rhs)
{
oakcore_color_div_scalar_assign(handle_, rhs);
return *this;
}
// Binary math operators
Color operator+(const Color &rhs) const
@@ -175,8 +278,29 @@ public:
return c;
}
/**
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
*/
OakColor *handle() const
{
return handle_;
}
/**
* @brief Wraps an owned C handle (takes ownership)
*/
static Color from_handle(OakColor *handle)
{
return Color(handle);
}
private:
DataType data_[rgba];
explicit Color(OakColor *handle)
: handle_(handle)
{
}
OakColor *handle_;
};
}
+21 -5
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -24,14 +24,20 @@
#include <stdint.h>
#include "olive/core/oakcore/fractionutils.h"
namespace olive::core
{
/**
* @brief Rounding modes for RescaleRnd()
* @brief Rounding modes for rescale_rnd()
*
* Mirrors the FFmpeg AVRounding modes that this codebase used before the
* FFmpeg dependency was removed from core.
*
* Consumer-side wrapper over the liboakcore C ABI: every function forwards
* across the C boundary. The public API is unchanged from the original
* implementation.
*/
enum class FractionRounding {
/**
@@ -55,7 +61,10 @@ enum class FractionRounding {
*
* A zero denominator is preserved (with the numerator set to zero).
*/
void reduce_fraction(int64_t &num, int64_t &den, int64_t max);
inline void reduce_fraction(int64_t &num, int64_t &den, int64_t max)
{
oakcore_fractionutils_reduce_fraction(&num, &den, max);
}
/**
* @brief Compare two fractions
@@ -64,7 +73,10 @@ void reduce_fraction(int64_t &num, int64_t &den, int64_t max);
* 0 if a == b, 1 if a > b, and INT_MIN when the comparison is meaningless
* (degenerate zero-denominator fractions).
*/
int compare_fractions(int an, int ad, int bn, int bd);
inline int compare_fractions(int an, int ad, int bn, int bd)
{
return oakcore_fractionutils_compare_fractions(an, ad, bn, bd);
}
/**
* @brief Rescale `a` by the fraction b/c: returns a * b / c
@@ -73,7 +85,11 @@ int compare_fractions(int an, int ad, int bn, int bd);
* product is computed with 128-bit arithmetic where available so that no
* precision is lost for large timestamps.
*/
int64_t rescale_rnd(int64_t a, int64_t b, int64_t c, FractionRounding rnd);
inline int64_t rescale_rnd(int64_t a, int64_t b, int64_t c, FractionRounding rnd)
{
return oakcore_fractionutils_rescale_rnd(a, b, c,
static_cast<OakFractionRounding>(rnd));
}
}
+215 -51
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -24,131 +24,295 @@
#include <climits>
#include <iostream>
#include <string>
#ifdef USE_OTIO
#include <opentime/rationalTime.h>
#endif
#include "olive/core/oakcore/rational.h"
namespace olive::core
{
/**
* @brief Rational number value type
*
* Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* opaque OakRational handle and forwards every call across the C boundary.
* The public API is unchanged from the original implementation.
*/
class Rational {
public:
Rational(const int &numerator = 0)
: handle_(oakcore_rational_create(numerator))
{
num_ = numerator;
den_ = 1;
}
Rational(const int &numerator, const int &denominator)
: handle_(oakcore_rational_create_nd(numerator, denominator))
{
num_ = numerator;
den_ = denominator;
fix_signs();
reduce();
}
Rational(const Rational &rhs) = default;
Rational(const Rational &rhs)
: handle_(oakcore_rational_copy(rhs.handle_))
{
}
static Rational from_double(const double &flt, bool *ok = nullptr);
static Rational from_string(const std::string &str, bool *ok = nullptr);
Rational(Rational &&rhs) noexcept
: handle_(rhs.handle_)
{
rhs.handle_ = nullptr;
}
~Rational()
{
oakcore_rational_free(handle_);
}
static Rational from_double(const double &flt, bool *ok = nullptr)
{
int c_ok = 0;
Rational r(from_handle(oakcore_rational_from_double(flt, &c_ok)));
if (ok) {
*ok = (c_ok != 0);
}
return r;
}
static Rational from_string(const std::string &str, bool *ok = nullptr)
{
int c_ok = 0;
Rational r(
from_handle(oakcore_rational_from_string(str.c_str(), &c_ok)));
if (ok) {
*ok = (c_ok != 0);
}
return r;
}
static const Rational na_n;
//Assignment Operators
const Rational &operator=(const Rational &rhs);
const Rational &operator+=(const Rational &rhs);
const Rational &operator-=(const Rational &rhs);
const Rational &operator/=(const Rational &rhs);
const Rational &operator*=(const Rational &rhs);
Rational &operator=(const Rational &rhs)
{
if (this != &rhs) {
oakcore_rational_free(handle_);
handle_ = oakcore_rational_copy(rhs.handle_);
}
return *this;
}
Rational &operator=(Rational &&rhs) noexcept
{
if (this != &rhs) {
oakcore_rational_free(handle_);
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
}
return *this;
}
Rational &operator+=(const Rational &rhs)
{
oakcore_rational_add_assign(handle_, rhs.handle_);
return *this;
}
Rational &operator-=(const Rational &rhs)
{
oakcore_rational_sub_assign(handle_, rhs.handle_);
return *this;
}
Rational &operator/=(const Rational &rhs)
{
oakcore_rational_div_assign(handle_, rhs.handle_);
return *this;
}
Rational &operator*=(const Rational &rhs)
{
oakcore_rational_mul_assign(handle_, rhs.handle_);
return *this;
}
//Binary math operators
Rational operator+(const Rational &rhs) const;
Rational operator-(const Rational &rhs) const;
Rational operator/(const Rational &rhs) const;
Rational operator*(const Rational &rhs) const;
Rational operator+(const Rational &rhs) const
{
Rational answer(*this);
answer += rhs;
return answer;
}
Rational operator-(const Rational &rhs) const
{
Rational answer(*this);
answer -= rhs;
return answer;
}
Rational operator/(const Rational &rhs) const
{
Rational answer(*this);
answer /= rhs;
return answer;
}
Rational operator*(const Rational &rhs) const
{
Rational answer(*this);
answer *= rhs;
return answer;
}
//Relational and equality operators
bool operator<(const Rational &rhs) const;
bool operator<=(const Rational &rhs) const;
bool operator>(const Rational &rhs) const;
bool operator>=(const Rational &rhs) const;
bool operator==(const Rational &rhs) const;
bool operator!=(const Rational &rhs) const;
bool operator<(const Rational &rhs) const
{
return oakcore_rational_compare(handle_, rhs.handle_) < 0;
}
bool operator<=(const Rational &rhs) const
{
return oakcore_rational_compare(handle_, rhs.handle_) <= 0;
}
bool operator>(const Rational &rhs) const
{
return oakcore_rational_compare(handle_, rhs.handle_) > 0;
}
bool operator>=(const Rational &rhs) const
{
return oakcore_rational_compare(handle_, rhs.handle_) >= 0;
}
bool operator==(const Rational &rhs) const
{
return oakcore_rational_compare(handle_, rhs.handle_) == 0;
}
bool operator!=(const Rational &rhs) const
{
return !(*this == rhs);
}
//Unary operators
const Rational &operator+() const
Rational operator+() const
{
return *this;
}
Rational operator-() const
{
return Rational(num_, -den_);
return Rational(numerator(), -denominator());
}
bool operator!() const
{
return !num_;
return numerator() == 0;
}
//Function: convert to double
double to_double() const;
double to_double() const
{
return oakcore_rational_to_double(handle_);
}
#ifdef USE_OTIO
static Rational fromRationalTime(const opentime::RationalTime &t)
{
// Is this the best way to do this?
return fromDouble(t.to_seconds());
return from_double(t.to_seconds());
}
// Convert Olive rationals to opentime rationals with the given framerate (defaults to 24)
opentime::RationalTime toRationalTime(double framerate = 24) const;
opentime::RationalTime toRationalTime(double framerate = 24) const
{
// Olive can store rationals as 0/0 which causes errors in OTIO
const int den = denominator();
opentime::RationalTime time(numerator(), den == 0 ? 1 : den);
return time.rescaled_to(framerate);
}
#endif
// Produce "flipped" version
Rational flipped() const;
void flip();
Rational flipped() const
{
return from_handle(oakcore_rational_flipped(handle_));
}
void flip()
{
oakcore_rational_flip(handle_);
}
// Returns whether the Rational is valid but equal to zero or not
//
// A NaN is always a null, but a null is not always a NaN
bool isNull() const
{
return num_ == 0;
return oakcore_rational_is_null(handle_) != 0;
}
// Returns whether this Rational is not a valid number (denominator == 0)
bool isNaN() const
{
return den_ == 0;
return oakcore_rational_is_nan(handle_) != 0;
}
const int &numerator() const
int numerator() const
{
return num_;
}
const int &denominator() const
{
return den_;
return oakcore_rational_numerator(handle_);
}
std::string to_string() const;
int denominator() const
{
return oakcore_rational_denominator(handle_);
}
std::string to_string() const
{
const int size = oakcore_rational_to_string(handle_, nullptr, 0);
std::string s(size_t(size) + 1, '\0');
oakcore_rational_to_string(handle_, s.data(), size + 1);
s.resize(size_t(size));
return s;
}
friend std::ostream &operator<<(std::ostream &out, const Rational &value)
{
out << value.num_ << '/' << value.den_;
out << value.numerator() << '/' << value.denominator();
return out;
}
private:
void fix_signs();
void reduce();
/**
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
*/
OakRational *handle() const
{
return handle_;
}
int num_;
int den_;
/**
* @brief Wraps an owned C handle (takes ownership)
*/
static Rational from_handle(OakRational *handle)
{
return Rational(handle);
}
private:
explicit Rational(OakRational *handle)
: handle_(handle)
{
}
OakRational *handle_;
};
inline const Rational Rational::na_n =
Rational::from_handle(oakcore_rational_create_nan());
#define RATIONAL_MIN Rational(INT_MIN)
#define RATIONAL_MAX Rational(INT_MAX)
+75 -6
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -23,13 +23,24 @@
#define OAK_LIBOLIVECORE_STRINGUTILS_H
#include <algorithm>
#include <cstdarg>
#include <regex>
#include <vector>
#include <string>
#include "olive/core/oakcore/stringutils.h"
namespace olive::core
{
/**
* @brief String utility functions
*
* Consumer-side wrapper over the liboakcore C ABI: every non-inlined call
* is forwarded across the C boundary. The public API is unchanged from the
* original implementation. The class has static members only, so there is
* no opaque handle.
*/
class StringUtils {
public:
/**
@@ -47,7 +58,20 @@ public:
*
* A vector of strings split by the specified delimiter.
*/
static std::vector<std::string> split(const std::string &s, char separator);
static std::vector<std::string> split(const std::string &s, char separator)
{
int count = 0;
char **arr = oakcore_stringutils_split(s.c_str(), separator, &count);
std::vector<std::string> output;
if (arr) {
output.reserve(size_t(count));
for (int i = 0; i < count; i++) {
output.emplace_back(arr[i]);
}
oakcore_stringutils_free_string_array(arr, count);
}
return output;
}
/**
* @brief Splits a string into a list of strings using regular expressions.
@@ -65,10 +89,26 @@ public:
* A vector of strings split wherever the regular expression matched.
*/
static std::vector<std::string> split_regex(const std::string &s,
const std::regex &regex);
const std::regex &regex)
{
// std::regex cannot cross the C ABI, so this overload is implemented
// inline here with only standard library facilities, exactly like the
// original. The C ABI exposes the same functionality as
// oakcore_stringutils_split_regex() taking a pattern string.
std::vector<std::string> output;
std::sregex_token_iterator iter(s.begin(), s.end(), regex, -1);
std::sregex_token_iterator end;
for (; iter != end; iter++) {
output.push_back(*iter);
}
return output;
}
/**
* @brief Convert a string to int using a bool pointer to determine success rather than an exception
* @brief Convert a string to int using a bool pointer to determine
* success rather than an exception
*
* @param s
*
@@ -86,7 +126,15 @@ public:
*
* Either the int parsed from the string, or 0 (with *ok set to false) on parser error.
*/
static int to_int(const std::string &s, int base, bool *ok = nullptr);
static int to_int(const std::string &s, int base, bool *ok = nullptr)
{
int c_ok = 0;
const int x = oakcore_stringutils_to_int(s.c_str(), base, &c_ok);
if (ok) {
*ok = (c_ok != 0);
}
return x;
}
/**
* @brief Overloaded function
@@ -157,7 +205,28 @@ public:
*
* A formatted string in std::string form.
*/
static std::string format(const char *fmt, ...);
static std::string format(const char *fmt, ...)
{
va_list ap1, ap2;
va_start(ap1, fmt);
// Need to duplicate because the va_list is consumed by each C API call
va_copy(ap2, ap1);
const int size = oakcore_stringutils_format_v(nullptr, 0, fmt, ap1);
// Create string with size, adding 1 for the null terminator
std::string r(size_t(size) + 1, '\0');
oakcore_stringutils_format_v(r.data(), size + 1, fmt, ap2);
va_end(ap2);
va_end(ap1);
// Pop null terminator
r.pop_back();
return r;
}
// trim from start (in place)
static inline void ltrim(std::string &s)
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -25,6 +25,9 @@
#include "rational.h"
#include <cstdint>
#include <string>
#include "olive/core/oakcore/timecodefunctions.h"
namespace olive::core
{
@@ -32,11 +35,17 @@ namespace olive::core
/**
* @brief Functions for converting times/timecodes/timestamps
*
* Consumer-side wrapper over the liboakcore C ABI: the class is all-static
* and every call is forwarded across the C boundary (times and timebases
* cross it as borrowed Rational handles). The public API is unchanged from
* the original implementation.
*
* Olive uses the following terminology through its code:
*
* `time` - time in seconds presented in a Rational form
* `timebase` - the base time unit of an audio/video stream in seconds
* `timestamp` - an integer representation of a time in timebase units (in many cases is used like a frame number)
* `timestamp` - an integer representation of a time in timebase units
* (in many cases is used like a frame number)
* `timecode` a user-friendly string representation of a time according to Timecode::Display
*/
class Timecode {
@@ -52,40 +61,104 @@ public:
enum Rounding { k_ceil, k_floor, k_round };
/**
* @brief Convert a timestamp (according to a Rational timebase) to a user-friendly string representation
* @brief Convert a timestamp (according to a Rational timebase) to a
* user-friendly string representation
*/
static std::string time_to_timecode(const Rational &time,
const Rational &timebase,
const Display &display,
bool show_plus_if_positive = false);
bool show_plus_if_positive = false)
{
const OakTimecodeDisplay c_display =
static_cast<OakTimecodeDisplay>(display);
const int c_show_plus = show_plus_if_positive ? 1 : 0;
const int size = oakcore_timecode_time_to_timecode(
time.handle(), timebase.handle(), c_display, c_show_plus, nullptr,
0);
std::string s(size_t(size) + 1, '\0');
oakcore_timecode_time_to_timecode(time.handle(), timebase.handle(),
c_display, c_show_plus, s.data(),
size + 1);
s.resize(size_t(size));
return s;
}
static Rational timecode_to_time(std::string timecode,
const Rational &timebase,
const Display &display,
bool *ok = nullptr);
bool *ok = nullptr)
{
int c_ok = 0;
Rational r = Rational::from_handle(oakcore_timecode_timecode_to_time(
timecode.c_str(), timebase.handle(),
static_cast<OakTimecodeDisplay>(display), &c_ok));
if (ok) {
*ok = (c_ok != 0);
}
return r;
}
static std::string time_to_string(int64_t ms);
static std::string time_to_string(int64_t ms)
{
const int size = oakcore_timecode_time_to_string(ms, nullptr, 0);
std::string s(size_t(size) + 1, '\0');
oakcore_timecode_time_to_string(ms, s.data(), size + 1);
s.resize(size_t(size));
return s;
}
static Rational snap_time_to_timebase(const Rational &time,
const Rational &timebase,
Rounding floor = k_round);
Rounding floor = k_round)
{
return Rational::from_handle(oakcore_timecode_snap_time_to_timebase(
time.handle(), timebase.handle(),
static_cast<OakTimecodeRounding>(floor)));
}
static int64_t time_to_timestamp(const Rational &time,
const Rational &timebase,
Rounding floor = k_round);
Rounding floor = k_round)
{
return oakcore_timecode_time_to_timestamp(
time.handle(), timebase.handle(),
static_cast<OakTimecodeRounding>(floor));
}
static int64_t time_to_timestamp(const double &time,
const Rational &timebase,
Rounding floor = k_round);
Rounding floor = k_round)
{
return oakcore_timecode_time_to_timestamp_d(
time, timebase.handle(), static_cast<OakTimecodeRounding>(floor));
}
static int64_t rescale_timestamp(const int64_t &ts, const Rational &source,
const Rational &dest);
const Rational &dest)
{
return oakcore_timecode_rescale_timestamp(ts, source.handle(),
dest.handle());
}
static int64_t rescale_timestamp_ceil(const int64_t &ts,
const Rational &source,
const Rational &dest);
const Rational &dest)
{
return oakcore_timecode_rescale_timestamp_ceil(ts, source.handle(),
dest.handle());
}
static Rational timestamp_to_time(const int64_t &timestamp,
const Rational &timebase);
const Rational &timebase)
{
return Rational::from_handle(oakcore_timecode_timestamp_to_time(
timestamp, timebase.handle()));
}
static bool timebase_is_drop_frame(const Rational &timebase);
static bool timebase_is_drop_frame(const Rational &timebase)
{
return oakcore_timecode_timebase_is_drop_frame(timebase.handle()) != 0;
}
};
}
+383 -46
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
Modifications Copyright (C) 2025 mikesolar
Modifications 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
@@ -22,67 +22,223 @@
#ifndef OAK_LIBOLIVECORE_TIMERANGE_H
#define OAK_LIBOLIVECORE_TIMERANGE_H
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <list>
#include <vector>
#include "olive/core/oakcore/timerange.h"
#include "rational.h"
#include "timecodefunctions.h"
namespace olive::core
{
/**
* @brief A range of time, normalized so that in() <= out()
*
* Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* opaque OakTimeRange handle and forwards every call across the C boundary.
* The public API is unchanged from the original implementation, except that
* the getters return Rational by value instead of by const reference.
*/
class TimeRange {
public:
TimeRange() = default;
TimeRange(const Rational &in, const Rational &out);
TimeRange(const TimeRange &r)
: TimeRange(r.in(), r.out())
TimeRange()
: handle_(oakcore_timerange_create())
{
}
TimeRange(const Rational &in, const Rational &out)
: handle_(oakcore_timerange_create_io(in.handle(), out.handle()))
{
}
TimeRange(const TimeRange &r)
: handle_(oakcore_timerange_copy(r.handle_))
{
}
TimeRange(TimeRange &&r) noexcept
: handle_(r.handle_)
{
r.handle_ = nullptr;
}
~TimeRange()
{
oakcore_timerange_free(handle_);
}
TimeRange &operator=(const TimeRange &r)
{
set_range(r.in(), r.out());
if (this != &r) {
oakcore_timerange_free(handle_);
handle_ = oakcore_timerange_copy(r.handle_);
}
return *this;
}
const Rational &in() const;
const Rational &out() const;
const Rational &length() const;
TimeRange &operator=(TimeRange &&r) noexcept
{
if (this != &r) {
oakcore_timerange_free(handle_);
handle_ = r.handle_;
r.handle_ = nullptr;
}
return *this;
}
void set_in(const Rational &in);
void set_out(const Rational &out);
void set_range(const Rational &in, const Rational &out);
Rational in() const
{
return Rational::from_handle(oakcore_timerange_in(handle_));
}
bool operator==(const TimeRange &r) const;
bool operator!=(const TimeRange &r) const;
Rational out() const
{
return Rational::from_handle(oakcore_timerange_out(handle_));
}
Rational length() const
{
return Rational::from_handle(oakcore_timerange_length(handle_));
}
void set_in(const Rational &in)
{
oakcore_timerange_set_in(handle_, in.handle());
}
void set_out(const Rational &out)
{
oakcore_timerange_set_out(handle_, out.handle());
}
void set_range(const Rational &in, const Rational &out)
{
oakcore_timerange_set_range(handle_, in.handle(), out.handle());
}
bool operator==(const TimeRange &r) const
{
return oakcore_timerange_equal(handle_, r.handle_) != 0;
}
bool operator!=(const TimeRange &r) const
{
return !(*this == r);
}
bool overlaps_with(const TimeRange &a, bool in_inclusive = true,
bool out_inclusive = true) const;
bool out_inclusive = true) const
{
return oakcore_timerange_overlaps_with(handle_, a.handle_,
in_inclusive ? 1 : 0,
out_inclusive ? 1 : 0) != 0;
}
bool contains(const TimeRange &a, bool in_inclusive = true,
bool out_inclusive = true) const;
bool contains(const Rational &r) const;
bool out_inclusive = true) const
{
return oakcore_timerange_contains_range(handle_, a.handle_,
in_inclusive ? 1 : 0,
out_inclusive ? 1 : 0) != 0;
}
TimeRange combined(const TimeRange &a) const;
static TimeRange combine(const TimeRange &a, const TimeRange &b);
TimeRange intersected(const TimeRange &a) const;
static TimeRange intersect(const TimeRange &a, const TimeRange &b);
bool contains(const Rational &r) const
{
return oakcore_timerange_contains_time(handle_, r.handle()) != 0;
}
TimeRange operator+(const Rational &rhs) const;
TimeRange operator-(const Rational &rhs) const;
TimeRange combined(const TimeRange &a) const
{
return from_handle(oakcore_timerange_combined(handle_, a.handle_));
}
const TimeRange &operator+=(const Rational &rhs);
const TimeRange &operator-=(const Rational &rhs);
static TimeRange combine(const TimeRange &a, const TimeRange &b)
{
return from_handle(oakcore_timerange_combine(a.handle_, b.handle_));
}
std::list<TimeRange> split(const int &chunk_size) const;
TimeRange intersected(const TimeRange &a) const
{
return from_handle(oakcore_timerange_intersected(handle_, a.handle_));
}
static TimeRange intersect(const TimeRange &a, const TimeRange &b)
{
return from_handle(oakcore_timerange_intersect(a.handle_, b.handle_));
}
TimeRange operator+(const Rational &rhs) const
{
return from_handle(oakcore_timerange_add(handle_, rhs.handle()));
}
TimeRange operator-(const Rational &rhs) const
{
return from_handle(oakcore_timerange_sub(handle_, rhs.handle()));
}
const TimeRange &operator+=(const Rational &rhs)
{
oakcore_timerange_add_assign(handle_, rhs.handle());
return *this;
}
const TimeRange &operator-=(const Rational &rhs)
{
oakcore_timerange_sub_assign(handle_, rhs.handle());
return *this;
}
std::list<TimeRange> split(const int &chunk_size) const
{
const int count = oakcore_timerange_split_count(handle_, chunk_size);
std::vector<OakTimeRange *> handles{size_t(count)};
oakcore_timerange_split(handle_, chunk_size, handles.data(), count);
std::list<TimeRange> ranges;
for (OakTimeRange *h : handles) {
ranges.push_back(from_handle(h));
}
return ranges;
}
/**
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
*/
OakTimeRange *handle() const
{
return handle_;
}
/**
* @brief Wraps an owned C handle (takes ownership)
*/
static TimeRange from_handle(OakTimeRange *handle)
{
return TimeRange(handle);
}
private:
void normalize();
explicit TimeRange(OakTimeRange *handle)
: handle_(handle)
{
}
Rational in_;
Rational out_;
Rational length_;
OakTimeRange *handle_;
};
/**
* @brief A list of TimeRanges, kept merged and non-overlapping on insert()
*
* Consumer-side inline reimplementation over the wrapped TimeRange: every
* operation is expressed through the public API of olive::core::TimeRange
* and olive::core::Rational, so a container like this needs no C ABI surface
* of its own. The public API is unchanged from the original implementation.
*/
class TimeRangeList {
public:
TimeRangeList() = default;
@@ -92,11 +248,46 @@ public:
{
}
void insert(const TimeRangeList &list_to_add);
void insert(TimeRange range_to_add);
void insert(const TimeRangeList &list_to_add)
{
for (auto it = list_to_add.cbegin(); it != list_to_add.cend(); it++) {
insert(*it);
}
}
void remove(const TimeRange &remove);
void remove(const TimeRangeList &list);
void insert(TimeRange range_to_add)
{
// See if list contains this range
if (contains(range_to_add)) {
return;
}
// Does not contain range, so we'll almost certainly be adding it in some way
for (auto it = array_.begin(); it != array_.end();) {
const TimeRange &compare = *it;
if (compare.overlaps_with(range_to_add)) {
range_to_add = TimeRange::combine(range_to_add, compare);
it = array_.erase(it);
} else {
it++;
}
}
array_.push_back(range_to_add);
}
void remove(const TimeRange &remove)
{
util_remove(&array_, remove);
}
void remove(const TimeRangeList &list)
{
for (const TimeRange &r : list) {
remove(r);
}
}
template <typename T>
static void util_remove(std::vector<T> *list, const TimeRange &remove)
@@ -138,7 +329,16 @@ public:
}
bool contains(const TimeRange &range, bool in_inclusive = true,
bool out_inclusive = true) const;
bool out_inclusive = true) const
{
for (int i = 0; i < size(); i++) {
if (array_.at(i).contains(range, in_inclusive, out_inclusive)) {
return true;
}
}
return false;
}
bool contains(const Rational &r) const
{
@@ -178,13 +378,62 @@ public:
return array_.size();
}
void shift(const Rational &diff);
void shift(const Rational &diff)
{
for (int i = 0; i < array_.size(); i++) {
array_[i] += diff;
}
}
void trim_in(const Rational &diff);
void trim_in(const Rational &diff)
{
// Re-do list since we want to handle overlaps
TimeRangeList temp = *this;
void trim_out(const Rational &diff);
clear();
TimeRangeList intersects(const TimeRange &range) const;
for (auto it = temp.array_.begin(); it != temp.array_.end(); it++) {
TimeRange &r = *it;
r.set_in(r.in() + diff);
insert(r);
}
}
void trim_out(const Rational &diff)
{
// Re-do list since we want to handle overlaps
TimeRangeList temp = *this;
clear();
for (auto it = temp.array_.begin(); it != temp.array_.end(); it++) {
TimeRange &r = *it;
r.set_out(r.out() + diff);
insert(r);
}
}
TimeRangeList intersects(const TimeRange &range) const
{
TimeRangeList intersect_list;
for (int i = 0; i < size(); i++) {
const TimeRange &compare = array_.at(i);
if (compare.out() <= range.in() || compare.in() >= range.out()) {
// No intersect
continue;
} else {
// Crop the time range to the range and add it to the list
TimeRange cropped(std::max(range.in(), compare.in()),
std::min(range.out(), compare.out()));
intersect_list.insert(cropped);
}
}
return intersect_list;
}
using const_iterator = std::vector<TimeRange>::const_iterator;
@@ -237,17 +486,70 @@ private:
std::vector<TimeRange> array_;
};
/**
* @brief Steps through a TimeRangeList frame by frame at a given timebase
*
* Consumer-side inline reimplementation: frame snapping and timestamp
* conversion go through the wrapped olive::core::Timecode functions, the
* range storage through the wrapped olive::core::TimeRangeList above. The
* public API is unchanged from the original implementation.
*/
class TimeRangeListFrameIterator {
public:
TimeRangeListFrameIterator();
TimeRangeListFrameIterator()
: TimeRangeListFrameIterator(TimeRangeList(), Rational::na_n)
{
}
TimeRangeListFrameIterator(const TimeRangeList &list,
const Rational &timebase);
const Rational &timebase)
: list_(list)
, timebase_(timebase)
, range_index_(-1)
, size_(-1)
, frame_index_(0)
, custom_range_(false)
{
if (!list_.isEmpty() && timebase_.isNull()) {
std::cerr
<< "TimeRangeListFrameIterator created with null timebase but "
"non-empty list, this will likely lead to infinite loops"
<< std::endl;
}
Rational snap(const Rational &r) const;
update_index_if_necessary();
}
bool get_next(Rational *out);
Rational snap(const Rational &r) const
{
return Timecode::snap_time_to_timebase(r, timebase_, Timecode::k_floor);
}
bool has_next() const;
bool get_next(Rational *out)
{
if (!has_next()) {
return false;
}
// Output current value
*out = current_;
// Determine next value by adding timebase
current_ += timebase_;
// If this time is outside the current range, jump to the next one
update_index_if_necessary();
// Increment frame index
frame_index_++;
return true;
}
bool has_next() const
{
return range_index_ < list_.size();
}
std::vector<Rational> to_vector() const
{
@@ -260,7 +562,31 @@ public:
return times;
}
int size();
int size()
{
if (size_ == -1) {
// Size isn't calculated automatically for optimization, so we'll calculate it now
size_ = 0;
for (const TimeRange &range : list_) {
Rational start = snap(range.in());
Rational end = Timecode::snap_time_to_timebase(
range.out(), timebase_, Timecode::k_floor);
if (end == range.out()) {
end -= timebase_;
}
int64_t start_ts =
Timecode::time_to_timestamp(start, timebase_);
int64_t end_ts = Timecode::time_to_timestamp(end, timebase_);
size_ += 1 + (end_ts - start_ts);
}
}
return size_;
}
void reset()
{
@@ -293,7 +619,18 @@ public:
}
private:
void update_index_if_necessary();
void update_index_if_necessary()
{
while (range_index_ < list_.size() &&
(range_index_ == -1 ||
current_ >= list_.at(range_index_).out())) {
range_index_++;
if (range_index_ < list_.size()) {
current_ = snap(list_.at(range_index_).in());
}
}
}
TimeRangeList list_;
-96
View File
@@ -1,96 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC
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_LIBOLIVECORE_VALUE_H
#define OAK_LIBOLIVECORE_VALUE_H
#include <map>
#include <stdint.h>
#include <string>
#include <string.h>
#include <vector>
namespace olive::core
{
/**
* @brief Generic type container
*/
class Value {
public:
enum Type {
/// Null/no data
none,
/// Signed int64
INT,
/// Double-precision float
FLOAT,
/// UTF-8 string
string
};
Value()
{
type_ = none;
}
Value(int64_t v)
{
data_.resize(sizeof(int64_t));
memcpy(data_.data(), &v, sizeof(int64_t));
type_ = INT;
}
Value(double v)
{
data_.resize(sizeof(double));
memcpy(data_.data(), &v, sizeof(int64_t));
type_ = FLOAT;
}
Value(const char *s)
{
size_t sz = strlen(s);
data_.resize(sz);
memcpy(data_.data(), s, sz);
type_ = string;
}
Value(const std::string &s)
{
data_.resize(s.size());
memcpy(data_.data(), s.data(), data_.size());
type_ = string;
}
private:
std::vector<uint8_t> data_;
Type type_;
};
using ValueMap = std::map<std::string, Value>;
}
#endif // OAK_LIBOLIVECORE_VALUE_H