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
+5 -1
View File
@@ -67,10 +67,12 @@ jobs:
# The FFmpeg bridge DLL is built outside app/ and is not reachable # The FFmpeg bridge DLL is built outside app/ and is not reachable
# via PATH, so ntldd cannot discover it from the executables alone # via PATH, so ntldd cannot discover it from the executables alone
cp build/ffmpeg_bridge/bin/ffmpeg_bridge.dll app/packaging/windows/nsis/oak-editor/ cp build/ffmpeg_bridge/bin/ffmpeg_bridge.dll app/packaging/windows/nsis/oak-editor/
# Same for the liboakcore DLL
cp build/core/oakcore.dll app/packaging/windows/nsis/oak-editor/
windeployqt6 app/packaging/windows/nsis/oak-editor/oak-editor.exe windeployqt6 app/packaging/windows/nsis/oak-editor/oak-editor.exe
# Copy all non-Qt MSYS2 DLLs recursively for every binary we ship # Copy all non-Qt MSYS2 DLLs recursively for every binary we ship
cd app/packaging/windows/nsis/oak-editor cd app/packaging/windows/nsis/oak-editor
for binary in oak-editor.exe oak-render-worker.exe oakgl.dll oakvulkan.dll ffmpeg_bridge.dll; do for binary in oak-editor.exe oak-render-worker.exe oakgl.dll oakvulkan.dll ffmpeg_bridge.dll oakcore.dll; do
[ -f "$binary" ] || continue [ -f "$binary" ] || continue
for l in $(ntldd -R "$binary" | grep -E 'mingw64|ucrt64|clang64' | sed 's/^[ \t]*//' | cut -d' ' -f3); do for l in $(ntldd -R "$binary" | grep -E 'mingw64|ucrt64|clang64' | sed 's/^[ \t]*//' | cut -d' ' -f3); do
cp -v "$l" . cp -v "$l" .
@@ -369,9 +371,11 @@ jobs:
ls AppDir/usr/lib/liboakvulkan.so || (echo "Missing liboakvulkan.so" && exit 1) ls AppDir/usr/lib/liboakvulkan.so || (echo "Missing liboakvulkan.so" && exit 1)
ls AppDir/usr/lib/libvulkan.so* || (echo "Missing libvulkan" && exit 1) ls AppDir/usr/lib/libvulkan.so* || (echo "Missing libvulkan" && exit 1)
ls AppDir/usr/lib/libffmpeg_bridge.so || (echo "Missing libffmpeg_bridge.so" && exit 1) ls AppDir/usr/lib/libffmpeg_bridge.so || (echo "Missing libffmpeg_bridge.so" && exit 1)
ls AppDir/usr/lib/liboakcore.so || (echo "Missing liboakcore.so" && exit 1)
# Ensure every shipped binary resolves all of its dependencies # Ensure every shipped binary resolves all of its dependencies
! ldd AppDir/usr/bin/oak-editor | grep "not found" ! ldd AppDir/usr/bin/oak-editor | grep "not found"
! ldd AppDir/usr/bin/oak-render-worker | grep "not found" ! ldd AppDir/usr/bin/oak-render-worker | grep "not found"
! ldd AppDir/usr/lib/liboakcore.so | grep "not found"
! ldd AppDir/usr/lib/liboakgl.so | grep "not found" ! ldd AppDir/usr/lib/liboakgl.so | grep "not found"
! ldd AppDir/usr/lib/liboakvulkan.so | grep "not found" ! ldd AppDir/usr/lib/liboakvulkan.so | grep "not found"
+10 -1
View File
@@ -295,7 +295,8 @@ elseif (APPLE)
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olive-render-worker> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/ COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olive-render-worker> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakgl> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/ COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakgl> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:ffmpeg_bridge> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/ COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:ffmpeg_bridge> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMENT "Copying oak-render-worker, render backends, and ffmpeg_bridge into Oak.app" COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olivecore> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMENT "Copying oak-render-worker, render backends, ffmpeg_bridge and liboakcore into Oak.app"
) )
if (TARGET oakvulkan) if (TARGET oakvulkan)
add_custom_command(TARGET olive-editor POST_BUILD add_custom_command(TARGET olive-editor POST_BUILD
@@ -307,6 +308,14 @@ elseif (UNIX)
install(TARGETS olive-editor RUNTIME DESTINATION bin) install(TARGETS olive-editor RUNTIME DESTINATION bin)
endif () endif ()
if (WIN32)
# Windows has no RPATH: shared libraries must sit next to the executable
add_custom_command(TARGET olive-editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:ffmpeg_bridge> $<TARGET_FILE_DIR:olive-editor>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olivecore> $<TARGET_FILE_DIR:olive-editor>
)
endif ()
# Set link libraries # Set link libraries
target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES}) target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES}) target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES})
+3 -3
View File
@@ -106,17 +106,17 @@ void TimelineWorkArea::save(QXmlStreamWriter *writer) const
QString::fromStdString(this->out().to_string())); QString::fromStdString(this->out().to_string()));
} }
const Rational &TimelineWorkArea::in() const Rational TimelineWorkArea::in() const
{ {
return workarea_range_.in(); return workarea_range_.in();
} }
const Rational &TimelineWorkArea::out() const Rational TimelineWorkArea::out() const
{ {
return workarea_range_.out(); return workarea_range_.out();
} }
const Rational &TimelineWorkArea::length() const Rational TimelineWorkArea::length() const
{ {
return workarea_range_.length(); return workarea_range_.length();
} }
+3 -3
View File
@@ -40,9 +40,9 @@ public:
bool enabled() const; bool enabled() const;
void set_enabled(bool e); void set_enabled(bool e);
const Rational &in() const; Rational in() const;
const Rational &out() const; Rational out() const;
const Rational &length() const; Rational length() const;
const TimeRange &range() const; const TimeRange &range() const;
void set_range(const TimeRange &range); void set_range(const TimeRange &range);
+55 -8
View File
@@ -19,7 +19,7 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
project(libolivecore VERSION 1.0.0 LANGUAGES CXX) project(libolivecore VERSION 1.0.0 LANGUAGES CXX)
option(OLIVECORE_BUILD_TESTS ON) option(OLIVECORE_BUILD_TESTS "Build liboakcore tests" ON)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -36,7 +36,20 @@ if(UNIX AND NOT APPLE AND NOT DEFINED OpenGL_GL_PREFERENCE)
endif() endif()
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
add_library(olivecore # liboakcore is a shared library. It exports a pure C ABI (see
# include/olive/core/oakcore/): every public function is declared with
# OAKCORE_API and all C++ symbols are hidden, so no C++ ABI crosses the
# library boundary. Consumers use the wrapper headers in include/olive/core.
add_library(olivecore SHARED
src/capi/audioparams.cpp
src/capi/bezier.cpp
src/capi/color.cpp
src/capi/fractionutils.cpp
src/capi/rational.cpp
src/capi/samplebuffer.cpp
src/capi/stringutils.cpp
src/capi/timecodefunctions.cpp
src/capi/timerange.cpp
src/render/audioparams.cpp src/render/audioparams.cpp
src/render/samplebuffer.cpp src/render/samplebuffer.cpp
src/util/bezier.cpp src/util/bezier.cpp
@@ -50,7 +63,23 @@ add_library(olivecore
src/util/value.cpp src/util/value.cpp
) )
set_target_properties(olivecore PROPERTIES
OUTPUT_NAME oakcore
POSITION_INDEPENDENT_CODE ON
C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
# OAKCORE_BUILD marks the library side of the export macros (dllexport)
target_compile_definitions(olivecore PRIVATE OAKCORE_BUILD)
# The library builds against its internal implementation headers
# (src/oliveimpl); consumers only ever see the public C API and wrapper
# headers (include/olive/core). oliveimpl must come first so that internal
# sources never pick up a wrapper header by accident.
target_include_directories(olivecore PRIVATE target_include_directories(olivecore PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src/oliveimpl"
"${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core" "${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core"
"${CMAKE_SOURCE_DIR}/third_party/openfx/include/" "${CMAKE_SOURCE_DIR}/third_party/openfx/include/"
) )
@@ -70,8 +99,19 @@ else()
message(" OpenTimelineIO interchange will be disabled.") message(" OpenTimelineIO interchange will be disabled.")
endif() endif()
install(TARGETS olivecore) # liboakcore installs into the platform's standard library directory
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/olive" DESTINATION "${CMAKE_INSTALL_PREFIX}/include") # (/usr/lib, /usr/lib64 or the Debian multiarch path), not a custom one.
# Windows DLLs go next to the executables in bin.
include(GNUInstallDirs)
if (WIN32)
install(TARGETS olivecore
RUNTIME DESTINATION bin
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
else ()
install(TARGETS olivecore
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif ()
if (OLIVECORE_BUILD_TESTS) if (OLIVECORE_BUILD_TESTS)
enable_testing() enable_testing()
@@ -82,13 +122,20 @@ if (OLIVECORE_BUILD_TESTS)
) )
target_link_libraries(${name} PRIVATE olivecore) target_link_libraries(${name} PRIVATE olivecore)
target_include_directories(${name} PRIVATE target_include_directories(${name} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core" "${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core"
) )
add_test(${name} ${name}) add_test(${name} ${name})
endfunction() endfunction()
make_test(rational-test) # Pure C ABI tests for the liboakcore public interface
make_test(stringutils-test) make_test(oakcore_audioparams_test)
make_test(timecode-test) make_test(oakcore_bezier_test)
make_test(timerange-test) make_test(oakcore_color_test)
make_test(oakcore_fractionutils_test)
make_test(oakcore_rational_test)
make_test(oakcore_samplebuffer_test)
make_test(oakcore_stringutils_test)
make_test(oakcore_timecodefunctions_test)
make_test(oakcore_timerange_test)
endif() endif()
+2 -1
View File
@@ -33,6 +33,7 @@
#include "util/tests.h" #include "util/tests.h"
#include "util/timecodefunctions.h" #include "util/timecodefunctions.h"
#include "util/timerange.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 #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 Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -21,11 +21,12 @@
#ifndef OAK_LIBOLIVECORE_AUDIOPARAMS_H #ifndef OAK_LIBOLIVECORE_AUDIOPARAMS_H
#define OAK_LIBOLIVECORE_AUDIOPARAMS_H #define OAK_LIBOLIVECORE_AUDIOPARAMS_H
#include <cstring>
#include <assert.h> #include <cstdint>
#include <vector> #include <vector>
#include "olive/core/oakcore/audioparams.h"
#include "channellayout.h" #include "channellayout.h"
#include "sampleformat.h" #include "sampleformat.h"
#include "../util/rational.h" #include "../util/rational.h"
@@ -36,9 +37,9 @@ namespace olive::core
/** /**
* @brief Audio parameters class managing audio stream configuration * @brief Audio parameters class managing audio stream configuration
* *
* Channel layouts are stored as plain 64-bit masks (see channellayout.h). * Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* Because the mask is a simple value type, AudioParams has value semantics * opaque OakAudioParams handle and forwards every call across the C boundary.
* and can be copied freely. * The public API is unchanged from the original implementation.
*/ */
class AudioParams { class AudioParams {
public: public:
@@ -47,12 +48,8 @@ public:
* sample_rate=0, channel_layout empty, format=INVALID * sample_rate=0, channel_layout empty, format=INVALID
*/ */
AudioParams() AudioParams()
: sample_rate_(0) : handle_(oakcore_audioparams_create_invalid())
, channel_layout_mask_(0)
, channel_count_(0)
, format_(SampleFormat::invalid)
{ {
set_default_footage_parameters();
} }
/** /**
@@ -63,31 +60,61 @@ public:
*/ */
AudioParams(const int &sample_rate, uint64_t channel_layout, AudioParams(const int &sample_rate, uint64_t channel_layout,
const SampleFormat &format) const SampleFormat &format)
: sample_rate_(sample_rate) : handle_(oakcore_audioparams_create(sample_rate, channel_layout, format))
, channel_layout_mask_(channel_layout)
, channel_count_(0)
, format_(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 int sample_rate() const
{ {
return sample_rate_; return oakcore_audioparams_sample_rate(handle_);
} }
void set_sample_rate(int sample_rate) 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) * @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) void set_channel_layout(uint64_t mask)
{ {
channel_layout_mask_ = mask; oakcore_audioparams_set_channel_layout(handle_, mask);
calculate_channel_count();
} }
Rational time_base() const Rational time_base() const
{ {
return timebase_; return Rational::from_handle(oakcore_audioparams_time_base(handle_));
} }
void set_time_base(const Rational &timebase) void set_time_base(const Rational &timebase)
{ {
timebase_ = timebase; oakcore_audioparams_set_time_base(handle_, timebase.handle());
} }
Rational sample_rate_as_time_base() const 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 SampleFormat format() const
{ {
return format_; return SampleFormat(
static_cast<SampleFormat::Format>(oakcore_audioparams_format(handle_)));
} }
void set_format(SampleFormat format) void set_format(SampleFormat format)
{ {
format_ = format; oakcore_audioparams_set_format(handle_, format);
} }
bool enabled() const bool enabled() const
{ {
return enabled_; return oakcore_audioparams_enabled(handle_) != 0;
} }
void set_enabled(bool e) void set_enabled(bool e)
{ {
enabled_ = e; oakcore_audioparams_set_enabled(handle_, e ? 1 : 0);
} }
int stream_index() const int stream_index() const
{ {
return stream_index_; return oakcore_audioparams_stream_index(handle_);
} }
void set_stream_index(int s) void set_stream_index(int s)
{ {
stream_index_ = s; oakcore_audioparams_set_stream_index(handle_, s);
} }
int64_t duration() const int64_t duration() const
{ {
return duration_; return oakcore_audioparams_duration(handle_);
} }
void set_duration(int64_t duration) 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 double &time) const
int64_t time_to_bytes(const Rational &time) const; {
int64_t time_to_bytes_per_channel(const double &time) const; return oakcore_audioparams_time_to_bytes(handle_, time);
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;
bool operator==(const AudioParams &other) const; int64_t time_to_bytes(const Rational &time) const
bool operator!=(const AudioParams &other) 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<uint64_t> k_supported_channel_layouts;
static const std::vector<int> k_supported_sample_rates; 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; return handle_;
stream_index_ = 0;
duration_ = 0;
} }
/** /**
* @brief Updates channel_count_ from the current channel_layout_mask_ * @brief Wraps an owned C handle (takes ownership)
* Called after any channel layout modification.
*/ */
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)
{
}
/** OakAudioParams *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
}; };
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 #endif // OAK_LIBOLIVECORE_AUDIOPARAMS_H
+214 -51
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -22,11 +22,11 @@
#ifndef OAK_LIBOLIVECORE_SAMPLEBUFFER_H #ifndef OAK_LIBOLIVECORE_SAMPLEBUFFER_H
#define OAK_LIBOLIVECORE_SAMPLEBUFFER_H #define OAK_LIBOLIVECORE_SAMPLEBUFFER_H
#include <memory>
#include <vector> #include <vector>
#include "audioparams.h" #include "olive/core/oakcore/samplebuffer.h"
#include "../util/rational.h" #include "olive/core/render/audioparams.h"
#include "olive/core/util/rational.h"
namespace olive::core namespace olive::core
{ {
@@ -34,102 +34,265 @@ namespace olive::core
/** /**
* @brief A buffer of audio samples * @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 * Consumer-side wrapper over the liboakcore C ABI: the object only holds an
* rendering code. This replaces the old system of using QByteArrays (containing packed audio) and while SampleBuffer * opaque OakSampleBuffer handle and forwards every call across the C boundary.
* replaces many of those in the rendering/processing side of things, QByteArrays are currently still in use for * The public API is unchanged from the original implementation, except that
* playback, including reading to and from the cache. * 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 { class SampleBuffer {
public: public:
SampleBuffer(); SampleBuffer()
SampleBuffer(const AudioParams &audio_params, const Rational &length); : handle_(oakcore_samplebuffer_create())
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
{ {
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) 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) float *data(int channel)
{ {
return data_[channel].data(); return oakcore_samplebuffer_data(handle_, channel);
} }
const float *data(int channel) const 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 *> to_raw_ptrs()
{ {
std::vector<float *> r(data_.size()); std::vector<float *> r(static_cast<size_t>(channel_count()));
for (size_t i = 0; i < r.size(); i++) { if (!r.empty()) {
r[i] = data_[i].data(); oakcore_samplebuffer_to_raw_ptrs(handle_, r.data());
} }
return r; return r;
} }
int channel_count() const int channel_count() const
{ {
return data_.size(); return oakcore_samplebuffer_channel_count(handle_);
} }
bool is_allocated() const bool is_allocated() const
{ {
return !data_.empty(); return oakcore_samplebuffer_is_allocated(handle_) != 0;
} }
void allocate();
void destroy();
void reverse(); void allocate()
void speed(double speed); {
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, 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, static void transform_volume_for_channel(int channel, float volume,
const SampleBuffer *input, 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, 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()
void silence(size_t start_sample, size_t end_sample); {
void silence_bytes(size_t start_byte, size_t end_byte); 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, 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) void set(int channel, const float *data, size_t sample_length)
{ {
set(channel, data, 0, 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: private:
void clamp_channel(int channel); explicit SampleBuffer(OakSampleBuffer *handle)
: handle_(handle)
{
}
AudioParams audio_params_; OakSampleBuffer *handle_;
size_t sample_count_per_channel_;
std::vector<std::vector<float>> data_;
}; };
} }
+123 -43
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -24,113 +24,193 @@
#include <Imath/ImathVec.h> #include <Imath/ImathVec.h>
#include "olive/core/oakcore/bezier.h"
namespace olive::core 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 { class Bezier {
public: public:
Bezier(); Bezier()
Bezier(double x, double y); : handle_(oakcore_bezier_create())
Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, {
double cp2_y); }
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 Imath::V2d to_vec() const
{ {
return Imath::V2d(x_, y_); return Imath::V2d(x(), y());
} }
Imath::V2d control_point_1_to_vec() const 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 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) void set_x(const double &x)
{ {
x_ = x; oakcore_bezier_set_x(handle_, x);
} }
void set_y(const double &y) void set_y(const double &y)
{ {
y_ = y; oakcore_bezier_set_y(handle_, y);
} }
void set_cp1_x(const double &cp1_x) 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) 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) 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) 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, 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)); 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, 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: private:
static double calculate_t_from_x(bool cubic, double x, double a, double b, explicit Bezier(OakBezier *handle)
double c, double d); : handle_(handle)
{
}
double x_; OakBezier *handle_;
double y_;
double cp1_x_;
double cp1_y_;
double cp2_x_;
double cp2_y_;
}; };
} }
+173 -49
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@
#ifndef OAK_LIBOLIVECORE_COLOR_H #ifndef OAK_LIBOLIVECORE_COLOR_H
#define OAK_LIBOLIVECORE_COLOR_H #define OAK_LIBOLIVECORE_COLOR_H
#include "olive/core/oakcore/color.h"
#include "../render/pixelformat.h" #include "../render/pixelformat.h"
namespace olive::core namespace olive::core
@@ -29,6 +30,10 @@ namespace olive::core
/** /**
* @brief High precision 32-bit DataType based RGBA color value * @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 { class Color {
public: public:
@@ -36,22 +41,55 @@ public:
static constexpr unsigned int rgba = 4; static constexpr unsigned int rgba = 4;
Color() 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, Color(const DataType &r, const DataType &g, const DataType &b,
const DataType &a = 1.0f) 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 * @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. * 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, static Color from_hsv(const DataType &h, const DataType &s,
const DataType &v); const DataType &v)
const DataType &red() const
{ {
return data_[0]; return from_handle(oakcore_color_from_hsv(h, s, v));
}
const DataType &green() const
{
return data_[1];
}
const DataType &blue() const
{
return data_[2];
}
const DataType &alpha() const
{
return data_[3];
} }
void to_hsv(DataType *hue, DataType *sat, DataType *val) const; DataType red() const
DataType hsv_hue() const; {
DataType hsv_saturation() const; return oakcore_color_red(handle_);
DataType value() const; }
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; void to_hsv(DataType *hue, DataType *sat, DataType *val) const
DataType hsl_hue() const; {
DataType hsl_saturation() const; oakcore_color_to_hsv(handle_, hue, sat, val);
DataType lightness() const; }
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) void set_red(const DataType &red)
{ {
data_[0] = red; oakcore_color_set_red(handle_, red);
} }
void set_green(const DataType &green) void set_green(const DataType &green)
{ {
data_[1] = green; oakcore_color_set_green(handle_, green);
} }
void set_blue(const DataType &blue) void set_blue(const DataType &blue)
{ {
data_[2] = blue; oakcore_color_set_blue(handle_, blue);
} }
void set_alpha(const DataType &alpha) void set_alpha(const DataType &alpha)
{ {
data_[3] = alpha; oakcore_color_set_alpha(handle_, alpha);
} }
DataType *data() DataType *data()
{ {
return data_; return oakcore_color_data(handle_);
} }
const DataType *data() const const DataType *data() const
{ {
return data_; return oakcore_color_const_data(handle_);
} }
void to_data(char *out, const PixelFormat &format, 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, 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 // Suuuuper rough luminance value mostly used for UI (determining whether to overlay with black
// or white text) // or white text)
DataType get_rough_luminance() const; DataType get_rough_luminance() const
{
return oakcore_color_get_rough_luminance(handle_);
}
// Assignment math operators // Assignment math operators
Color &operator+=(const Color &rhs); Color &operator+=(const Color &rhs)
Color &operator-=(const Color &rhs); {
Color &operator+=(const DataType &rhs); oakcore_color_add_assign(handle_, rhs.handle_);
Color &operator-=(const DataType &rhs); return *this;
Color &operator*=(const DataType &rhs); }
Color &operator/=(const DataType &rhs);
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 // Binary math operators
Color operator+(const Color &rhs) const Color operator+(const Color &rhs) const
@@ -175,8 +278,29 @@ public:
return c; 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: 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 Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -24,14 +24,20 @@
#include <stdint.h> #include <stdint.h>
#include "olive/core/oakcore/fractionutils.h"
namespace olive::core 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 * Mirrors the FFmpeg AVRounding modes that this codebase used before the
* FFmpeg dependency was removed from core. * 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 { enum class FractionRounding {
/** /**
@@ -55,7 +61,10 @@ enum class FractionRounding {
* *
* A zero denominator is preserved (with the numerator set to zero). * 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 * @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 * 0 if a == b, 1 if a > b, and INT_MIN when the comparison is meaningless
* (degenerate zero-denominator fractions). * (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 * @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 * product is computed with 128-bit arithmetic where available so that no
* precision is lost for large timestamps. * 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 Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -24,131 +24,295 @@
#include <climits> #include <climits>
#include <iostream> #include <iostream>
#include <string>
#ifdef USE_OTIO #ifdef USE_OTIO
#include <opentime/rationalTime.h> #include <opentime/rationalTime.h>
#endif #endif
#include "olive/core/oakcore/rational.h"
namespace olive::core 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 { class Rational {
public: public:
Rational(const int &numerator = 0) Rational(const int &numerator = 0)
: handle_(oakcore_rational_create(numerator))
{ {
num_ = numerator;
den_ = 1;
} }
Rational(const int &numerator, const int &denominator) 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); Rational(Rational &&rhs) noexcept
static Rational from_string(const std::string &str, bool *ok = nullptr); : 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; static const Rational na_n;
//Assignment Operators //Assignment Operators
const Rational &operator=(const Rational &rhs); Rational &operator=(const Rational &rhs)
const Rational &operator+=(const Rational &rhs); {
const Rational &operator-=(const Rational &rhs); if (this != &rhs) {
const Rational &operator/=(const Rational &rhs); oakcore_rational_free(handle_);
const Rational &operator*=(const Rational &rhs); 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 //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 answer(*this);
Rational operator*(const Rational &rhs) const; 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 //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; return oakcore_rational_compare(handle_, rhs.handle_) < 0;
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 !(*this == rhs);
}
//Unary operators //Unary operators
const Rational &operator+() const Rational operator+() const
{ {
return *this; return *this;
} }
Rational operator-() const Rational operator-() const
{ {
return Rational(num_, -den_); return Rational(numerator(), -denominator());
} }
bool operator!() const bool operator!() const
{ {
return !num_; return numerator() == 0;
} }
//Function: convert to double //Function: convert to double
double to_double() const; double to_double() const
{
return oakcore_rational_to_double(handle_);
}
#ifdef USE_OTIO #ifdef USE_OTIO
static Rational fromRationalTime(const opentime::RationalTime &t) static Rational fromRationalTime(const opentime::RationalTime &t)
{ {
// Is this the best way to do this? return from_double(t.to_seconds());
return fromDouble(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 #endif
// Produce "flipped" version // Produce "flipped" version
Rational flipped() const; Rational flipped() const
void flip(); {
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 // 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 // A NaN is always a null, but a null is not always a NaN
bool isNull() const bool isNull() const
{ {
return num_ == 0; return oakcore_rational_is_null(handle_) != 0;
} }
// Returns whether this Rational is not a valid number (denominator == 0) // Returns whether this Rational is not a valid number (denominator == 0)
bool isNaN() const bool isNaN() const
{ {
return den_ == 0; return oakcore_rational_is_nan(handle_) != 0;
} }
const int &numerator() const int numerator() const
{ {
return num_; return oakcore_rational_numerator(handle_);
}
const int &denominator() const
{
return den_;
} }
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) friend std::ostream &operator<<(std::ostream &out, const Rational &value)
{ {
out << value.num_ << '/' << value.den_; out << value.numerator() << '/' << value.denominator();
return out; return out;
} }
private: /**
void fix_signs(); * @brief The wrapped C handle, for cross-type wrappers and direct C API use
void reduce(); */
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_MIN Rational(INT_MIN)
#define RATIONAL_MAX Rational(INT_MAX) #define RATIONAL_MAX Rational(INT_MAX)
+75 -6
View File
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -23,13 +23,24 @@
#define OAK_LIBOLIVECORE_STRINGUTILS_H #define OAK_LIBOLIVECORE_STRINGUTILS_H
#include <algorithm> #include <algorithm>
#include <cstdarg>
#include <regex> #include <regex>
#include <vector> #include <vector>
#include <string> #include <string>
#include "olive/core/oakcore/stringutils.h"
namespace olive::core 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 { class StringUtils {
public: public:
/** /**
@@ -47,7 +58,20 @@ public:
* *
* A vector of strings split by the specified delimiter. * 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. * @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. * A vector of strings split wherever the regular expression matched.
*/ */
static std::vector<std::string> split_regex(const std::string &s, 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 * @param s
* *
@@ -86,7 +126,15 @@ public:
* *
* Either the int parsed from the string, or 0 (with *ok set to false) on parser error. * 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 * @brief Overloaded function
@@ -157,7 +205,28 @@ public:
* *
* A formatted string in std::string form. * 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) // trim from start (in place)
static inline void ltrim(std::string &s) static inline void ltrim(std::string &s)
@@ -2,7 +2,7 @@
Olive - Non-Linear Video Editor Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -25,6 +25,9 @@
#include "rational.h" #include "rational.h"
#include <cstdint> #include <cstdint>
#include <string>
#include "olive/core/oakcore/timecodefunctions.h"
namespace olive::core namespace olive::core
{ {
@@ -32,11 +35,17 @@ namespace olive::core
/** /**
* @brief Functions for converting times/timecodes/timestamps * @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: * Olive uses the following terminology through its code:
* *
* `time` - time in seconds presented in a Rational form * `time` - time in seconds presented in a Rational form
* `timebase` - the base time unit of an audio/video stream in seconds * `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 * `timecode` a user-friendly string representation of a time according to Timecode::Display
*/ */
class Timecode { class Timecode {
@@ -52,40 +61,104 @@ public:
enum Rounding { k_ceil, k_floor, k_round }; 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, static std::string time_to_timecode(const Rational &time,
const Rational &timebase, const Rational &timebase,
const Display &display, 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, static Rational timecode_to_time(std::string timecode,
const Rational &timebase, const Rational &timebase,
const Display &display, 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, static Rational snap_time_to_timebase(const Rational &time,
const Rational &timebase, 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, static int64_t time_to_timestamp(const Rational &time,
const Rational &timebase, 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, static int64_t time_to_timestamp(const double &time,
const Rational &timebase, 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, 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, static int64_t rescale_timestamp_ceil(const int64_t &ts,
const Rational &source, 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, 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 Olive - Non-Linear Video Editor
Copyright (C) 2023 Olive Studios LLC 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 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 it under the terms of the GNU General Public License as published by
@@ -22,67 +22,223 @@
#ifndef OAK_LIBOLIVECORE_TIMERANGE_H #ifndef OAK_LIBOLIVECORE_TIMERANGE_H
#define OAK_LIBOLIVECORE_TIMERANGE_H #define OAK_LIBOLIVECORE_TIMERANGE_H
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <list> #include <list>
#include <vector> #include <vector>
#include "olive/core/oakcore/timerange.h"
#include "rational.h" #include "rational.h"
#include "timecodefunctions.h"
namespace olive::core 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 { class TimeRange {
public: public:
TimeRange() = default; TimeRange()
TimeRange(const Rational &in, const Rational &out); : handle_(oakcore_timerange_create())
TimeRange(const TimeRange &r)
: TimeRange(r.in(), r.out())
{ {
} }
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) 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; return *this;
} }
const Rational &in() const; TimeRange &operator=(TimeRange &&r) noexcept
const Rational &out() const; {
const Rational &length() const; if (this != &r) {
oakcore_timerange_free(handle_);
handle_ = r.handle_;
r.handle_ = nullptr;
}
return *this;
}
void set_in(const Rational &in); Rational in() const
void set_out(const Rational &out); {
void set_range(const Rational &in, const Rational &out); return Rational::from_handle(oakcore_timerange_in(handle_));
}
bool operator==(const TimeRange &r) const; Rational out() const
bool operator!=(const TimeRange &r) 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 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 contains(const TimeRange &a, bool in_inclusive = true,
bool out_inclusive = true) const; bool out_inclusive = true) const
bool contains(const Rational &r) const; {
return oakcore_timerange_contains_range(handle_, a.handle_,
in_inclusive ? 1 : 0,
out_inclusive ? 1 : 0) != 0;
}
TimeRange combined(const TimeRange &a) const; bool contains(const Rational &r) const
static TimeRange combine(const TimeRange &a, const TimeRange &b); {
TimeRange intersected(const TimeRange &a) const; return oakcore_timerange_contains_time(handle_, r.handle()) != 0;
static TimeRange intersect(const TimeRange &a, const TimeRange &b); }
TimeRange operator+(const Rational &rhs) const; TimeRange combined(const TimeRange &a) const
TimeRange operator-(const Rational &rhs) const; {
return from_handle(oakcore_timerange_combined(handle_, a.handle_));
}
const TimeRange &operator+=(const Rational &rhs); static TimeRange combine(const TimeRange &a, const TimeRange &b)
const TimeRange &operator-=(const Rational &rhs); {
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: private:
void normalize(); explicit TimeRange(OakTimeRange *handle)
: handle_(handle)
{
}
Rational in_; OakTimeRange *handle_;
Rational out_;
Rational length_;
}; };
/**
* @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 { class TimeRangeList {
public: public:
TimeRangeList() = default; TimeRangeList() = default;
@@ -92,11 +248,46 @@ public:
{ {
} }
void insert(const TimeRangeList &list_to_add); void insert(const TimeRangeList &list_to_add)
void insert(TimeRange range_to_add); {
for (auto it = list_to_add.cbegin(); it != list_to_add.cend(); it++) {
insert(*it);
}
}
void remove(const TimeRange &remove); void insert(TimeRange range_to_add)
void remove(const TimeRangeList &list); {
// 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> template <typename T>
static void util_remove(std::vector<T> *list, const TimeRange &remove) 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 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 bool contains(const Rational &r) const
{ {
@@ -178,13 +378,62 @@ public:
return array_.size(); 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; using const_iterator = std::vector<TimeRange>::const_iterator;
@@ -237,17 +486,70 @@ private:
std::vector<TimeRange> array_; 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 { class TimeRangeListFrameIterator {
public: public:
TimeRangeListFrameIterator(); TimeRangeListFrameIterator()
: TimeRangeListFrameIterator(TimeRangeList(), Rational::na_n)
{
}
TimeRangeListFrameIterator(const TimeRangeList &list, 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 std::vector<Rational> to_vector() const
{ {
@@ -260,7 +562,31 @@ public:
return times; 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() void reset()
{ {
@@ -293,7 +619,18 @@ public:
} }
private: 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_; TimeRangeList list_;
+315
View File
@@ -0,0 +1,315 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oakcore/audioparams.h"
#include "render/audioparams.h"
namespace
{
olive::core::internal::AudioParams *impl(OakAudioParams *h)
{
return reinterpret_cast<olive::core::internal::AudioParams *>(h);
}
const olive::core::internal::AudioParams *impl(const OakAudioParams *h)
{
return reinterpret_cast<const olive::core::internal::AudioParams *>(h);
}
OakAudioParams *wrap(olive::core::internal::AudioParams *p)
{
return reinterpret_cast<OakAudioParams *>(p);
}
const olive::core::internal::Rational *impl(const OakRational *h)
{
return reinterpret_cast<const olive::core::internal::Rational *>(h);
}
OakRational *wrap(olive::core::internal::Rational *r)
{
return reinterpret_cast<OakRational *>(r);
}
olive::core::SampleFormat to_format(int f)
{
return olive::core::SampleFormat(
static_cast<olive::core::SampleFormat::Format>(f));
}
// The supported channel layouts / sample rates as constant data. These
// mirror AudioParams::k_supported_channel_layouts /
// k_supported_sample_rates (src/render/audioparams.cpp) and must be kept in
// sync with them. The C API serves these instead of reading the
// implementation's std::vector statics directly so that the functions are
// safe to call during a consumer's static initialization even when
// liboakcore is linked statically (cross-TU static init order is
// unspecified; the implementation's vectors may not be constructed yet).
constexpr uint64_t k_supported_channel_layouts[] = {
olive::core::k_channel_layout_mono,
olive::core::k_channel_layout_stereo,
olive::core::k_channel_layout2_1,
olive::core::k_channel_layout5_point1,
olive::core::k_channel_layout7_point1,
};
constexpr int k_supported_sample_rates[] = {
8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000,
};
template <typename T, size_t N>
T at_or_zero(const T (&arr)[N], int index)
{
if (index < 0 || size_t(index) >= N) {
return 0;
}
return arr[index];
}
} // namespace
extern "C"
{
OakAudioParams *oakcore_audioparams_create(int sample_rate,
uint64_t channel_layout, int format)
{
return wrap(new olive::core::internal::AudioParams(
sample_rate, channel_layout, to_format(format)));
}
OakAudioParams *oakcore_audioparams_create_invalid(void)
{
return wrap(new olive::core::internal::AudioParams());
}
OakAudioParams *oakcore_audioparams_copy(const OakAudioParams *self)
{
return wrap(new olive::core::internal::AudioParams(*impl(self)));
}
void oakcore_audioparams_free(OakAudioParams *self)
{
delete impl(self);
}
int oakcore_audioparams_sample_rate(const OakAudioParams *self)
{
return impl(self)->sample_rate();
}
void oakcore_audioparams_set_sample_rate(OakAudioParams *self, int sample_rate)
{
impl(self)->set_sample_rate(sample_rate);
}
uint64_t oakcore_audioparams_channel_layout(const OakAudioParams *self)
{
return impl(self)->channel_layout();
}
void oakcore_audioparams_set_channel_layout(OakAudioParams *self, uint64_t mask)
{
impl(self)->set_channel_layout(mask);
}
OakRational *oakcore_audioparams_time_base(const OakAudioParams *self)
{
return wrap(new olive::core::internal::Rational(impl(self)->time_base()));
}
void oakcore_audioparams_set_time_base(OakAudioParams *self,
const OakRational *timebase)
{
impl(self)->set_time_base(*impl(timebase));
}
OakRational *oakcore_audioparams_sample_rate_as_time_base(
const OakAudioParams *self)
{
return wrap(
new olive::core::internal::Rational(impl(self)->sample_rate_as_time_base()));
}
int oakcore_audioparams_format(const OakAudioParams *self)
{
return int(impl(self)->format());
}
void oakcore_audioparams_set_format(OakAudioParams *self, int format)
{
impl(self)->set_format(to_format(format));
}
int oakcore_audioparams_enabled(const OakAudioParams *self)
{
return impl(self)->enabled() ? 1 : 0;
}
void oakcore_audioparams_set_enabled(OakAudioParams *self, int enabled)
{
impl(self)->set_enabled(enabled != 0);
}
int oakcore_audioparams_stream_index(const OakAudioParams *self)
{
return impl(self)->stream_index();
}
void oakcore_audioparams_set_stream_index(OakAudioParams *self, int stream_index)
{
impl(self)->set_stream_index(stream_index);
}
int64_t oakcore_audioparams_duration(const OakAudioParams *self)
{
return impl(self)->duration();
}
void oakcore_audioparams_set_duration(OakAudioParams *self, int64_t duration)
{
impl(self)->set_duration(duration);
}
int64_t oakcore_audioparams_time_to_bytes(const OakAudioParams *self, double time)
{
return impl(self)->time_to_bytes(time);
}
int64_t oakcore_audioparams_time_to_bytes_rational(const OakAudioParams *self,
const OakRational *time)
{
return impl(self)->time_to_bytes(*impl(time));
}
int64_t oakcore_audioparams_time_to_bytes_per_channel(const OakAudioParams *self,
double time)
{
return impl(self)->time_to_bytes_per_channel(time);
}
int64_t oakcore_audioparams_time_to_bytes_per_channel_rational(
const OakAudioParams *self, const OakRational *time)
{
return impl(self)->time_to_bytes_per_channel(*impl(time));
}
int64_t oakcore_audioparams_time_to_samples(const OakAudioParams *self,
double time)
{
return impl(self)->time_to_samples(time);
}
int64_t oakcore_audioparams_time_to_samples_rational(const OakAudioParams *self,
const OakRational *time)
{
return impl(self)->time_to_samples(*impl(time));
}
int64_t oakcore_audioparams_samples_to_bytes(const OakAudioParams *self,
int64_t samples)
{
return impl(self)->samples_to_bytes(samples);
}
int64_t oakcore_audioparams_samples_to_bytes_per_channel(
const OakAudioParams *self, int64_t samples)
{
return impl(self)->samples_to_bytes_per_channel(samples);
}
OakRational *oakcore_audioparams_samples_to_time(const OakAudioParams *self,
int64_t samples)
{
return wrap(
new olive::core::internal::Rational(impl(self)->samples_to_time(samples)));
}
int64_t oakcore_audioparams_bytes_to_samples(const OakAudioParams *self,
int64_t bytes)
{
return impl(self)->bytes_to_samples(bytes);
}
OakRational *oakcore_audioparams_bytes_to_time(const OakAudioParams *self,
int64_t bytes)
{
return wrap(
new olive::core::internal::Rational(impl(self)->bytes_to_time(bytes)));
}
OakRational *oakcore_audioparams_bytes_per_channel_to_time(
const OakAudioParams *self, int64_t bytes)
{
return wrap(new olive::core::internal::Rational(
impl(self)->bytes_per_channel_to_time(bytes)));
}
int oakcore_audioparams_channel_count(const OakAudioParams *self)
{
return impl(self)->channel_count();
}
int oakcore_audioparams_bytes_per_sample_per_channel(const OakAudioParams *self)
{
return impl(self)->bytes_per_sample_per_channel();
}
int oakcore_audioparams_bits_per_sample(const OakAudioParams *self)
{
return impl(self)->bits_per_sample();
}
int oakcore_audioparams_is_valid(const OakAudioParams *self)
{
return impl(self)->is_valid() ? 1 : 0;
}
int oakcore_audioparams_equals(const OakAudioParams *self,
const OakAudioParams *other)
{
return (*impl(self) == *impl(other)) ? 1 : 0;
}
int oakcore_audioparams_supported_channel_layout_count(void)
{
return int(sizeof(k_supported_channel_layouts) /
sizeof(k_supported_channel_layouts[0]));
}
uint64_t oakcore_audioparams_supported_channel_layout_at(int index)
{
return at_or_zero(k_supported_channel_layouts, index);
}
int oakcore_audioparams_supported_sample_rate_count(void)
{
return int(sizeof(k_supported_sample_rates) /
sizeof(k_supported_sample_rates[0]));
}
int oakcore_audioparams_supported_sample_rate_at(int index)
{
return at_or_zero(k_supported_sample_rates, index);
}
} // extern "C"
+157
View File
@@ -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/>.
***/
#include "oakcore/bezier.h"
#include "util/bezier.h"
namespace
{
olive::core::internal::Bezier *impl(OakBezier *h)
{
return reinterpret_cast<olive::core::internal::Bezier *>(h);
}
const olive::core::internal::Bezier *impl(const OakBezier *h)
{
return reinterpret_cast<const olive::core::internal::Bezier *>(h);
}
OakBezier *wrap(olive::core::internal::Bezier *b)
{
return reinterpret_cast<OakBezier *>(b);
}
} // namespace
extern "C"
{
OakBezier *oakcore_bezier_create(void)
{
return wrap(new olive::core::internal::Bezier());
}
OakBezier *oakcore_bezier_create_xy(double x, double y)
{
return wrap(new olive::core::internal::Bezier(x, y));
}
OakBezier *oakcore_bezier_create_full(double x, double y, double cp1_x,
double cp1_y, double cp2_x, double cp2_y)
{
return wrap(new olive::core::internal::Bezier(x, y, cp1_x, cp1_y, cp2_x,
cp2_y));
}
OakBezier *oakcore_bezier_copy(const OakBezier *self)
{
return wrap(new olive::core::internal::Bezier(*impl(self)));
}
void oakcore_bezier_free(OakBezier *self)
{
delete impl(self);
}
double oakcore_bezier_x(const OakBezier *self)
{
return impl(self)->x();
}
double oakcore_bezier_y(const OakBezier *self)
{
return impl(self)->y();
}
double oakcore_bezier_cp1_x(const OakBezier *self)
{
return impl(self)->cp1_x();
}
double oakcore_bezier_cp1_y(const OakBezier *self)
{
return impl(self)->cp1_y();
}
double oakcore_bezier_cp2_x(const OakBezier *self)
{
return impl(self)->cp2_x();
}
double oakcore_bezier_cp2_y(const OakBezier *self)
{
return impl(self)->cp2_y();
}
void oakcore_bezier_set_x(OakBezier *self, double x)
{
impl(self)->set_x(x);
}
void oakcore_bezier_set_y(OakBezier *self, double y)
{
impl(self)->set_y(y);
}
void oakcore_bezier_set_cp1_x(OakBezier *self, double cp1_x)
{
impl(self)->set_cp1_x(cp1_x);
}
void oakcore_bezier_set_cp1_y(OakBezier *self, double cp1_y)
{
impl(self)->set_cp1_y(cp1_y);
}
void oakcore_bezier_set_cp2_x(OakBezier *self, double cp2_x)
{
impl(self)->set_cp2_x(cp2_x);
}
void oakcore_bezier_set_cp2_y(OakBezier *self, double cp2_y)
{
impl(self)->set_cp2_y(cp2_y);
}
double oakcore_bezier_quadratic_xto_t(double x, double a, double b, double c)
{
return olive::core::internal::Bezier::quadratic_xto_t(x, a, b, c);
}
double oakcore_bezier_quadratic_tto_y(double a, double b, double c, double t)
{
return olive::core::internal::Bezier::quadratic_tto_y(a, b, c, t);
}
double oakcore_bezier_cubic_xto_t(double x, double a, double b, double c,
double d)
{
return olive::core::internal::Bezier::cubic_xto_t(x, a, b, c, d);
}
double oakcore_bezier_cubic_tto_y(double a, double b, double c, double d,
double t)
{
return olive::core::internal::Bezier::cubic_tto_y(a, b, c, d, t);
}
} // extern "C"
+220
View File
@@ -0,0 +1,220 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oakcore/color.h"
#include "util/color.h"
namespace
{
olive::core::internal::Color *impl(OakColor *h)
{
return reinterpret_cast<olive::core::internal::Color *>(h);
}
const olive::core::internal::Color *impl(const OakColor *h)
{
return reinterpret_cast<const olive::core::internal::Color *>(h);
}
OakColor *wrap(olive::core::internal::Color *c)
{
return reinterpret_cast<OakColor *>(c);
}
olive::core::PixelFormat to_format(int f)
{
return olive::core::PixelFormat(
static_cast<olive::core::PixelFormat::Format>(f));
}
} // namespace
extern "C"
{
OakColor *oakcore_color_create(void)
{
return wrap(new olive::core::internal::Color());
}
OakColor *oakcore_color_create_rgba(float r, float g, float b, float a)
{
return wrap(new olive::core::internal::Color(r, g, b, a));
}
OakColor *oakcore_color_copy(const OakColor *self)
{
return wrap(new olive::core::internal::Color(*impl(self)));
}
void oakcore_color_free(OakColor *self)
{
delete impl(self);
}
OakColor *oakcore_color_from_hsv(float h, float s, float v)
{
return wrap(
new olive::core::internal::Color(olive::core::internal::Color::from_hsv(h, s, v)));
}
OakColor *oakcore_color_from_data(const char *data, int format, int nb_channels)
{
return wrap(new olive::core::internal::Color(
olive::core::internal::Color::from_data(data, to_format(format),
static_cast<unsigned int>(nb_channels))));
}
float oakcore_color_red(const OakColor *self)
{
return impl(self)->red();
}
float oakcore_color_green(const OakColor *self)
{
return impl(self)->green();
}
float oakcore_color_blue(const OakColor *self)
{
return impl(self)->blue();
}
float oakcore_color_alpha(const OakColor *self)
{
return impl(self)->alpha();
}
void oakcore_color_set_red(OakColor *self, float red)
{
impl(self)->set_red(red);
}
void oakcore_color_set_green(OakColor *self, float green)
{
impl(self)->set_green(green);
}
void oakcore_color_set_blue(OakColor *self, float blue)
{
impl(self)->set_blue(blue);
}
void oakcore_color_set_alpha(OakColor *self, float alpha)
{
impl(self)->set_alpha(alpha);
}
void oakcore_color_to_hsv(const OakColor *self, float *hue, float *sat, float *val)
{
impl(self)->to_hsv(hue, sat, val);
}
float oakcore_color_hsv_hue(const OakColor *self)
{
return impl(self)->hsv_hue();
}
float oakcore_color_hsv_saturation(const OakColor *self)
{
return impl(self)->hsv_saturation();
}
float oakcore_color_value(const OakColor *self)
{
return impl(self)->value();
}
void oakcore_color_to_hsl(const OakColor *self, float *hue, float *sat,
float *lightness)
{
impl(self)->to_hsl(hue, sat, lightness);
}
float oakcore_color_hsl_hue(const OakColor *self)
{
return impl(self)->hsl_hue();
}
float oakcore_color_hsl_saturation(const OakColor *self)
{
return impl(self)->hsl_saturation();
}
float oakcore_color_lightness(const OakColor *self)
{
return impl(self)->lightness();
}
float *oakcore_color_data(OakColor *self)
{
return impl(self)->data();
}
const float *oakcore_color_const_data(const OakColor *self)
{
return impl(self)->data();
}
void oakcore_color_to_data(const OakColor *self, char *out, int format,
int nb_channels)
{
impl(self)->to_data(out, to_format(format),
static_cast<unsigned int>(nb_channels));
}
float oakcore_color_get_rough_luminance(const OakColor *self)
{
return impl(self)->get_rough_luminance();
}
void oakcore_color_add_assign(OakColor *self, const OakColor *other)
{
*impl(self) += *impl(other);
}
void oakcore_color_sub_assign(OakColor *self, const OakColor *other)
{
*impl(self) -= *impl(other);
}
void oakcore_color_add_scalar_assign(OakColor *self, float value)
{
*impl(self) += value;
}
void oakcore_color_sub_scalar_assign(OakColor *self, float value)
{
*impl(self) -= value;
}
void oakcore_color_mul_scalar_assign(OakColor *self, float value)
{
*impl(self) *= value;
}
void oakcore_color_div_scalar_assign(OakColor *self, float value)
{
*impl(self) /= value;
}
} // extern "C"
+53
View File
@@ -0,0 +1,53 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oakcore/fractionutils.h"
#include "util/fractionutils.h"
namespace
{
olive::core::internal::FractionRounding impl(OakFractionRounding rnd)
{
return static_cast<olive::core::internal::FractionRounding>(rnd);
}
} // namespace
extern "C"
{
void oakcore_fractionutils_reduce_fraction(int64_t *num, int64_t *den, int64_t max)
{
olive::core::internal::reduce_fraction(*num, *den, max);
}
int oakcore_fractionutils_compare_fractions(int an, int ad, int bn, int bd)
{
return olive::core::internal::compare_fractions(an, ad, bn, bd);
}
int64_t oakcore_fractionutils_rescale_rnd(int64_t a, int64_t b, int64_t c, OakFractionRounding rnd)
{
return olive::core::internal::rescale_rnd(a, b, c, impl(rnd));
}
} // extern "C"
+172
View File
@@ -0,0 +1,172 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oakcore/rational.h"
#include <stdio.h>
#include "util/rational.h"
namespace
{
olive::core::internal::Rational *impl(OakRational *h)
{
return reinterpret_cast<olive::core::internal::Rational *>(h);
}
const olive::core::internal::Rational *impl(const OakRational *h)
{
return reinterpret_cast<const olive::core::internal::Rational *>(h);
}
OakRational *wrap(olive::core::internal::Rational *r)
{
return reinterpret_cast<OakRational *>(r);
}
} // namespace
extern "C"
{
OakRational *oakcore_rational_create(int numerator)
{
return wrap(new olive::core::internal::Rational(numerator));
}
OakRational *oakcore_rational_create_nd(int numerator, int denominator)
{
return wrap(new olive::core::internal::Rational(numerator, denominator));
}
OakRational *oakcore_rational_create_nan(void)
{
return wrap(new olive::core::internal::Rational(olive::core::internal::Rational::na_n));
}
OakRational *oakcore_rational_copy(const OakRational *self)
{
return wrap(new olive::core::internal::Rational(*impl(self)));
}
void oakcore_rational_free(OakRational *self)
{
delete impl(self);
}
int oakcore_rational_numerator(const OakRational *self)
{
return impl(self)->numerator();
}
int oakcore_rational_denominator(const OakRational *self)
{
return impl(self)->denominator();
}
double oakcore_rational_to_double(const OakRational *self)
{
return impl(self)->to_double();
}
int oakcore_rational_to_string(const OakRational *self, char *buf, int buf_size)
{
const std::string s = impl(self)->to_string();
if (buf && buf_size > 0) {
snprintf(buf, size_t(buf_size), "%s", s.c_str());
}
return int(s.size());
}
OakRational *oakcore_rational_from_double(double value, int *ok)
{
bool b = false;
olive::core::internal::Rational *r =
new olive::core::internal::Rational(olive::core::internal::Rational::from_double(value, &b));
if (ok) {
*ok = b ? 1 : 0;
}
return wrap(r);
}
OakRational *oakcore_rational_from_string(const char *str, int *ok)
{
bool b = false;
olive::core::internal::Rational *r = new olive::core::internal::Rational(
olive::core::internal::Rational::from_string(str ? str : "", &b));
if (ok) {
*ok = b ? 1 : 0;
}
return wrap(r);
}
int oakcore_rational_is_null(const OakRational *self)
{
return impl(self)->isNull() ? 1 : 0;
}
int oakcore_rational_is_nan(const OakRational *self)
{
return impl(self)->isNaN() ? 1 : 0;
}
OakRational *oakcore_rational_flipped(const OakRational *self)
{
return wrap(new olive::core::internal::Rational(impl(self)->flipped()));
}
void oakcore_rational_flip(OakRational *self)
{
impl(self)->flip();
}
void oakcore_rational_add_assign(OakRational *self, const OakRational *other)
{
*impl(self) += *impl(other);
}
void oakcore_rational_sub_assign(OakRational *self, const OakRational *other)
{
*impl(self) -= *impl(other);
}
void oakcore_rational_mul_assign(OakRational *self, const OakRational *other)
{
*impl(self) *= *impl(other);
}
void oakcore_rational_div_assign(OakRational *self, const OakRational *other)
{
*impl(self) /= *impl(other);
}
int oakcore_rational_compare(const OakRational *self, const OakRational *other)
{
if (*impl(self) < *impl(other)) {
return -1;
}
if (*impl(other) < *impl(self)) {
return 1;
}
return 0;
}
} // extern "C"
+272
View File
@@ -0,0 +1,272 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oakcore/samplebuffer.h"
#include <algorithm>
#include <vector>
#include "render/audioparams.h"
#include "render/samplebuffer.h"
#include "util/rational.h"
namespace
{
olive::core::internal::SampleBuffer *impl(OakSampleBuffer *h)
{
return reinterpret_cast<olive::core::internal::SampleBuffer *>(h);
}
const olive::core::internal::SampleBuffer *impl(const OakSampleBuffer *h)
{
return reinterpret_cast<const olive::core::internal::SampleBuffer *>(h);
}
OakSampleBuffer *wrap(olive::core::internal::SampleBuffer *b)
{
return reinterpret_cast<OakSampleBuffer *>(b);
}
const olive::core::internal::AudioParams *apimpl(const OakAudioParams *h)
{
return reinterpret_cast<const olive::core::internal::AudioParams *>(h);
}
OakAudioParams *apwrap(olive::core::internal::AudioParams *p)
{
return reinterpret_cast<OakAudioParams *>(p);
}
const olive::core::internal::Rational *rimpl(const OakRational *h)
{
return reinterpret_cast<const olive::core::internal::Rational *>(h);
}
} // namespace
extern "C"
{
OakSampleBuffer *oakcore_samplebuffer_create(void)
{
return wrap(new olive::core::internal::SampleBuffer());
}
OakSampleBuffer *oakcore_samplebuffer_create_length(
const OakAudioParams *params, const OakRational *length)
{
return wrap(new olive::core::internal::SampleBuffer(*apimpl(params),
*rimpl(length)));
}
OakSampleBuffer *oakcore_samplebuffer_create_samples(
const OakAudioParams *params, size_t samples_per_channel)
{
return wrap(new olive::core::internal::SampleBuffer(*apimpl(params),
samples_per_channel));
}
OakSampleBuffer *oakcore_samplebuffer_copy(const OakSampleBuffer *self)
{
return wrap(new olive::core::internal::SampleBuffer(*impl(self)));
}
void oakcore_samplebuffer_free(OakSampleBuffer *self)
{
delete impl(self);
}
OakSampleBuffer *oakcore_samplebuffer_rip_channel(const OakSampleBuffer *self,
int channel)
{
return wrap(
new olive::core::internal::SampleBuffer(impl(self)->rip_channel(channel)));
}
int oakcore_samplebuffer_rip_channel_vector(const OakSampleBuffer *self,
int channel, float *out,
int out_size)
{
const std::vector<float> v = impl(self)->rip_channel_vector(channel);
if (out && out_size > 0) {
const size_t n = std::min(v.size(), size_t(out_size));
std::copy(v.begin(), v.begin() + n, out);
}
return int(v.size());
}
OakAudioParams *oakcore_samplebuffer_audio_params(const OakSampleBuffer *self)
{
return apwrap(
new olive::core::internal::AudioParams(impl(self)->audio_params()));
}
void oakcore_samplebuffer_set_audio_params(OakSampleBuffer *self,
const OakAudioParams *params)
{
impl(self)->set_audio_params(*apimpl(params));
}
size_t oakcore_samplebuffer_sample_count(const OakSampleBuffer *self)
{
return impl(self)->sample_count();
}
void oakcore_samplebuffer_set_sample_count(OakSampleBuffer *self,
size_t sample_count)
{
impl(self)->set_sample_count(sample_count);
}
void oakcore_samplebuffer_set_sample_count_length(OakSampleBuffer *self,
const OakRational *length)
{
impl(self)->set_sample_count(*rimpl(length));
}
float *oakcore_samplebuffer_data(OakSampleBuffer *self, int channel)
{
if (!impl(self)->is_allocated() || channel < 0 ||
channel >= impl(self)->channel_count()) {
return nullptr;
}
return impl(self)->data(channel);
}
void oakcore_samplebuffer_to_raw_ptrs(OakSampleBuffer *self, float **out)
{
if (!out) {
return;
}
const std::vector<float *> ptrs = impl(self)->to_raw_ptrs();
std::copy(ptrs.begin(), ptrs.end(), out);
}
int oakcore_samplebuffer_channel_count(const OakSampleBuffer *self)
{
return impl(self)->channel_count();
}
int oakcore_samplebuffer_is_allocated(const OakSampleBuffer *self)
{
return impl(self)->is_allocated() ? 1 : 0;
}
void oakcore_samplebuffer_allocate(OakSampleBuffer *self)
{
impl(self)->allocate();
}
void oakcore_samplebuffer_destroy(OakSampleBuffer *self)
{
impl(self)->destroy();
}
void oakcore_samplebuffer_reverse(OakSampleBuffer *self)
{
impl(self)->reverse();
}
void oakcore_samplebuffer_speed(OakSampleBuffer *self, double speed)
{
impl(self)->speed(speed);
}
void oakcore_samplebuffer_transform_volume(OakSampleBuffer *self, float f)
{
impl(self)->transform_volume(f);
}
void oakcore_samplebuffer_transform_volume_for_channel(OakSampleBuffer *self,
int channel,
float volume)
{
impl(self)->transform_volume_for_channel(channel, volume);
}
void oakcore_samplebuffer_transform_volume_to(float f,
const OakSampleBuffer *input,
OakSampleBuffer *output)
{
olive::core::internal::SampleBuffer::transform_volume(f, impl(input),
impl(output));
}
void oakcore_samplebuffer_transform_volume_for_channel_to(
int channel, float volume, const OakSampleBuffer *input,
OakSampleBuffer *output)
{
olive::core::internal::SampleBuffer::transform_volume_for_channel(
channel, volume, impl(input), impl(output));
}
void oakcore_samplebuffer_transform_volume_for_sample(OakSampleBuffer *self,
size_t sample_index,
float volume)
{
impl(self)->transform_volume_for_sample(sample_index, volume);
}
void oakcore_samplebuffer_transform_volume_for_sample_on_channel(
OakSampleBuffer *self, size_t sample_index, int channel, float volume)
{
impl(self)->transform_volume_for_sample_on_channel(sample_index, channel,
volume);
}
void oakcore_samplebuffer_clamp(OakSampleBuffer *self)
{
impl(self)->clamp();
}
void oakcore_samplebuffer_silence(OakSampleBuffer *self)
{
impl(self)->silence();
}
void oakcore_samplebuffer_silence_range(OakSampleBuffer *self,
size_t start_sample,
size_t end_sample)
{
impl(self)->silence(start_sample, end_sample);
}
void oakcore_samplebuffer_silence_bytes(OakSampleBuffer *self,
size_t start_byte, size_t end_byte)
{
impl(self)->silence_bytes(start_byte, end_byte);
}
void oakcore_samplebuffer_set(OakSampleBuffer *self, int channel,
const float *data, size_t sample_offset,
size_t sample_length)
{
impl(self)->set(channel, data, sample_offset, sample_length);
}
void oakcore_samplebuffer_fast_set(OakSampleBuffer *self,
const OakSampleBuffer *other, int to,
int from)
{
impl(self)->fast_set(*impl(other), to, from);
}
} // extern "C"
+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/>.
***/
#include "oakcore/stringutils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex>
#include <string>
#include <vector>
#include "util/stringutils.h"
namespace
{
char **copy_string_vector(const std::vector<std::string> &v, int *count)
{
char **arr = static_cast<char **>(malloc(sizeof(char *) * v.size()));
if (!arr) {
*count = 0;
return nullptr;
}
for (size_t i = 0; i < v.size(); i++) {
arr[i] = static_cast<char *>(malloc(v[i].size() + 1));
if (!arr[i]) {
for (size_t j = 0; j < i; j++) {
free(arr[j]);
}
free(arr);
*count = 0;
return nullptr;
}
memcpy(arr[i], v[i].c_str(), v[i].size() + 1);
}
*count = int(v.size());
return arr;
}
} // namespace
extern "C"
{
char **oakcore_stringutils_split(const char *s, char separator, int *count)
{
const std::vector<std::string> v =
olive::core::internal::StringUtils::split(s ? s : "", separator);
return copy_string_vector(v, count);
}
char **oakcore_stringutils_split_regex(const char *s, const char *pattern,
int *count)
{
const std::vector<std::string> v =
olive::core::internal::StringUtils::split_regex(
s ? s : "", std::regex(pattern ? pattern : ""));
return copy_string_vector(v, count);
}
void oakcore_stringutils_free_string_array(char **arr, int count)
{
if (!arr) {
return;
}
for (int i = 0; i < count; i++) {
free(arr[i]);
}
free(arr);
}
int oakcore_stringutils_to_int(const char *s, int base, int *ok)
{
bool b = false;
const int x = olive::core::internal::StringUtils::to_int(s ? s : "", base, &b);
if (ok) {
*ok = b ? 1 : 0;
}
return x;
}
int oakcore_stringutils_format(char *buf, int buf_size, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
const int r = oakcore_stringutils_format_v(buf, buf_size, fmt, args);
va_end(args);
return r;
}
int oakcore_stringutils_format_v(char *buf, int buf_size, const char *fmt,
va_list args)
{
// The implementation class only exposes a variadic format(), so the
// va_list form applies the same vsnprintf semantics directly here.
va_list copy;
va_copy(copy, args);
const int needed =
vsnprintf(buf, buf_size > 0 ? size_t(buf_size) : 0, fmt, copy);
va_end(copy);
return needed;
}
} // extern "C"
+157
View File
@@ -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/>.
***/
#include "oakcore/timecodefunctions.h"
#include <stdio.h>
#include <string>
#include "util/rational.h"
#include "util/timecodefunctions.h"
namespace
{
const olive::core::internal::Rational *impl(const OakRational *h)
{
return reinterpret_cast<const olive::core::internal::Rational *>(h);
}
OakRational *wrap(olive::core::internal::Rational *r)
{
return reinterpret_cast<OakRational *>(r);
}
olive::core::internal::Timecode::Display to_display(OakTimecodeDisplay d)
{
return static_cast<olive::core::internal::Timecode::Display>(d);
}
olive::core::internal::Timecode::Rounding to_rounding(OakTimecodeRounding r)
{
return static_cast<olive::core::internal::Timecode::Rounding>(r);
}
int write_string(const std::string &s, char *buf, int buf_size)
{
if (buf && buf_size > 0) {
snprintf(buf, size_t(buf_size), "%s", s.c_str());
}
return int(s.size());
}
} // namespace
extern "C"
{
int oakcore_timecode_time_to_timecode(const OakRational *time,
const OakRational *timebase,
OakTimecodeDisplay display,
int show_plus_if_positive, char *buf,
int buf_size)
{
const std::string s = olive::core::internal::Timecode::time_to_timecode(
*impl(time), *impl(timebase), to_display(display),
show_plus_if_positive != 0);
return write_string(s, buf, buf_size);
}
OakRational *oakcore_timecode_timecode_to_time(const char *timecode,
const OakRational *timebase,
OakTimecodeDisplay display,
int *ok)
{
bool b = false;
olive::core::internal::Rational *r = new olive::core::internal::Rational(
olive::core::internal::Timecode::timecode_to_time(
timecode ? timecode : "", *impl(timebase), to_display(display),
&b));
if (ok) {
*ok = b ? 1 : 0;
}
return wrap(r);
}
int oakcore_timecode_time_to_string(int64_t ms, char *buf, int buf_size)
{
return write_string(olive::core::internal::Timecode::time_to_string(ms),
buf, buf_size);
}
OakRational *oakcore_timecode_snap_time_to_timebase(const OakRational *time,
const OakRational *timebase,
OakTimecodeRounding rounding)
{
return wrap(new olive::core::internal::Rational(
olive::core::internal::Timecode::snap_time_to_timebase(
*impl(time), *impl(timebase), to_rounding(rounding))));
}
int64_t oakcore_timecode_time_to_timestamp(const OakRational *time,
const OakRational *timebase,
OakTimecodeRounding rounding)
{
return olive::core::internal::Timecode::time_to_timestamp(
*impl(time), *impl(timebase), to_rounding(rounding));
}
int64_t oakcore_timecode_time_to_timestamp_d(double time,
const OakRational *timebase,
OakTimecodeRounding rounding)
{
return olive::core::internal::Timecode::time_to_timestamp(
time, *impl(timebase), to_rounding(rounding));
}
int64_t oakcore_timecode_rescale_timestamp(int64_t ts,
const OakRational *source,
const OakRational *dest)
{
return olive::core::internal::Timecode::rescale_timestamp(
ts, *impl(source), *impl(dest));
}
int64_t oakcore_timecode_rescale_timestamp_ceil(int64_t ts,
const OakRational *source,
const OakRational *dest)
{
return olive::core::internal::Timecode::rescale_timestamp_ceil(
ts, *impl(source), *impl(dest));
}
OakRational *oakcore_timecode_timestamp_to_time(int64_t timestamp,
const OakRational *timebase)
{
return wrap(new olive::core::internal::Rational(
olive::core::internal::Timecode::timestamp_to_time(timestamp,
*impl(timebase))));
}
int oakcore_timecode_timebase_is_drop_frame(const OakRational *timebase)
{
return olive::core::internal::Timecode::timebase_is_drop_frame(
*impl(timebase))
? 1
: 0;
}
} // extern "C"
+221
View File
@@ -0,0 +1,221 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oakcore/timerange.h"
#include <list>
#include "util/rational.h"
#include "util/timerange.h"
namespace
{
olive::core::internal::TimeRange *impl(OakTimeRange *h)
{
return reinterpret_cast<olive::core::internal::TimeRange *>(h);
}
const olive::core::internal::TimeRange *impl(const OakTimeRange *h)
{
return reinterpret_cast<const olive::core::internal::TimeRange *>(h);
}
OakTimeRange *wrap(olive::core::internal::TimeRange *r)
{
return reinterpret_cast<OakTimeRange *>(r);
}
const olive::core::internal::Rational *rimpl(const OakRational *h)
{
return reinterpret_cast<const olive::core::internal::Rational *>(h);
}
OakRational *rwrap(olive::core::internal::Rational *r)
{
return reinterpret_cast<OakRational *>(r);
}
} // namespace
extern "C"
{
OakTimeRange *oakcore_timerange_create(void)
{
return wrap(new olive::core::internal::TimeRange());
}
OakTimeRange *oakcore_timerange_create_io(const OakRational *in,
const OakRational *out)
{
return wrap(
new olive::core::internal::TimeRange(*rimpl(in), *rimpl(out)));
}
OakTimeRange *oakcore_timerange_copy(const OakTimeRange *self)
{
return wrap(new olive::core::internal::TimeRange(*impl(self)));
}
void oakcore_timerange_free(OakTimeRange *self)
{
delete impl(self);
}
OakRational *oakcore_timerange_in(const OakTimeRange *self)
{
return rwrap(new olive::core::internal::Rational(impl(self)->in()));
}
OakRational *oakcore_timerange_out(const OakTimeRange *self)
{
return rwrap(new olive::core::internal::Rational(impl(self)->out()));
}
OakRational *oakcore_timerange_length(const OakTimeRange *self)
{
return rwrap(new olive::core::internal::Rational(impl(self)->length()));
}
void oakcore_timerange_set_in(OakTimeRange *self, const OakRational *in)
{
impl(self)->set_in(*rimpl(in));
}
void oakcore_timerange_set_out(OakTimeRange *self, const OakRational *out)
{
impl(self)->set_out(*rimpl(out));
}
void oakcore_timerange_set_range(OakTimeRange *self, const OakRational *in,
const OakRational *out)
{
impl(self)->set_range(*rimpl(in), *rimpl(out));
}
int oakcore_timerange_equal(const OakTimeRange *self,
const OakTimeRange *other)
{
return *impl(self) == *impl(other) ? 1 : 0;
}
int oakcore_timerange_overlaps_with(const OakTimeRange *self,
const OakTimeRange *other,
int in_inclusive, int out_inclusive)
{
return impl(self)
->overlaps_with(*impl(other), in_inclusive != 0,
out_inclusive != 0) ?
1 :
0;
}
int oakcore_timerange_contains_range(const OakTimeRange *self,
const OakTimeRange *other,
int in_inclusive, int out_inclusive)
{
return impl(self)
->contains(*impl(other), in_inclusive != 0,
out_inclusive != 0) ?
1 :
0;
}
int oakcore_timerange_contains_time(const OakTimeRange *self,
const OakRational *time)
{
return impl(self)->contains(*rimpl(time)) ? 1 : 0;
}
OakTimeRange *oakcore_timerange_combined(const OakTimeRange *self,
const OakTimeRange *other)
{
return wrap(
new olive::core::internal::TimeRange(impl(self)->combined(*impl(other))));
}
OakTimeRange *oakcore_timerange_combine(const OakTimeRange *a,
const OakTimeRange *b)
{
return wrap(new olive::core::internal::TimeRange(
olive::core::internal::TimeRange::combine(*impl(a), *impl(b))));
}
OakTimeRange *oakcore_timerange_intersected(const OakTimeRange *self,
const OakTimeRange *other)
{
return wrap(new olive::core::internal::TimeRange(
impl(self)->intersected(*impl(other))));
}
OakTimeRange *oakcore_timerange_intersect(const OakTimeRange *a,
const OakTimeRange *b)
{
return wrap(new olive::core::internal::TimeRange(
olive::core::internal::TimeRange::intersect(*impl(a), *impl(b))));
}
OakTimeRange *oakcore_timerange_add(const OakTimeRange *self,
const OakRational *rhs)
{
return wrap(
new olive::core::internal::TimeRange(*impl(self) + *rimpl(rhs)));
}
OakTimeRange *oakcore_timerange_sub(const OakTimeRange *self,
const OakRational *rhs)
{
return wrap(
new olive::core::internal::TimeRange(*impl(self) - *rimpl(rhs)));
}
void oakcore_timerange_add_assign(OakTimeRange *self, const OakRational *rhs)
{
*impl(self) += *rimpl(rhs);
}
void oakcore_timerange_sub_assign(OakTimeRange *self, const OakRational *rhs)
{
*impl(self) -= *rimpl(rhs);
}
int oakcore_timerange_split_count(const OakTimeRange *self, int chunk_size)
{
return int(impl(self)->split(chunk_size).size());
}
int oakcore_timerange_split(const OakTimeRange *self, int chunk_size,
OakTimeRange **out_ranges, int out_size)
{
const std::list<olive::core::internal::TimeRange> ranges =
impl(self)->split(chunk_size);
int n = 0;
for (const olive::core::internal::TimeRange &r : ranges) {
if (out_ranges && n < out_size) {
out_ranges[n] =
wrap(new olive::core::internal::TimeRange(r));
}
n++;
}
return n;
}
} // extern "C"
+217
View File
@@ -0,0 +1,217 @@
/***
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_AUDIOPARAMS_H
#define OAK_LIBOLIVECORE_AUDIOPARAMS_H
#include <cstring>
#include <assert.h>
#include <vector>
#include "render/channellayout.h"
#include "render/sampleformat.h"
#include "../util/rational.h"
namespace olive::core::internal
{
/**
* @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.
*/
class AudioParams {
public:
/**
* @brief Default constructor creates invalid AudioParams
* sample_rate=0, channel_layout empty, format=INVALID
*/
AudioParams()
: sample_rate_(0)
, channel_layout_mask_(0)
, channel_count_(0)
, format_(SampleFormat::invalid)
{
set_default_footage_parameters();
}
/**
* @brief Constructor from channel layout mask
* @param sample_rate Audio sample rate
* @param channel_layout Channel layout mask (e.g., kChannelLayoutStereo)
* @param format Sample format
*/
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)
{
set_default_footage_parameters();
timebase_ = sample_rate_as_time_base();
calculate_channel_count();
}
int sample_rate() const
{
return sample_rate_;
}
void set_sample_rate(int sample_rate)
{
sample_rate_ = sample_rate;
}
/**
* @brief Channel layout as a 64-bit mask (0 if unspecified)
*/
const uint64_t &channel_layout() const
{
return channel_layout_mask_;
}
/**
* @brief Set channel layout from mask
* @param mask Channel layout mask (e.g., kChannelLayoutStereo)
*/
void set_channel_layout(uint64_t mask)
{
channel_layout_mask_ = mask;
calculate_channel_count();
}
Rational time_base() const
{
return timebase_;
}
void set_time_base(const Rational &timebase)
{
timebase_ = timebase;
}
Rational sample_rate_as_time_base() const
{
return Rational(1, sample_rate());
}
SampleFormat format() const
{
return format_;
}
void set_format(SampleFormat format)
{
format_ = format;
}
bool enabled() const
{
return enabled_;
}
void set_enabled(bool e)
{
enabled_ = e;
}
int stream_index() const
{
return stream_index_;
}
void set_stream_index(int s)
{
stream_index_ = s;
}
int64_t duration() const
{
return duration_;
}
void set_duration(int64_t duration)
{
duration_ = 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;
bool operator==(const AudioParams &other) const;
bool operator!=(const AudioParams &other) const;
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()
{
enabled_ = true;
stream_index_ = 0;
duration_ = 0;
}
/**
* @brief Updates channel_count_ from the current channel_layout_mask_
* Called after any channel layout modification.
*/
void calculate_channel_count();
int sample_rate_; ///< Audio sample rate in Hz (e.g., 48000)
/**
* @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
};
}
#endif // OAK_LIBOLIVECORE_AUDIOPARAMS_H
+137
View File
@@ -0,0 +1,137 @@
/***
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_SAMPLEBUFFER_H
#define OAK_LIBOLIVECORE_SAMPLEBUFFER_H
#include <memory>
#include <vector>
#include "audioparams.h"
#include "../util/rational.h"
namespace olive::core::internal
{
/**
* @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.
*/
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
{
return sample_count_per_channel_;
}
void set_sample_count(const size_t &sample_count);
void set_sample_count(const Rational &length)
{
set_sample_count(audio_params_.time_to_samples(length));
}
float *data(int channel)
{
return data_[channel].data();
}
const float *data(int channel) const
{
return data_.at(channel).data();
}
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();
}
return r;
}
int channel_count() const
{
return data_.size();
}
bool is_allocated() const
{
return !data_.empty();
}
void allocate();
void destroy();
void reverse();
void speed(double speed);
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);
static void transform_volume_for_channel(int channel, float volume,
const SampleBuffer *input,
SampleBuffer *output);
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);
void clamp();
void silence();
void silence(size_t start_sample, size_t end_sample);
void silence_bytes(size_t start_byte, size_t end_byte);
void set(int channel, const float *data, size_t sample_offset,
size_t 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);
private:
void clamp_channel(int channel);
AudioParams audio_params_;
size_t sample_count_per_channel_;
std::vector<std::vector<float>> data_;
};
}
#endif // OAK_LIBOLIVECORE_SAMPLEBUFFER_H
+138
View File
@@ -0,0 +1,138 @@
/***
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_BEZIER_H
#define OAK_LIBOLIVECORE_BEZIER_H
#include <Imath/ImathVec.h>
namespace olive::core::internal
{
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);
const double &x() const
{
return x_;
}
const double &y() const
{
return y_;
}
const double &cp1_x() const
{
return cp1_x_;
}
const double &cp1_y() const
{
return cp1_y_;
}
const double &cp2_x() const
{
return cp2_x_;
}
const double &cp2_y() const
{
return cp2_y_;
}
Imath::V2d to_vec() const
{
return Imath::V2d(x_, y_);
}
Imath::V2d control_point_1_to_vec() const
{
return Imath::V2d(cp1_x_, cp1_y_);
}
Imath::V2d control_point_2_to_vec() const
{
return Imath::V2d(cp2_x_, cp2_y_);
}
void set_x(const double &x)
{
x_ = x;
}
void set_y(const double &y)
{
y_ = y;
}
void set_cp1_x(const double &cp1_x)
{
cp1_x_ = cp1_x;
}
void set_cp1_y(const double &cp1_y)
{
cp1_y_ = cp1_y;
}
void set_cp2_x(const double &cp2_x)
{
cp2_x_ = cp2_x;
}
void set_cp2_y(const double &cp2_y)
{
cp2_y_ = cp2_y;
}
static double quadratic_xto_t(double x, double a, double b, double c);
static double quadratic_tto_y(double a, double b, double c, double t);
static double quadratic_xto_y(double x, const Imath::V2d &a,
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_tto_y(double a, double b, double c, double d, double t);
static double cubic_xto_y(double x, const Imath::V2d &a, const Imath::V2d &b,
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));
}
private:
static double calculate_t_from_x(bool cubic, double x, double a, double b,
double c, double d);
double x_;
double y_;
double cp1_x_;
double cp1_y_;
double cp2_x_;
double cp2_y_;
};
}
#endif // OAK_LIBOLIVECORE_BEZIER_H
+184
View File
@@ -0,0 +1,184 @@
/***
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_COLOR_H
#define OAK_LIBOLIVECORE_COLOR_H
#include "render/pixelformat.h"
namespace olive::core::internal
{
/**
* @brief High precision 32-bit DataType based RGBA color value
*/
class Color {
public:
using DataType = float;
static constexpr unsigned int rgba = 4;
Color()
{
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)
{
data_[0] = r;
data_[1] = g;
data_[2] = b;
data_[3] = a;
}
Color(const char *data, const PixelFormat &format, int ch_layout);
/**
* @brief Creates a Color struct 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.
*/
static Color from_hsv(const DataType &h, const DataType &s,
const DataType &v);
const DataType &red() const
{
return data_[0];
}
const DataType &green() const
{
return data_[1];
}
const DataType &blue() const
{
return data_[2];
}
const DataType &alpha() const
{
return data_[3];
}
void to_hsv(DataType *hue, DataType *sat, DataType *val) const;
DataType hsv_hue() const;
DataType hsv_saturation() const;
DataType value() const;
void to_hsl(DataType *hue, DataType *sat, DataType *lightness) const;
DataType hsl_hue() const;
DataType hsl_saturation() const;
DataType lightness() const;
void set_red(const DataType &red)
{
data_[0] = red;
}
void set_green(const DataType &green)
{
data_[1] = green;
}
void set_blue(const DataType &blue)
{
data_[2] = blue;
}
void set_alpha(const DataType &alpha)
{
data_[3] = alpha;
}
DataType *data()
{
return data_;
}
const DataType *data() const
{
return data_;
}
void to_data(char *out, const PixelFormat &format,
unsigned int nb_channels) const;
static Color from_data(const char *in, const PixelFormat &format,
unsigned 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;
// 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);
// Binary math operators
Color operator+(const Color &rhs) const
{
Color c(*this);
c += rhs;
return c;
}
Color operator-(const Color &rhs) const
{
Color c(*this);
c -= rhs;
return c;
}
Color operator+(const DataType &rhs) const
{
Color c(*this);
c += rhs;
return c;
}
Color operator-(const DataType &rhs) const
{
Color c(*this);
c -= rhs;
return c;
}
Color operator*(const DataType &rhs) const
{
Color c(*this);
c *= rhs;
return c;
}
Color operator/(const DataType &rhs) const
{
Color c(*this);
c /= rhs;
return c;
}
private:
DataType data_[rgba];
};
}
#endif // OAK_LIBOLIVECORE_COLOR_H
+80
View File
@@ -0,0 +1,80 @@
/***
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_FRACTIONUTILS_H
#define OAK_LIBOLIVECORE_FRACTIONUTILS_H
#include <stdint.h>
namespace olive::core::internal
{
/**
* @brief Rounding modes for RescaleRnd()
*
* Mirrors the FFmpeg AVRounding modes that this codebase used before the
* FFmpeg dependency was removed from core.
*/
enum class FractionRounding {
/**
* Round to the nearest value; halfway cases are rounded away from zero.
* Equivalent to FFmpeg's AV_ROUND_NEAR_INF.
*/
k_near_inf,
/**
* Round toward positive infinity. Equivalent to FFmpeg's AV_ROUND_UP.
*/
k_up
};
/**
* @brief Reduce a fraction so that numerator and denominator fit within `max`
*
* Native re-implementation of FFmpeg's av_reduce(): 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).
*/
void reduce_fraction(int64_t &num, int64_t &den, int64_t max);
/**
* @brief Compare two fractions
*
* Native re-implementation of FFmpeg's av_cmp_q(): 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).
*/
int compare_fractions(int an, int ad, int bn, int bd);
/**
* @brief Rescale `a` by the fraction b/c: returns a * b / c
*
* Native re-implementation of FFmpeg's av_rescale_rnd(). The intermediate
* 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);
}
#endif // OAK_LIBOLIVECORE_FRACTIONUTILS_H
+157
View File
@@ -0,0 +1,157 @@
/***
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_RATIONAL_H
#define OAK_LIBOLIVECORE_RATIONAL_H
#include <climits>
#include <iostream>
#ifdef USE_OTIO
#include <opentime/rationalTime.h>
#endif
namespace olive::core::internal
{
class Rational {
public:
Rational(const int &numerator = 0)
{
num_ = numerator;
den_ = 1;
}
Rational(const int &numerator, const int &denominator)
{
num_ = numerator;
den_ = denominator;
fix_signs();
reduce();
}
Rational(const Rational &rhs) = default;
static Rational from_double(const double &flt, bool *ok = nullptr);
static Rational from_string(const std::string &str, bool *ok = nullptr);
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);
//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;
//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;
//Unary operators
const Rational &operator+() const
{
return *this;
}
Rational operator-() const
{
return Rational(num_, -den_);
}
bool operator!() const
{
return !num_;
}
//Function: convert to double
double to_double() const;
#ifdef USE_OTIO
static Rational fromRationalTime(const opentime::RationalTime &t)
{
// Is this the best way to do this?
return fromDouble(t.to_seconds());
}
// Convert Olive rationals to opentime rationals with the given framerate (defaults to 24)
opentime::RationalTime toRationalTime(double framerate = 24) const;
#endif
// Produce "flipped" version
Rational flipped() const;
void flip();
// 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;
}
// Returns whether this Rational is not a valid number (denominator == 0)
bool isNaN() const
{
return den_ == 0;
}
const int &numerator() const
{
return num_;
}
const int &denominator() const
{
return den_;
}
std::string to_string() const;
friend std::ostream &operator<<(std::ostream &out, const Rational &value)
{
out << value.num_ << '/' << value.den_;
return out;
}
private:
void fix_signs();
void reduce();
int num_;
int den_;
};
#define RATIONAL_MIN Rational(INT_MIN)
#define RATIONAL_MAX Rational(INT_MAX)
}
#endif // OAK_LIBOLIVECORE_RATIONAL_H
+211
View File
@@ -0,0 +1,211 @@
/***
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_STRINGUTILS_H
#define OAK_LIBOLIVECORE_STRINGUTILS_H
#include <algorithm>
#include <regex>
#include <vector>
#include <string>
namespace olive::core::internal
{
class StringUtils {
public:
/**
* @brief Split a string into a list of strings using a specific delimiter
*
* @param s
*
* The string to split.
*
* @param separator
*
* The character to split the string on.
*
* @return
*
* A vector of strings split by the specified delimiter.
*/
static std::vector<std::string> split(const std::string &s, char separator);
/**
* @brief Splits a string into a list of strings using regular expressions.
*
* @param s
*
* The string to split.
*
* @param regex
*
* The regular expression to split the string on.
*
* @return
*
* 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);
/**
* @brief Convert a string to int using a bool pointer to determine success rather than an exception
*
* @param s
*
* The string to parse an int from.
*
* @param base
*
* The base of the number in the string (usually 10, or 16 for hex).
*
* @param ok
*
* (Optional) a boolean output parameter specifying whether the conversion was successful or not.
*
* @return
*
* 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);
/**
* @brief Overloaded function
*
* @param s
*
* The string to parse an int from.
*
* @param ok
*
* (Optional) a boolean output parameter specifying whether the conversion was successful or not.
*
* @return
*
* 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, bool *ok = nullptr)
{
return to_int(s, 10, ok);
}
/**
* @brief Convert a number to a string with left padding
*
* Usually used for converting a number to a string with leading zeroes.
*
* @param val
*
* The number to convert. This is a templated function and will accept any type, e.g.
* int/long/float/double/etc.
*
* @param padding
*
* Total desired length of the string. For example, setting this to `2` will ensure the string
* is at least 2 characters in size, using `c` to pad the left side where necessary.
*
* @param c
*
* The character to pad with. This defaults to `0` assuming you'll be using this function to
* create leading zeroes.
*
* @return
*
* The padded string.
*/
template <typename T>
static std::string to_string_leftpad(T val, size_t padding, char c = '0')
{
std::string s = std::to_string(val);
if (s.size() < padding) {
s.insert(0, padding - s.size(), c);
}
return s;
}
/**
* @brief Format a string
*
* A sprintf wrapper that returns a std::string.
*
* @param fmt
*
* The format to use.
*
* @return
*
* A formatted string in std::string form.
*/
static std::string format(const char *fmt, ...);
// trim from start (in place)
static inline void ltrim(std::string &s)
{
s.erase(s.begin(),
std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s)
{
s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char ch) { return !std::isspace(ch); })
.base(),
s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s)
{
rtrim(s);
ltrim(s);
}
// trim from start (copying)
static inline std::string ltrimmed(std::string s)
{
ltrim(s);
return s;
}
// trim from end (copying)
static inline std::string rtrimmed(std::string s)
{
rtrim(s);
return s;
}
// trim from both ends (copying)
static inline std::string trimmed(std::string s)
{
trim(s);
return s;
}
};
}
#endif // OAK_LIBOLIVECORE_STRINGUTILS_H
@@ -0,0 +1,93 @@
/***
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_TIMECODEFUNCTIONS_H
#define OAK_LIBOLIVECORE_TIMECODEFUNCTIONS_H
#include "rational.h"
#include <cstdint>
namespace olive::core::internal
{
/**
* @brief Functions for converting times/timecodes/timestamps
*
* 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)
* `timecode` a user-friendly string representation of a time according to Timecode::Display
*/
class Timecode {
public:
enum Display {
k_timecode_drop_frame,
k_timecode_non_drop_frame,
k_timecode_seconds,
k_frames,
k_milliseconds
};
enum Rounding { k_ceil, k_floor, k_round };
/**
* @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);
static Rational timecode_to_time(std::string timecode,
const Rational &timebase,
const Display &display,
bool *ok = nullptr);
static std::string time_to_string(int64_t ms);
static Rational snap_time_to_timebase(const Rational &time,
const Rational &timebase,
Rounding floor = k_round);
static int64_t time_to_timestamp(const Rational &time,
const Rational &timebase,
Rounding floor = k_round);
static int64_t time_to_timestamp(const double &time,
const Rational &timebase,
Rounding floor = k_round);
static int64_t rescale_timestamp(const int64_t &ts, const Rational &source,
const Rational &dest);
static int64_t rescale_timestamp_ceil(const int64_t &ts,
const Rational &source,
const Rational &dest);
static Rational timestamp_to_time(const int64_t &timestamp,
const Rational &timebase);
static bool timebase_is_drop_frame(const Rational &timebase);
};
}
#endif // OAK_LIBOLIVECORE_TIMECODEFUNCTIONS_H
+315
View File
@@ -0,0 +1,315 @@
/***
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_TIMERANGE_H
#define OAK_LIBOLIVECORE_TIMERANGE_H
#include <list>
#include <vector>
#include "rational.h"
namespace olive::core::internal
{
class TimeRange {
public:
TimeRange() = default;
TimeRange(const Rational &in, const Rational &out);
TimeRange(const TimeRange &r)
: TimeRange(r.in(), r.out())
{
}
TimeRange &operator=(const TimeRange &r)
{
set_range(r.in(), r.out());
return *this;
}
const Rational &in() const;
const Rational &out() const;
const Rational &length() const;
void set_in(const Rational &in);
void set_out(const Rational &out);
void set_range(const Rational &in, const Rational &out);
bool operator==(const TimeRange &r) const;
bool operator!=(const TimeRange &r) const;
bool overlaps_with(const TimeRange &a, bool in_inclusive = true,
bool out_inclusive = true) const;
bool contains(const TimeRange &a, bool in_inclusive = true,
bool out_inclusive = true) const;
bool contains(const Rational &r) const;
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);
TimeRange operator+(const Rational &rhs) const;
TimeRange operator-(const Rational &rhs) const;
const TimeRange &operator+=(const Rational &rhs);
const TimeRange &operator-=(const Rational &rhs);
std::list<TimeRange> split(const int &chunk_size) const;
private:
void normalize();
Rational in_;
Rational out_;
Rational length_;
};
class TimeRangeList {
public:
TimeRangeList() = default;
TimeRangeList(std::initializer_list<TimeRange> r)
: array_(r)
{
}
void insert(const TimeRangeList &list_to_add);
void insert(TimeRange range_to_add);
void remove(const TimeRange &remove);
void remove(const TimeRangeList &list);
template <typename T>
static void util_remove(std::vector<T> *list, const TimeRange &remove)
{
std::vector<T> additions;
for (auto it = list->begin(); it != list->end();) {
T &compare = *it;
if (remove.contains(compare)) {
// This element is entirely encompassed in this range, remove it
it = list->erase(it);
} else {
if (compare.contains(remove, false, false)) {
// The remove range is within this element, only choice is to split the element into two
T new_range = compare;
new_range.set_in(remove.out());
compare.set_out(remove.in());
additions.push_back(new_range);
break;
} else {
if (compare.in() < remove.in() &&
compare.out() > remove.in()) {
// This element's out point overlaps the range's in, we'll trim it
compare.set_out(remove.in());
} else if (compare.in() < remove.out() &&
compare.out() > remove.out()) {
// This element's in point overlaps the range's out, we'll trim it
compare.set_in(remove.out());
}
it++;
}
}
}
list->insert(list->end(), additions.begin(), additions.end());
}
bool contains(const TimeRange &range, bool in_inclusive = true,
bool out_inclusive = true) const;
bool contains(const Rational &r) const
{
for (const TimeRange &range : array_) {
if (range.contains(r)) {
return true;
}
}
return false;
}
bool overlaps_with(const TimeRange &r, bool in_inclusive = true,
bool out_inclusive = true) const
{
for (const TimeRange &range : array_) {
if (range.overlaps_with(r, in_inclusive, out_inclusive)) {
return true;
}
}
return false;
}
bool isEmpty() const
{
return array_.empty();
}
void clear()
{
array_.clear();
}
int size() const
{
return array_.size();
}
void shift(const Rational &diff);
void trim_in(const Rational &diff);
void trim_out(const Rational &diff);
TimeRangeList intersects(const TimeRange &range) const;
using const_iterator = std::vector<TimeRange>::const_iterator;
const_iterator begin() const
{
return array_.cbegin();
}
const_iterator end() const
{
return array_.cend();
}
const_iterator cbegin() const
{
return begin();
}
const_iterator cend() const
{
return end();
}
const TimeRange &first() const
{
return array_.front();
}
const TimeRange &last() const
{
return array_.back();
}
const TimeRange &at(int index) const
{
return array_.at(index);
}
const std::vector<TimeRange> &internal_array() const
{
return array_;
}
bool operator==(const TimeRangeList &rhs) const
{
return array_ == rhs.array_;
}
private:
std::vector<TimeRange> array_;
};
class TimeRangeListFrameIterator {
public:
TimeRangeListFrameIterator();
TimeRangeListFrameIterator(const TimeRangeList &list,
const Rational &timebase);
Rational snap(const Rational &r) const;
bool get_next(Rational *out);
bool has_next() const;
std::vector<Rational> to_vector() const
{
TimeRangeListFrameIterator copy(list_, timebase_);
std::vector<Rational> times;
Rational r;
while (copy.get_next(&r)) {
times.push_back(r);
}
return times;
}
int size();
void reset()
{
*this = TimeRangeListFrameIterator();
}
void insert(const TimeRange &range)
{
list_.insert(range);
}
void insert(const TimeRangeList &list)
{
list_.insert(list);
}
bool is_custom_range() const
{
return custom_range_;
}
void set_custom_range(bool e)
{
custom_range_ = e;
}
int frame_index() const
{
return frame_index_;
}
private:
void update_index_if_necessary();
TimeRangeList list_;
Rational timebase_;
Rational current_;
int range_index_;
int size_;
int frame_index_;
bool custom_range_;
};
}
#endif // OAK_LIBOLIVECORE_TIMERANGE_H
@@ -28,7 +28,7 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
namespace olive::core namespace olive::core::internal
{ {
/** /**
+1 -1
View File
@@ -23,7 +23,7 @@
#include <cmath> #include <cmath>
namespace olive::core namespace olive::core::internal
{ {
const std::vector<int> AudioParams::k_supported_sample_rates = { const std::vector<int> AudioParams::k_supported_sample_rates = {
+1 -1
View File
@@ -29,7 +29,7 @@
#include "util/cpuoptimize.h" #include "util/cpuoptimize.h"
#include "util/log.h" #include "util/log.h"
namespace olive::core namespace olive::core::internal
{ {
SampleBuffer::SampleBuffer() SampleBuffer::SampleBuffer()
+1 -1
View File
@@ -23,7 +23,7 @@
#include <algorithm> #include <algorithm>
namespace olive::core namespace olive::core::internal
{ {
Bezier::Bezier() Bezier::Bezier()
+1 -1
View File
@@ -27,7 +27,7 @@
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
namespace olive::core namespace olive::core::internal
{ {
Color Color::from_hsv(const DataType &h, const DataType &s, const DataType &v) Color Color::from_hsv(const DataType &h, const DataType &s, const DataType &v)
+1 -1
View File
@@ -27,7 +27,7 @@
#include <limits> #include <limits>
namespace olive::core namespace olive::core::internal
{ {
namespace namespace
+1 -1
View File
@@ -31,7 +31,7 @@
#include "util/fractionutils.h" #include "util/fractionutils.h"
#include "util/stringutils.h" #include "util/stringutils.h"
namespace olive::core namespace olive::core::internal
{ {
const Rational Rational::na_n = Rational(0, 0); const Rational Rational::na_n = Rational(0, 0);
+1 -1
View File
@@ -24,7 +24,7 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdexcept> #include <stdexcept>
namespace olive::core namespace olive::core::internal
{ {
std::vector<std::string> StringUtils::split(const std::string &s, std::vector<std::string> StringUtils::split(const std::string &s,
+1 -1
View File
@@ -27,7 +27,7 @@
#include "util/fractionutils.h" #include "util/fractionutils.h"
#include "util/stringutils.h" #include "util/stringutils.h"
namespace olive::core namespace olive::core::internal
{ {
std::string Timecode::time_to_timecode(const Rational &time, std::string Timecode::time_to_timecode(const Rational &time,
+1 -1
View File
@@ -27,7 +27,7 @@
#include "util/timecodefunctions.h" #include "util/timecodefunctions.h"
namespace olive::core namespace olive::core::internal
{ {
TimeRange::TimeRange(const Rational &in, const Rational &out) TimeRange::TimeRange(const Rational &in, const Rational &out)
+1 -1
View File
@@ -21,7 +21,7 @@
#include "util/value.h" #include "util/value.h"
namespace olive::core namespace olive::core::internal
{ {
} }
+247
View File
@@ -0,0 +1,247 @@
/***
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/>.
***/
// Pure C API test for oakcore/audioparams.h: no gtest, no C++ wrappers.
// oakcore/audioparams.h includes oakcore/rational.h for the OakRational
// handles crossing the boundary.
#include <cassert>
#include <cstdint>
#include <cstdio>
#include "olive/core/oakcore/audioparams.h"
// Mirror of render/channellayout.h values, spelled out so this test only
// includes the C API header.
static const uint64_t k_layout_mono = 0x4;
static const uint64_t k_layout_stereo = 0x3;
static const uint64_t k_layout_2_1 = 0x103;
static const uint64_t k_layout_5_point1 = 0x60F;
static const uint64_t k_layout_7_point1 = 0x63F;
// Mirror of render/sampleformat.h enum values.
static const int k_format_invalid = -1;
static const int k_format_u8_p = 0;
static const int k_format_f32_p = 4;
static const int k_format_s16 = 7;
static const int k_format_f64 = 11;
int main()
{
// Default constructor: invalid parameters with footage defaults
{
OakAudioParams *p = oakcore_audioparams_create_invalid();
assert(p != nullptr);
assert(oakcore_audioparams_is_valid(p) == 0);
assert(oakcore_audioparams_sample_rate(p) == 0);
assert(oakcore_audioparams_channel_layout(p) == 0);
assert(oakcore_audioparams_channel_count(p) == 0);
assert(oakcore_audioparams_format(p) == k_format_invalid);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 0);
assert(oakcore_audioparams_bits_per_sample(p) == 0);
assert(oakcore_audioparams_enabled(p) == 1);
assert(oakcore_audioparams_stream_index(p) == 0);
assert(oakcore_audioparams_duration(p) == 0);
OakRational *tb = oakcore_audioparams_time_base(p);
assert(oakcore_rational_is_null(tb) == 1);
oakcore_rational_free(tb);
oakcore_audioparams_free(p);
}
// Full constructor: 48000 Hz stereo s16
OakAudioParams *p =
oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
assert(p != nullptr);
assert(oakcore_audioparams_is_valid(p) == 1);
assert(oakcore_audioparams_sample_rate(p) == 48000);
assert(oakcore_audioparams_channel_layout(p) == k_layout_stereo);
assert(oakcore_audioparams_channel_count(p) == 2);
assert(oakcore_audioparams_format(p) == k_format_s16);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 2);
assert(oakcore_audioparams_bits_per_sample(p) == 16);
assert(oakcore_audioparams_enabled(p) == 1);
assert(oakcore_audioparams_stream_index(p) == 0);
assert(oakcore_audioparams_duration(p) == 0);
// Timebase defaults to 1/sample_rate
{
OakRational *tb = oakcore_audioparams_time_base(p);
assert(oakcore_rational_numerator(tb) == 1);
assert(oakcore_rational_denominator(tb) == 48000);
oakcore_rational_free(tb);
OakRational *srtb = oakcore_audioparams_sample_rate_as_time_base(p);
assert(oakcore_rational_numerator(srtb) == 1);
assert(oakcore_rational_denominator(srtb) == 48000);
oakcore_rational_free(srtb);
}
// Setters / getters
oakcore_audioparams_set_sample_rate(p, 44100);
assert(oakcore_audioparams_sample_rate(p) == 44100);
oakcore_audioparams_set_sample_rate(p, 48000);
assert(oakcore_audioparams_sample_rate(p) == 48000);
// Changing the layout recalculates the channel count
oakcore_audioparams_set_channel_layout(p, k_layout_mono);
assert(oakcore_audioparams_channel_layout(p) == k_layout_mono);
assert(oakcore_audioparams_channel_count(p) == 1);
oakcore_audioparams_set_channel_layout(p, k_layout_5_point1);
assert(oakcore_audioparams_channel_count(p) == 6);
oakcore_audioparams_set_channel_layout(p, k_layout_stereo);
assert(oakcore_audioparams_channel_count(p) == 2);
{
OakRational *tb = oakcore_rational_create_nd(1, 1000);
oakcore_audioparams_set_time_base(p, tb);
oakcore_rational_free(tb);
OakRational *got = oakcore_audioparams_time_base(p);
assert(oakcore_rational_numerator(got) == 1);
assert(oakcore_rational_denominator(got) == 1000);
oakcore_rational_free(got);
// Restore the default 1/48000 timebase
tb = oakcore_rational_create_nd(1, 48000);
oakcore_audioparams_set_time_base(p, tb);
oakcore_rational_free(tb);
}
oakcore_audioparams_set_format(p, k_format_f32_p);
assert(oakcore_audioparams_format(p) == k_format_f32_p);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 4);
assert(oakcore_audioparams_bits_per_sample(p) == 32);
oakcore_audioparams_set_format(p, k_format_s16);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 2);
oakcore_audioparams_set_enabled(p, 0);
assert(oakcore_audioparams_enabled(p) == 0);
oakcore_audioparams_set_enabled(p, 1);
assert(oakcore_audioparams_enabled(p) == 1);
oakcore_audioparams_set_stream_index(p, 3);
assert(oakcore_audioparams_stream_index(p) == 3);
oakcore_audioparams_set_stream_index(p, 0);
const int64_t big_duration = int64_t(1) << 40;
oakcore_audioparams_set_duration(p, big_duration);
assert(oakcore_audioparams_duration(p) == big_duration);
oakcore_audioparams_set_duration(p, 0);
// Time/sample/byte conversions (48000 Hz, 2 channels, 2 bytes/sample)
assert(oakcore_audioparams_time_to_samples(p, 1.0) == 48000);
assert(oakcore_audioparams_time_to_samples(p, 0.5) == 24000);
assert(oakcore_audioparams_time_to_samples(p, -1.0) == -48000);
assert(oakcore_audioparams_time_to_samples(p, 0.0) == 0);
assert(oakcore_audioparams_samples_to_bytes_per_channel(p, 100) == 200);
assert(oakcore_audioparams_samples_to_bytes(p, 100) == 400);
assert(oakcore_audioparams_samples_to_bytes(p, 0) == 0);
assert(oakcore_audioparams_time_to_bytes_per_channel(p, 1.0) == 96000);
assert(oakcore_audioparams_time_to_bytes(p, 1.0) == 192000);
assert(oakcore_audioparams_bytes_to_samples(p, 400) == 100);
assert(oakcore_audioparams_bytes_to_samples(p, 0) == 0);
// Rational-taking overloads
{
OakRational *half = oakcore_rational_create_nd(1, 2);
assert(oakcore_audioparams_time_to_samples_rational(p, half) == 24000);
assert(oakcore_audioparams_time_to_bytes_rational(p, half) == 96000);
assert(oakcore_audioparams_time_to_bytes_per_channel_rational(p, half) ==
48000);
oakcore_rational_free(half);
}
// Rational-returning conversions
{
OakRational *t = oakcore_audioparams_samples_to_time(p, 48000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 1);
oakcore_rational_free(t);
t = oakcore_audioparams_samples_to_time(p, 24000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 2);
oakcore_rational_free(t);
t = oakcore_audioparams_bytes_to_time(p, 192000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 1);
oakcore_rational_free(t);
t = oakcore_audioparams_bytes_per_channel_to_time(p, 96000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 1);
oakcore_rational_free(t);
}
// Copy + equality
{
OakAudioParams *copy = oakcore_audioparams_copy(p);
assert(copy != nullptr);
assert(oakcore_audioparams_equals(p, copy) == 1);
assert(oakcore_audioparams_equals(copy, copy) == 1);
oakcore_audioparams_set_sample_rate(copy, 96000);
assert(oakcore_audioparams_equals(p, copy) == 0);
oakcore_audioparams_set_sample_rate(copy, 48000);
assert(oakcore_audioparams_equals(p, copy) == 1);
OakAudioParams *invalid = oakcore_audioparams_create_invalid();
assert(oakcore_audioparams_equals(p, invalid) == 0);
assert(oakcore_audioparams_equals(invalid, invalid) == 1);
oakcore_audioparams_free(invalid);
oakcore_audioparams_free(copy);
}
// Supported channel layouts / sample rates
{
assert(oakcore_audioparams_supported_channel_layout_count() == 5);
assert(oakcore_audioparams_supported_channel_layout_at(0) == k_layout_mono);
assert(oakcore_audioparams_supported_channel_layout_at(1) ==
k_layout_stereo);
assert(oakcore_audioparams_supported_channel_layout_at(2) == k_layout_2_1);
assert(oakcore_audioparams_supported_channel_layout_at(3) ==
k_layout_5_point1);
assert(oakcore_audioparams_supported_channel_layout_at(4) ==
k_layout_7_point1);
// Out-of-range indices return 0
assert(oakcore_audioparams_supported_channel_layout_at(-1) == 0);
assert(oakcore_audioparams_supported_channel_layout_at(5) == 0);
assert(oakcore_audioparams_supported_sample_rate_count() == 10);
assert(oakcore_audioparams_supported_sample_rate_at(0) == 8000);
assert(oakcore_audioparams_supported_sample_rate_at(6) == 44100);
assert(oakcore_audioparams_supported_sample_rate_at(7) == 48000);
assert(oakcore_audioparams_supported_sample_rate_at(9) == 96000);
assert(oakcore_audioparams_supported_sample_rate_at(-1) == 0);
assert(oakcore_audioparams_supported_sample_rate_at(10) == 0);
}
oakcore_audioparams_free(p);
std::printf("oakcore_audioparams_test: all assertions passed\n");
return 0;
}
+144
View File
@@ -0,0 +1,144 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include "olive/core/oakcore/bezier.h"
static int double_eq(double a, double b)
{
return fabs(a - b) < 1e-9;
}
int main(void)
{
// Default constructor: everything zeroed
OakBezier *b = oakcore_bezier_create();
assert(b != NULL);
assert(double_eq(oakcore_bezier_x(b), 0.0));
assert(double_eq(oakcore_bezier_y(b), 0.0));
assert(double_eq(oakcore_bezier_cp1_x(b), 0.0));
assert(double_eq(oakcore_bezier_cp1_y(b), 0.0));
assert(double_eq(oakcore_bezier_cp2_x(b), 0.0));
assert(double_eq(oakcore_bezier_cp2_y(b), 0.0));
// Setters write through to the getters
oakcore_bezier_set_x(b, 1.5);
oakcore_bezier_set_y(b, -2.25);
oakcore_bezier_set_cp1_x(b, 0.1);
oakcore_bezier_set_cp1_y(b, 0.2);
oakcore_bezier_set_cp2_x(b, 0.3);
oakcore_bezier_set_cp2_y(b, 0.4);
assert(double_eq(oakcore_bezier_x(b), 1.5));
assert(double_eq(oakcore_bezier_y(b), -2.25));
assert(double_eq(oakcore_bezier_cp1_x(b), 0.1));
assert(double_eq(oakcore_bezier_cp1_y(b), 0.2));
assert(double_eq(oakcore_bezier_cp2_x(b), 0.3));
assert(double_eq(oakcore_bezier_cp2_y(b), 0.4));
// x/y constructor: control points stay zeroed
OakBezier *xy = oakcore_bezier_create_xy(3.0, 4.0);
assert(double_eq(oakcore_bezier_x(xy), 3.0));
assert(double_eq(oakcore_bezier_y(xy), 4.0));
assert(double_eq(oakcore_bezier_cp1_x(xy), 0.0));
assert(double_eq(oakcore_bezier_cp1_y(xy), 0.0));
assert(double_eq(oakcore_bezier_cp2_x(xy), 0.0));
assert(double_eq(oakcore_bezier_cp2_y(xy), 0.0));
// Full constructor
OakBezier *full =
oakcore_bezier_create_full(1.0, 2.0, 0.25, 0.5, 0.75, 1.0);
assert(double_eq(oakcore_bezier_x(full), 1.0));
assert(double_eq(oakcore_bezier_y(full), 2.0));
assert(double_eq(oakcore_bezier_cp1_x(full), 0.25));
assert(double_eq(oakcore_bezier_cp1_y(full), 0.5));
assert(double_eq(oakcore_bezier_cp2_x(full), 0.75));
assert(double_eq(oakcore_bezier_cp2_y(full), 1.0));
// Copy: same values, independent storage
OakBezier *dup = oakcore_bezier_copy(full);
assert(double_eq(oakcore_bezier_x(dup), 1.0));
assert(double_eq(oakcore_bezier_cp2_y(dup), 1.0));
oakcore_bezier_set_x(dup, 9.0);
oakcore_bezier_set_cp1_y(dup, 8.0);
assert(double_eq(oakcore_bezier_x(full), 1.0));
assert(double_eq(oakcore_bezier_cp1_y(full), 0.5));
// Quadratic evaluation: endpoints and x->t->y round trip
assert(double_eq(oakcore_bezier_quadratic_tto_y(0.0, 0.5, 1.0, 0.0), 0.0));
assert(double_eq(oakcore_bezier_quadratic_tto_y(0.0, 0.5, 1.0, 1.0), 1.0));
{
const double x = 0.3;
const double t =
oakcore_bezier_quadratic_xto_t(x, 0.0, 0.25, 1.0);
const double y =
oakcore_bezier_quadratic_tto_y(0.0, 0.75, 1.0, t);
assert(t >= 0.0 && t <= 1.0);
// xto_t solves on the x curve; feeding the same x back must hold
assert(fabs(oakcore_bezier_quadratic_tto_y(0.0, 0.25, 1.0, t) - x) <
1e-5);
(void) y;
}
// xto_t clamps x into [a, c] instead of diverging
{
const double t_lo = oakcore_bezier_quadratic_xto_t(-5.0, 0.0, 0.5, 1.0);
const double t_hi = oakcore_bezier_quadratic_xto_t(5.0, 0.0, 0.5, 1.0);
assert(t_lo >= 0.0 && t_lo <= 1.0);
assert(t_hi >= 0.0 && t_hi <= 1.0);
}
// Cubic evaluation: endpoints and x->t->y round trip
assert(double_eq(oakcore_bezier_cubic_tto_y(0.0, 0.25, 0.75, 1.0, 0.0),
0.0));
assert(double_eq(oakcore_bezier_cubic_tto_y(0.0, 0.25, 0.75, 1.0, 1.0),
1.0));
{
const double x = 0.6;
const double t =
oakcore_bezier_cubic_xto_t(x, 0.0, 0.1, 0.9, 1.0);
const double y =
oakcore_bezier_cubic_tto_y(0.0, 0.8, 0.2, 1.0, t);
assert(t >= 0.0 && t <= 1.0);
assert(fabs(oakcore_bezier_cubic_tto_y(0.0, 0.1, 0.9, 1.0, t) - x) <
1e-5);
(void) y;
}
// xto_t clamps x into [a, d] instead of diverging
{
const double t_lo =
oakcore_bezier_cubic_xto_t(-5.0, 0.0, 0.25, 0.75, 1.0);
const double t_hi =
oakcore_bezier_cubic_xto_t(5.0, 0.0, 0.25, 0.75, 1.0);
assert(t_lo >= 0.0 && t_lo <= 1.0);
assert(t_hi >= 0.0 && t_hi <= 1.0);
}
// Ownership: every handle released exactly once
oakcore_bezier_free(b);
oakcore_bezier_free(xy);
oakcore_bezier_free(full);
oakcore_bezier_free(dup);
oakcore_bezier_free(NULL);
printf("oakcore_bezier_test: all assertions passed\n");
return 0;
}
+351
View File
@@ -0,0 +1,351 @@
/***
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/>.
***/
/**
* @file oakcore_color_test.cpp
* @brief Pure C API test for the OakColor ABI
*
* Exercises every function of oakcore/color.h through the C boundary only:
* no C++ wrapper, no test framework, just main() + assert().
*/
#include "olive/core/oakcore/color.h"
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* PixelFormat::Format values (render/pixelformat.h) */
enum { PF_U8 = 0, PF_U10 = 1, PF_U16 = 2, PF_F16 = 3, PF_F32 = 4 };
static int feq(float a, float b)
{
return fabsf(a - b) < 1e-5f;
}
static void test_create_and_channels(void)
{
/* Default construction: all channels zero */
OakColor *c = oakcore_color_create();
assert(c != NULL);
assert(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 0.0f));
oakcore_color_free(c);
/* RGBA construction + getters */
c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f);
assert(feq(oakcore_color_red(c), 0.25f));
assert(feq(oakcore_color_green(c), 0.5f));
assert(feq(oakcore_color_blue(c), 0.75f));
assert(feq(oakcore_color_alpha(c), 1.0f));
/* Setters, including out-of-gamut HDR values */
oakcore_color_set_red(c, -0.5f);
oakcore_color_set_green(c, 2.0f);
oakcore_color_set_blue(c, 0.0f);
oakcore_color_set_alpha(c, 0.125f);
assert(feq(oakcore_color_red(c), -0.5f));
assert(feq(oakcore_color_green(c), 2.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 0.125f));
oakcore_color_free(c);
}
static void test_copy_and_ownership(void)
{
OakColor *c = oakcore_color_create_rgba(0.1f, 0.2f, 0.3f, 0.4f);
OakColor *copy = oakcore_color_copy(c);
assert(copy != NULL);
assert(feq(oakcore_color_red(copy), 0.1f));
assert(feq(oakcore_color_green(copy), 0.2f));
assert(feq(oakcore_color_blue(copy), 0.3f));
assert(feq(oakcore_color_alpha(copy), 0.4f));
/* The copy is independent from the original */
oakcore_color_set_red(copy, 0.9f);
assert(feq(oakcore_color_red(copy), 0.9f));
assert(feq(oakcore_color_red(c), 0.1f));
oakcore_color_free(copy);
oakcore_color_free(c);
/* Releasing a NULL handle must be safe */
oakcore_color_free(NULL);
}
static void test_hsv(void)
{
/* Primary colors from HSV */
OakColor *c = oakcore_color_from_hsv(0.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_red(c), 1.0f));
assert(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 1.0f));
oakcore_color_free(c);
c = oakcore_color_from_hsv(120.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 1.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
oakcore_color_free(c);
c = oakcore_color_from_hsv(240.0f, 1.0f, 0.5f);
assert(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.5f));
oakcore_color_free(c);
/* to_hsv of pure red and gray */
c = oakcore_color_create_rgba(1.0f, 0.0f, 0.0f, 1.0f);
float h = -1.0f, s = -1.0f, v = -1.0f;
oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 0.0f));
assert(feq(s, 1.0f));
assert(feq(v, 1.0f));
assert(feq(oakcore_color_hsv_hue(c), h));
assert(feq(oakcore_color_hsv_saturation(c), s));
assert(feq(oakcore_color_value(c), v));
oakcore_color_free(c);
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f);
oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 0.0f));
assert(feq(s, 0.0f));
assert(feq(v, 0.5f));
oakcore_color_free(c);
/* Roundtrip: color -> hsv -> color */
c = oakcore_color_create_rgba(0.2f, 0.4f, 0.8f, 1.0f);
oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 220.0f));
assert(feq(s, 0.75f));
assert(feq(v, 0.8f));
OakColor *rt = oakcore_color_from_hsv(h, s, v);
assert(feq(oakcore_color_red(rt), 0.2f));
assert(feq(oakcore_color_green(rt), 0.4f));
assert(feq(oakcore_color_blue(rt), 0.8f));
oakcore_color_free(rt);
oakcore_color_free(c);
}
static void test_hsl(void)
{
/* Pure red: l = 0.5, s = 1, h = 0 */
OakColor *c = oakcore_color_create_rgba(1.0f, 0.0f, 0.0f, 1.0f);
float h = -1.0f, s = -1.0f, l = -1.0f;
oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 0.0f));
assert(feq(s, 1.0f));
assert(feq(l, 0.5f));
assert(feq(oakcore_color_hsl_hue(c), h));
assert(feq(oakcore_color_hsl_saturation(c), s));
assert(feq(oakcore_color_lightness(c), l));
oakcore_color_free(c);
/* Gray: zero saturation */
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f);
oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 0.0f));
assert(feq(s, 0.0f));
assert(feq(l, 0.5f));
oakcore_color_free(c);
/* Arbitrary color */
c = oakcore_color_create_rgba(0.2f, 0.4f, 0.8f, 1.0f);
oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 220.0f));
assert(feq(s, 0.6f));
assert(feq(l, 0.5f));
oakcore_color_free(c);
}
static void test_data_access(void)
{
OakColor *c = oakcore_color_create_rgba(0.1f, 0.2f, 0.3f, 0.4f);
/* Const read access */
const float *cd = oakcore_color_const_data(c);
assert(cd != NULL);
assert(feq(cd[0], 0.1f));
assert(feq(cd[1], 0.2f));
assert(feq(cd[2], 0.3f));
assert(feq(cd[3], 0.4f));
/* Mutable write access */
float *d = oakcore_color_data(c);
assert(d != NULL);
d[0] = 0.125f;
d[3] = 1.0f;
assert(feq(oakcore_color_red(c), 0.125f));
assert(feq(oakcore_color_alpha(c), 1.0f));
oakcore_color_free(c);
}
static void test_pixel_data(void)
{
char buf[16];
memset(buf, 0, sizeof(buf));
/* u8 roundtrip, 4 channels */
OakColor *c = oakcore_color_create_rgba(1.0f, 0.0f, 1.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_U8, 4);
assert((uint8_t)buf[0] == 255);
assert((uint8_t)buf[1] == 0);
assert((uint8_t)buf[2] == 255);
assert((uint8_t)buf[3] == 255);
OakColor *back = oakcore_color_from_data(buf, PF_U8, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.0f));
assert(feq(oakcore_color_blue(back), 1.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
/* u8 with only 3 channels: alpha stays 0 */
oakcore_color_to_data(c, buf, PF_U8, 3);
back = oakcore_color_from_data(buf, PF_U8, 3);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.0f));
assert(feq(oakcore_color_blue(back), 1.0f));
assert(feq(oakcore_color_alpha(back), 0.0f));
oakcore_color_free(back);
/* u16 roundtrip */
oakcore_color_to_data(c, buf, PF_U16, 4);
assert(((uint16_t *)buf)[0] == 65535);
assert(((uint16_t *)buf)[1] == 0);
back = oakcore_color_from_data(buf, PF_U16, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
oakcore_color_free(c);
/* f32 roundtrip, exact */
c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f);
oakcore_color_to_data(c, buf, PF_F32, 4);
assert(feq(((float *)buf)[0], 0.25f));
assert(feq(((float *)buf)[3], 1.0f));
back = oakcore_color_from_data(buf, PF_F32, 4);
assert(feq(oakcore_color_red(back), 0.25f));
assert(feq(oakcore_color_green(back), 0.5f));
assert(feq(oakcore_color_blue(back), 0.75f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
/* f16 roundtrip: 1.0 = 0x3C00, 0.5 = 0x3800 in IEEE half */
c = oakcore_color_create_rgba(1.0f, 0.5f, 0.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_F16, 4);
assert(((uint16_t *)buf)[0] == 0x3C00);
assert(((uint16_t *)buf)[1] == 0x3800);
back = oakcore_color_from_data(buf, PF_F16, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.5f));
assert(feq(oakcore_color_blue(back), 0.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
/* u10 packed 4-channel roundtrip: all ones packs to 0xFFFFFFFF */
c = oakcore_color_create_rgba(1.0f, 1.0f, 1.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_U10, 4);
assert(((uint32_t *)buf)[0] == 0xFFFFFFFFu);
back = oakcore_color_from_data(buf, PF_U10, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 1.0f));
assert(feq(oakcore_color_blue(back), 1.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
oakcore_color_free(c);
}
static void test_luminance(void)
{
/* (2r + b + 3g) / 6 */
OakColor *c = oakcore_color_create_rgba(1.0f, 1.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c), 1.0f));
oakcore_color_free(c);
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c), 0.5f));
oakcore_color_free(c);
c = oakcore_color_create_rgba(0.6f, 0.2f, 0.4f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c),
(2.0f * 0.6f + 0.4f + 3.0f * 0.2f) / 6.0f));
oakcore_color_free(c);
}
static void test_math(void)
{
OakColor *a = oakcore_color_create_rgba(0.1f, 0.2f, 0.3f, 0.4f);
OakColor *b = oakcore_color_create_rgba(0.4f, 0.3f, 0.2f, 0.1f);
/* Color +=/-= Color */
oakcore_color_add_assign(a, b);
assert(feq(oakcore_color_red(a), 0.5f));
assert(feq(oakcore_color_green(a), 0.5f));
assert(feq(oakcore_color_blue(a), 0.5f));
assert(feq(oakcore_color_alpha(a), 0.5f));
oakcore_color_sub_assign(a, b);
assert(feq(oakcore_color_red(a), 0.1f));
assert(feq(oakcore_color_green(a), 0.2f));
assert(feq(oakcore_color_blue(a), 0.3f));
assert(feq(oakcore_color_alpha(a), 0.4f));
/* Scalar assign operators (apply to all channels) */
oakcore_color_add_scalar_assign(a, 1.0f);
assert(feq(oakcore_color_red(a), 1.1f));
assert(feq(oakcore_color_alpha(a), 1.4f));
oakcore_color_sub_scalar_assign(a, 0.6f);
assert(feq(oakcore_color_red(a), 0.5f));
assert(feq(oakcore_color_alpha(a), 0.8f));
oakcore_color_mul_scalar_assign(a, 2.0f);
assert(feq(oakcore_color_red(a), 1.0f));
assert(feq(oakcore_color_green(a), 1.2f));
assert(feq(oakcore_color_blue(a), 1.4f));
assert(feq(oakcore_color_alpha(a), 1.6f));
oakcore_color_div_scalar_assign(a, 4.0f);
assert(feq(oakcore_color_red(a), 0.25f));
assert(feq(oakcore_color_green(a), 0.3f));
assert(feq(oakcore_color_blue(a), 0.35f));
assert(feq(oakcore_color_alpha(a), 0.4f));
oakcore_color_free(b);
oakcore_color_free(a);
}
int main(void)
{
test_create_and_channels();
test_copy_and_ownership();
test_hsv();
test_hsl();
test_data_access();
test_pixel_data();
test_luminance();
test_math();
printf("oakcore_color_test: all assertions passed\n");
return 0;
}
+126
View File
@@ -0,0 +1,126 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oakcore/fractionutils.h"
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
/**
* @file oakcore_fractionutils_test.cpp
* @brief Pure C API test for the fraction utility functions
*
* Exercises every oakcore_fractionutils_* function directly, without the
* C++ wrapper and without a test framework.
*/
int main()
{
// reduce_fraction: greatest common divisor is divided out
{
int64_t num = 6, den = 9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == 2 && den == 3);
}
// reduce_fraction: the sign is normalized into the numerator
{
int64_t num = -6, den = 9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == -2 && den == 3);
}
{
int64_t num = 6, den = -9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == -2 && den == 3);
}
// reduce_fraction: a zero denominator is preserved, numerator zeroed
{
int64_t num = 42, den = 0;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == 0 && den == 0);
}
// reduce_fraction: a zero numerator reduces to 0/1
{
int64_t num = 0, den = 7;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == 0 && den == 1);
}
// reduce_fraction: exact reduction well within max
{
int64_t num = 1000000, den = 1000000;
oakcore_fractionutils_reduce_fraction(&num, &den, 100);
assert(num == 1 && den == 1);
}
// reduce_fraction: continued-fraction approximation respects max
{
int64_t num = 1048576, den = 1000000;
oakcore_fractionutils_reduce_fraction(&num, &den, 10000);
assert(num > 0 && num <= 10000 && den > 0 && den <= 10000);
const double approx = double(num) / double(den);
assert(fabs(approx - 1.048576) < 0.001);
}
// compare_fractions: three-way result -1 / 0 / 1
assert(oakcore_fractionutils_compare_fractions(1, 3, 1, 2) == -1);
assert(oakcore_fractionutils_compare_fractions(1, 2, 2, 4) == 0);
assert(oakcore_fractionutils_compare_fractions(2, 3, 1, 2) == 1);
// compare_fractions: meaningless degenerate comparison returns INT_MIN
assert(oakcore_fractionutils_compare_fractions(0, 0, 0, 5) == INT_MIN);
// rescale_rnd: exact division needs no rounding
assert(oakcore_fractionutils_rescale_rnd(100, 3, 4,
OAK_FRACTION_ROUNDING_NEAR_INF) == 75);
// rescale_rnd: round to nearest, halfway cases away from zero
assert(oakcore_fractionutils_rescale_rnd(5, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF) == 3);
assert(oakcore_fractionutils_rescale_rnd(-5, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF) == -3);
assert(oakcore_fractionutils_rescale_rnd(7, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF) == 4);
// rescale_rnd: round toward positive infinity
assert(oakcore_fractionutils_rescale_rnd(5, 1, 2,
OAK_FRACTION_ROUNDING_UP) == 3);
assert(oakcore_fractionutils_rescale_rnd(-5, 1, 2,
OAK_FRACTION_ROUNDING_UP) == -2);
assert(oakcore_fractionutils_rescale_rnd(4, 1, 2,
OAK_FRACTION_ROUNDING_UP) == 2);
// rescale_rnd: a negative divisor is normalized into the multiplier
assert(oakcore_fractionutils_rescale_rnd(10, 1, -2,
OAK_FRACTION_ROUNDING_NEAR_INF) == -5);
// rescale_rnd: large intermediates stay exact (128-bit path)
assert(oakcore_fractionutils_rescale_rnd(INT64_MAX / 2, 4, 2,
OAK_FRACTION_ROUNDING_NEAR_INF)
== INT64_MAX - 1);
printf("oakcore_fractionutils_test: all assertions passed\n");
return 0;
}
+110
View File
@@ -0,0 +1,110 @@
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "olive/core/oakcore/rational.h"
int main()
{
// create_nd reduces and normalizes signs
OakRational *r = oakcore_rational_create_nd(2, 4);
assert(r != NULL);
assert(oakcore_rational_numerator(r) == 1);
assert(oakcore_rational_denominator(r) == 2);
OakRational *neg = oakcore_rational_create_nd(1, -2);
assert(oakcore_rational_numerator(neg) == -1);
assert(oakcore_rational_denominator(neg) == 2);
oakcore_rational_free(neg);
// create defaults to n/1
OakRational *five = oakcore_rational_create(5);
assert(oakcore_rational_numerator(five) == 5);
assert(oakcore_rational_denominator(five) == 1);
oakcore_rational_free(five);
// NaN
OakRational *nan = oakcore_rational_create_nan();
assert(oakcore_rational_is_nan(nan) == 1);
assert(oakcore_rational_is_null(nan) == 1);
assert(isnan(oakcore_rational_to_double(nan)));
oakcore_rational_free(nan);
// copy is deep
OakRational *copy = oakcore_rational_copy(r);
assert(copy != r);
assert(oakcore_rational_compare(copy, r) == 0);
oakcore_rational_add_assign(copy, r);
assert(oakcore_rational_numerator(copy) == 1);
assert(oakcore_rational_denominator(copy) == 1);
assert(oakcore_rational_numerator(r) == 1);
assert(oakcore_rational_denominator(r) == 2);
oakcore_rational_free(copy);
// to_double / to_string
assert(fabs(oakcore_rational_to_double(r) - 0.5) < 1e-12);
char buf[32];
int needed = oakcore_rational_to_string(r, NULL, 0);
assert(needed == 3);
assert(oakcore_rational_to_string(r, buf, sizeof(buf)) == 3);
assert(strcmp(buf, "1/2") == 0);
assert(oakcore_rational_to_string(r, buf, 2) == 3); // truncated write
assert(strcmp(buf, "1") == 0);
// from_double / from_string
int ok = 0;
OakRational *half = oakcore_rational_from_double(0.5, &ok);
assert(ok == 1);
assert(oakcore_rational_compare(half, r) == 0);
oakcore_rational_free(half);
OakRational *bad = oakcore_rational_from_double(NAN, &ok);
assert(ok == 0);
assert(oakcore_rational_is_nan(bad));
oakcore_rational_free(bad);
OakRational *parsed = oakcore_rational_from_string("3/4", &ok);
assert(ok == 1);
assert(fabs(oakcore_rational_to_double(parsed) - 0.75) < 1e-12);
oakcore_rational_free(parsed);
OakRational *unparsed = oakcore_rational_from_string("1/2/3", &ok);
assert(ok == 0);
oakcore_rational_free(unparsed);
// flip
OakRational *flip = oakcore_rational_flipped(r);
assert(oakcore_rational_numerator(flip) == 2);
assert(oakcore_rational_denominator(flip) == 1);
oakcore_rational_flip(flip);
assert(oakcore_rational_compare(flip, r) == 0);
oakcore_rational_free(flip);
// arithmetic assignments
OakRational *a = oakcore_rational_create_nd(1, 3);
OakRational *b = oakcore_rational_create_nd(1, 6);
oakcore_rational_add_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 2);
oakcore_rational_sub_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 3);
oakcore_rational_mul_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 18);
oakcore_rational_div_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 3);
// compare ordering
assert(oakcore_rational_compare(b, a) < 0);
assert(oakcore_rational_compare(a, b) > 0);
oakcore_rational_free(a);
oakcore_rational_free(b);
oakcore_rational_free(r);
printf("oakcore_rational_test: all assertions passed\n");
return 0;
}
+359
View File
@@ -0,0 +1,359 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include "olive/core/oakcore/samplebuffer.h"
/* These functions are declared in oakcore/audioparams.h (a parallel
* delivery); they are re-declared here so that this test only includes the
* samplebuffer C header it is meant to exercise. */
extern "C" {
OakAudioParams *oakcore_audioparams_create(int sample_rate,
uint64_t channel_layout,
int format);
void oakcore_audioparams_free(OakAudioParams *self);
int oakcore_audioparams_sample_rate(const OakAudioParams *self);
int oakcore_audioparams_channel_count(const OakAudioParams *self);
}
/* Values mirror render/channellayout.h (k_channel_layout_stereo) and
* render/sampleformat.h (SampleFormat::f32_p) on the library side. */
#define SAMPLE_RATE 48000
#define CHANNEL_LAYOUT_STEREO 0x3
#define FORMAT_F32P 4
static int float_eq(float a, float b)
{
return fabsf(a - b) < 1e-6f;
}
int main(void)
{
/* --- Default construction: empty, unallocated --- */
OakSampleBuffer *def = oakcore_samplebuffer_create();
assert(def != NULL);
assert(oakcore_samplebuffer_is_allocated(def) == 0);
assert(oakcore_samplebuffer_sample_count(def) == 0);
assert(oakcore_samplebuffer_channel_count(def) == 0);
assert(oakcore_samplebuffer_data(def, 0) == NULL);
/* audio_params() hands back an owned copy of the (invalid) defaults */
OakAudioParams *def_params = oakcore_samplebuffer_audio_params(def);
assert(def_params != NULL);
assert(oakcore_audioparams_sample_rate(def_params) == 0);
oakcore_audioparams_free(def_params);
/* Operations that need an allocation warn and leave the buffer alone */
oakcore_samplebuffer_allocate(def);
assert(oakcore_samplebuffer_is_allocated(def) == 0);
oakcore_samplebuffer_silence(def);
oakcore_samplebuffer_reverse(def);
assert(oakcore_samplebuffer_is_allocated(def) == 0);
oakcore_samplebuffer_free(def);
/* --- Construction from audio params + sample count --- */
OakAudioParams *stereo = oakcore_audioparams_create(
SAMPLE_RATE, CHANNEL_LAYOUT_STEREO, FORMAT_F32P);
assert(stereo != NULL);
OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo, 100);
assert(oakcore_samplebuffer_is_allocated(buf) == 1);
assert(oakcore_samplebuffer_sample_count(buf) == 100);
assert(oakcore_samplebuffer_channel_count(buf) == 2);
/* A fresh allocation is silent */
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(buf, ch);
assert(d != NULL);
for (int i = 0; i < 100; i++) {
assert(float_eq(d[i], 0.0f));
}
}
/* audio_params() copy reflects the construction parameters */
OakAudioParams *p = oakcore_samplebuffer_audio_params(buf);
assert(oakcore_audioparams_sample_rate(p) == SAMPLE_RATE);
assert(oakcore_audioparams_channel_count(p) == 2);
oakcore_audioparams_free(p);
/* data() rejects out-of-range channels */
assert(oakcore_samplebuffer_data(buf, -1) == NULL);
assert(oakcore_samplebuffer_data(buf, 2) == NULL);
/* --- Construction from audio params + rational length (0.5s) --- */
OakRational *half_sec = oakcore_rational_create_nd(1, 2);
OakSampleBuffer *timed =
oakcore_samplebuffer_create_length(stereo, half_sec);
assert(oakcore_samplebuffer_is_allocated(timed) == 1);
assert(oakcore_samplebuffer_sample_count(timed) == 24000);
oakcore_rational_free(half_sec);
oakcore_samplebuffer_free(timed);
/* --- Manual lifecycle: params + count + allocate, destroy, repeat --- */
OakSampleBuffer *manual = oakcore_samplebuffer_create();
oakcore_samplebuffer_set_audio_params(manual, stereo);
oakcore_samplebuffer_set_sample_count(manual, 50);
oakcore_samplebuffer_allocate(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 1);
assert(oakcore_samplebuffer_sample_count(manual) == 50);
assert(oakcore_samplebuffer_channel_count(manual) == 2);
oakcore_samplebuffer_destroy(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 0);
assert(oakcore_samplebuffer_channel_count(manual) == 0);
assert(oakcore_samplebuffer_data(manual, 0) == NULL);
oakcore_samplebuffer_set_sample_count(manual, 30);
oakcore_samplebuffer_allocate(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 1);
assert(oakcore_samplebuffer_sample_count(manual) == 30);
oakcore_samplebuffer_free(manual);
/* set_sample_count via a rational length: 0.001s at 48kHz = 48 samples */
OakSampleBuffer *manual2 = oakcore_samplebuffer_create();
oakcore_samplebuffer_set_audio_params(manual2, stereo);
OakRational *one_ms = oakcore_rational_create_nd(1, 1000);
oakcore_samplebuffer_set_sample_count_length(manual2, one_ms);
oakcore_rational_free(one_ms);
oakcore_samplebuffer_allocate(manual2);
assert(oakcore_samplebuffer_sample_count(manual2) == 48);
oakcore_samplebuffer_free(manual2);
/* --- data() direct access, set(), to_raw_ptrs() --- */
float *ch0 = oakcore_samplebuffer_data(buf, 0);
float *ch1 = oakcore_samplebuffer_data(buf, 1);
for (int i = 0; i < 100; i++) {
ch0[i] = (float)i; /* ramp 0..99 on channel 0 */
ch1[i] = (float)(i * 10); /* ramp 0..990 on channel 1 */
}
const float ins[4] = { -1.0f, -2.0f, -3.0f, -4.0f };
oakcore_samplebuffer_set(buf, 0, ins, 10, 4); /* write at offset 10 */
for (int k = 0; k < 4; k++) {
assert(float_eq(ch0[10 + k], ins[k]));
}
assert(float_eq(ch0[9], 9.0f)); /* neighbours untouched */
assert(float_eq(ch0[14], 14.0f));
oakcore_samplebuffer_set(buf, 1, ins, 0, 4); /* write at offset 0 */
for (int k = 0; k < 4; k++) {
assert(float_eq(ch1[k], ins[k]));
}
for (int k = 0; k < 4; k++) { /* restore the channel 1 ramp */
ch1[k] = (float)(k * 10);
}
float *ptrs[2] = { NULL, NULL };
oakcore_samplebuffer_to_raw_ptrs(buf, ptrs);
assert(ptrs[0] == ch0);
assert(ptrs[1] == ch1);
ptrs[1][50] = 123.0f; /* writes through the raw pointer land in the buffer */
assert(float_eq(oakcore_samplebuffer_data(buf, 1)[50], 123.0f));
ch1[50] = 500.0f; /* restore ramp value (50 * 10) */
/* --- copy: an independent deep copy --- */
OakSampleBuffer *cp = oakcore_samplebuffer_copy(buf);
assert(oakcore_samplebuffer_is_allocated(cp) == 1);
assert(oakcore_samplebuffer_sample_count(cp) == 100);
assert(oakcore_samplebuffer_channel_count(cp) == 2);
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 9.0f));
/* --- transform_volume (in place, all channels) --- */
oakcore_samplebuffer_transform_volume(cp, 2.0f);
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f));
assert(float_eq(oakcore_samplebuffer_data(cp, 1)[9], 180.0f));
assert(float_eq(oakcore_samplebuffer_data(buf, 0)[9], 9.0f)); /* source keeps its data */
/* --- transform_volume_for_channel (in place, one channel) --- */
oakcore_samplebuffer_transform_volume_for_channel(cp, 1, 0.5f);
assert(float_eq(oakcore_samplebuffer_data(cp, 1)[9], 90.0f));
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f)); /* other channel untouched */
oakcore_samplebuffer_free(cp);
/* --- static-style transforms: input -> output --- */
OakSampleBuffer *tin = oakcore_samplebuffer_create_samples(stereo, 10);
OakSampleBuffer *tout = oakcore_samplebuffer_create_samples(stereo, 10);
for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(tin, 0)[i] = (float)i;
oakcore_samplebuffer_data(tin, 1)[i] = (float)(100 + i);
}
oakcore_samplebuffer_transform_volume_to(0.5f, tin, tout);
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(tout, 0)[i], (float)i * 0.5f));
assert(float_eq(oakcore_samplebuffer_data(tout, 1)[i],
(float)(100 + i) * 0.5f));
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[i], (float)i)); /* input unchanged */
}
oakcore_samplebuffer_silence(tout);
oakcore_samplebuffer_transform_volume_for_channel_to(1, 2.0f, tin, tout);
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(tout, 1)[i],
(float)(100 + i) * 2.0f));
assert(float_eq(oakcore_samplebuffer_data(tout, 0)[i], 0.0f)); /* channel 0 untouched */
}
/* --- per-sample volume transforms --- */
oakcore_samplebuffer_transform_volume_for_sample(tin, 3, 10.0f);
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[3], 30.0f));
assert(float_eq(oakcore_samplebuffer_data(tin, 1)[3], 1030.0f));
oakcore_samplebuffer_transform_volume_for_sample_on_channel(tin, 4, 0,
100.0f);
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[4], 400.0f));
assert(float_eq(oakcore_samplebuffer_data(tin, 1)[4], 104.0f)); /* channel 1 untouched */
oakcore_samplebuffer_free(tin);
oakcore_samplebuffer_free(tout);
/* --- clamp() limits every channel to [-1, 1] --- */
OakSampleBuffer *cl = oakcore_samplebuffer_create_samples(stereo, 4);
oakcore_samplebuffer_data(cl, 0)[0] = 2.5f;
oakcore_samplebuffer_data(cl, 0)[1] = -2.5f;
oakcore_samplebuffer_data(cl, 1)[0] = 42.0f;
oakcore_samplebuffer_data(cl, 1)[1] = 0.25f;
oakcore_samplebuffer_clamp(cl);
assert(float_eq(oakcore_samplebuffer_data(cl, 0)[0], 1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 0)[1], -1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 1)[0], 1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 1)[1], 0.25f));
oakcore_samplebuffer_free(cl);
/* --- silence / silence_range / silence_bytes --- */
OakSampleBuffer *si = oakcore_samplebuffer_create_samples(stereo, 10);
for (int ch = 0; ch < 2; ch++) {
for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(si, ch)[i] = 1.0f;
}
}
oakcore_samplebuffer_silence_range(si, 2, 5); /* samples [2, 5) */
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch);
assert(float_eq(d[1], 1.0f));
assert(float_eq(d[2], 0.0f));
assert(float_eq(d[4], 0.0f));
assert(float_eq(d[5], 1.0f));
}
oakcore_samplebuffer_silence_bytes(si, 0, 2 * sizeof(float)); /* samples [0, 2) */
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch);
assert(float_eq(d[0], 0.0f));
assert(float_eq(d[1], 0.0f));
assert(float_eq(d[5], 1.0f));
}
oakcore_samplebuffer_silence(si); /* everything */
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch);
for (int i = 0; i < 10; i++) {
assert(float_eq(d[i], 0.0f));
}
}
oakcore_samplebuffer_free(si);
/* --- reverse() flips every channel --- */
OakSampleBuffer *rv = oakcore_samplebuffer_create_samples(stereo, 5);
for (int i = 0; i < 5; i++) {
oakcore_samplebuffer_data(rv, 0)[i] = (float)i;
oakcore_samplebuffer_data(rv, 1)[i] = (float)(10 * i);
}
oakcore_samplebuffer_reverse(rv);
for (int i = 0; i < 5; i++) {
assert(float_eq(oakcore_samplebuffer_data(rv, 0)[i], (float)(4 - i)));
assert(float_eq(oakcore_samplebuffer_data(rv, 1)[i],
(float)(10 * (4 - i))));
}
oakcore_samplebuffer_free(rv);
/* --- speed(2.0) halves the sample count, sampling the ramp exactly --- */
OakSampleBuffer *sp = oakcore_samplebuffer_create_samples(stereo, 100);
for (int i = 0; i < 100; i++) {
oakcore_samplebuffer_data(sp, 0)[i] = (float)i;
}
oakcore_samplebuffer_speed(sp, 2.0);
assert(oakcore_samplebuffer_sample_count(sp) == 50);
for (int i = 0; i < 50; i++) {
assert(float_eq(oakcore_samplebuffer_data(sp, 0)[i], (float)(2 * i)));
}
oakcore_samplebuffer_free(sp);
/* --- fast_set(): channel copy between buffers --- */
OakSampleBuffer *fa = oakcore_samplebuffer_create_samples(stereo, 10);
OakSampleBuffer *fb = oakcore_samplebuffer_create_samples(stereo, 10);
for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(fa, 0)[i] = (float)i;
oakcore_samplebuffer_data(fa, 1)[i] = (float)(100 + i);
}
oakcore_samplebuffer_fast_set(fb, fa, 1, 0); /* fb channel 1 <- fa channel 0 */
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(fb, 1)[i], (float)i));
assert(float_eq(oakcore_samplebuffer_data(fb, 0)[i], 0.0f));
}
oakcore_samplebuffer_fast_set(fb, fa, 0, -1); /* from == -1 mirrors to */
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(fb, 0)[i], (float)i));
}
oakcore_samplebuffer_free(fa);
oakcore_samplebuffer_free(fb);
/* --- rip_channel(): a new mono buffer with one channel's samples --- */
OakSampleBuffer *rip = oakcore_samplebuffer_rip_channel(buf, 1);
assert(oakcore_samplebuffer_is_allocated(rip) == 1);
assert(oakcore_samplebuffer_channel_count(rip) == 1);
assert(oakcore_samplebuffer_sample_count(rip) == 100);
for (int i = 0; i < 100; i++) {
assert(float_eq(oakcore_samplebuffer_data(rip, 0)[i], (float)(i * 10)));
}
OakAudioParams *rip_params = oakcore_samplebuffer_audio_params(rip);
assert(oakcore_audioparams_sample_rate(rip_params) == SAMPLE_RATE);
assert(oakcore_audioparams_channel_count(rip_params) == 1);
oakcore_audioparams_free(rip_params);
oakcore_samplebuffer_free(rip);
/* --- rip_channel_vector(): query size, partial copy, full copy --- */
assert(oakcore_samplebuffer_rip_channel_vector(buf, 1, NULL, 0) == 100);
float partial[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
assert(oakcore_samplebuffer_rip_channel_vector(buf, 1, partial, 4) == 100);
for (int k = 0; k < 4; k++) {
assert(float_eq(partial[k], (float)(k * 10)));
}
float full[100];
assert(oakcore_samplebuffer_rip_channel_vector(buf, 1, full, 100) == 100);
for (int i = 0; i < 100; i++) {
assert(float_eq(full[i], (float)(i * 10)));
}
/* --- Ownership: everything released --- */
oakcore_samplebuffer_free(buf);
oakcore_audioparams_free(stereo);
printf("oakcore_samplebuffer_test: all assertions passed\n");
return 0;
}
+174
View File
@@ -0,0 +1,174 @@
/***
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/>.
***/
/**
* @file oakcore_stringutils_test.cpp
* @brief Pure C API test for oakcore/stringutils.h
*
* Exercises every C function directly (no C++ wrapper, no test framework).
*/
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "olive/core/oakcore/stringutils.h"
static void test_split()
{
int count = 0;
// Basic split
char **arr = oakcore_stringutils_split("a,b,c", ',', &count);
assert(arr != NULL);
assert(count == 3);
assert(strcmp(arr[0], "a") == 0);
assert(strcmp(arr[1], "b") == 0);
assert(strcmp(arr[2], "c") == 0);
oakcore_stringutils_free_string_array(arr, count);
// No separator present: single element equal to the whole string
arr = oakcore_stringutils_split("abc", ',', &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "abc") == 0);
oakcore_stringutils_free_string_array(arr, count);
// Trailing separator yields a trailing empty string
arr = oakcore_stringutils_split("a,", ',', &count);
assert(arr != NULL);
assert(count == 2);
assert(strcmp(arr[0], "a") == 0);
assert(strcmp(arr[1], "") == 0);
oakcore_stringutils_free_string_array(arr, count);
// Empty string yields one empty string
arr = oakcore_stringutils_split("", ',', &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "") == 0);
oakcore_stringutils_free_string_array(arr, count);
// NULL string behaves like an empty string
arr = oakcore_stringutils_split(NULL, ',', &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "") == 0);
oakcore_stringutils_free_string_array(arr, count);
}
static void test_split_regex()
{
int count = 0;
// Split on runs of digits
char **arr = oakcore_stringutils_split_regex("a1b22c", "[0-9]+", &count);
assert(arr != NULL);
assert(count == 3);
assert(strcmp(arr[0], "a") == 0);
assert(strcmp(arr[1], "b") == 0);
assert(strcmp(arr[2], "c") == 0);
oakcore_stringutils_free_string_array(arr, count);
// Pattern that never matches yields the whole string
arr = oakcore_stringutils_split_regex("abc", "[0-9]+", &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "abc") == 0);
oakcore_stringutils_free_string_array(arr, count);
// Freeing NULL is safe
oakcore_stringutils_free_string_array(NULL, 0);
}
static void test_to_int()
{
int ok = 0;
// Base 10
assert(oakcore_stringutils_to_int("42", 10, &ok) == 42);
assert(ok == 1);
// Negative
assert(oakcore_stringutils_to_int("-17", 10, &ok) == -17);
assert(ok == 1);
// Base 16
assert(oakcore_stringutils_to_int("ff", 16, &ok) == 255);
assert(ok == 1);
// Parser error: returns 0 and reports failure
assert(oakcore_stringutils_to_int("xyz", 10, &ok) == 0);
assert(ok == 0);
// ok output parameter is optional
assert(oakcore_stringutils_to_int("7", 10, NULL) == 7);
}
/* Variadic forwarder to exercise oakcore_stringutils_format_v() */
static int format_forward(char *buf, int buf_size, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
const int r = oakcore_stringutils_format_v(buf, buf_size, fmt, args);
va_end(args);
return r;
}
static void test_format()
{
char buf[64];
// Format with mixed argument types
const int needed = oakcore_stringutils_format(buf, sizeof(buf), "%d-%s-%.2f",
42, "mid", 1.5);
assert(needed == 11);
assert(strcmp(buf, "42-mid-1.50") == 0);
// NULL buffer queries the required size
assert(oakcore_stringutils_format(NULL, 0, "%d-%s-%.2f", 42, "mid",
1.5) == needed);
// Too-small buffer truncates but still reports the full required size
char small[5];
const int needed2 =
oakcore_stringutils_format(small, sizeof(small), "%s", "abcdefgh");
assert(needed2 == 8);
assert(strcmp(small, "abcd") == 0);
// va_list form produces identical results
char buf2[64];
const int needed3 = format_forward(buf2, sizeof(buf2), "%d-%s-%.2f", 42,
"mid", 1.5);
assert(needed3 == needed);
assert(strcmp(buf2, buf) == 0);
}
int main()
{
test_split();
test_split_regex();
test_to_int();
test_format();
printf("oakcore_stringutils_test: all tests passed\n");
return 0;
}
@@ -0,0 +1,254 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "olive/core/oakcore/timecodefunctions.h"
int main()
{
char buf[64];
int ok = 0;
OakRational *r = NULL;
/* Common timebases: 30 fps non-drop, 29.97 drop-frame, 60 fps */
OakRational *tb_30 = oakcore_rational_create_nd(1, 30);
OakRational *tb_df = oakcore_rational_create_nd(1001, 30000);
OakRational *tb_60 = oakcore_rational_create_nd(1, 60);
/* oakcore_timecode_time_to_timecode: drop-frame ( Olive's own test case ) */
{
OakRational *time = oakcore_rational_create(1);
const int size = oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, NULL, 0);
assert(size == 11);
assert(oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf,
sizeof(buf)) == 11);
assert(strcmp(buf, "00:00:01;00") == 0);
/* Truncating buffer still reports the full required size */
assert(oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf,
6) == 11);
assert(strcmp(buf, "00:00") == 0);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timecode: invalid timebase */
{
OakRational *time = oakcore_rational_create(0);
OakRational *bizarre = oakcore_rational_create(156632219);
assert(oakcore_timecode_time_to_timecode(
time, bizarre, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf,
sizeof(buf)) == 16);
assert(strcmp(buf, "INVALID TIMEBASE") == 0);
oakcore_rational_free(bizarre);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timecode: non-drop, plus sign, negative */
{
OakRational *time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf,
sizeof(buf)) == 11);
assert(strcmp(buf, "00:00:01:15") == 0);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 1, buf,
sizeof(buf)) == 12);
assert(strcmp(buf, "+00:00:01:15") == 0);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(-3, 2);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf,
sizeof(buf)) == 12);
assert(strcmp(buf, "-00:00:01:15") == 0);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timecode: seconds / frames / milliseconds */
{
OakRational *time = oakcore_rational_create_nd(123, 2); /* 61.5 s */
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_SECONDS, 0, buf,
sizeof(buf)) == 12);
assert(strcmp(buf, "00:01:01.500") == 0);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_FRAMES, 0, buf,
sizeof(buf)) == 2);
assert(strcmp(buf, "45") == 0);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_MILLISECONDS, 0, buf,
sizeof(buf)) == 4);
assert(strcmp(buf, "1500") == 0);
oakcore_rational_free(time);
}
/* oakcore_timecode_timecode_to_time: non-drop */
r = oakcore_timecode_timecode_to_time("00:00:01:15", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME,
&ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
/* oakcore_timecode_timecode_to_time: drop-frame */
r = oakcore_timecode_timecode_to_time("00:00:01;00", tb_df,
OAK_TIMECODE_DISPLAY_DROP_FRAME,
&ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 1001 &&
oakcore_rational_denominator(r) == 1000);
oakcore_rational_free(r);
/* oakcore_timecode_timecode_to_time: seconds / frames / milliseconds */
r = oakcore_timecode_timecode_to_time("00:00:01.5", tb_30,
OAK_TIMECODE_DISPLAY_SECONDS, &ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time("45", tb_30,
OAK_TIMECODE_DISPLAY_FRAMES, &ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time("1500", tb_30,
OAK_TIMECODE_DISPLAY_MILLISECONDS,
&ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
/* oakcore_timecode_timecode_to_time: invalid and NULL input */
r = oakcore_timecode_timecode_to_time("abc", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME,
&ok);
assert(ok == 0);
assert(oakcore_rational_numerator(r) == 0);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time(NULL, tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME,
&ok);
assert(ok == 0);
oakcore_rational_free(r);
/* ok pointer itself may be NULL */
r = oakcore_timecode_timecode_to_time("45", tb_30,
OAK_TIMECODE_DISPLAY_FRAMES, NULL);
assert(oakcore_rational_to_double(r) == 1.5);
oakcore_rational_free(r);
/* oakcore_timecode_time_to_string */
assert(oakcore_timecode_time_to_string(3661000, NULL, 0) == 8);
assert(oakcore_timecode_time_to_string(3661000, buf, sizeof(buf)) == 8);
assert(strcmp(buf, "01:01:01") == 0);
assert(oakcore_timecode_time_to_string(0, buf, sizeof(buf)) == 8);
assert(strcmp(buf, "00:00:00") == 0);
/* oakcore_timecode_snap_time_to_timebase */
{
OakRational *time = oakcore_rational_create_nd(102, 100); /* 1.02 s */
r = oakcore_timecode_snap_time_to_timebase(
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND);
assert(oakcore_rational_numerator(r) == 31 &&
oakcore_rational_denominator(r) == 30);
oakcore_rational_free(r);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(151, 100); /* 1.51 s -> 45.3 frames */
r = oakcore_timecode_snap_time_to_timebase(
time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_snap_time_to_timebase(
time, tb_30, OAK_TIMECODE_ROUNDING_CEIL);
assert(oakcore_rational_numerator(r) == 23 &&
oakcore_rational_denominator(r) == 15);
oakcore_rational_free(r);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timestamp (Rational) */
{
OakRational *time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND) == 45);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(151, 100); /* 45.3 frames */
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND) == 45);
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR) == 45);
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_CEIL) == 46);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timestamp_d (double) */
assert(oakcore_timecode_time_to_timestamp_d(1.5, tb_30,
OAK_TIMECODE_ROUNDING_ROUND) ==
45);
assert(oakcore_timecode_time_to_timestamp_d(NAN, tb_30,
OAK_TIMECODE_ROUNDING_ROUND) ==
0);
/* oakcore_timecode_rescale_timestamp */
assert(oakcore_timecode_rescale_timestamp(30, tb_30, tb_60) == 60);
assert(oakcore_timecode_rescale_timestamp(30, tb_30, tb_30) == 30);
{
/* 1 * (1*4) / (5*3) = 0.2666... -> nearest is 0 */
OakRational *src = oakcore_rational_create_nd(1, 5);
OakRational *dst = oakcore_rational_create_nd(3, 4);
assert(oakcore_timecode_rescale_timestamp(1, src, dst) == 0);
/* ... but ceil rounds up to 1 */
assert(oakcore_timecode_rescale_timestamp_ceil(1, src, dst) == 1);
oakcore_rational_free(dst);
oakcore_rational_free(src);
}
assert(oakcore_timecode_rescale_timestamp_ceil(30, tb_30, tb_60) == 60);
/* oakcore_timecode_timestamp_to_time */
r = oakcore_timecode_timestamp_to_time(45, tb_30);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_timestamp_to_time(0, tb_30);
assert(oakcore_rational_to_double(r) == 0.0);
oakcore_rational_free(r);
/* oakcore_timecode_timebase_is_drop_frame */
assert(oakcore_timecode_timebase_is_drop_frame(tb_df) == 1);
assert(oakcore_timecode_timebase_is_drop_frame(tb_30) == 0);
oakcore_rational_free(tb_60);
oakcore_rational_free(tb_df);
oakcore_rational_free(tb_30);
printf("oakcore_timecodefunctions_test: all tests passed\n");
return 0;
}
+352
View File
@@ -0,0 +1,352 @@
/***
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/>.
***/
/**
* @file oakcore_timerange_test.cpp
* @brief Smoke test for the OakTimeRange C ABI
*
* Pure C API test: no gtest, no C++ wrapper classes. Every allocated handle
* is released with the matching free function.
*/
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include "olive/core/oakcore/timerange.h"
static OakRational *mk_time(int n)
{
return oakcore_rational_create(n);
}
static OakTimeRange *mk_range(int in, int out)
{
OakRational *ri = mk_time(in);
OakRational *ro = mk_time(out);
OakTimeRange *r = oakcore_timerange_create_io(ri, ro);
oakcore_rational_free(ri);
oakcore_rational_free(ro);
return r;
}
static void expect_int_time(const OakRational *r, int expected)
{
assert(oakcore_rational_numerator(r) == expected);
assert(oakcore_rational_denominator(r) == 1);
}
static void expect_range(const OakTimeRange *r, int in, int out)
{
OakRational *ri = oakcore_timerange_in(r);
OakRational *ro = oakcore_timerange_out(r);
expect_int_time(ri, in);
expect_int_time(ro, out);
oakcore_rational_free(ri);
oakcore_rational_free(ro);
}
static void test_create_default(void)
{
OakTimeRange *r = oakcore_timerange_create();
expect_range(r, 0, 0);
OakRational *length = oakcore_timerange_length(r);
expect_int_time(length, 0);
oakcore_rational_free(length);
oakcore_timerange_free(r);
}
static void test_create_and_getters(void)
{
OakTimeRange *r = mk_range(10, 20);
expect_range(r, 10, 20);
OakRational *length = oakcore_timerange_length(r);
expect_int_time(length, 10);
oakcore_rational_free(length);
oakcore_timerange_free(r);
}
static void test_normalize_swaps_in_out(void)
{
// out earlier than in must be swapped
OakTimeRange *r = mk_range(20, 10);
expect_range(r, 10, 20);
OakRational *length = oakcore_timerange_length(r);
expect_int_time(length, 10);
oakcore_rational_free(length);
oakcore_timerange_free(r);
}
static void test_length_nan_on_extremes(void)
{
// RATIONAL_MIN/RATIONAL_MAX endpoints make the length NaN
OakTimeRange *r = mk_range(INT_MIN, 0);
OakRational *length = oakcore_timerange_length(r);
assert(oakcore_rational_is_nan(length) == 1);
oakcore_rational_free(length);
oakcore_timerange_free(r);
r = mk_range(0, INT_MAX);
length = oakcore_timerange_length(r);
assert(oakcore_rational_is_nan(length) == 1);
oakcore_rational_free(length);
oakcore_timerange_free(r);
}
static void test_copy_and_equal(void)
{
OakTimeRange *r = mk_range(3, 7);
OakTimeRange *copy = oakcore_timerange_copy(r);
assert(oakcore_timerange_equal(r, copy) == 1);
expect_range(copy, 3, 7);
// Mutating the copy must not affect the original (deep ownership)
OakRational *nine = mk_time(9);
oakcore_timerange_set_in(copy, nine);
oakcore_rational_free(nine);
// set_in(9) with out==7 normalizes by swapping to (7, 9)
expect_range(copy, 7, 9);
assert(oakcore_timerange_equal(r, copy) == 0);
expect_range(r, 3, 7);
oakcore_timerange_free(copy);
oakcore_timerange_free(r);
}
static void test_setters(void)
{
OakTimeRange *r = mk_range(0, 10);
OakRational *t = mk_time(5);
oakcore_timerange_set_in(r, t);
expect_range(r, 5, 10);
oakcore_timerange_set_out(r, t);
// set_out(5) with in==5 collapses to a zero-length range
expect_range(r, 5, 5);
{
OakRational *in = mk_time(30);
OakRational *out = mk_time(20);
oakcore_timerange_set_range(r, in, out);
oakcore_rational_free(in);
oakcore_rational_free(out);
}
// set_range also normalizes
expect_range(r, 20, 30);
oakcore_rational_free(t);
oakcore_timerange_free(r);
}
static void test_overlaps_with(void)
{
OakTimeRange *a = mk_range(0, 10);
OakTimeRange *b = mk_range(10, 20);
OakTimeRange *c = mk_range(5, 15);
// Touching ranges (0,10) vs (10,20): in_inclusive governs the
// other.out-vs-self.in comparison, out_inclusive the other.in-vs-self.out
// one, so the results are asymmetric for mixed flags
assert(oakcore_timerange_overlaps_with(a, b, 1, 1) == 1);
assert(oakcore_timerange_overlaps_with(a, b, 0, 0) == 0);
assert(oakcore_timerange_overlaps_with(a, b, 1, 0) == 0);
assert(oakcore_timerange_overlaps_with(a, b, 0, 1) == 1);
// Genuinely overlapping ranges overlap under any inclusivity
assert(oakcore_timerange_overlaps_with(a, c, 1, 1) == 1);
assert(oakcore_timerange_overlaps_with(a, c, 0, 0) == 1);
oakcore_timerange_free(c);
oakcore_timerange_free(b);
oakcore_timerange_free(a);
}
static void test_contains_range(void)
{
OakTimeRange *outer = mk_range(0, 30);
OakTimeRange *inner = mk_range(5, 10);
OakTimeRange *same = mk_range(0, 30);
OakTimeRange *partial = mk_range(5, 40);
assert(oakcore_timerange_contains_range(outer, inner, 1, 1) == 1);
assert(oakcore_timerange_contains_range(outer, inner, 0, 0) == 1);
// Identical ranges: contained inclusively, not exclusively
assert(oakcore_timerange_contains_range(outer, same, 1, 1) == 1);
assert(oakcore_timerange_contains_range(outer, same, 0, 0) == 0);
assert(oakcore_timerange_contains_range(outer, partial, 1, 1) == 0);
assert(oakcore_timerange_contains_range(inner, outer, 1, 1) == 0);
oakcore_timerange_free(partial);
oakcore_timerange_free(same);
oakcore_timerange_free(inner);
oakcore_timerange_free(outer);
}
static void test_contains_time(void)
{
OakTimeRange *r = mk_range(0, 10);
OakRational *inside = mk_time(5);
OakRational *edge_in = mk_time(0);
OakRational *edge_out = mk_time(10);
OakRational *outside = mk_time(-1);
// contains(time) is in-inclusive but out-exclusive
assert(oakcore_timerange_contains_time(r, inside) == 1);
assert(oakcore_timerange_contains_time(r, edge_in) == 1);
assert(oakcore_timerange_contains_time(r, edge_out) == 0);
assert(oakcore_timerange_contains_time(r, outside) == 0);
oakcore_rational_free(outside);
oakcore_rational_free(edge_out);
oakcore_rational_free(edge_in);
oakcore_rational_free(inside);
oakcore_timerange_free(r);
}
static void test_combine_and_intersect(void)
{
OakTimeRange *a = mk_range(0, 10);
OakTimeRange *b = mk_range(20, 30);
OakTimeRange *combined = oakcore_timerange_combined(a, b);
expect_range(combined, 0, 30);
oakcore_timerange_free(combined);
combined = oakcore_timerange_combine(b, a);
expect_range(combined, 0, 30);
oakcore_timerange_free(combined);
// Disjoint ranges intersect to (20, 10), which normalize() swaps to (10, 20)
OakTimeRange *intersected = oakcore_timerange_intersected(a, b);
expect_range(intersected, 10, 20);
oakcore_timerange_free(intersected);
oakcore_timerange_free(b);
oakcore_timerange_free(a);
a = mk_range(0, 20);
b = mk_range(10, 30);
intersected = oakcore_timerange_intersected(a, b);
expect_range(intersected, 10, 20);
oakcore_timerange_free(intersected);
intersected = oakcore_timerange_intersect(b, a);
expect_range(intersected, 10, 20);
oakcore_timerange_free(intersected);
oakcore_timerange_free(b);
oakcore_timerange_free(a);
}
static void test_arithmetic(void)
{
OakTimeRange *r = mk_range(0, 10);
OakRational *five = mk_time(5);
OakTimeRange *shifted = oakcore_timerange_add(r, five);
expect_range(shifted, 5, 15);
OakTimeRange *back = oakcore_timerange_sub(shifted, five);
expect_range(back, 0, 10);
oakcore_timerange_free(back);
oakcore_timerange_free(shifted);
oakcore_timerange_add_assign(r, five);
expect_range(r, 5, 15);
oakcore_timerange_sub_assign(r, five);
expect_range(r, 0, 10);
OakRational *length = oakcore_timerange_length(r);
expect_int_time(length, 10);
oakcore_rational_free(length);
oakcore_rational_free(five);
oakcore_timerange_free(r);
}
static void test_split(void)
{
OakTimeRange *r = mk_range(0, 10);
// Size query, both directly and through the NULL-buffer form
assert(oakcore_timerange_split_count(r, 4) == 3);
assert(oakcore_timerange_split(r, 4, NULL, 0) == 3);
// Full split: (0,4), (4,8), (8,10)
OakTimeRange *chunks[3];
assert(oakcore_timerange_split(r, 4, chunks, 3) == 3);
expect_range(chunks[0], 0, 4);
expect_range(chunks[1], 4, 8);
expect_range(chunks[2], 8, 10);
for (int i = 0; i < 3; i++) {
oakcore_timerange_free(chunks[i]);
}
// A too-small buffer receives a prefix, the return value is the total
OakTimeRange *prefix[2];
assert(oakcore_timerange_split(r, 4, prefix, 2) == 3);
expect_range(prefix[0], 0, 4);
expect_range(prefix[1], 4, 8);
oakcore_timerange_free(prefix[0]);
oakcore_timerange_free(prefix[1]);
// Zero-length range splits into exactly one chunk
OakTimeRange *zero = mk_range(5, 5);
assert(oakcore_timerange_split_count(zero, 4) == 1);
OakTimeRange *single = NULL;
assert(oakcore_timerange_split(zero, 4, &single, 1) == 1);
expect_range(single, 5, 5);
oakcore_timerange_free(single);
oakcore_timerange_free(zero);
oakcore_timerange_free(r);
}
int main(void)
{
test_create_default();
test_create_and_getters();
test_normalize_swaps_in_out();
test_length_nan_on_extremes();
test_copy_and_equal();
test_setters();
test_overlaps_with();
test_contains_range();
test_contains_time();
test_combine_and_intersect();
test_arithmetic();
test_split();
printf("oakcore_timerange_test: all tests passed\n");
return 0;
}
-109
View File
@@ -1,109 +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/>.
***/
#include <cstring>
#include "util/rational.h"
#include "util/tests.h"
using namespace olive::core;
bool rational_to_from_string_test()
{
Rational r(1, 30);
std::string s = r.toString();
Rational r2 = Rational::fromString(s);
return r == r2;
}
bool rational_to_from_string_test2()
{
Rational r(69, 420);
std::string s = r.toString();
Rational r2 = Rational::fromString(s);
return r == r2;
}
bool rational_defaults()
{
// By default, rationals are valid 0/1
Rational basic_constructor;
if (!basic_constructor.isNull()) {
return false;
}
if (basic_constructor.isNaN()) {
return false;
}
return true;
}
bool rational_nan()
{
// Create a NaN with a 0 denominator
Rational nan = Rational(0, 0);
if (!nan.isNaN())
return false;
if (!nan.isNull())
return false;
// Create a non-NaN with a zero numerator
Rational zero_nonnan(0, 999);
if (!zero_nonnan.isNull())
return false;
if (zero_nonnan.isNaN())
return false;
// Create a non-NaN with a non-zero numerator
Rational nonzer_nonnan(1, 30);
if (nonzer_nonnan.isNull())
return false;
if (nonzer_nonnan.isNaN())
return false;
return true;
}
bool rational_nan_constant()
{
return Rational::NaN.isNaN();
}
int main()
{
Tester t;
t.add("Rational::defaults", rational_defaults);
t.add("Rational::NaN", rational_nan);
t.add("Rational::NaN_constant", rational_nan_constant);
t.add("Rational::toString/fromString", rational_to_from_string_test);
t.add("Rational::toString/fromString2", rational_to_from_string_test2);
return t.exec();
}
-47
View File
@@ -1,47 +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/>.
***/
#include <cstring>
#include "util/stringutils.h"
#include "util/tests.h"
using namespace olive::core;
bool stringutils_format_test()
{
const char *expected = "Hello, world!";
std::string f = StringUtils::format("%s, %s!", "Hello", "world");
if (strcmp(f.c_str(), expected) != 0) {
return false;
}
return true;
}
int main()
{
Tester t;
t.add("StringUtils::format", stringutils_format_test);
return t.exec();
}
-65
View File
@@ -1,65 +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/>.
***/
#include <cstring>
#include "util/timecodefunctions.h"
#include "util/tests.h"
using namespace olive::core;
bool timecodefunctions_time_to_timecode_test()
{
Rational drop_frame_30(1001, 30000);
std::string timecode = Timecode::time_to_timecode(
Rational(1), drop_frame_30, Timecode::kTimecodeDropFrame);
if (strcmp(timecode.c_str(), "00:00:01;00") != 0) {
return false;
}
return true;
}
bool timecodefunctions_time_to_timecode_test2()
{
Rational bizarre_timebase(156632219);
std::string timecode = Timecode::time_to_timecode(
Rational(0), bizarre_timebase, Timecode::kTimecodeDropFrame);
if (strcmp(timecode.c_str(), "INVALID TIMEBASE") != 0) {
return false;
}
return true;
}
int main()
{
Tester t;
t.add("Timecode::time_to_timecode",
timecodefunctions_time_to_timecode_test);
t.add("Timecode::time_to_timecode2",
timecodefunctions_time_to_timecode_test2);
return t.exec();
}
-124
View File
@@ -1,124 +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/>.
***/
#include <cstring>
#include "util/timerange.h"
#include "util/tests.h"
using namespace olive::core;
bool timerangelist_remove_test()
{
TimeRangeList t;
t.insert(TimeRange(0, 30));
t.remove(TimeRange(2, 5));
return true;
}
bool timerangelist_mergeadjacent_test()
{
TimeRangeList t;
// TimeRangeList should merge 1 and 3 together since they're adjacent
t.insert(TimeRange(0, 6));
t.insert(TimeRange(20, 30));
t.insert(TimeRange(6, 10));
if (!(t.size() == 2)) {
return false;
}
if (!(t.first() == TimeRange(20, 30))) {
return false;
}
if (!(t.at(1) == TimeRange(0, 10))) {
return false;
}
// TimeRangeList should ignore these because it's already contained
TimeRangeList noop_test = t;
noop_test.insert(TimeRange(4, 7));
if (!(noop_test == t)) {
return false;
}
noop_test.insert(TimeRange(0, 3));
if (!(noop_test == t)) {
return false;
}
noop_test.insert(TimeRange(25, 30));
if (!(noop_test == t)) {
return false;
}
// TimeRangeList should combine all these together
TimeRangeList combine_test_no_overlap = t;
combine_test_no_overlap.insert(TimeRange(10, 20));
if (!(combine_test_no_overlap.size() == 1)) {
return false;
}
if (!(combine_test_no_overlap.first() == TimeRange(0, 30))) {
return false;
}
TimeRangeList combine_test_in_overlap = t;
combine_test_in_overlap.insert(TimeRange(9, 20));
if (!(combine_test_in_overlap.size() == 1)) {
return false;
}
if (!(combine_test_in_overlap.first() == TimeRange(0, 30))) {
return false;
}
TimeRangeList combine_test_out_overlap = t;
combine_test_out_overlap.insert(TimeRange(10, 21));
if (!(combine_test_out_overlap.size() == 1)) {
return false;
}
if (!(combine_test_out_overlap.first() == TimeRange(0, 30))) {
return false;
}
TimeRangeList combine_test_both_overlap = t;
combine_test_both_overlap.insert(TimeRange(9, 21));
if (!(combine_test_both_overlap.size() == 1)) {
return false;
}
if (!(combine_test_both_overlap.first() == TimeRange(0, 30))) {
return false;
}
return true;
}
int main()
{
Tester t;
t.add("TimeRangeList::remove", timerangelist_remove_test);
t.add("TimeRangeList::merge_adjacent", timerangelist_mergeadjacent_test);
return t.exec();
}
+4 -1
View File
@@ -198,8 +198,11 @@ else()
endif() endif()
if (WIN32) if (WIN32)
# Windows has no RPATH: ffmpeg_bridge.dll must sit next to the executable # Windows has no RPATH: shared libraries must sit next to the executable
add_custom_command(TARGET olive-gtest POST_BUILD add_custom_command(TARGET olive-gtest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:ffmpeg_bridge> $<TARGET_FILE_DIR:olive-gtest>) $<TARGET_FILE:ffmpeg_bridge> $<TARGET_FILE_DIR:olive-gtest>)
add_custom_command(TARGET olive-gtest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:olivecore> $<TARGET_FILE_DIR:olive-gtest>)
endif() endif()
+8
View File
@@ -70,6 +70,14 @@ endif ()
# copies, Linux install() into the same bin directory) # copies, Linux install() into the same bin directory)
add_dependencies(olive-editor olive-render-worker) add_dependencies(olive-editor olive-render-worker)
if (WIN32)
# Windows has no RPATH: shared libraries must sit next to the executable
add_custom_command(TARGET olive-render-worker POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:ffmpeg_bridge> $<TARGET_FILE_DIR:olive-render-worker>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olivecore> $<TARGET_FILE_DIR:olive-render-worker>
)
endif ()
# Install the render worker alongside the editor on Linux. # Install the render worker alongside the editor on Linux.
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
install(TARGETS olive-render-worker RUNTIME DESTINATION bin) install(TARGETS olive-render-worker RUNTIME DESTINATION bin)