refactor(common): de-Qt oakcommon and wrap it in a pure C ABI

- de-Qt all classes under src/common (std::string/vector/mutex,
  std::filesystem, expat-based XmlStreamReader/Writer)
- add pure C ABI in include/common + src/common/c_api: opaque handles,
  init returns NULL on failure, free(NULL) is a no-op, out-params with
  negative OAKCOMMON_E_* error codes (include/common/error.h)
- remove single-consumer classes from oakcommon (html, jobtime,
  otioutils, playbackaudioclock, tohex, util, avframeptr,
  crashpadinterface/crashpadutils, autoscroll, digit, range); their
  destinations are recorded in docs/zh/plans/riir/notes.md
- add gtest suites under src/common/tests (127 cases), standalone
  build driver in src/common/standalone
This commit is contained in:
2026-08-05 17:20:19 +08:00
parent bb09872a2f
commit bad52feda3
73 changed files with 8851 additions and 2 deletions
+5 -2
View File
@@ -48,8 +48,11 @@ set(CMAKE_AUTORCC ON)
# root to enrich your IDE with clangd language server protocol functionalities
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include_directories(include/)
# Sanitizers
add_library(olive-sanitizers INTERFACE)
add_library(olive-sanitizers INTERFACE
src/common/c_api/commandlineparser.cpp
include/common/commandlineparser.h)
include(cmake/Sanitizers.cmake)
enable_sanitizers(olive-sanitizers)
list(APPEND OLIVE_LIBRARIES olive-sanitizers)
@@ -368,7 +371,7 @@ add_subdirectory(cli)
add_subdirectory(app)
add_subdirectory(worker)
add_subdirectory(src)
if (BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
+64
View File
@@ -101,3 +101,67 @@ OAK/OIIO/FFmpeg 类型全部句柄化或拍平字段。)
- xmlreader 解析样例串、attr/text 读取、skip、writer 产出解析回读。
- files:临时目录建/写/读/尺寸/删除。
- 每函数 1 正常 + 1 错误路径;`oakcommon_debug_alive_count()` 泄漏断言。
## 实施现状(2026-08-05
M1 已落地并可独立构建、测试全绿(127 个用例:126 通过,1 个
`GTEST_SKIP`)。以下为与上文计划的实际差异。
### 最终目录结构
- `src/common/src/` — 去 Qt 化 C++ 实现(`olive::` 命名空间),target
`oakcommon`SHARED)。
- `src/common/c_api/` — 纯 C ABI 包装,通过 `target_sources` 合并进
`oakcommon`,不单独成库。
- `src/common/tests/` — gtesttarget `oakcommon-gtest`
`gtest_discover_tests`
- `include/common/`(仓库根)— 公共 C 头:`commandlineparser.h`
`current.h``debug.h``dropworkflowbehavior.h``error.h`
`ffmpegutils.h``filefunctions.h``miscutils.h``ocioutils.h`
`oiioutils.h``power.h``qtutils.h``xmlutils.h`
- `src/common/standalone/CMakeLists.txt` — 独立构建 driver(见下)。
### 独立构建与测试
```sh
cmake -S src/common/standalone -B build-oakcommon
cmake --build build-oakcommon -j
ctest --test-dir build-oakcommon --output-on-failure
```
driver 通过 `find_package(... CONFIG)` 使用 Homebrew 的 OCIO/OIIO/GTest
(顶层 `cmake/FindOpenColorIO.cmake`/`FindOpenImageIO.cmake` 面向
`.so`,macOS 下不适用),并把 config target 映射到 oakcommon
CMakeLists 消费的 `${OCIO_LIBRARIES}` 等变量。driver 中额外处理了两点:
`olivecore``third_party/openfx/include` 头路径(顶层靠全局
include);禁用 OpenTimelineIO`/opt/otio` 的 dylib 用 `@loader_path`
安装名,构建树内无法加载,且 oakcommon 不需要 OTIO)。
### 实际依赖
- 第三方:EXPATXML 解析)、OpenColorIO、OpenImageIO、FFmpeg(经
ffmpeg_bridge 间接)、GTest(仅测试)。
- Oak 内部库:**仍链接 `olivecore``ffmpeg_bridge`**,二者均为真实
符号依赖而非纯头文件:
- `olive::core::Rational``core/include/olive/core/util/rational.h`
是对 `oakcore_rational_*` C ABI 的包装,构造/析构/运算都需要
olivecore 的符号;
- `FFmpegUtils::get_compatible_bridge_pixel_format` 调用
`fb_find_best_pix_fmt_of_list`ffmpeg_bridge 导出符号)。
- `pixelformat.h`/`sampleformat.h` 本身是 header-only,只需头路径
`core/include``ffmpeg_bridge/include`,因这两个 target 的
include 目录是 PRIVATE,在 oakcommon 里显式补为 PUBLIC)。
- 未按计划只链 oakcore 头;OLIVECORE_BUILD_TESTS 在独立构建中关闭。
### 与计划的主要差异
- 接口未按 §2 冻结清单逐条实现,而是按实际使用面包装:C ABI 头放在
仓库根 `include/common/`,命名 `oakcommon_<模块>_<动词>`,两段式
buffer(先查询尺寸再拷入)约定统一。
- `xmlutils` 用 expat 实现(事件预先解析成队列);C API 的
`oakcommon_xml_reader_read_element_text` 因底层是消费型读取,在
handle 内缓存最近一次文本以兼容两段式调用。
- 被移除/上移的类(播放钟、autoscroll 等反向 include 涉及项)见
`notes.md`
- 计划中提到的 `switch(PixelFormat)` 编译问题实际不存在:
`olive::core::PixelFormat` 提供 `operator Format()`,可直接 switch。
+82
View File
@@ -0,0 +1,82 @@
# 记录项
## common
CancelableObject只在render和task处有🚰,移入对应模块。
## 从 oakcommonsrc/common)移除的类(2026-08-05
判据:不严重依赖 common 其他类,且只有一个非 common 模块使用它
tests/gtest 不计入使用方)。以下类已从 `src/common/` 移除,
拆分时应放到对应模块:
| 类 | 唯一使用方 | 应放到 |
|---|---|---|
| `Html`html.h/.cpp | engine/node | oaknodeM3)。注意它依赖 common 的 xmlutils,迁移时需连同 XML 辅助或改为调用 oakcommon C API |
| `JobTime`jobtime.h/.cpp | engine/render | oakrenderM7 |
| `OTIOUtils`otioutils.h | engine/task | oaktaskM8 |
| `PlaybackAudioClock`playbackaudioclock.h | engine/audio | oakaudioM6 |
| `to_hex`tohex.h | engine/node | oaknodeM3 |
| `mid()`util.h | engine/node | oaknodeM3 |
| `AVFramePtr`avframeptr.h | engine/render | oakrenderM7 |
| `CrashpadInterface`crashpadinterface.h/.cpp | app/main.cpp | appcrashhandler 相关) |
| crashpadutils.h | app/crashhandler | app/crashhandler |
| `AutoScroll`autoscroll.hoakutil shim | app/dialog、app/widget | app |
| `digit.h`oakutil shim | app/dialog | app |
| `range.h`oakutil shim | app/widget | app |
另:`power.h``memorypool.h``threadsafemap.h` 当前没有任何
common 模块外的使用方(零用户),不满足移除判据,暂保留在
oakcommon;后续若确认无用途可直接删除。
## oakcommon 去Qt化的删除与语义变更(2026-08-05)
去Qt化过程中以下函数被删除或语义变化,迁移调用方时需注意:
- `CommandLineParser::print_help()`:不再自动读取
`QCoreApplication::applicationName()/applicationVersion()`,需先调
`set_app_info()`C API`oakcommon_commandlineparser_set_app_info`),
否则打印默认 `"oak"` + 空版本。
- `FileFunctions::get_unique_file_identifier`:哈希由 SHA-1 改为
FNV-1a 64-bit,旧缓存 key 全部失效(需一次重建)。
`get_configuration_location`/`get_application_path`/
`get_temp_file_path`/`get_auto_recovery_root` 的标准路径改为手写
平台实现,路径与原 Qt 版不同。`read_file_as_string(":/...")`
qrc 资源路径不再可用,shader 等资源调用方需改为磁盘路径或内嵌。
- xmlutils`xml_read_next_start_element(reader, CancelAtom*)`
重载与 cancel_atom 参数删除(依赖 render/cancelatom.h),取消语义
由调用方在循环外自行检查;`XMLAttributeLoop` 宏删除,由
`XmlStreamReader::attributes()` + range-for 替代;
`QXmlStreamReader/Writer` 由基于 expat 的
`olive::XmlStreamReader/Writer` 替代(不支持 XML 命名空间)。
- `OIIOUtils::frame_to_buffer/buffer_to_frame`:未进 C APIC++ 侧签名
拍平为 `(const void *data, int64_t linesize_bytes, OIIO::ImageBuf *)`
engine/codec/frame.cpp 迁移时传成员即可。`OakCommonPixelFormat` 枚举
目前定义在 include/common/ocioutils.hoiioutils 复用,后续可抽成
独立的 include/common/pixelformat.h。
- `MemoryPool`:删除 QTimer 每 5 秒自动回收空 arena,改为公有
`clear_empty_arenas()` 由调用方周期调用;不再继承 QObject。
memorypool/threadsafemap 零使用方且为模板/多态基类,未包 C API。
- `debug_handler`:丢失 `QMessageLogContext`(文件/行号)参数(原实现
本就未用);`qInstallMessageHandler` 无对应物。
- `Current::set_current_video_params/audio_params`:由按值拷贝改为持有
`std::shared_ptr<void>`(类型擦除),调用方需自行管理生命周期并用
`static_pointer_cast` 还原(调用点:olivehost.cpp、
timebasedwidget.cpp、plugin.cpp 等 6 处)。
- qtutilsengine 未使用的纯 UI 函数(`q_font_metrics_width`
`create_horizontal/vertical_line``set_combo_box_data`
`word_wrap_string``flip_control_and_shift_modifiers`
`get_formatted_date_time``to_q_color``core::qHash`
`Q_DECLARE_METATYPE` 宏组)未迁入 oakcommonapp 层继续用 Qt 版。
`ptr_to_value/value_to_ptr` 载体由 QVariant 改为 `uintptr_t`
迁移时调用点需同步改(renderprocessor.cpp、rendermanager.cpp、
previewautocacher.cpp、src/capi/worker.cpp)。`get_parent_of_type`
依赖 C++ 模板+RTTI,未进 C API。
- define.h:删除 `MACRO_NAME_AS_STR`/`MACRO_VAL_AS_STR`/
`OLIVE_NS_CONST_ARG`/`OLIVE_NS_ARG`/`OLIVE_NS_RETURN_ARG`
(依赖 Qt QArgument,全仓无使用)。
- decibel.h:删除 `ALLOW_RETURNING_INFINITY` 编译开关分支(代码库
未定义该宏,行为不变)。
- `FFmpegUtils`:新增类内常量 `k_rgb_channel_count/k_rgba_channel_count`
3/4)取代对 `VideoParams` 同名常量的引用,消除 common→render
反向依赖。
+193
View File
@@ -0,0 +1,193 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_COMMANDLINEPARSER_H
#define OAK_EDITOR_COMMANDLINEPARSER_H
#ifndef __cplusplus
#include <stdbool.h>
#endif
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Opaque handle to a command-line parser instance.
*/
typedef struct OakCommonCommandLineParser OakCommonCommandLineParser;
/**
* @brief Opaque handle to a registered command-line option.
*
* The handle wrapper is freed with oakcommon_commandlineoption_free();
* the underlying option is owned by the parser and stays valid until
* the parser is freed.
*/
typedef struct OakCommonCommandLineOption OakCommonCommandLineOption;
/**
* @brief Opaque handle to a registered positional argument.
*
* The handle wrapper is freed with
* oakcommon_commandlinepositionalargument_free(); the underlying argument
* is owned by the parser and stays valid until the parser is freed.
*/
typedef struct OakCommonCommandLinePositionalArgument
OakCommonCommandLinePositionalArgument;
/**
* @brief Create a command-line parser.
*
* @return Parser handle, or NULL on allocation failure.
*/
OakCommonCommandLineParser *oakcommon_commandlineparser_init(void);
/**
* @brief Destroy a command-line parser.
*
* Destroys all option and positional-argument handles created from it.
* NULL is a no-op.
*/
void oakcommon_commandlineparser_free(OakCommonCommandLineParser *parser);
/**
* @brief Set the application name/version shown by print_help.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
const char *name,
const char *version);
/**
* @brief Register an option with one or more name strings.
*
* @param names Array of option name strings (without leading dash).
* @param name_count Number of entries in names. Must be > 0.
* @param description Help text, may be NULL.
* @param takes_arg Non-zero if the option consumes the following argument.
* @param arg_placeholder Placeholder shown in help, may be NULL.
* @param hidden Non-zero to omit from help output.
* @param out_option Receives the option handle. May be NULL if unused.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_add_option(
OakCommonCommandLineParser *parser, const char *const *names, int name_count,
const char *description, int takes_arg, const char *arg_placeholder,
int hidden, OakCommonCommandLineOption **out_option);
/**
* @brief Register a positional argument.
*
* @param out_argument Receives the argument handle. May be NULL if unused.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_add_positional_argument(
OakCommonCommandLineParser *parser, const char *name,
const char *description, int required,
OakCommonCommandLinePositionalArgument **out_argument);
/**
* @brief Parse an argv-style argument list.
*
* argv[0] is skipped as the program name, matching C main() convention.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
const char *const *argv, int argc);
/**
* @brief Print usage/help text to stdout.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
const char *filename);
/**
* @brief Query whether an option was present on the command line.
*
* @param is_set Receives the result. Must not be NULL.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineoption_is_set(OakCommonCommandLineOption *option,
bool *is_set);
/**
* @brief Free an option handle wrapper.
*
* Does not unregister the option from the parser. NULL is a no-op.
*/
void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option);
/**
* @brief Get an option's argument value (two-stage string getter).
*
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
char *buf, int buf_size);
/**
* @brief Set an option's argument value.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
const char *value);
/**
* @brief Get a positional argument's value (two-stage string getter).
*
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlinepositionalargument_get_setting(
OakCommonCommandLinePositionalArgument *argument, char *buf, int buf_size);
/**
* @brief Set a positional argument's value.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlinepositionalargument_set_setting(
OakCommonCommandLinePositionalArgument *argument, const char *value);
/**
* @brief Free a positional argument handle wrapper.
*
* Does not unregister the argument from the parser. NULL is a no-op.
*/
void oakcommon_commandlinepositionalargument_free(
OakCommonCommandLinePositionalArgument *argument);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_COMMANDLINEPARSER_H
+107
View File
@@ -0,0 +1,107 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_CURRENT_H
#define OAK_EDITOR_CURRENT_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct OakCommonCurrent OakCommonCurrent;
/**
* @brief Destructor callback for objects handed to Current slots.
*
* Called when the slot is overwritten or cleared. May be NULL if the
* caller keeps ownership of the object.
*/
typedef void (*OakCommonDestroyFn)(void *obj);
/**
* @brief Return a handle to the process-wide Current singleton.
*
* The returned handle is borrowed: it is valid for the lifetime of the
* process and must not be freed with oakcommon_current_free() more
* than out of symmetry (free is a no-op for the singleton).
*/
OakCommonCurrent *oakcommon_current_instance(void);
/**
* @brief Release a Current handle.
*
* No-op: the underlying object is a singleton. Safe to call with NULL.
*/
void oakcommon_current_free(OakCommonCurrent *self);
/**
* @brief Store a pointer in a Current slot, taking over destruction.
*
* Passing NULL for obj clears the slot (destroy is ignored). If a
* previous object with a destroy callback was stored, it is destroyed.
*
* @param self Handle from oakcommon_current_instance().
* @param obj Opaque pointer to the external object (e.g. a
* VideoParams), or NULL to clear.
* @param destroy Optional destructor invoked when the slot is replaced.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self is NULL.
*/
int oakcommon_current_set_video_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
int oakcommon_current_set_audio_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
int oakcommon_current_set_plugin_host(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
/**
* @brief Fetch the raw pointer currently stored in a slot.
*
* The returned pointer is borrowed and remains valid until the slot is
* overwritten or cleared. *out is set to NULL when the slot is empty.
*
* @param self Handle from oakcommon_current_instance().
* @param out Receives the stored pointer.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self or out
* is NULL.
*/
int oakcommon_current_get_video_params(OakCommonCurrent *self, void **out);
int oakcommon_current_get_audio_params(OakCommonCurrent *self, void **out);
int oakcommon_current_get_plugin_host(OakCommonCurrent *self, void **out);
int oakcommon_current_get_plugin_cache(OakCommonCurrent *self, void **out);
/**
* @brief Query whether the session is interactive.
*
* @param self Handle from oakcommon_current_instance().
* @param out Receives 1 for interactive, 0 otherwise.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self or out
* is NULL.
*/
int oakcommon_current_is_interactive(OakCommonCurrent *self, int *out);
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_CURRENT_H
+75
View File
@@ -0,0 +1,75 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_DEBUG_H
#define OAK_EDITOR_DEBUG_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Severity levels for oakcommon debug output.
*
* Mirrors olive::DebugLevel in src/common/src/debug.h.
*/
enum OakCommonDebugLevel {
OAKCOMMON_DEBUG_DEBUG = 0, /**< Verbose debug message. */
OAKCOMMON_DEBUG_INFO = 1, /**< Informational message. */
OAKCOMMON_DEBUG_WARNING = 2, /**< Warning message. */
OAKCOMMON_DEBUG_ERROR = 3, /**< Error message. */
OAKCOMMON_DEBUG_FATAL = 4 /**< Fatal error message. */
};
/**
* @brief Print a debug message to stderr, prefixed with its level.
*
* De-Qt replacement for the old Qt message handler. The line is
* flushed immediately.
*
* @param level One of OakCommonDebugLevel; out-of-range values print
* as "UNKNOWN".
* @param msg NUL-terminated message; NULL is treated as an empty
* string.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if msg is NULL.
*/
int oakcommon_debug_log(int level, const char *msg);
/**
* @brief Copy the printable name of a debug level into buf.
*
* Two-segment string getter: if buf is NULL or buf_size is too small,
* nothing is written.
*
* @param level One of OakCommonDebugLevel.
* @param buf Destination buffer, may be NULL to query the size.
* @param buf_size Size of buf in bytes.
* @return Required buffer size in bytes including the terminating NUL
* (non-negative).
*/
int oakcommon_debug_level_name(int level, char *buf, int buf_size);
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_DEBUG_H
+73
View File
@@ -0,0 +1,73 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_DROPWORKFLOWBEHAVIOR_H
#define OAK_EDITOR_DROPWORKFLOWBEHAVIOR_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Behavior when media is dropped onto a timeline without a
* sequence.
*
* Mirrors olive::DropWithoutSequenceBehavior in
* src/common/src/dropworkflowbehavior.h; enumerator order and values
* must stay identical because the config layer persists them as ints.
*/
enum OakCommonDropWorkflowBehavior {
OAKCOMMON_DWS_ASK = 0, /**< Ask the user every time. */
OAKCOMMON_DWS_AUTO = 1, /**< Automatically create a sequence. */
OAKCOMMON_DWS_MANUAL = 2, /**< Never create; import manually. */
OAKCOMMON_DWS_DISABLE = 3 /**< Disable dropping entirely. */
};
/**
* @brief Check whether value is a valid OakCommonDropWorkflowBehavior.
*
* @param value Integer behavior value (e.g. read from config).
* @return 1 if valid, 0 otherwise (this is a predicate, not a status
* code).
*/
int oakcommon_drop_workflow_behavior_is_valid(int value);
/**
* @brief Copy the printable name of a behavior into buf.
*
* Two-segment string getter: if buf is NULL or buf_size is too small,
* nothing is written. Invalid values yield "UNKNOWN".
*
* @param value One of OakCommonDropWorkflowBehavior.
* @param buf Destination buffer, may be NULL to query the size.
* @param buf_size Size of buf in bytes.
* @return Required buffer size in bytes including the terminating NUL
* (non-negative).
*/
int oakcommon_drop_workflow_behavior_name(int value, char *buf,
int buf_size);
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_DROPWORKFLOWBEHAVIOR_H
+41
View File
@@ -0,0 +1,41 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_ERROR_H
#define OAK_EDITOR_ERROR_H
/**
* @brief Status and error codes shared by all oakcommon C API families.
*
* Return-code convention (mirrors engine/include/oakengine/init.h):
* 0 (OAKCOMMON_OK) on success, a negative OAKCOMMON_E_* error code on
* failure. String getters return the required buffer size in bytes
* (including the terminating NUL) as a non-negative value instead.
*/
#define OAKCOMMON_OK 0 /**< Success. */
#define OAKCOMMON_E_INVALID (-1) /**< NULL handle or invalid argument. */
#define OAKCOMMON_E_STATE (-2) /**< Call not valid in the current state. */
#define OAKCOMMON_E_FAILED (-3) /**< The underlying operation failed. */
#define OAKCOMMON_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
#define OAKCOMMON_E_NOMEM (-5) /**< Allocation failed. */
#define SUCCESS OAKCOMMON_OK /**< @deprecated Use OAKCOMMON_OK. */
#endif //OAK_EDITOR_ERROR_H
+116
View File
@@ -0,0 +1,116 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_FFMPEGUTILS_H
#define OAK_EDITOR_FFMPEGUTILS_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Stateless mappings between native pixel/sample formats and the
* opaque FBPixelFormat / FBSampleFormat constants of ffmpeg_bridge
*
* All functions are pure format conversions; there is no handle to create
* or free. Native pixel/sample formats are passed as plain ints matching
* the olive::core::PixelFormat::Format / SampleFormat::Format enum values
* (invalid = -1). Bridge formats are the fb_pix_fmt_* / fb_sample_fmt_*
* constants from ffmpeg_bridge/ffmpeg_bridge.h.
*/
/**
* @brief RGB / RGBA channel counts (flattened from VideoParams)
*/
#define OAKCOMMON_RGB_CHANNEL_COUNT 3
#define OAKCOMMON_RGBA_CHANNEL_COUNT 4
/**
* @brief Returns a bridge pixel format that a frame can be converted to
* with minimal data loss, clamped to a maximum native precision
*
* @param pix_fmt bridge pixel format to find a compatible conversion for
* @param maximum_pix_fmt maximum native pixel format, or -1 for no limit
* @param out receives the chosen bridge pixel format
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out is NULL
*/
int oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(
int pix_fmt, int maximum_pix_fmt, int *out);
/**
* @brief Returns a native pixel format usable to convert from a native
* frame to a bridge frame with minimal data loss
*
* @param pix_fmt native pixel format
* @param out receives the compatible native pixel format (-1 if none)
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out is NULL
*/
int oakcommon_ffmpegutils_get_compatible_pixel_format(int pix_fmt,
int *out);
/**
* @brief Returns a bridge pixel format for a given native pixel format
*
* @param pix_fmt native pixel format
* @param channel_count OAKCOMMON_RGB_CHANNEL_COUNT or
* OAKCOMMON_RGBA_CHANNEL_COUNT
* @param out receives the bridge pixel format (fb_pix_fmt_none if none)
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out is NULL
*/
int oakcommon_ffmpegutils_get_ffmpeg_pixel_format(int pix_fmt,
int channel_count,
int *out);
/**
* @brief Returns a native sample format for a given bridge sample format
*
* @param smp_fmt bridge sample format
* @param out receives the native sample format (-1 if unknown)
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out is NULL
*/
int oakcommon_ffmpegutils_get_native_sample_format(int smp_fmt, int *out);
/**
* @brief Returns a bridge sample format for a given native sample format
*
* @param smp_fmt native sample format
* @param out receives the bridge sample format (fb_sample_fmt_none if none)
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out is NULL
*/
int oakcommon_ffmpegutils_get_ffmpeg_sample_format(int smp_fmt, int *out);
/**
* @brief Converts a "JPEG" full-range bridge pixel format to its regular
* counterpart
*
* @param pix_fmt bridge pixel format
* @param out receives the regular-range format (unchanged if not JPEG)
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out is NULL
*/
int oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(int pix_fmt,
int *out);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_FFMPEGUTILS_H
+153
View File
@@ -0,0 +1,153 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_FILEFUNCTIONS_H
#define OAK_EDITOR_FILEFUNCTIONS_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Opaque handle for the filefunctions family
*
* File functions are stateless; the handle only exists to keep the C API
* shape uniform across oakcommon families.
*/
typedef struct OakCommonFileFunctions OakCommonFileFunctions;
/**
* @brief Creates a filefunctions handle
*
* @return A new handle, or NULL on failure.
*/
OakCommonFileFunctions *oakcommon_filefunctions_init(void);
/**
* @brief Destroys a filefunctions handle (NULL is a no-op)
*/
void oakcommon_filefunctions_free(OakCommonFileFunctions *self);
/**
* @brief Returns a deterministic identifier string for a file
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code. Returns an empty string (required size 1) if
* the file does not exist.
*/
int oakcommon_filefunctions_get_unique_file_identifier(
OakCommonFileFunctions *self, const char *filename, char *buf,
int buf_size);
int oakcommon_filefunctions_get_configuration_location(
OakCommonFileFunctions *self, char *buf, int buf_size);
int oakcommon_filefunctions_get_application_path(
OakCommonFileFunctions *self, char *buf, int buf_size);
int oakcommon_filefunctions_get_temp_file_path(
OakCommonFileFunctions *self, char *buf, int buf_size);
int oakcommon_filefunctions_get_auto_recovery_root(
OakCommonFileFunctions *self, char *buf, int buf_size);
/**
* @brief Checks whether `source` can be copied to `dest` without
* overwriting anything
*
* @param out Receives 1 (safe) or 0 (would overwrite).
*/
int oakcommon_filefunctions_can_copy_directory_without_overwriting(
OakCommonFileFunctions *self, const char *source, const char *dest,
int *out);
/**
* @brief Recursively copies a directory
*/
int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
const char *source,
const char *dest, int overwrite);
/**
* @brief Checks whether a directory exists, optionally creating it
*
* @param out Receives 1 (valid) or 0 (invalid).
*/
int oakcommon_filefunctions_directory_is_valid(
OakCommonFileFunctions *self, const char *dir,
int try_to_create_if_not_exists, int *out);
/**
* @brief Ensures a filename ends with the given extension (no dot)
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_filefunctions_ensure_filename_extension(
OakCommonFileFunctions *self, const char *filename,
const char *extension, char *buf, int buf_size);
/**
* @brief Reads an entire file into a string
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code. Returns an empty string (required size 1) if
* the file cannot be read.
*/
int oakcommon_filefunctions_read_file_as_string(
OakCommonFileFunctions *self, const char *filename, char *buf,
int buf_size);
/**
* @brief Returns a non-existing temporary variant of `original`
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_filefunctions_get_safe_temporary_filename(
OakCommonFileFunctions *self, const char *original, char *buf,
int buf_size);
/**
* @brief Renames `from` to `to`, deleting `to` first if it exists
*
* @param out Receives 1 (renamed) or 0 (failed).
*/
int oakcommon_filefunctions_rename_file_allow_overwrite(
OakCommonFileFunctions *self, const char *from, const char *to,
int *out);
/**
* @brief Appends the platform executable suffix (".exe" on Windows)
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_filefunctions_get_formatted_executable_for_platform(
OakCommonFileFunctions *self, const char *unformatted, char *buf,
int buf_size);
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_FILEFUNCTIONS_H
+117
View File
@@ -0,0 +1,117 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_MISCUTILS_H
#define OAK_EDITOR_MISCUTILS_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Minimum decibel value used by the editor (-200.0 dB).
*
* In basically all circumstances, this calculates to 0.0 linear.
*/
#define OAKCOMMON_DECIBEL_MINIMUM (-200.0)
/**
* @brief Convert a linear amplitude to decibels.
*
* A linear value of 0.0 (or anything yielding an infinite result) returns
* OAKCOMMON_DECIBEL_MINIMUM.
*
* @param linear Linear amplitude.
* @param out_db Receives the decibel value. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_db is NULL.
*/
int oakcommon_decibel_from_linear(double linear, double *out_db);
/**
* @brief Convert decibels to a linear amplitude.
*
* Results below 1e-6 are clamped to 0.0.
*
* @param db Decibel value.
* @param out_linear Receives the linear amplitude. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_linear is NULL.
*/
int oakcommon_decibel_to_linear(double db, double *out_linear);
/**
* @brief Convert a logarithmic slider position (0..1) to decibels.
*
* @param logarithmic Logarithmic position.
* @param out_db Receives the decibel value. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_db is NULL.
*/
int oakcommon_decibel_from_logarithmic(double logarithmic, double *out_db);
/**
* @brief Convert decibels to a logarithmic slider position (0..1).
*
* @param db Decibel value.
* @param out_logarithmic Receives the logarithmic position. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_logarithmic
* is NULL.
*/
int oakcommon_decibel_to_logarithmic(double db, double *out_logarithmic);
/**
* @brief Convert a linear amplitude directly to a logarithmic position.
*
* @param linear Linear amplitude.
* @param out_logarithmic Receives the logarithmic position. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_logarithmic
* is NULL.
*/
int oakcommon_decibel_linear_to_logarithmic(double linear,
double *out_logarithmic);
/**
* @brief Convert a logarithmic position directly to a linear amplitude.
*
* @param logarithmic Logarithmic position.
* @param out_linear Receives the linear amplitude. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_linear is NULL.
*/
int oakcommon_decibel_logarithmic_to_linear(double logarithmic,
double *out_linear);
/**
* @brief Linearly interpolate between a and b using t.
*
* t should be between 0.0 and 1.0: 0.0 returns a, 1.0 returns b.
*
* @param a Start value.
* @param b End value.
* @param t Interpolation factor.
* @param out_value Receives the interpolated value. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_value is NULL.
*/
int oakcommon_lerp(double a, double b, double t, double *out_value);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_MISCUTILS_H
+88
View File
@@ -0,0 +1,88 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_OCIOUTILS_H
#define OAK_EDITOR_OCIOUTILS_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Native pixel format codes, mirroring olive::core::PixelFormat
*
* The numeric values must stay in sync with
* olive/core/render/pixelformat.h (Format enum).
*/
enum OakCommonPixelFormat {
OAKCOMMON_PIXEL_FORMAT_INVALID = -1, /**< Invalid/unknown format. */
OAKCOMMON_PIXEL_FORMAT_U8 = 0, /**< 8-bit unsigned integer. */
OAKCOMMON_PIXEL_FORMAT_U10 = 1, /**< 10-bit unsigned integer. */
OAKCOMMON_PIXEL_FORMAT_U16 = 2, /**< 16-bit unsigned integer. */
OAKCOMMON_PIXEL_FORMAT_F16 = 3, /**< 16-bit float (half). */
OAKCOMMON_PIXEL_FORMAT_F32 = 4, /**< 32-bit float. */
OAKCOMMON_PIXEL_FORMAT_COUNT = 5 /**< Sentinel, not a valid format. */
};
/**
* @brief OpenColorIO bit depth codes, matching OCIO::BitDepth
*
* Returned through the out parameter of
* oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format() as a plain
* int so that callers never see OCIO types. Values match the OCIO
* BitDepth enum: 0 = unknown, 1 = uint8, 2 = uint10, 3 = uint12,
* 4 = uint14, 5 = uint16, 6 = uint32, 7 = f16, 8 = f32 (OCIO v2).
*/
typedef struct OakCommonOCIOUtils OakCommonOCIOUtils;
/**
* @brief Creates an OCIOUtils handle
*
* The object is stateless; the handle exists only to satisfy the C API
* lifetime contract. Returns NULL on failure.
*/
OakCommonOCIOUtils *oakcommon_ocioutils_init(void);
/**
* @brief Destroys an OCIOUtils handle; no-op on NULL
*/
void oakcommon_ocioutils_free(OakCommonOCIOUtils *self);
/**
* @brief Maps a native pixel format to an OCIO bit depth
*
* @param self handle from oakcommon_ocioutils_init()
* @param pixel_format one of the OakCommonPixelFormat values
* @param out_bit_depth receives the OCIO bit depth as an int (see the
* OakCommonOCIOUtils typedef documentation); set to 0
* (BIT_DEPTH_UNKNOWN) for invalid formats
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or
* out_bit_depth is NULL or pixel_format is not a known code
*/
int oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
OakCommonOCIOUtils *self, int pixel_format, int *out_bit_depth);
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_OCIOUTILS_H
+109
View File
@@ -0,0 +1,109 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_OIIOUTILS_H
#define OAK_EDITOR_OIIOUTILS_H
#include "common/error.h"
/* Reuses the OakCommonPixelFormat enum (mirroring
* olive::core::PixelFormat) rather than redefining it here. */
#include "common/ocioutils.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief OIIO base type codes, matching OIIO::TypeDesc::BASETYPE
*
* Passed through the C API as plain ints so callers never see OIIO
* types. Values match the OIIO TypeDesc::BASETYPE enum: 0 = UNKNOWN,
* 1 = NONE, 2 = UINT8, 3 = INT8, 4 = UINT16, 5 = INT16, 6 = UINT32,
* 7 = INT32, 8 = UINT64, 9 = INT64, 10 = HALF, 11 = FLOAT, 12 = DOUBLE,
* 13 = STRING, 14 = PTR. OIIO >= 2.5 adds 15 = USTRINGHASH and shifts
* LASTBASE, so the exact LASTBASE value is version-dependent.
*/
typedef struct OakCommonOIIOUtils OakCommonOIIOUtils;
/**
* @brief Creates an OIIOUtils handle
*
* The object is stateless; the handle exists only to satisfy the C API
* lifetime contract. Returns NULL on failure.
*/
OakCommonOIIOUtils *oakcommon_oiioutils_init(void);
/**
* @brief Destroys an OIIOUtils handle; no-op on NULL
*/
void oakcommon_oiioutils_free(OakCommonOIIOUtils *self);
/**
* @brief Maps a native pixel format to an OIIO base type
*
* @param self handle from oakcommon_oiioutils_init()
* @param pixel_format one of the OakCommonPixelFormat values
* @param out_base_type receives the OIIO base type as an int (see the
* OakCommonOIIOUtils typedef documentation); set to 0
* (TypeDesc::UNKNOWN) for invalid or unmappable formats
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or out_base_type
* is NULL or pixel_format is not a known code
*/
int oakcommon_oiioutils_get_oiio_base_type_from_format(
OakCommonOIIOUtils *self, int pixel_format, int *out_base_type);
/**
* @brief Maps an OIIO base type to a native pixel format
*
* @param self handle from oakcommon_oiioutils_init()
* @param base_type an OIIO TypeDesc::BASETYPE value as an int
* @param out_pixel_format receives one of the OakCommonPixelFormat
* values; set to OAKCOMMON_PIXEL_FORMAT_INVALID for unknown or
* unmappable base types
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or
* out_pixel_format is NULL or base_type is negative
*/
int oakcommon_oiioutils_get_format_from_oiio_basetype(
OakCommonOIIOUtils *self, int base_type, int *out_pixel_format);
/**
* @brief Converts a PixelAspectRatio attribute value to a rational
*
* Flattened form of the former ImageSpec-based helper: the caller reads
* the "PixelAspectRatio" float attribute from the OIIO::ImageSpec
* (defaulting to 1.0 when absent) and passes it here.
*
* @param self handle from oakcommon_oiioutils_init()
* @param pixel_aspect_ratio the PixelAspectRatio attribute value
* @param out_numerator receives the rational numerator
* @param out_denominator receives the rational denominator
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self, out_numerator
* or out_denominator is NULL
*/
int oakcommon_oiioutils_get_pixel_aspect_ratio(
OakCommonOIIOUtils *self, double pixel_aspect_ratio, int *out_numerator,
int *out_denominator);
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_OIIOUTILS_H
+58
View File
@@ -0,0 +1,58 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_POWER_H
#define OAK_EDITOR_POWER_H
#include <stdint.h>
#include "error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Round `value` up to the next power of two
*
* Stateless pure function, no handle required. Writes the result to `out`.
*
* @param value Input value.
* @param out Receives the rounded value. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if `out` is NULL.
*/
int oakcommon_power_ceil_to_power_of_2(uint32_t value, uint32_t *out);
/**
* @brief Round `value` down to the nearest power of two
*
* Stateless pure function, no handle required. Writes the result to `out`.
*
* @param value Input value.
* @param out Receives the rounded value. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if `out` is NULL.
*/
int oakcommon_power_floor_to_power_of_2(uint32_t value, uint32_t *out);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_POWER_H
+67
View File
@@ -0,0 +1,67 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_COMMON_QTUTILS_H
#define OAK_COMMON_QTUTILS_H
#include <stdint.h>
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Convert a pointer to an integer value
*
* @param ptr Pointer to convert (may be NULL, yielding 0).
* @param out_value Receives the integer representation of ptr.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_value is NULL.
*/
int oakcommon_qtutils_ptr_to_value(void *ptr, uint64_t *out_value);
/**
* @brief Convert an integer produced by oakcommon_qtutils_ptr_to_value() back to a pointer
*
* @param value Integer representation of a pointer.
* @param out_ptr Receives the decoded pointer.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_ptr is NULL.
*/
int oakcommon_qtutils_value_to_ptr(uint64_t value, void **out_ptr);
/**
* @brief Get the creation (birth) time of a file as seconds since the Unix epoch
*
* Falls back to the last metadata change time when the filesystem does not
* record birth times.
*
* @param path NUL-terminated filesystem path.
* @param out_secs Receives the creation time in seconds since the epoch.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID for NULL arguments,
* OAKCOMMON_E_NOT_FOUND if the file does not exist or cannot be stat'ed.
*/
int oakcommon_qtutils_get_creation_date(const char *path, int64_t *out_secs);
#ifdef __cplusplus
}
#endif
#endif // OAK_COMMON_QTUTILS_H
+156
View File
@@ -0,0 +1,156 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_XMLUTILS_H
#define OAK_EDITOR_XMLUTILS_H
#include "common/error.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct OakCommonXmlReader OakCommonXmlReader;
typedef struct OakCommonXmlWriter OakCommonXmlWriter;
/**
* @brief Create a streaming XML reader over a complete document.
*
* @param data NUL-terminated XML text. Must not be NULL.
* @return A new reader, or NULL on failure (NULL data, out of memory).
*/
OakCommonXmlReader *oakcommon_xml_reader_init(const char *data);
/**
* @brief Destroy a reader. No-op on NULL.
*/
void oakcommon_xml_reader_free(OakCommonXmlReader *reader);
/**
* @brief Advance until the next start element, an end element, or the end
* of the document.
*
* @param reader Reader handle.
* @param found Out: 1 if positioned on a start element, 0 otherwise.
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
int *found);
/**
* @brief Name of the current element token.
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
int buf_size);
/**
* @brief Read the concatenated character data of the current element.
*
* Must be called on a start element; consumes up to the matching end
* element.
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
char *buf, int buf_size);
/**
* @brief Skip the current element and all of its children.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_skip_current_element(OakCommonXmlReader *reader);
/**
* @brief Number of attributes on the current start element.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
int *count);
/**
* @brief Name of the attribute at @p index on the current start element.
*
* @return Required buffer size in bytes (including NUL), OAKCOMMON_E_NOT_FOUND
* if @p index is out of range, or another negative OAKCOMMON_E_* code.
*/
int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
char *buf, int buf_size);
/**
* @brief Value of the attribute at @p index on the current start element.
*
* @return Required buffer size in bytes (including NUL), OAKCOMMON_E_NOT_FOUND
* if @p index is out of range, or another negative OAKCOMMON_E_* code.
*/
int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
int index, char *buf, int buf_size);
/**
* @brief Whether the document failed to parse.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_has_error(OakCommonXmlReader *reader,
int *has_error);
/**
* @brief Create a streaming XML writer.
*
* @return A new writer, or NULL on failure.
*/
OakCommonXmlWriter *oakcommon_xml_writer_init(void);
/**
* @brief Destroy a writer. No-op on NULL.
*/
void oakcommon_xml_writer_free(OakCommonXmlWriter *writer);
int oakcommon_xml_writer_write_start_element(OakCommonXmlWriter *writer,
const char *name);
int oakcommon_xml_writer_write_attribute(OakCommonXmlWriter *writer,
const char *name, const char *value);
int oakcommon_xml_writer_write_characters(OakCommonXmlWriter *writer,
const char *text);
int oakcommon_xml_writer_write_text_element(OakCommonXmlWriter *writer,
const char *name,
const char *text);
int oakcommon_xml_writer_write_end_element(OakCommonXmlWriter *writer);
int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer);
/**
* @brief The document written so far.
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_xml_writer_output(OakCommonXmlWriter *writer, char *buf,
int buf_size);
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_XMLUTILS_H
+1
View File
@@ -0,0 +1 @@
add_subdirectory(common)
+6
View File
@@ -0,0 +1,6 @@
add_subdirectory(src)
add_subdirectory(c_api)
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
+14
View File
@@ -0,0 +1,14 @@
target_sources(oakcommon PRIVATE
commandlineparser.cpp
current.cpp
debug.cpp
dropworkflowbehavior.cpp
ffmpegutils.cpp
filefunctions.cpp
miscutils.cpp
ocioutils.cpp
oiioutils.cpp
power.cpp
qtutils.cpp
xmlutils.cpp
)
+282
View File
@@ -0,0 +1,282 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/commandlineparser.h"
#include <cstring>
#include <new>
#include <string>
#include <vector>
#include "../src/commandlineparser.h"
struct OakCommonCommandLineParser {
CommandLineParser impl;
};
struct OakCommonCommandLineOption {
CommandLineParser::Option *option;
};
struct OakCommonCommandLinePositionalArgument {
CommandLineParser::PositionalArgument *argument;
};
OakCommonCommandLineParser *oakcommon_commandlineparser_init(void)
{
try {
return new (std::nothrow) OakCommonCommandLineParser();
} catch (...) {
return NULL;
}
}
void oakcommon_commandlineparser_free(OakCommonCommandLineParser *parser)
{
delete parser;
}
int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
const char *name,
const char *version)
{
if (!parser || !name) {
return OAKCOMMON_E_INVALID;
}
try {
parser->impl.set_app_info(name, version ? version : "");
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineparser_add_option(
OakCommonCommandLineParser *parser, const char *const *names, int name_count,
const char *description, int takes_arg, const char *arg_placeholder,
int hidden, OakCommonCommandLineOption **out_option)
{
if (!parser || !names || name_count <= 0) {
return OAKCOMMON_E_INVALID;
}
try {
std::vector<std::string> strings;
strings.reserve(static_cast<size_t>(name_count));
for (int i = 0; i < name_count; i++) {
if (!names[i]) {
return OAKCOMMON_E_INVALID;
}
strings.emplace_back(names[i]);
}
const CommandLineParser::Option *option = parser->impl.add_option(
strings, description ? description : "", takes_arg != 0,
arg_placeholder ? arg_placeholder : "", hidden != 0);
if (out_option) {
auto *handle =
new (std::nothrow) OakCommonCommandLineOption();
if (!handle) {
return OAKCOMMON_E_NOMEM;
}
handle->option = const_cast<CommandLineParser::Option *>(option);
*out_option = handle;
}
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineparser_add_positional_argument(
OakCommonCommandLineParser *parser, const char *name,
const char *description, int required,
OakCommonCommandLinePositionalArgument **out_argument)
{
if (!parser || !name) {
return OAKCOMMON_E_INVALID;
}
try {
const CommandLineParser::PositionalArgument *argument =
parser->impl.add_positional_argument(
name, description ? description : "", required != 0);
if (out_argument) {
auto *handle =
new (std::nothrow) OakCommonCommandLinePositionalArgument();
if (!handle) {
return OAKCOMMON_E_NOMEM;
}
handle->argument =
const_cast<CommandLineParser::PositionalArgument *>(argument);
*out_argument = handle;
}
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
const char *const *argv, int argc)
{
if (!parser || !argv || argc < 0) {
return OAKCOMMON_E_INVALID;
}
try {
std::vector<std::string> args;
args.reserve(static_cast<size_t>(argc));
for (int i = 0; i < argc; i++) {
args.emplace_back(argv[i] ? argv[i] : "");
}
parser->impl.process(args);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
const char *filename)
{
if (!parser || !filename) {
return OAKCOMMON_E_INVALID;
}
try {
parser->impl.print_help(filename);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineoption_is_set(OakCommonCommandLineOption *option,
bool *is_set)
{
if (!option || !option->option || !is_set) {
return OAKCOMMON_E_INVALID;
}
try {
*is_set = option->option->is_set();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
/**
* @brief Shared two-stage string getter.
*
* Returns the required buffer size in bytes (including the terminating
* NUL) as a non-negative value, or a negative error code.
*/
static int copy_setting(const std::string &value, char *buf, int buf_size)
{
int required = static_cast<int>(value.size()) + 1;
if (buf && buf_size > 0) {
size_t copy_len = value.size();
if (copy_len > static_cast<size_t>(buf_size) - 1) {
copy_len = static_cast<size_t>(buf_size) - 1;
}
memcpy(buf, value.data(), copy_len);
buf[copy_len] = '\0';
}
return required;
}
int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
char *buf, int buf_size)
{
if (!option || !option->option) {
return OAKCOMMON_E_INVALID;
}
try {
return copy_setting(option->option->get_setting(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
const char *value)
{
if (!option || !option->option || !value) {
return OAKCOMMON_E_INVALID;
}
try {
option->option->set_setting(value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlinepositionalargument_get_setting(
OakCommonCommandLinePositionalArgument *argument, char *buf, int buf_size)
{
if (!argument || !argument->argument) {
return OAKCOMMON_E_INVALID;
}
try {
return copy_setting(argument->argument->get_setting(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlinepositionalargument_set_setting(
OakCommonCommandLinePositionalArgument *argument, const char *value)
{
if (!argument || !argument->argument || !value) {
return OAKCOMMON_E_INVALID;
}
try {
argument->argument->set_setting(value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option)
{
delete option;
}
void oakcommon_commandlinepositionalargument_free(
OakCommonCommandLinePositionalArgument *argument)
{
delete argument;
}
+149
View File
@@ -0,0 +1,149 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/current.h"
#include "../src/current.h"
struct OakCommonCurrent {
Current *current;
};
OakCommonCurrent *oakcommon_current_instance(void)
{
static OakCommonCurrent handle = { &Current::get_instance() };
return &handle;
}
void oakcommon_current_free(OakCommonCurrent *self)
{
// No-op: the handle wraps a process-wide singleton.
(void)self;
}
static int current_set(Current *current,
void (Current::*set_fn)(std::shared_ptr<void>),
void *obj, OakCommonDestroyFn destroy)
{
try {
std::shared_ptr<void> value;
if (obj) {
if (destroy)
value = std::shared_ptr<void>(obj, destroy);
else
value = std::shared_ptr<void>(
obj, [](void *) {});
}
(current->*set_fn)(value);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
static int current_get(Current *current,
std::shared_ptr<void> (Current::*get_fn)() const,
void **out)
{
try {
*out = (current->*get_fn)().get();
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_current_set_video_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
{
if (!self)
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_current_video_params,
obj, destroy);
}
int oakcommon_current_set_audio_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
{
if (!self)
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_current_audio_params,
obj, destroy);
}
int oakcommon_current_set_plugin_host(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
{
if (!self)
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_plugin_host, obj,
destroy);
}
int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
{
if (!self)
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_plugin_cache, obj,
destroy);
}
int oakcommon_current_get_video_params(OakCommonCurrent *self, void **out)
{
if (!self || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::current_video_params,
out);
}
int oakcommon_current_get_audio_params(OakCommonCurrent *self, void **out)
{
if (!self || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::current_audio_params,
out);
}
int oakcommon_current_get_plugin_host(OakCommonCurrent *self, void **out)
{
if (!self || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::plugin_host, out);
}
int oakcommon_current_get_plugin_cache(OakCommonCurrent *self, void **out)
{
if (!self || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::plugin_cache, out);
}
int oakcommon_current_is_interactive(OakCommonCurrent *self, int *out)
{
if (!self || !out)
return OAKCOMMON_E_INVALID;
try {
*out = self->current->interactive() ? 1 : 0;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
+45
View File
@@ -0,0 +1,45 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/debug.h"
#include "../src/debug.h"
int oakcommon_debug_log(int level, const char *msg)
{
if (!msg)
return OAKCOMMON_E_INVALID;
try {
olive::debug_handler(level, msg);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_debug_level_name(int level, char *buf, int buf_size)
{
try {
return olive::debug_level_name(level, buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+66
View File
@@ -0,0 +1,66 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/dropworkflowbehavior.h"
#include <cstring>
#include "../src/dropworkflowbehavior.h"
static const char *behavior_name(int value)
{
switch (value) {
case olive::k_dws_ask:
return "ASK";
case olive::k_dws_auto:
return "AUTO";
case olive::k_dws_manual:
return "MANUAL";
case olive::k_dws_disable:
return "DISABLE";
}
return "UNKNOWN";
}
int oakcommon_drop_workflow_behavior_is_valid(int value)
{
switch (value) {
case olive::k_dws_ask:
case olive::k_dws_auto:
case olive::k_dws_manual:
case olive::k_dws_disable:
return 1;
}
return 0;
}
int oakcommon_drop_workflow_behavior_name(int value, char *buf, int buf_size)
{
try {
const char *name = behavior_name(value);
int needed = (int)strlen(name) + 1;
if (buf && buf_size >= needed)
memcpy(buf, name, needed);
return needed;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+118
View File
@@ -0,0 +1,118 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/ffmpegutils.h"
#include "../src/ffmpegutils.h"
int oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(
int pix_fmt, int maximum_pix_fmt, int *out)
{
if (!out)
return OAKCOMMON_E_INVALID;
try {
*out = olive::FFmpegUtils::get_compatible_bridge_pixel_format(
pix_fmt,
static_cast<olive::core::PixelFormat::Format>(
maximum_pix_fmt));
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_ffmpegutils_get_compatible_pixel_format(int pix_fmt, int *out)
{
if (!out)
return OAKCOMMON_E_INVALID;
try {
*out = olive::FFmpegUtils::get_compatible_pixel_format(
static_cast<olive::core::PixelFormat::Format>(pix_fmt));
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_ffmpegutils_get_ffmpeg_pixel_format(int pix_fmt,
int channel_count, int *out)
{
if (!out)
return OAKCOMMON_E_INVALID;
try {
*out = olive::FFmpegUtils::get_ffmpeg_pixel_format(
static_cast<olive::core::PixelFormat::Format>(pix_fmt),
channel_count);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_ffmpegutils_get_native_sample_format(int smp_fmt, int *out)
{
if (!out)
return OAKCOMMON_E_INVALID;
try {
*out = olive::FFmpegUtils::get_native_sample_format(smp_fmt);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_ffmpegutils_get_ffmpeg_sample_format(int smp_fmt, int *out)
{
if (!out)
return OAKCOMMON_E_INVALID;
try {
*out = olive::FFmpegUtils::get_ffmpeg_sample_format(
static_cast<olive::core::SampleFormat::Format>(smp_fmt));
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(int pix_fmt,
int *out)
{
if (!out)
return OAKCOMMON_E_INVALID;
try {
*out = olive::FFmpegUtils::convert_jpeg_space_to_regular_space(
pix_fmt);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
+295
View File
@@ -0,0 +1,295 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/filefunctions.h"
#include <cstring>
#include <string>
#include "../src/filefunctions.h"
struct OakCommonFileFunctions {
int unused; /**< Stateless family; handle kept for API uniformity. */
};
namespace
{
/**
* @brief Copies `value` into the caller's two-stage buffer
*
* @return Required buffer size in bytes (including the terminating NUL).
* Always non-negative; error codes are handled by the callers.
*/
int write_string_result(const std::string &value, char *buf, int buf_size)
{
int required = static_cast<int>(value.size()) + 1;
if (buf != nullptr && buf_size >= required) {
memcpy(buf, value.c_str(), required);
}
return required;
}
bool is_valid_string_out(const char *buf, int buf_size)
{
return buf_size >= 0 && (buf_size == 0 || buf != nullptr);
}
} // namespace
OakCommonFileFunctions *oakcommon_filefunctions_init(void)
{
try {
return new OakCommonFileFunctions{0};
} catch (...) {
return nullptr;
}
}
void oakcommon_filefunctions_free(OakCommonFileFunctions *self)
{
delete self;
}
int oakcommon_filefunctions_get_unique_file_identifier(
OakCommonFileFunctions *self, const char *filename, char *buf,
int buf_size)
{
if (self == nullptr || filename == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(
FileFunctions::get_unique_file_identifier(filename), buf,
buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_get_configuration_location(
OakCommonFileFunctions *self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(
FileFunctions::get_configuration_location(), buf,
buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_get_application_path(
OakCommonFileFunctions *self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(FileFunctions::get_application_path(),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_get_temp_file_path(
OakCommonFileFunctions *self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(FileFunctions::get_temp_file_path(),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_get_auto_recovery_root(
OakCommonFileFunctions *self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(
FileFunctions::get_auto_recovery_root(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_can_copy_directory_without_overwriting(
OakCommonFileFunctions *self, const char *source, const char *dest,
int *out)
{
if (self == nullptr || source == nullptr || dest == nullptr ||
out == nullptr) {
return OAKCOMMON_E_INVALID;
}
try {
*out = FileFunctions::can_copy_directory_without_overwriting(
source, dest)
? 1
: 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
const char *source,
const char *dest, int overwrite)
{
if (self == nullptr || source == nullptr || dest == nullptr) {
return OAKCOMMON_E_INVALID;
}
try {
FileFunctions::copy_directory(source, dest, overwrite != 0);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_directory_is_valid(
OakCommonFileFunctions *self, const char *dir,
int try_to_create_if_not_exists, int *out)
{
if (self == nullptr || dir == nullptr || out == nullptr) {
return OAKCOMMON_E_INVALID;
}
try {
*out = FileFunctions::directory_is_valid(
dir, try_to_create_if_not_exists != 0)
? 1
: 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_ensure_filename_extension(
OakCommonFileFunctions *self, const char *filename,
const char *extension, char *buf, int buf_size)
{
if (self == nullptr || filename == nullptr || extension == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(
FileFunctions::ensure_filename_extension(filename,
extension),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_read_file_as_string(
OakCommonFileFunctions *self, const char *filename, char *buf,
int buf_size)
{
if (self == nullptr || filename == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(
FileFunctions::read_file_as_string(filename), buf,
buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_get_safe_temporary_filename(
OakCommonFileFunctions *self, const char *original, char *buf,
int buf_size)
{
if (self == nullptr || original == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(
FileFunctions::get_safe_temporary_filename(original),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_rename_file_allow_overwrite(
OakCommonFileFunctions *self, const char *from, const char *to,
int *out)
{
if (self == nullptr || from == nullptr || to == nullptr ||
out == nullptr) {
return OAKCOMMON_E_INVALID;
}
try {
*out = FileFunctions::rename_file_allow_overwrite(from, to) ? 1
: 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_filefunctions_get_formatted_executable_for_platform(
OakCommonFileFunctions *self, const char *unformatted, char *buf,
int buf_size)
{
if (self == nullptr || unformatted == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
try {
return write_string_result(
FileFunctions::get_formatted_executable_for_platform(
unformatted),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+117
View File
@@ -0,0 +1,117 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/miscutils.h"
#include "../src/decibel.h"
#include "../src/lerp.h"
int oakcommon_decibel_from_linear(double linear, double *out_db)
{
if (!out_db) {
return OAKCOMMON_E_INVALID;
}
try {
*out_db = olive::Decibel::from_linear(linear);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_decibel_to_linear(double db, double *out_linear)
{
if (!out_linear) {
return OAKCOMMON_E_INVALID;
}
try {
*out_linear = olive::Decibel::to_linear(db);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_decibel_from_logarithmic(double logarithmic, double *out_db)
{
if (!out_db) {
return OAKCOMMON_E_INVALID;
}
try {
*out_db = olive::Decibel::from_logarithmic(logarithmic);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_decibel_to_logarithmic(double db, double *out_logarithmic)
{
if (!out_logarithmic) {
return OAKCOMMON_E_INVALID;
}
try {
*out_logarithmic = olive::Decibel::to_logarithmic(db);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_decibel_linear_to_logarithmic(double linear,
double *out_logarithmic)
{
if (!out_logarithmic) {
return OAKCOMMON_E_INVALID;
}
try {
*out_logarithmic = olive::Decibel::linear_to_logarithmic(linear);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_decibel_logarithmic_to_linear(double logarithmic,
double *out_linear)
{
if (!out_linear) {
return OAKCOMMON_E_INVALID;
}
try {
*out_linear = olive::Decibel::logarithmic_to_linear(logarithmic);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_lerp(double a, double b, double t, double *out_value)
{
if (!out_value) {
return OAKCOMMON_E_INVALID;
}
try {
*out_value = lerp(a, b, t);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+62
View File
@@ -0,0 +1,62 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/ocioutils.h"
#include <new>
#include "../src/ocioutils.h"
struct OakCommonOCIOUtils {
int unused; /**< Stateless; only the address matters. */
};
OakCommonOCIOUtils *oakcommon_ocioutils_init(void)
{
try {
return new (std::nothrow) OakCommonOCIOUtils{};
} catch (...) {
return NULL;
}
}
void oakcommon_ocioutils_free(OakCommonOCIOUtils *self)
{
delete self;
}
int oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
OakCommonOCIOUtils *self, int pixel_format, int *out_bit_depth)
{
try {
if (self == NULL || out_bit_depth == NULL)
return OAKCOMMON_E_INVALID;
if (pixel_format < OAKCOMMON_PIXEL_FORMAT_INVALID ||
pixel_format >= OAKCOMMON_PIXEL_FORMAT_COUNT)
return OAKCOMMON_E_INVALID;
ocio::BitDepth depth = OCIOUtils::get_ocio_bit_depth_from_pixel_format(
static_cast<olive::core::PixelFormat::Format>(pixel_format));
*out_bit_depth = static_cast<int>(depth);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+100
View File
@@ -0,0 +1,100 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/oiioutils.h"
#include <new>
#include "../src/oiioutils.h"
struct OakCommonOIIOUtils {
int unused; /**< Stateless; only the address matters. */
};
OakCommonOIIOUtils *oakcommon_oiioutils_init(void)
{
try {
return new (std::nothrow) OakCommonOIIOUtils{};
} catch (...) {
return NULL;
}
}
void oakcommon_oiioutils_free(OakCommonOIIOUtils *self)
{
delete self;
}
int oakcommon_oiioutils_get_oiio_base_type_from_format(
OakCommonOIIOUtils *self, int pixel_format, int *out_base_type)
{
try {
if (self == NULL || out_base_type == NULL)
return OAKCOMMON_E_INVALID;
if (pixel_format < OAKCOMMON_PIXEL_FORMAT_INVALID ||
pixel_format >= OAKCOMMON_PIXEL_FORMAT_COUNT)
return OAKCOMMON_E_INVALID;
OIIO::TypeDesc::BASETYPE base_type =
OIIOUtils::get_oiio_base_type_from_format(
static_cast<olive::core::PixelFormat::Format>(pixel_format));
*out_base_type = static_cast<int>(base_type);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_oiioutils_get_format_from_oiio_basetype(
OakCommonOIIOUtils *self, int base_type, int *out_pixel_format)
{
try {
if (self == NULL || out_pixel_format == NULL)
return OAKCOMMON_E_INVALID;
if (base_type < 0 || base_type >= OIIO::TypeDesc::LASTBASE)
return OAKCOMMON_E_INVALID;
olive::core::PixelFormat format =
OIIOUtils::get_format_from_oiio_basetype(
static_cast<OIIO::TypeDesc::BASETYPE>(base_type));
*out_pixel_format = static_cast<int>(format);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_oiioutils_get_pixel_aspect_ratio(
OakCommonOIIOUtils *self, double pixel_aspect_ratio, int *out_numerator,
int *out_denominator)
{
try {
if (self == NULL || out_numerator == NULL || out_denominator == NULL)
return OAKCOMMON_E_INVALID;
olive::core::Rational par =
olive::core::Rational::from_double(pixel_aspect_ratio);
*out_numerator = par.numerator();
*out_denominator = par.denominator();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+53
View File
@@ -0,0 +1,53 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "../../include/common/power.h"
#include "../src/power.h"
int oakcommon_power_ceil_to_power_of_2(uint32_t value, uint32_t *out)
{
try {
if (!out) {
return OAKCOMMON_E_INVALID;
}
*out = olive::ceil_to_power_of_2(value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_power_floor_to_power_of_2(uint32_t value, uint32_t *out)
{
try {
if (!out) {
return OAKCOMMON_E_INVALID;
}
*out = olive::floor_to_power_of_2(value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+74
View File
@@ -0,0 +1,74 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/qtutils.h"
#include "../src/qtutils.h"
int oakcommon_qtutils_ptr_to_value(void *ptr, uint64_t *out_value)
{
if (!out_value) {
return OAKCOMMON_E_INVALID;
}
try {
*out_value = static_cast<uint64_t>(olive::QtUtils::ptr_to_value(ptr));
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_qtutils_value_to_ptr(uint64_t value, void **out_ptr)
{
if (!out_ptr) {
return OAKCOMMON_E_INVALID;
}
try {
*out_ptr =
olive::QtUtils::value_to_ptr<void>(static_cast<uintptr_t>(value));
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_qtutils_get_creation_date(const char *path, int64_t *out_secs)
{
if (!path || !out_secs) {
return OAKCOMMON_E_INVALID;
}
try {
std::chrono::system_clock::time_point t =
olive::QtUtils::get_creation_date(std::filesystem::path(path));
if (t == std::chrono::system_clock::time_point{}) {
return OAKCOMMON_E_NOT_FOUND;
}
*out_secs = static_cast<int64_t>(
std::chrono::system_clock::to_time_t(t));
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+297
View File
@@ -0,0 +1,297 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/xmlutils.h"
#include <cstring>
#include <new>
#include "../src/xmlutils.h"
struct OakCommonXmlReader {
olive::XmlStreamReader reader;
std::string cached_text;
bool has_cached_text = false;
explicit OakCommonXmlReader(const char *data)
: reader(data)
{
}
};
struct OakCommonXmlWriter {
olive::XmlStreamWriter writer;
};
namespace
{
/**
* @brief Copy @p value into the two-stage string buffer.
*
* @return Required buffer size in bytes (including NUL). The buffer is only
* written to when it is large enough.
*/
int copy_string(const std::string &value, char *buf, int buf_size)
{
int needed = static_cast<int>(value.size()) + 1;
if (buf && buf_size >= needed) {
memcpy(buf, value.c_str(), needed);
}
return needed;
}
} // namespace
extern "C" {
OakCommonXmlReader *oakcommon_xml_reader_init(const char *data)
{
if (!data)
return nullptr;
try {
return new (std::nothrow) OakCommonXmlReader(data);
} catch (...) {
return nullptr;
}
}
void oakcommon_xml_reader_free(OakCommonXmlReader *reader)
{
delete reader;
}
int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
int *found)
{
if (!reader || !found)
return OAKCOMMON_E_INVALID;
try {
reader->has_cached_text = false;
*found = olive::xml_read_next_start_element(&reader->reader) ? 1 : 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
int buf_size)
{
if (!reader)
return OAKCOMMON_E_INVALID;
try {
return copy_string(reader->reader.name(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
char *buf, int buf_size)
{
if (!reader)
return OAKCOMMON_E_INVALID;
try {
// read_element_text() consumes the stream, so cache the result to
// keep the two-stage (size query then copy) buffer convention working.
if (!reader->has_cached_text) {
reader->cached_text = reader->reader.read_element_text();
reader->has_cached_text = true;
}
return copy_string(reader->cached_text, buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_skip_current_element(OakCommonXmlReader *reader)
{
if (!reader)
return OAKCOMMON_E_INVALID;
try {
reader->has_cached_text = false;
reader->reader.skip_current_element();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
int *count)
{
if (!reader || !count)
return OAKCOMMON_E_INVALID;
try {
*count = static_cast<int>(reader->reader.attributes().size());
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
char *buf, int buf_size)
{
if (!reader)
return OAKCOMMON_E_INVALID;
try {
const auto &attrs = reader->reader.attributes();
if (index < 0 || index >= static_cast<int>(attrs.size()))
return OAKCOMMON_E_NOT_FOUND;
return copy_string(attrs[index].name, buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
int index, char *buf, int buf_size)
{
if (!reader)
return OAKCOMMON_E_INVALID;
try {
const auto &attrs = reader->reader.attributes();
if (index < 0 || index >= static_cast<int>(attrs.size()))
return OAKCOMMON_E_NOT_FOUND;
return copy_string(attrs[index].value, buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_has_error(OakCommonXmlReader *reader,
int *has_error)
{
if (!reader || !has_error)
return OAKCOMMON_E_INVALID;
try {
*has_error = reader->reader.has_error() ? 1 : 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
OakCommonXmlWriter *oakcommon_xml_writer_init(void)
{
try {
return new (std::nothrow) OakCommonXmlWriter();
} catch (...) {
return nullptr;
}
}
void oakcommon_xml_writer_free(OakCommonXmlWriter *writer)
{
delete writer;
}
int oakcommon_xml_writer_write_start_element(OakCommonXmlWriter *writer,
const char *name)
{
if (!writer || !name)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_start_element(name);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_attribute(OakCommonXmlWriter *writer,
const char *name, const char *value)
{
if (!writer || !name || !value)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_attribute(name, value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_characters(OakCommonXmlWriter *writer,
const char *text)
{
if (!writer || !text)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_characters(text);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_text_element(OakCommonXmlWriter *writer,
const char *name,
const char *text)
{
if (!writer || !name || !text)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_text_element(name, text);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_end_element(OakCommonXmlWriter *writer)
{
if (!writer)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_end_element();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer)
{
if (!writer)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_end_document();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_output(OakCommonXmlWriter *writer, char *buf,
int buf_size)
{
if (!writer)
return OAKCOMMON_E_INVALID;
try {
return copy_string(writer->writer.output(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
} // extern "C"
+74
View File
@@ -0,0 +1,74 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive Team
# Modifications Copyright (C) 2025 mikesolar
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_library(oakcommon SHARED
commandlineparser.cpp
commandlineparser.h
current.cpp
current.h
debug.cpp
debug.h
decibel.h
define.h
dropworkflowbehavior.h
ffmpegutils.cpp
ffmpegutils.h
filefunctions.cpp
filefunctions.h
lerp.h
memorypool.h
ocioutils.cpp
ocioutils.h
oiioutils.cpp
oiioutils.h
power.h
qtutils.cpp
qtutils.h
threadsafemap.h
xmlutils.cpp
xmlutils.h
)
# In a full-tree build the repo root is CMAKE_SOURCE_DIR; a standalone
# build (see src/common/standalone) sets OAK_REPO_ROOT explicitly.
if(NOT DEFINED OAK_REPO_ROOT)
set(OAK_REPO_ROOT ${CMAKE_SOURCE_DIR})
endif()
target_include_directories(oakcommon PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${OAK_REPO_ROOT}/include
${OAK_REPO_ROOT}/third_party/openfx/include
${OCIO_INCLUDE_DIRS}
${OIIO_INCLUDE_DIRS}
)
# Public headers of oakcommon include the olive/core wrapper headers
# (pixelformat.h, sampleformat.h, rational.h) and the ffmpeg_bridge C ABI
# header. Their include dirs are PRIVATE to those targets, so re-export them.
target_include_directories(oakcommon PUBLIC
${OAK_REPO_ROOT}/core/include
${OAK_REPO_ROOT}/ffmpeg_bridge/include
)
target_link_libraries(oakcommon PUBLIC
olivecore
ffmpeg_bridge
EXPAT::EXPAT
${OCIO_LIBRARIES}
${OIIO_LIBRARIES}
)
+197
View File
@@ -0,0 +1,197 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "commandlineparser.h"
#include <cctype>
#include <cstdio>
#include <cstring>
CommandLineParser::~CommandLineParser()
{
for (const KnownOption &o : options_) {
delete o.option;
}
for (const KnownPositionalArgument &a : positional_args_) {
delete a.option;
}
}
const CommandLineParser::Option *
CommandLineParser::add_option(const std::vector<std::string> &strings,
const std::string &description, bool takes_arg,
const std::string &arg_placeholder, bool hidden)
{
Option *o = new Option();
options_.push_back(
{ strings, description, o, takes_arg, arg_placeholder, hidden });
return o;
}
const CommandLineParser::PositionalArgument *
CommandLineParser::add_positional_argument(const std::string &name,
const std::string &description,
bool required)
{
PositionalArgument *a = new PositionalArgument();
positional_args_.push_back({ name, description, a, required });
return a;
}
static bool string_equals_case_insensitive(const std::string &a,
const std::string &b)
{
if (a.size() != b.size()) {
return false;
}
for (size_t i = 0; i < a.size(); i++) {
if (tolower(static_cast<unsigned char>(a[i])) !=
tolower(static_cast<unsigned char>(b[i]))) {
return false;
}
}
return true;
}
void CommandLineParser::process(const std::vector<std::string> &argv)
{
size_t positional_index = 0;
for (size_t i = 1; i < argv.size(); i++) {
if (!argv[i].empty() && argv[i][0] == '-') {
// Must be an option
// Skip past first dash
std::string arg_basename = argv[i].substr(1);
bool matched_known = false;
for (KnownOption &o : options_) {
for (const std::string &s : o.args) {
if (string_equals_case_insensitive(s, arg_basename)) {
// Flag discovered!
o.option->set();
if (o.takes_arg && i + 1 < argv.size()) {
o.option->set_setting(argv[i + 1]);
i++;
}
matched_known = true;
goto found_flag;
}
}
}
found_flag:
if (!matched_known) {
fprintf(stderr, "Unknown parameter: %s\n", argv[i].c_str());
}
} else {
// Must be a positional flag
if (positional_index < positional_args_.size()) {
positional_args_[positional_index].option->set_setting(argv[i]);
positional_index++;
} else {
fprintf(stderr, "Unknown parameter: %s\n", argv[i].c_str());
}
}
}
}
void CommandLineParser::print_help(const char *filename)
{
printf("%s %s\n", app_name_.c_str(), app_version_.c_str());
printf("Copyright (C) 2018-2022 Oak Video Editor Team\n");
std::string positional_args;
for (size_t i = 0; i < positional_args_.size(); i++) {
if (i > 0) {
positional_args.append(" ");
}
positional_args.append("[");
positional_args.append(positional_args_[i].name);
positional_args.append("]");
}
const char *basename;
#ifdef _WIN32
basename = strrchr(filename, '\\');
if (!basename) {
basename = strrchr(filename, '/');
}
#else
basename = strrchr(filename, '/');
#endif
if (basename) {
// Slash found, increment pointer to avoid showing the slash itself
basename++;
} else {
// If no slashes are found, assume string is already a basename
basename = filename;
}
printf("Usage: %s [options] %s\n\n", basename, positional_args.c_str());
for (const KnownOption &o : options_) {
if (o.hidden) {
continue;
}
std::string all_args;
for (size_t i = 0; i < o.args.size(); i++) {
if (i > 0) {
all_args.append(", ");
}
all_args.append("-");
all_args.append(o.args[i]);
}
if (o.arg_placeholder.empty()) {
printf(" %s\n", all_args.c_str());
} else {
printf(" %s <%s>\n", all_args.c_str(), o.arg_placeholder.c_str());
}
printf(" %s\n\n", o.description.c_str());
}
printf("\n");
}
void CommandLineParser::set_app_info(const std::string &name,
const std::string &version)
{
app_name_ = name;
app_version_ = version;
}
+137
View File
@@ -0,0 +1,137 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_COMMANDLINEPARSER_H
#define OAK_COMMANDLINEPARSER_H
#include <string>
#include <vector>
/**
* @brief Command-line argument parser
*
* You may be wondering why we don't use a library implementation like
* QCommandLineParser instead of a custom implementation like this. The
* reason why is because QCommandLineParser requires a QApplication object
* of some kind to already have been created before it can parse anything,
* but we need to be able to control whether a QApplication (GUI-mode) or
* a QCoreApplication (CLI-mode) is created which is set by the user as a
* command line argument. Therefore we needed a custom implementation that
* could parse arguments without the need for any application object to be
* present already.
*/
class CommandLineParser {
public:
CommandLineParser() = default;
~CommandLineParser();
CommandLineParser(const CommandLineParser &) = delete;
CommandLineParser(CommandLineParser &&) = delete;
CommandLineParser &operator=(const CommandLineParser &) = delete;
CommandLineParser &operator=(CommandLineParser &&) = delete;
class PositionalArgument {
public:
PositionalArgument() = default;
const std::string &get_setting() const
{
return setting_;
}
void set_setting(const std::string &s)
{
setting_ = s;
}
private:
std::string setting_;
};
class Option : public PositionalArgument {
public:
Option()
{
is_set_ = false;
}
bool is_set() const
{
return is_set_;
}
void set()
{
is_set_ = true;
}
private:
bool is_set_;
};
const Option *add_option(const std::vector<std::string> &strings,
const std::string &description, bool takes_arg = false,
const std::string &arg_placeholder = std::string(),
bool hidden = false);
const PositionalArgument *add_positional_argument(const std::string &name,
const std::string &description,
bool required = false);
void process(const std::vector<std::string> &argv);
void print_help(const char *filename);
/**
* @brief Set the application name/version shown by print_help()
*
* Replaces the former QCoreApplication::applicationName()/applicationVersion()
* dependency. Defaults to "oak" with an empty version.
*/
void set_app_info(const std::string &name, const std::string &version);
private:
struct KnownOption {
std::vector<std::string> args;
std::string description;
Option *option;
bool takes_arg;
std::string arg_placeholder;
bool hidden;
};
struct KnownPositionalArgument {
std::string name;
std::string description;
PositionalArgument *option;
bool required;
};
std::vector<KnownOption> options_;
std::vector<KnownPositionalArgument> positional_args_;
std::string app_name_ = "oak";
std::string app_version_;
};
#endif // OAK_COMMANDLINEPARSER_H
+23
View File
@@ -0,0 +1,23 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "current.h"
Current Current::current_;
+95
View File
@@ -0,0 +1,95 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_CURRENT_H
#define OAK_CURRENT_H
#include <memory>
/**
* @brief Thread-shared holder for the "current" session-wide objects.
*
* De-Qt version of the old engine/common Current. The external objects
* (video/audio params, plugin host, plugin cache) live outside
* oakcommon, so they are kept as type-erased pointers
* (std::shared_ptr<void>); callers recover the concrete type with
* std::static_pointer_cast. Slots may be empty (nullptr).
*/
class Current {
public:
static Current &get_instance()
{
return current_;
}
std::shared_ptr<void> current_video_params() const
{
return video_params_;
}
std::shared_ptr<void> current_audio_params() const
{
return audio_params_;
}
void set_current_video_params(std::shared_ptr<void> params)
{
video_params_ = params;
}
void set_current_audio_params(std::shared_ptr<void> params)
{
audio_params_ = params;
}
bool interactive() const
{
return true;
}
std::shared_ptr<void> plugin_host() const
{
return plugin_host_;
}
void set_plugin_host(std::shared_ptr<void> host)
{
plugin_host_ = host;
}
std::shared_ptr<void> plugin_cache() const
{
return plugin_cache_;
}
void set_plugin_cache(std::shared_ptr<void> cache)
{
plugin_cache_ = cache;
}
private:
static Current current_;
std::shared_ptr<void> video_params_;
std::shared_ptr<void> audio_params_;
std::shared_ptr<void> plugin_host_;
std::shared_ptr<void> plugin_cache_;
};
#endif // OAK_CURRENT_H
+73
View File
@@ -0,0 +1,73 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "debug.h"
#include <cstdio>
#include <cstring>
namespace olive
{
int debug_level_name(int level, char *buf, int buf_size)
{
const char *name;
switch (level) {
case k_debug_debug:
name = "DEBUG";
break;
case k_debug_info:
name = "INFO";
break;
case k_debug_warning:
name = "WARNING";
break;
case k_debug_error:
name = "ERROR";
break;
case k_debug_fatal:
name = "FATAL";
break;
default:
name = "UNKNOWN";
break;
}
int needed = (int)strlen(name) + 1;
if (buf && buf_size >= needed)
memcpy(buf, name, needed);
return needed;
}
void debug_handler(int level, const char *msg)
{
char level_name[16];
debug_level_name(level, level_name, sizeof(level_name));
fprintf(stderr, "[%s] %s\n", level_name, msg ? msg : "");
// Always flush so debug messages appear immediately, even on
// platforms that buffer stderr.
fflush(stderr);
}
}
+64
View File
@@ -0,0 +1,64 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_DEBUG_H
#define OAK_DEBUG_H
namespace olive
{
/**
* @brief Severity levels for debug output.
*
* Replaces Qt's QtMsgType now that the debug handler no longer depends
* on QDebug.
*/
enum DebugLevel {
k_debug_debug,
k_debug_info,
k_debug_warning,
k_debug_error,
k_debug_fatal
};
/**
* @brief Write the printable name of a debug level (e.g. "WARNING") into
* a caller-provided buffer.
*
* Two-stage form: pass `buf == NULL` or a too-small `buf_size` to query
* the required size. Writes "UNKNOWN" for levels outside the DebugLevel
* range.
*
* @return Required buffer size in bytes, including the terminating NUL.
*/
int debug_level_name(int level, char *buf, int buf_size);
/**
* @brief Print a debug message to stderr, prefixed with its level.
*
* De-Qt replacement for the old Qt message handler: qDebug() output is
* replaced by fprintf(stderr). A NULL message is treated as an empty
* string. Lines are always flushed so messages appear immediately.
*/
void debug_handler(int level, const char *msg);
}
#endif // OAK_DEBUG_H
+96
View File
@@ -0,0 +1,96 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_DECIBEL_H
#define OAK_DECIBEL_H
#include <cmath>
#include <limits>
namespace olive
{
class Decibel {
public:
// In basically all circumstances, this should calculate to 0.0 linear
static constexpr double minimum = -200.0;
static double from_linear(double linear)
{
double v = double(20.0) * std::log10(linear);
if (std::isinf(v)) {
return minimum;
}
return v;
}
static double to_linear(double decibel)
{
double to_linear = std::pow(double(10.0), decibel / double(20.0));
// Minimum threshold that we figure is close enough to 0 that we may as well just return 0
if (to_linear < 0.000001) {
return 0;
} else {
return to_linear;
}
}
static double from_logarithmic(double logarithmic)
{
if (logarithmic < 0.001)
return minimum;
else if (logarithmic > 0.99)
return 0;
else
return 20.0 * std::log10(-std::log(1 - logarithmic) / lo_g100);
}
static double to_logarithmic(double decibel)
{
if (std::abs(decibel) <= 1e-12) {
return 1;
} else {
return 1 - std::exp(-std::pow(10.0, decibel / 20.0) * lo_g100);
}
}
static double linear_to_logarithmic(double linear)
{
return 1 - std::exp(-linear * lo_g100);
}
static double logarithmic_to_linear(double logarithmic)
{
if (logarithmic > 0.99) {
return 1;
} else {
return -std::log(1 - logarithmic) / lo_g100;
}
}
private:
static constexpr double lo_g100 = 4.60517018599;
};
}
#endif // OAK_DECIBEL_H
+58
View File
@@ -0,0 +1,58 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_OLIVECOMMONDEFINE_H
#define OAK_OLIVECOMMONDEFINE_H
namespace olive
{
/// The minimum size an icon in ProjectExplorer can be
const int k_project_icon_size_minimum = 16;
/// The maximum size an icon in ProjectExplorer can be
const int k_project_icon_size_maximum = 256;
/// The default size an icon in ProjectExplorer can be
const int k_project_icon_size_default = 64;
const int k_bytes_in_gigabyte = 1073741824;
}
/**
* Copy/move deleters. Similar to Q_DISABLE_COPY_MOVE, et al. but those functions are not present in Qt < 5.13 so we
* use our own functions for portability.
*/
#define DISABLE_COPY(Class) \
Class(const Class &) = delete; \
Class &operator=(const Class &) = delete;
#define DISABLE_MOVE(Class) \
Class(Class &&) = delete; \
Class &operator=(Class &&) = delete;
#define DISABLE_COPY_MOVE(Class) \
DISABLE_COPY(Class) \
DISABLE_MOVE(Class)
#endif // OAK_OLIVECOMMONDEFINE_H
+43
View File
@@ -0,0 +1,43 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_DROPWORKFLOWBEHAVIOR_H
#define OAK_DROPWORKFLOWBEHAVIOR_H
namespace olive
{
/**
* @brief Behavior when media is dropped onto a timeline without a sequence
*
* Shared by the config defaults (engine layer) and the timeline import
* tool (UI layer). Enumerator order matches the previous
* ImportTool::DropWithoutSequenceBehavior.
*/
enum DropWithoutSequenceBehavior {
k_dws_ask,
k_dws_auto,
k_dws_manual,
k_dws_disable
};
}
#endif // OAK_DROPWORKFLOWBEHAVIOR_H
+197
View File
@@ -0,0 +1,197 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "ffmpegutils.h"
namespace olive
{
int FFmpegUtils::get_compatible_bridge_pixel_format(int pix_fmt,
PixelFormat maximum)
{
int possible_pix_fmts[4];
possible_pix_fmts[0] = fb_pix_fmt_rgba;
if (maximum == PixelFormat::u8) {
possible_pix_fmts[1] = fb_pix_fmt_none;
} else {
possible_pix_fmts[1] = fb_pix_fmt_rgb_a64_le;
if (maximum == PixelFormat::f32) {
possible_pix_fmts[2] = fb_pix_fmt_rgba_f32_le;
possible_pix_fmts[3] = fb_pix_fmt_none;
} else {
possible_pix_fmts[2] = fb_pix_fmt_none;
}
}
return fb_find_best_pix_fmt_of_list(possible_pix_fmts, pix_fmt);
}
SampleFormat FFmpegUtils::get_native_sample_format(int smp_fmt)
{
switch (smp_fmt) {
case fb_sample_fmt_u8:
return SampleFormat::u8;
case fb_sample_fmt_s16:
return SampleFormat::s16;
case fb_sample_fmt_s32:
return SampleFormat::s32;
case fb_sample_fmt_s64:
return SampleFormat::s64;
case fb_sample_fmt_flt:
return SampleFormat::f32;
case fb_sample_fmt_dbl:
return SampleFormat::f64;
case fb_sample_fmt_u8_p:
return SampleFormat::u8_p;
case fb_sample_fmt_s16_p:
return SampleFormat::s16_p;
case fb_sample_fmt_s32_p:
return SampleFormat::s32_p;
case fb_sample_fmt_s64_p:
return SampleFormat::s64_p;
case fb_sample_fmt_fltp:
return SampleFormat::f32_p;
case fb_sample_fmt_dblp:
return SampleFormat::f64_p;
default:
break;
}
return SampleFormat::invalid;
}
int FFmpegUtils::get_ffmpeg_sample_format(const SampleFormat &smp_fmt)
{
switch (smp_fmt) {
case SampleFormat::u8:
return fb_sample_fmt_u8;
case SampleFormat::s16:
return fb_sample_fmt_s16;
case SampleFormat::s32:
return fb_sample_fmt_s32;
case SampleFormat::s64:
return fb_sample_fmt_s64;
case SampleFormat::f32:
return fb_sample_fmt_flt;
case SampleFormat::f64:
return fb_sample_fmt_dbl;
case SampleFormat::u8_p:
return fb_sample_fmt_u8_p;
case SampleFormat::s16_p:
return fb_sample_fmt_s16_p;
case SampleFormat::s32_p:
return fb_sample_fmt_s32_p;
case SampleFormat::s64_p:
return fb_sample_fmt_s64_p;
case SampleFormat::f32_p:
return fb_sample_fmt_fltp;
case SampleFormat::f64_p:
return fb_sample_fmt_dblp;
case SampleFormat::invalid:
case SampleFormat::count:
break;
}
return fb_sample_fmt_none;
}
int FFmpegUtils::convert_jpeg_space_to_regular_space(int f)
{
switch (f) {
case fb_pix_fmt_yuv_j420_p:
return fb_pix_fmt_yu_v420_p;
case fb_pix_fmt_yuv_j422_p:
return fb_pix_fmt_yu_v422_p;
case fb_pix_fmt_yuv_j444_p:
return fb_pix_fmt_yu_v444_p;
case fb_pix_fmt_yuv_j440_p:
return fb_pix_fmt_yu_v440_p;
case fb_pix_fmt_yuv_j411_p:
return fb_pix_fmt_yu_v411_p;
default:
break;
}
return f;
}
int FFmpegUtils::get_ffmpeg_pixel_format(const PixelFormat &pix_fmt,
int channel_layout)
{
if (channel_layout == k_rgb_channel_count) {
switch (pix_fmt) {
case PixelFormat::u8:
return fb_pix_fmt_rg_b24;
case PixelFormat::u10:
return fb_pix_fmt_none;
case PixelFormat::u16:
return fb_pix_fmt_rg_b48_le;
case PixelFormat::f16:
return fb_pix_fmt_rgb_f16_le;
case PixelFormat::f32:
return fb_pix_fmt_rgb_f32_le;
case PixelFormat::invalid:
case PixelFormat::count:
break;
}
} else if (channel_layout == k_rgba_channel_count) {
switch (pix_fmt) {
case PixelFormat::u8:
return fb_pix_fmt_rgba;
case PixelFormat::u10:
return fb_pix_fmt_none;
case PixelFormat::u16:
return fb_pix_fmt_rgb_a64_le;
case PixelFormat::f16:
return fb_pix_fmt_rgba_f16_le;
case PixelFormat::f32:
return fb_pix_fmt_rgba_f32_le;
case PixelFormat::invalid:
case PixelFormat::count:
break;
}
}
return fb_pix_fmt_none;
}
PixelFormat FFmpegUtils::get_compatible_pixel_format(const PixelFormat &pix_fmt)
{
switch (pix_fmt) {
case PixelFormat::u8:
return PixelFormat::u8;
case PixelFormat::u10:
return PixelFormat::u8;
case PixelFormat::u16:
case PixelFormat::f16:
case PixelFormat::f32:
return PixelFormat::u16;
case PixelFormat::invalid:
case PixelFormat::count:
break;
}
return PixelFormat::invalid;
}
}
+97
View File
@@ -0,0 +1,97 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_FFMPEGABSTRACTION_H
#define OAK_FFMPEGABSTRACTION_H
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include <olive/core/render/pixelformat.h>
#include <olive/core/render/sampleformat.h>
namespace olive
{
using namespace core;
/**
* @brief C++ adapter mapping Olive's native enums to bridge pixel/sample
* formats
*
* All "FFmpeg" formats here are actually the opaque FBPixelFormat /
* FBSampleFormat constants of the ffmpeg_bridge library; no FFmpeg header
* or structure is ever seen by the editor.
*/
class FFmpegUtils {
public:
/**
* @brief Channel counts formerly taken from VideoParams
*
* Flattened to plain constants here so that this header no longer
* depends on render/videoparams.h (common must not depend on render).
*/
static constexpr int k_rgb_channel_count = 3;
static constexpr int k_rgba_channel_count = 4;
/**
* @brief Returns a bridge pixel format that can be used to convert a frame to a data type Olive supports with minimal data loss
*
* Named distinctly from the native PixelFormat overload below: with both
* taking a single argument, an unscoped enum argument would silently
* prefer an int overload over the PixelFormat one.
*/
static int get_compatible_bridge_pixel_format(
int pix_fmt, PixelFormat maximum = PixelFormat::invalid);
/**
* @brief Returns a native pixel format that can be used to convert from a native frame to a bridge frame with minimal data loss
*/
static PixelFormat get_compatible_pixel_format(const PixelFormat &pix_fmt);
/**
* @brief Returns a bridge pixel format for a given native pixel format
*/
static int get_ffmpeg_pixel_format(const PixelFormat &pix_fmt,
int channel_layout);
/**
* @brief Returns a native sample format type for a given bridge sample format
*/
static SampleFormat get_native_sample_format(int smp_fmt);
/**
* @brief Returns a bridge sample format type for a given native type
*/
static int get_ffmpeg_sample_format(const SampleFormat &smp_fmt);
/**
* @brief Convert "JPEG"/full-range colorspace to its regular counterpart
*
* "JPEG "spaces are deprecated in favor of the regular space and setting `color_range`. For the
* time being, FFmpeg still uses these JPEG spaces, so for simplicity (since we *are* color_range
* aware), we use this function.
*/
static int convert_jpeg_space_to_regular_space(int f);
};
}
#endif // OAK_FFMPEGABSTRACTION_H
+375
View File
@@ -0,0 +1,375 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "filefunctions.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <system_error>
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
namespace fs = std::filesystem;
namespace
{
/**
* @brief FNV-1a hash of a string, returned as lowercase hex
*/
std::string fnv1a_hex(const std::string &data)
{
uint64_t hash = 14695981039346656037ULL;
for (unsigned char c : data) {
hash ^= c;
hash *= 1099511628211ULL;
}
char buf[17];
snprintf(buf, sizeof(buf), "%016llx",
static_cast<unsigned long long>(hash));
return buf;
}
/**
* @brief Case-insensitive check whether `s` ends with `suffix`
*/
bool ends_with_case_insensitive(const std::string &s, const std::string &suffix)
{
if (suffix.size() > s.size()) {
return false;
}
size_t offset = s.size() - suffix.size();
for (size_t i = 0; i < suffix.size(); i++) {
char a = s[offset + i];
char b = suffix[i];
if (a >= 'A' && a <= 'Z') {
a += 'a' - 'A';
}
if (b >= 'A' && b <= 'Z') {
b += 'a' - 'A';
}
if (a != b) {
return false;
}
}
return true;
}
} // namespace
std::string FileFunctions::get_unique_file_identifier(
const std::string &filename)
{
std::error_code ec;
fs::path abs = fs::absolute(filename, ec);
if (ec || !fs::exists(abs, ec)) {
return std::string();
}
auto mtime = fs::last_write_time(abs, ec);
if (ec) {
return std::string();
}
std::string hash_input = abs.string();
hash_input.append(std::to_string(
static_cast<long long>(mtime.time_since_epoch().count())));
return fnv1a_hex(hash_input);
}
std::string FileFunctions::get_configuration_location()
{
// Tests and tooling can redirect the configuration (and, since most
// locations derive from it, the cache/data) root.
const char *override_dir = getenv("OAK_CONFIG_DIR");
if (override_dir != nullptr && override_dir[0] != '\0') {
std::error_code ec;
fs::create_directories(override_dir, ec);
return override_dir;
}
if (is_portable()) {
return get_application_path();
}
fs::path config_root;
#ifdef __APPLE__
const char *home = getenv("HOME");
if (home != nullptr && home[0] != '\0') {
config_root = fs::path(home) / "Library" / "Application Support";
}
#else
const char *xdg_config = getenv("XDG_CONFIG_HOME");
if (xdg_config != nullptr && xdg_config[0] != '\0') {
config_root = xdg_config;
} else {
const char *home = getenv("HOME");
if (home != nullptr && home[0] != '\0') {
config_root = fs::path(home) / ".config";
}
}
#endif
if (config_root.empty()) {
// Last resort: temp directory
config_root = fs::temp_directory_path();
}
fs::path config_dir = config_root / "oak";
std::error_code ec;
fs::create_directories(config_dir, ec);
return config_dir.string();
}
bool FileFunctions::is_portable()
{
std::error_code ec;
return fs::exists(fs::path(get_application_path()) / "portable", ec);
}
std::string FileFunctions::get_application_path()
{
#ifdef __APPLE__
uint32_t size = 0;
_NSGetExecutablePath(nullptr, &size);
std::string buf(size, '\0');
if (_NSGetExecutablePath(buf.data(), &size) == 0) {
std::error_code ec;
fs::path p = fs::weakly_canonical(buf, ec);
if (!ec) {
return p.parent_path().string();
}
}
#elif defined(__linux__)
std::error_code ec;
fs::path p = fs::read_symlink("/proc/self/exe", ec);
if (!ec) {
return p.parent_path().string();
}
#endif
// Fallback: current working directory
std::error_code ec;
fs::path cwd = fs::current_path(ec);
return ec ? std::string() : cwd.string();
}
std::string FileFunctions::get_temp_file_path()
{
std::error_code ec;
fs::path temp_path = fs::temp_directory_path(ec) / "oak";
if (ec) {
temp_path = fs::path(".") / "oak-temp";
}
// Ensure it exists
fs::create_directories(temp_path, ec);
return temp_path.string();
}
bool FileFunctions::can_copy_directory_without_overwriting(
const std::string &source, const std::string &dest)
{
std::error_code ec;
for (const auto &entry : fs::directory_iterator(source, ec)) {
fs::path dest_equivalent = fs::path(dest) / entry.path().filename();
if (entry.is_directory(ec)) {
if (!can_copy_directory_without_overwriting(
entry.path().string(),
dest_equivalent.string())) {
return false;
}
} else if (fs::exists(dest_equivalent, ec)) {
return false;
}
}
return true;
}
void FileFunctions::copy_directory(const std::string &source,
const std::string &dest, bool overwrite)
{
std::error_code ec;
if (!fs::is_directory(source, ec)) {
fprintf(stderr, "Failed to copy directory, source %s didn't exist\n",
source.c_str());
return;
}
if (!fs::create_directories(dest, ec) && ec) {
fprintf(stderr, "Failed to create destination directory %s\n",
dest.c_str());
return;
}
for (const auto &entry : fs::directory_iterator(source, ec)) {
fs::path dest_file_path =
fs::path(dest) / entry.path().filename();
if (entry.is_directory(ec)) {
// Copy dir
copy_directory(entry.path().string(),
dest_file_path.string(), overwrite);
} else {
// Copy file
if (overwrite) {
fs::permissions(dest_file_path,
fs::perms::owner_write |
fs::perms::group_write |
fs::perms::others_write,
fs::perm_options::add, ec);
ec.clear();
fs::remove(dest_file_path, ec);
ec.clear();
}
fs::copy_file(entry.path(), dest_file_path,
fs::copy_options::none, ec);
if (ec) {
fprintf(stderr,
"Failed to copy file %s to %s: %s\n",
entry.path().string().c_str(),
dest_file_path.string().c_str(),
ec.message().c_str());
}
}
}
}
bool FileFunctions::directory_is_valid(const std::string &dir,
bool try_to_create_if_not_exists)
{
// Return whether the directory exists, or whether it could be created
// if it doesn't
std::error_code ec;
if (fs::is_directory(dir, ec)) {
return true;
}
return try_to_create_if_not_exists && fs::create_directories(dir, ec);
}
std::string FileFunctions::ensure_filename_extension(
std::string fn, const std::string &extension)
{
// No-op if either input is empty
if (!fn.empty() && !extension.empty()) {
std::string extension_with_dot = "." + extension;
if (!ends_with_case_insensitive(fn, extension_with_dot)) {
fn.append(extension_with_dot);
}
}
return fn;
}
std::string FileFunctions::read_file_as_string(const std::string &filename)
{
std::ifstream f(filename, std::ios::in | std::ios::binary);
if (!f.is_open()) {
return std::string();
}
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
std::string FileFunctions::get_safe_temporary_filename(
const std::string &original)
{
int counter = 0;
fs::path original_path(original);
fs::path dir = original_path.parent_path();
std::string filename = original_path.filename().string();
// Split off the complete suffix (everything from the first dot), like
// QFileInfo::completeSuffix()
std::string basename = filename;
std::string complete_suffix;
size_t first_dot = filename.find('.');
if (first_dot != std::string::npos) {
basename = filename.substr(0, first_dot);
complete_suffix = filename.substr(first_dot);
}
fs::path temp_abs_path;
do {
temp_abs_path =
dir / (basename + ".tmp" + std::to_string(counter) +
complete_suffix);
counter++;
} while (fs::exists(temp_abs_path));
return temp_abs_path.string();
}
bool FileFunctions::rename_file_allow_overwrite(const std::string &from,
const std::string &to)
{
std::error_code ec;
if (fs::exists(to, ec) && !fs::remove(to, ec)) {
fprintf(stderr, "Couldn't remove existing file %s for overwrite\n",
to.c_str());
return false;
}
// By this point, we can assume `to` either never existed or has now
// been deleted
fs::rename(from, to, ec);
if (ec) {
fprintf(stderr, "Failed to rename file %s to %s: %s\n",
from.c_str(), to.c_str(), ec.message().c_str());
return false;
}
return true;
}
std::string FileFunctions::get_formatted_executable_for_platform(
std::string unformatted)
{
#ifdef _WIN32
unformatted.append(".exe");
#endif
return unformatted;
}
std::string FileFunctions::get_auto_recovery_root()
{
return (fs::path(get_configuration_location()) / "autorecovery")
.string();
}
+111
View File
@@ -0,0 +1,111 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_FILEFUNCTIONS_H
#define OAK_FILEFUNCTIONS_H
#include <string>
/**
* @brief A collection of static file and directory functions
*
* Qt-free reimplementation of the former olive::FileFunctions. All paths
* are UTF-8 std::strings and all filesystem operations use std::filesystem.
*/
class FileFunctions {
public:
/**
* @brief Returns true if the application is running in portable mode
*
* In portable mode, any persistent configuration files should be made in
* a path relative to the application rather than in the user's home
* folder.
*/
static bool is_portable();
/**
* @brief Returns a deterministic identifier string for a file
*
* Derived from the absolute path and the last modification time. Returns
* an empty string if the file does not exist. Uses FNV-1a hashing rather
* than SHA-1; identifiers are only used as cache keys, not for security.
*/
static std::string get_unique_file_identifier(const std::string &filename);
static std::string get_configuration_location();
static std::string get_application_path();
static std::string get_temp_file_path();
static bool can_copy_directory_without_overwriting(
const std::string &source, const std::string &dest);
static void copy_directory(const std::string &source,
const std::string &dest,
bool overwrite = false);
static bool directory_is_valid(const std::string &dir,
bool try_to_create_if_not_exists = true);
/**
* @brief Ensures a given filename has a certain extension
*
* Checks if the filename has the extension provided and appends it if
* not. The extension is checked case-insensitive. The extension should
* be provided with no dot (e.g. "ove" rather than ".ove").
*
* @return The filename provided either untouched or with the extension
* appended to it.
*/
static std::string ensure_filename_extension(std::string fn,
const std::string &extension);
static std::string read_file_as_string(const std::string &filename);
/**
* @brief Returns a temporary filename that can be used while writing
* rather than the original
*
* If overwriting a file, it's safest to write to a new file first and
* then only replace it at the end so that if the program crashes or the
* user cancels the save half way through, the original file is still
* intact.
*
* This function returns a slight variant of the filename provided that's
* guaranteed to not exist and therefore won't overwrite anything
* important.
*/
static std::string get_safe_temporary_filename(const std::string &original);
/**
* @brief Renames a file from `from` to `to`, deleting `to` if such a
* file already exists first
*/
static bool rename_file_allow_overwrite(const std::string &from,
const std::string &to);
static std::string get_formatted_executable_for_platform(
std::string unformatted);
static std::string get_auto_recovery_root();
};
#endif // OAK_FILEFUNCTIONS_H
+41
View File
@@ -0,0 +1,41 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_LERP_H
#define OAK_LERP_H
/**
* @brief Linearly interpolate a value between a and b using t
*
* t should be a number between 0.0 and 1.0. 0.0 will return a, 1.0 will return b, and between will return a value
* in between a and b at that point linearly.
*/
template <typename T> T lerp(T a, T b, double t)
{
return (a * (1.0 - t)) + (b * t);
}
template <typename T> T lerp(T a, T b, float t)
{
return (a * (1.0f - t)) + (b * t);
}
#endif // OAK_LERP_H
+440
View File
@@ -0,0 +1,440 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_MEMORYPOOL_H
#define OAK_MEMORYPOOL_H
#include <chrono>
#include <cstdio>
#include <list>
#include <memory>
#include <mutex>
#include <stdint.h>
#include <vector>
#include "define.h"
namespace olive
{
/**
* @brief MemoryPool base class
*
* A custom memory system that allocates several objects in a large chunk (as opposed to several small
* allocations). Improves performance and memory consumption.
*
* As a class, this base is usable by subclassing and overriding get_element_size() if elements are larger than
* one byte. The pool will then allocate `(element_count * get_element_size())` per arena. Arenas are allocated
* and destroyed on the fly - when an arena fills up, another is allocated.
*
* get() will return an ElementPtr. The original desired data can be accessed through ElementPtr::data(). This data
* will belong to the caller until ElementPtr goes out of scope and the memory is freed back into the pool.
*
* Empty arenas are NOT reaped automatically anymore (the Qt version used a QTimer driven by the Qt event loop).
* Call clear_empty_arenas() periodically from whatever scheduling mechanism the host application provides.
*/
class MemoryPool {
public:
/**
* @brief Constructor
* @param element_count
*
* Number of elements per arena
*/
MemoryPool(int element_count)
{
element_count_ = element_count;
}
/**
* @brief Destructor
*
* Deletes all arenas.
*/
virtual ~MemoryPool()
{
clear();
}
DISABLE_COPY_MOVE(MemoryPool)
/**
* @brief Clears all arenas, freeing all of their memory
*
* Note that this function is not safe, any elements that are still out there will be invalid
* and accessing them will cause a crash. You'll need to make sure all elements are already
* relinquished before then.
*/
void clear()
{
std::lock_guard<std::mutex> locker(lock_);
for (Arena *a : arenas_) {
delete a;
}
arenas_.clear();
}
/**
* @brief Returns whether any arenas are successfully allocated
*/
inline bool is_allocated() const
{
return !arenas_.empty();
}
/**
* @brief Returns current number of allocated arenas
*/
inline int get_arena_count() const
{
return arenas_.size();
}
/**
* @brief Deletes arenas that have been empty for at least kMaxEmptyArenaLife milliseconds
*
* In the Qt version this was a private slot invoked periodically by a QTimer. Without Qt it is now a
* public function that the caller is responsible for invoking (e.g. from a std::thread timer).
*/
void clear_empty_arenas()
{
std::lock_guard<std::mutex> locker(lock_);
const int64_t min_time = now_ms() - kMaxEmptyArenaLife;
for (auto it = arenas_.begin(); it != arenas_.end();) {
Arena *arena = (*it);
if (arena->get_usage_count() == 0 &&
arena->time_arena_was_made_empty() <= min_time) {
delete arena;
it = arenas_.erase(it);
} else {
it++;
}
}
}
class Arena;
/**
* @brief A handle for a chunk of memory in an arena
*
* Calling get() on the pool or arena will return a shared pointer to an element which will contain a pointer to
* the desired object/data in data(). When Element is destroyed (i.e. when ElementPtr goes out of scope), the
* memory is released back into the pool so it can be used by another class.
*/
class Element {
public:
/**
* @brief Element Constructor
*
* There is no need to use this outside of the memory pool's internal functions.
*/
Element(Arena *parent, uint8_t *data)
{
parent_ = parent;
data_ = data;
accessed_ = now_ms();
}
/**
* @brief Element Destructor
*
* Automatically releases this element's memory back to the arena it was retrieved from.
*/
~Element()
{
release();
}
DISABLE_COPY_MOVE(Element)
/**
* @brief Access data represented in the pool
*/
inline uint8_t *data() const
{
return data_;
}
inline const int64_t &timestamp() const
{
return timestamp_;
}
inline void set_timestamp(const int64_t &timestamp)
{
timestamp_ = timestamp;
}
/**
* @brief Register that this element has been accessed
*
* \see last_accessed()
*/
inline void access()
{
accessed_ = now_ms();
}
/**
* @brief Returns the last time `access()` was called on this function
*
* Useful for determining the relative age of an element (i.e. if it hasn't been accessed for a certain amount
* of time, it can probably be freed back into the pool). This requires all usages to call `access()`.
*/
inline const int64_t &last_accessed() const
{
return accessed_;
}
void release()
{
if (data_) {
parent_->release(this);
data_ = nullptr;
}
}
private:
Arena *parent_;
uint8_t *data_;
int64_t timestamp_;
int64_t accessed_;
};
using ElementPtr = std::shared_ptr<Element>;
/**
* @brief A memory pool arena - a subsection of memory
*
* The pool itself does not store memory, it stores "arenas". This is so that the pool can handle the situation of
* an arena becoming full with no more memory to lend. A pool can automatically allocate another arena and
* continue providing memory (and freeing arenas when they're no longer in use).
*/
class Arena {
public:
Arena(MemoryPool *parent)
{
parent_ = parent;
data_ = nullptr;
allocated_sz_ = 0;
empty_time_ = now_ms();
}
~Arena()
{
std::list<Element *> copy = lent_elements_;
for (Element *e : copy) {
e->release();
}
delete[] data_;
}
DISABLE_COPY_MOVE(Arena)
/**
* @brief Returns an element if there is free memory to do so
*/
ElementPtr get()
{
std::lock_guard<std::mutex> locker(lock_);
for (size_t i = 0; i < available_.size(); i++) {
if (available_.at(i)) {
// This buffer is available
available_[i] = false;
ElementPtr e = std::make_shared<Element>(
this,
reinterpret_cast<uint8_t *>(data_ + i * element_sz_));
lent_elements_.push_back(e.get());
return e;
}
}
return nullptr;
}
/**
* @brief Releases an element back into the pool for use elsewhere
*/
void release(Element *e)
{
std::lock_guard<std::mutex> locker(lock_);
uintptr_t diff = reinterpret_cast<uintptr_t>(e->data()) -
reinterpret_cast<uintptr_t>(data_);
size_t index = diff / element_sz_;
available_[index] = true;
lent_elements_.remove(e);
if (lent_elements_.empty()) {
empty_time_ = now_ms();
}
}
int get_usage_count()
{
std::lock_guard<std::mutex> locker(lock_);
return lent_elements_.size();
}
bool allocate(size_t ele_sz, size_t nb_elements)
{
if (is_allocated()) {
return true;
}
element_sz_ = ele_sz;
allocated_sz_ = element_sz_ * nb_elements;
if ((data_ = new (std::nothrow) uint8_t[allocated_sz_])) {
available_.assign(nb_elements, true);
return true;
} else {
available_.clear();
return false;
}
}
inline int get_element_count() const
{
return available_.size();
}
inline bool is_allocated() const
{
return data_;
}
inline int64_t time_arena_was_made_empty()
{
std::lock_guard<std::mutex> locker(lock_);
return empty_time_;
}
private:
MemoryPool *parent_;
uint8_t *data_;
size_t allocated_sz_;
std::vector<bool> available_;
std::mutex lock_;
size_t element_sz_;
std::list<Element *> lent_elements_;
int64_t empty_time_;
};
/**
* @brief Retrieves an element from an available arena
*/
ElementPtr get()
{
std::lock_guard<std::mutex> locker(lock_);
// Attempt to get an element from an arena
for (Arena *a : arenas_) {
ElementPtr e = a->get();
if (e) {
return e;
}
}
// All arenas were empty, we'll need to create a new one
size_t ele_sz = get_element_size();
if (!ele_sz) {
fprintf(stderr, "Failed to create arena, element size was 0\n");
return nullptr;
}
if (element_count_ <= 0) {
fprintf(stderr, "Failed to create arena, element count was invalid: %d\n",
element_count_);
return nullptr;
}
Arena *a = new Arena(this);
if (!a->allocate(ele_sz, element_count_)) {
fprintf(stderr,
"Failed to create arena, allocation failed. Out of memory?\n");
delete a;
return nullptr;
}
arenas_.push_back(a);
return a->get();
}
protected:
/**
* @brief The size of each element
*
* Override this to use a custom size (e.g. a char array where the element size is > 1)
*/
virtual size_t get_element_size()
{
return sizeof(uint8_t);
}
private:
/**
* @brief Milliseconds since the epoch (replaces QDateTime::currentMSecsSinceEpoch())
*/
static int64_t now_ms()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
int element_count_;
std::list<Arena *> arenas_;
std::mutex lock_;
static const int64_t kMaxEmptyArenaLife = 5000;
};
}
#endif // OAK_MEMORYPOOL_H
+43
View File
@@ -0,0 +1,43 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "ocioutils.h"
ocio::BitDepth OCIOUtils::get_ocio_bit_depth_from_pixel_format(
olive::core::PixelFormat format)
{
switch (format) {
case olive::core::PixelFormat::u8:
return ocio::BIT_DEPTH_UINT8;
case olive::core::PixelFormat::u10:
return ocio::BIT_DEPTH_UINT10;
case olive::core::PixelFormat::u16:
return ocio::BIT_DEPTH_UINT16;
case olive::core::PixelFormat::f16:
return ocio::BIT_DEPTH_F16;
case olive::core::PixelFormat::f32:
return ocio::BIT_DEPTH_F32;
case olive::core::PixelFormat::invalid:
case olive::core::PixelFormat::count:
break;
}
return ocio::BIT_DEPTH_UNKNOWN;
}
+47
View File
@@ -0,0 +1,47 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_OCIOUTILS_H
#define OAK_OCIOUTILS_H
#include <OpenColorIO/OpenColorIO.h>
namespace ocio = OCIO_NAMESPACE;
#include <olive/core/render/pixelformat.h>
/**
* @brief A collection of static OpenColorIO helper functions
*
* Qt-free reimplementation of the former olive::OCIOUtils. The dependency
* on render/videoparams.h (which pulls in Qt) was replaced with the
* Qt-free olive/core/render/pixelformat.h, where PixelFormat is defined.
*/
class OCIOUtils {
public:
/**
* @brief Returns the OCIO bit depth matching a native pixel format
*
* Returns ocio::BIT_DEPTH_UNKNOWN for invalid or out-of-range formats.
*/
static ocio::BitDepth get_ocio_bit_depth_from_pixel_format(
olive::core::PixelFormat format);
};
#endif // OAK_OCIOUTILS_H
+104
View File
@@ -0,0 +1,104 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "oiioutils.h"
#include <cstdio>
OIIO::TypeDesc::BASETYPE
OIIOUtils::get_oiio_base_type_from_format(olive::core::PixelFormat format)
{
switch (format) {
case olive::core::PixelFormat::u8:
return OIIO::TypeDesc::UINT8;
case olive::core::PixelFormat::u10:
return OIIO::TypeDesc::UNKNOWN;
case olive::core::PixelFormat::u16:
return OIIO::TypeDesc::UINT16;
case olive::core::PixelFormat::f16:
return OIIO::TypeDesc::HALF;
case olive::core::PixelFormat::f32:
return OIIO::TypeDesc::FLOAT;
case olive::core::PixelFormat::invalid:
case olive::core::PixelFormat::count:
break;
}
return OIIO::TypeDesc::UNKNOWN;
}
olive::core::PixelFormat
OIIOUtils::get_format_from_oiio_basetype(OIIO::TypeDesc::BASETYPE type)
{
switch (type) {
case OIIO::TypeDesc::UNKNOWN:
case OIIO::TypeDesc::NONE:
#if OIIO_VERSION >= 20500
case OIIO::TypeDesc::USTRINGHASH:
#endif
default:
break;
case OIIO::TypeDesc::INT8:
case OIIO::TypeDesc::INT16:
case OIIO::TypeDesc::INT32:
case OIIO::TypeDesc::UINT32:
case OIIO::TypeDesc::INT64:
case OIIO::TypeDesc::UINT64:
case OIIO::TypeDesc::STRING:
case OIIO::TypeDesc::PTR:
case OIIO::TypeDesc::LASTBASE:
case OIIO::TypeDesc::DOUBLE:
fprintf(stderr, "Tried to use unknown OIIO base type\n");
break;
case OIIO::TypeDesc::UINT8:
return olive::core::PixelFormat::u8;
case OIIO::TypeDesc::UINT16:
return olive::core::PixelFormat::u16;
case OIIO::TypeDesc::HALF:
return olive::core::PixelFormat::f16;
case OIIO::TypeDesc::FLOAT:
return olive::core::PixelFormat::f32;
}
return olive::core::PixelFormat::invalid;
}
olive::core::Rational
OIIOUtils::get_pixel_aspect_ratio_from_oiio(const OIIO::ImageSpec &spec)
{
return olive::core::Rational::from_double(
spec.get_float_attribute("PixelAspectRatio", 1));
}
void OIIOUtils::frame_to_buffer(const void *data, int64_t linesize_bytes,
OIIO::ImageBuf *buf)
{
buf->set_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
static_cast<OIIO::stride_t>(linesize_bytes));
}
void OIIOUtils::buffer_to_frame(OIIO::ImageBuf *buf, void *data,
int64_t linesize_bytes)
{
buf->get_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
static_cast<OIIO::stride_t>(linesize_bytes));
}
+88
View File
@@ -0,0 +1,88 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_OIIOUTILS_H
#define OAK_OIIOUTILS_H
#include <cstdint>
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/typedesc.h>
#include <olive/core/render/pixelformat.h>
#include <olive/core/util/rational.h>
/**
* @brief A collection of static OpenImageIO helper functions
*
* Qt-free reimplementation of the former olive::OIIOUtils. The reverse
* dependencies on codec/frame.h and render/videoparams.h (which pull in
* Qt) were replaced with the Qt-free olive/core/render/pixelformat.h and
* olive/core/util/rational.h. The Frame-based helpers were flattened to
* raw data pointer + linesize, which is all they ever used from Frame.
*/
class OIIOUtils {
public:
/**
* @brief Returns the OIIO base type matching a native pixel format
*
* Returns OIIO::TypeDesc::UNKNOWN for invalid, out-of-range or
* unmappable formats (e.g. u10, which OIIO has no base type for).
*/
static OIIO::TypeDesc::BASETYPE
get_oiio_base_type_from_format(olive::core::PixelFormat format);
/**
* @brief Returns the native pixel format matching an OIIO base type
*
* Returns olive::core::PixelFormat::invalid for unknown or unmappable
* base types.
*/
static olive::core::PixelFormat
get_format_from_oiio_basetype(OIIO::TypeDesc::BASETYPE type);
/**
* @brief Reads the PixelAspectRatio attribute of an image spec
*
* Defaults to 1:1 when the attribute is not present.
*/
static olive::core::Rational
get_pixel_aspect_ratio_from_oiio(const OIIO::ImageSpec &spec);
/**
* @brief Copies raw pixel data into an OIIO image buffer
*
* Flattened form of the former Frame-based frame_to_buffer(); pass
* Frame::const_data() and Frame::linesize_bytes() at the call site.
*/
static void frame_to_buffer(const void *data, int64_t linesize_bytes,
OIIO::ImageBuf *buf);
/**
* @brief Copies an OIIO image buffer's pixels into raw memory
*
* Flattened form of the former Frame-based buffer_to_frame(); pass
* Frame::data() and Frame::linesize_bytes() at the call site.
*/
static void buffer_to_frame(OIIO::ImageBuf *buf, void *data,
int64_t linesize_bytes);
};
#endif // OAK_OIIOUTILS_H
+58
View File
@@ -0,0 +1,58 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_POWER_H
#define OAK_POWER_H
#include <stdint.h>
#include "define.h"
namespace olive
{
inline uint32_t ceil_to_power_of_2(uint32_t v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
inline uint32_t floor_to_power_of_2(uint32_t x)
{
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
return x - (x >> 1);
}
}
#endif // OAK_POWER_H
+52
View File
@@ -0,0 +1,52 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "qtutils.h"
#include <sys/stat.h>
namespace olive
{
std::chrono::system_clock::time_point
QtUtils::get_creation_date(const std::filesystem::path &path)
{
struct stat st;
if (stat(path.c_str(), &st) != 0) {
return std::chrono::system_clock::time_point{};
}
#if defined(__APPLE__) || defined(__FreeBSD__)
// Filesystems that record a birth time expose it as st_birthtimespec;
// fall back to the metadata change time when it is not set.
time_t t = st.st_birthtimespec.tv_sec;
if (t == 0 || t == -1) {
t = st.st_ctimespec.tv_sec;
}
return std::chrono::system_clock::from_time_t(t);
#else
// Portable fallback: st_ctime (metadata change time), matching the
// Qt implementation's birthTime()/metadataChangeTime() fallback.
return std::chrono::system_clock::from_time_t(st.st_ctime);
#endif
}
}
+89
View File
@@ -0,0 +1,89 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_QTUTILS_H
#define OAK_QTUTILS_H
#include <chrono>
#include <cstdint>
#include <filesystem>
namespace olive
{
/**
* @brief Qt-free replacement for the former Qt-based QtUtils helper class.
*
* Only the functions actually used by the engine are kept. The UI-bound
* helpers (font metrics, combo boxes, QFrame creation, QColor conversion)
* remain available to Qt callers through the original Qt version.
*/
class QtUtils {
public:
/**
* @brief Convert a pointer to an integer value that can be passed around as data
*/
static uintptr_t ptr_to_value(void *ptr)
{
return reinterpret_cast<uintptr_t>(ptr);
}
/**
* @brief Convert an integer produced by ptr_to_value() back to a pointer of any kind
*/
template <class T> static T *value_to_ptr(uintptr_t value)
{
return reinterpret_cast<T *>(value);
}
/**
* @brief Walk the parent chain of an object looking for an ancestor of type T
*
* Works with any object type that provides a parent() member returning a
* pointer to the same (or a base) type, e.g. QObject.
*/
template <typename T, typename Obj>
static T *get_parent_of_type(Obj *child)
{
auto *t = child ? child->parent() : nullptr;
while (t) {
if (T *p = dynamic_cast<T *>(t)) {
return p;
}
t = t->parent();
}
return nullptr;
}
/**
* @brief Get the creation (birth) time of a file
*
* Falls back to the last metadata change / modification time on
* filesystems that do not record birth times.
*/
static std::chrono::system_clock::time_point
get_creation_date(const std::filesystem::path &path);
};
}
#endif // OAK_QTUTILS_H
+82
View File
@@ -0,0 +1,82 @@
/*
* Oak Video Editor - Non-Linear Video Editor
* Copyright (C) 2025 Olive CE Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OAK_THREADSAFEMAP_H
#define OAK_THREADSAFEMAP_H
#include <map>
#include <mutex>
/**
* @brief A minimal mutex-guarded associative container
*
* Drop-in stdlib replacement for the former QMap/QMutex based template.
*/
template <typename K, typename V> class ThreadSafeMap {
public:
ThreadSafeMap() = default;
/**
* @brief Insert or overwrite the value stored under `key`
*/
void insert(const K &key, const V &value)
{
std::lock_guard<std::mutex> locker(mutex_);
map_.insert_or_assign(key, value);
}
/**
* @brief Returns whether `key` exists in the map
*/
bool contains(const K &key) const
{
std::lock_guard<std::mutex> locker(mutex_);
return map_.find(key) != map_.end();
}
/**
* @brief Copy the value stored under `key` into `out`
* @return true if the key existed, false otherwise (`out` left untouched)
*/
bool get(const K &key, V *out) const
{
std::lock_guard<std::mutex> locker(mutex_);
auto it = map_.find(key);
if (it == map_.end()) {
return false;
}
*out = it->second;
return true;
}
/**
* @brief Returns the number of entries in the map
*/
size_t size() const
{
std::lock_guard<std::mutex> locker(mutex_);
return map_.size();
}
private:
mutable std::mutex mutex_;
std::map<K, V> map_;
};
#endif // OAK_THREADSAFEMAP_H
+294
View File
@@ -0,0 +1,294 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "xmlutils.h"
#include <cstdio>
#include <cstring>
#include <expat.h>
namespace olive
{
namespace
{
/**
* @brief Expat user-data used while parsing a document into an event list.
*/
struct ParseContext {
std::vector<XmlStreamReader::Event> *events;
bool has_error;
std::string error_string;
};
void XMLCALL on_start_element(void *user_data, const XML_Char *name,
const XML_Char **atts)
{
ParseContext *ctx = static_cast<ParseContext *>(user_data);
XmlStreamReader::Event ev;
ev.type = XmlStreamReader::StartElement;
ev.name = name;
for (int i = 0; atts[i]; i += 2)
ev.attributes.push_back({atts[i], atts[i + 1]});
ctx->events->push_back(std::move(ev));
}
void XMLCALL on_end_element(void *user_data, const XML_Char *name)
{
ParseContext *ctx = static_cast<ParseContext *>(user_data);
XmlStreamReader::Event ev;
ev.type = XmlStreamReader::EndElement;
ev.name = name;
ctx->events->push_back(std::move(ev));
}
void XMLCALL on_characters(void *user_data, const XML_Char *s, int len)
{
ParseContext *ctx = static_cast<ParseContext *>(user_data);
if (!len)
return;
if (!ctx->events->empty() &&
ctx->events->back().type == XmlStreamReader::Characters) {
ctx->events->back().text.append(s, len);
return;
}
XmlStreamReader::Event ev;
ev.type = XmlStreamReader::Characters;
ev.text.assign(s, len);
ctx->events->push_back(std::move(ev));
}
std::string escape_text(const std::string &in)
{
std::string out;
out.reserve(in.size());
for (char c : in) {
switch (c) {
case '&': out += "&amp;"; break;
case '<': out += "&lt;"; break;
case '>': out += "&gt;"; break;
default: out += c; break;
}
}
return out;
}
std::string escape_attribute(const std::string &in)
{
std::string out;
out.reserve(in.size());
for (char c : in) {
switch (c) {
case '&': out += "&amp;"; break;
case '<': out += "&lt;"; break;
case '>': out += "&gt;"; break;
case '"': out += "&quot;"; break;
default: out += c; break;
}
}
return out;
}
} // namespace
XmlStreamReader::XmlStreamReader(const std::string &data)
: pos_(0)
, token_(Invalid)
, has_error_(false)
{
ParseContext ctx{&events_, false, std::string()};
XML_Parser parser = XML_ParserCreate(nullptr);
if (!parser) {
has_error_ = true;
error_string_ = "failed to create XML parser";
return;
}
XML_SetUserData(parser, &ctx);
XML_SetElementHandler(parser, on_start_element, on_end_element);
XML_SetCharacterDataHandler(parser, on_characters);
if (XML_Parse(parser, data.data(), static_cast<int>(data.size()),
XML_TRUE) == XML_STATUS_ERROR) {
char buf[256];
snprintf(buf, sizeof(buf), "%s at line %lu",
XML_ErrorString(XML_GetErrorCode(parser)),
static_cast<unsigned long>(
XML_GetCurrentLineNumber(parser)));
has_error_ = true;
error_string_ = buf;
}
XML_ParserFree(parser);
}
XmlStreamReader::TokenType XmlStreamReader::read_next()
{
attributes_.clear();
text_.clear();
name_.clear();
if (has_error_) {
token_ = Invalid;
return token_;
}
if (pos_ >= events_.size()) {
token_ = EndDocument;
return token_;
}
const Event &ev = events_[pos_++];
token_ = ev.type;
name_ = ev.name;
text_ = ev.text;
attributes_ = ev.attributes;
return token_;
}
std::string XmlStreamReader::read_element_text()
{
if (!is_start_element())
return std::string();
std::string result;
int depth = 1;
while (true) {
TokenType t = read_next();
if (t == Invalid || t == EndDocument)
break;
if (t == StartElement) {
depth++;
} else if (t == EndElement) {
if (--depth == 0)
break;
} else if (t == Characters && depth == 1) {
result += text_;
}
}
return result;
}
void XmlStreamReader::skip_current_element()
{
if (!is_start_element())
return;
int depth = 1;
while (true) {
TokenType t = read_next();
if (t == Invalid || t == EndDocument)
break;
if (t == StartElement)
depth++;
else if (t == EndElement && --depth == 0)
break;
}
}
XmlStreamWriter::XmlStreamWriter()
: open_start_tag_(false)
{
}
void XmlStreamWriter::write_start_element(const std::string &name)
{
if (open_start_tag_) {
output_ += '>';
open_start_tag_ = false;
}
output_ += '<';
output_ += name;
stack_.push_back(name);
open_start_tag_ = true;
}
void XmlStreamWriter::write_attribute(const std::string &name,
const std::string &value)
{
if (!open_start_tag_)
return;
output_ += ' ';
output_ += name;
output_ += "=\"";
output_ += escape_attribute(value);
output_ += '"';
}
void XmlStreamWriter::write_characters(const std::string &text)
{
if (open_start_tag_) {
output_ += '>';
open_start_tag_ = false;
}
output_ += escape_text(text);
}
void XmlStreamWriter::write_text_element(const std::string &name,
const std::string &text)
{
write_start_element(name);
write_characters(text);
write_end_element();
}
void XmlStreamWriter::write_end_element()
{
if (stack_.empty())
return;
const std::string name = stack_.back();
stack_.pop_back();
if (open_start_tag_) {
output_ += "/>";
open_start_tag_ = false;
} else {
output_ += "</";
output_ += name;
output_ += '>';
}
}
void XmlStreamWriter::write_end_document()
{
while (!stack_.empty())
write_end_element();
}
bool xml_read_next_start_element(XmlStreamReader *reader)
{
XmlStreamReader::TokenType token;
while ((token = reader->read_next()) != XmlStreamReader::Invalid &&
token != XmlStreamReader::EndDocument) {
if (reader->is_end_element())
return false;
if (reader->is_start_element())
return true;
}
return false;
}
}
+191
View File
@@ -0,0 +1,191 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_XMLUTILS_H
#define OAK_XMLUTILS_H
#include <string>
#include <vector>
namespace olive
{
/**
* @brief A single XML attribute (name/value pair).
*
* De-Qt replacement for QXmlStreamAttribute.
*/
struct XmlStreamAttribute {
std::string name; /**< Attribute name. */
std::string value; /**< Attribute value (entity references resolved). */
};
/**
* @brief Minimal pull-style streaming XML reader (de-Qt replacement for QXmlStreamReader).
*
* Backed by expat. The whole document is parsed at construction time into an
* internal event list; navigation is done with read_next() and the state
* accessors, mirroring the QXmlStreamReader call patterns used by the engine
* (read_next_start_element loop, attribute iteration, read_element_text(),
* skip_current_element()).
*
* Only the subset of XML actually used by Oak project/config files is
* supported: elements, attributes, character data, comments and processing
* instructions (the latter two are skipped transparently by expat handler
* omission). Namespace processing is not performed.
*/
class XmlStreamReader
{
public:
/**
* @brief Token types, mirroring QXmlStreamReader::TokenType subset.
*/
enum TokenType {
Invalid, /**< Parse error occurred. */
StartElement,
EndElement,
Characters,
EndDocument
};
/**
* @brief Construct a reader over a complete XML document.
*
* @param data The full XML text. Parse errors are reported via
* has_error()/error_string() and make read_next() return Invalid.
*/
explicit XmlStreamReader(const std::string &data);
/**
* @brief Advance to the next token.
*
* @return The new current token type.
*/
TokenType read_next();
TokenType token_type() const { return token_; }
bool is_start_element() const { return token_ == StartElement; }
bool is_end_element() const { return token_ == EndElement; }
bool is_characters() const { return token_ == Characters; }
/**
* @brief Name of the current StartElement/EndElement token.
*/
const std::string &name() const { return name_; }
/**
* @brief Text of the current Characters token.
*/
const std::string &text() const { return text_; }
/**
* @brief Attributes of the current StartElement token.
*/
const std::vector<XmlStreamAttribute> &attributes() const { return attributes_; }
/**
* @brief Read the concatenated character data of the current element.
*
* Must be called on a StartElement token. Consumes tokens up to and
* including the matching EndElement. Mirrors
* QXmlStreamReader::readElementText().
*
* @return The element's text content.
*/
std::string read_element_text();
/**
* @brief Skip the current element and all of its children.
*
* Mirrors QXmlStreamReader::skipCurrentElement().
*/
void skip_current_element();
bool has_error() const { return has_error_; }
const std::string &error_string() const { return error_string_; }
/**
* @brief One parsed SAX event. Internal; exposed for the expat handlers.
*/
struct Event {
TokenType type;
std::string name;
std::string text;
std::vector<XmlStreamAttribute> attributes;
};
private:
std::vector<Event> events_; /**< Parsed event list. */
size_t pos_; /**< Index of the next event to consume. */
TokenType token_; /**< Current token type. */
std::string name_; /**< Current element name. */
std::string text_; /**< Current character data. */
std::vector<XmlStreamAttribute> attributes_; /**< Current attributes. */
bool has_error_;
std::string error_string_;
};
/**
* @brief Minimal streaming XML writer (de-Qt replacement for QXmlStreamWriter).
*
* Only the functions actually used by Oak serializers are provided:
* write_start_element/write_attribute/write_characters/write_text_element/
* write_end_element/write_end_document. Text and attribute values are escaped
* for the five predefined XML entities.
*/
class XmlStreamWriter
{
public:
XmlStreamWriter();
void write_start_element(const std::string &name);
void write_attribute(const std::string &name, const std::string &value);
void write_characters(const std::string &text);
void write_text_element(const std::string &name, const std::string &text);
void write_end_element();
void write_end_document();
/**
* @brief The document written so far.
*/
const std::string &output() const { return output_; }
private:
std::string output_; /**< Accumulated document text. */
std::vector<std::string> stack_; /**< Open element name stack. */
bool open_start_tag_; /**< A '>' is pending for the open tag. */
};
/**
* @brief Workaround for read-next-start-element not detecting the end of a document.
*
* De-Qt port of the original xml_read_next_start_element(). Reads until a
* start element is found (returns true), an end element or the end of the
* document is reached (returns false). The old cancel_atom parameter (an
* engine/render dependency) has been removed.
*
* @param reader The reader to advance.
* @return true if positioned on a start element, false otherwise.
*/
bool xml_read_next_start_element(XmlStreamReader *reader);
}
#endif // OAK_XMLUTILS_H
+70
View File
@@ -0,0 +1,70 @@
# 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/>.
# Standalone build driver for the oakcommon module. Builds only oakcommon,
# its tests, and the in-repo libraries it links against (olivecore,
# ffmpeg_bridge), without configuring the heavy top-level project.
#
# Usage (macOS/Homebrew):
# cmake -S src/common/standalone -B build-oakcommon
# cmake --build build-oakcommon -j
# ctest --test-dir build-oakcommon
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(oakcommon-standalone LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
get_filename_component(OAK_REPO_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE)
list(APPEND CMAKE_MODULE_PATH "${OAK_REPO_ROOT}/cmake")
if(EXISTS "/opt/homebrew")
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
endif()
# Third-party dependencies. The top-level project resolves OCIO/OIIO through
# cmake/FindOpenColorIO.cmake / FindOpenImageIO.cmake (which are .so-centric);
# here we use the upstream CMake config packages shipped by Homebrew and map
# them onto the variables oakcommon's CMakeLists consumes.
find_package(EXPAT REQUIRED)
find_package(OpenColorIO CONFIG REQUIRED)
find_package(OpenImageIO CONFIG REQUIRED)
set(OCIO_LIBRARIES OpenColorIO::OpenColorIO)
set(OCIO_INCLUDE_DIRS "")
set(OIIO_LIBRARIES OpenImageIO::OpenImageIO)
set(OIIO_INCLUDE_DIRS "")
# In-repo libraries oakcommon links against:
# - olivecore: olive::core::Rational wraps the oakcore_* C ABI (real symbols)
# - ffmpeg_bridge: fb_find_best_pix_fmt_of_list (real symbol)
set(OLIVECORE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
# The /opt/otio OpenTimelineIO dylibs use @loader_path install names that
# break when linked from a build tree; oakcommon does not need OTIO.
set(CMAKE_DISABLE_FIND_PACKAGE_OpenTimelineIO ON)
add_subdirectory(${OAK_REPO_ROOT}/core ${CMAKE_BINARY_DIR}/core)
# The top-level project puts third_party/openfx/include on the global
# include path; replicate it here for olivecore's pixelformat.h (ofxCore.h).
target_include_directories(olivecore PUBLIC ${OAK_REPO_ROOT}/third_party/openfx/include)
add_subdirectory(${OAK_REPO_ROOT}/ffmpeg_bridge ${CMAKE_BINARY_DIR}/ffmpeg_bridge)
set(BUILD_TESTING ON)
enable_testing()
add_subdirectory(${OAK_REPO_ROOT}/src/common ${CMAKE_BINARY_DIR}/common)
+44
View File
@@ -0,0 +1,44 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive Team
# Modifications Copyright (C) 2025 mikesolar
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
find_package(GTest REQUIRED)
include(GoogleTest)
add_executable(oakcommon-gtest
commandlineparser_test.cpp
current_test.cpp
debug_test.cpp
dropworkflowbehavior_test.cpp
ffmpegutils_test.cpp
filefunctions_test.cpp
memorypool_test.cpp
miscutils_test.cpp
ocioutils_test.cpp
oiioutils_test.cpp
power_test.cpp
qtutils_test.cpp
threadsafemap_test.cpp
xmlutils_test.cpp
)
target_link_libraries(oakcommon-gtest PRIVATE
oakcommon
GTest::gtest
GTest::gtest_main
)
gtest_discover_tests(oakcommon-gtest)
+275
View File
@@ -0,0 +1,275 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <cstring>
#include <vector>
#include <gtest/gtest.h>
#include "common/commandlineparser.h"
TEST(CommandLineParser, InitFree)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, FreeNull)
{
oakcommon_commandlineparser_free(nullptr);
oakcommon_commandlineoption_free(nullptr);
oakcommon_commandlinepositionalargument_free(nullptr);
}
TEST(CommandLineParser, AddOptionInvalidArgs)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
const char *names[] = { "h", "-help" };
OakCommonCommandLineOption *option = nullptr;
EXPECT_EQ(oakcommon_commandlineparser_add_option(
nullptr, names, 2, "desc", 0, nullptr, 0, &option),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlineparser_add_option(
parser, nullptr, 2, "desc", 0, nullptr, 0, &option),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 0, "desc", 0, nullptr, 0, &option),
OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, ProcessOptionHitAndMiss)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
const char *names[] = { "h", "-help" };
OakCommonCommandLineOption *hit = nullptr;
OakCommonCommandLineOption *miss = nullptr;
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 2, "Show help", 0, nullptr, 0, &hit),
OAKCOMMON_OK);
ASSERT_NE(hit, nullptr);
const char *other_names[] = { "v", "-version" };
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, other_names, 2, "Show version", 0, nullptr, 0, &miss),
OAKCOMMON_OK);
ASSERT_NE(miss, nullptr);
const char *argv[] = { "oak", "-h" };
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 2),
OAKCOMMON_OK);
bool is_set = true;
ASSERT_EQ(oakcommon_commandlineoption_is_set(hit, &is_set), OAKCOMMON_OK);
EXPECT_TRUE(is_set);
is_set = true;
ASSERT_EQ(oakcommon_commandlineoption_is_set(miss, &is_set), OAKCOMMON_OK);
EXPECT_FALSE(is_set);
oakcommon_commandlineoption_free(hit);
oakcommon_commandlineoption_free(miss);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, ProcessMatchesSecondAliasCaseInsensitive)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
const char *names[] = { "h", "-help" };
OakCommonCommandLineOption *option = nullptr;
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 2, "Show help", 0, nullptr, 0, &option),
OAKCOMMON_OK);
// Only the first dash is stripped; matching is case-insensitive
const char *argv[] = { "oak", "--HELP" };
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 2),
OAKCOMMON_OK);
bool is_set = false;
ASSERT_EQ(oakcommon_commandlineoption_is_set(option, &is_set),
OAKCOMMON_OK);
EXPECT_TRUE(is_set);
oakcommon_commandlineoption_free(option);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, OptionTakesArg)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
const char *names[] = { "e", "-export" };
OakCommonCommandLineOption *option = nullptr;
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 2, "Export", 1, "filename", 0, &option),
OAKCOMMON_OK);
const char *argv[] = { "oak", "-e", "/tmp/out.mp4" };
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 3),
OAKCOMMON_OK);
bool is_set = false;
ASSERT_EQ(oakcommon_commandlineoption_is_set(option, &is_set),
OAKCOMMON_OK);
EXPECT_TRUE(is_set);
int required =
oakcommon_commandlineoption_get_setting(option, nullptr, 0);
ASSERT_GT(required, 0);
EXPECT_EQ(required, static_cast<int>(strlen("/tmp/out.mp4")) + 1);
std::vector<char> buf(static_cast<size_t>(required));
ASSERT_EQ(oakcommon_commandlineoption_get_setting(option, buf.data(),
required),
required);
EXPECT_STREQ(buf.data(), "/tmp/out.mp4");
oakcommon_commandlineoption_free(option);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, PositionalArgument)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommonCommandLinePositionalArgument *arg = nullptr;
ASSERT_EQ(oakcommon_commandlineparser_add_positional_argument(
parser, "project", "Project file", 0, &arg),
OAKCOMMON_OK);
ASSERT_NE(arg, nullptr);
const char *argv[] = { "oak", "/tmp/project.ove" };
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 2),
OAKCOMMON_OK);
int required =
oakcommon_commandlinepositionalargument_get_setting(arg, nullptr, 0);
ASSERT_EQ(required, static_cast<int>(strlen("/tmp/project.ove")) + 1);
std::vector<char> buf(static_cast<size_t>(required));
ASSERT_EQ(oakcommon_commandlinepositionalargument_get_setting(
arg, buf.data(), required),
required);
EXPECT_STREQ(buf.data(), "/tmp/project.ove");
oakcommon_commandlinepositionalargument_free(arg);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, PositionalArgumentSetGetSetting)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommonCommandLinePositionalArgument *arg = nullptr;
ASSERT_EQ(oakcommon_commandlineparser_add_positional_argument(
parser, "project", "Project file", 0, &arg),
OAKCOMMON_OK);
ASSERT_NE(arg, nullptr);
ASSERT_EQ(oakcommon_commandlinepositionalargument_set_setting(arg,
"hello.ove"),
OAKCOMMON_OK);
char buf[64];
int required =
oakcommon_commandlinepositionalargument_get_setting(arg, buf,
sizeof(buf));
EXPECT_EQ(required, static_cast<int>(strlen("hello.ove")) + 1);
EXPECT_STREQ(buf, "hello.ove");
// Error paths
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(nullptr,
"x"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(arg,
nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlinepositionalargument_get_setting(nullptr,
buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
oakcommon_commandlinepositionalargument_free(arg);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, TwoStageStringGetterSmallBuffer)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
const char *names[] = { "e" };
OakCommonCommandLineOption *option = nullptr;
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 1, "Export", 1, "filename", 0, &option),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_commandlineoption_set_setting(option, "abcdef"),
OAKCOMMON_OK);
// Buffer too small: return value reports the required size (7) and
// the written contents are truncated but NUL-terminated
char buf[4];
int required = oakcommon_commandlineoption_get_setting(option, buf,
sizeof(buf));
EXPECT_EQ(required, 7);
EXPECT_STREQ(buf, "abc");
// NULL buffer / zero size only reports the required size
EXPECT_EQ(oakcommon_commandlineoption_get_setting(option, nullptr, 0), 7);
// Error paths
EXPECT_EQ(oakcommon_commandlineoption_get_setting(nullptr, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlineoption_is_set(option, nullptr),
OAKCOMMON_E_INVALID);
bool dummy = false;
EXPECT_EQ(oakcommon_commandlineoption_is_set(nullptr, &dummy),
OAKCOMMON_E_INVALID);
oakcommon_commandlineoption_free(option);
oakcommon_commandlineparser_free(parser);
}
TEST(CommandLineParser, ProcessInvalidArgs)
{
EXPECT_EQ(oakcommon_commandlineparser_process(nullptr, nullptr, 0),
OAKCOMMON_E_INVALID);
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
EXPECT_EQ(oakcommon_commandlineparser_process(parser, nullptr, 1),
OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(parser);
}
+130
View File
@@ -0,0 +1,130 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <cstdlib>
#include <gtest/gtest.h>
#include "common/current.h"
TEST(OakCommonCurrent, InstanceIsSingleton)
{
OakCommonCurrent *a = oakcommon_current_instance();
OakCommonCurrent *b = oakcommon_current_instance();
ASSERT_NE(a, nullptr);
EXPECT_EQ(a, b);
}
TEST(OakCommonCurrent, FreeNullIsNoOp)
{
oakcommon_current_free(nullptr);
oakcommon_current_free(oakcommon_current_instance());
SUCCEED();
}
TEST(OakCommonCurrent, VideoParamsSetGetRoundTrip)
{
OakCommonCurrent *c = oakcommon_current_instance();
int *params = static_cast<int *>(malloc(sizeof(int)));
ASSERT_NE(params, nullptr);
*params = 42;
ASSERT_EQ(oakcommon_current_set_video_params(c, params, free),
OAKCOMMON_OK);
void *out = nullptr;
ASSERT_EQ(oakcommon_current_get_video_params(c, &out), OAKCOMMON_OK);
EXPECT_EQ(out, params);
EXPECT_EQ(*static_cast<int *>(out), 42);
// Clearing the slot must invoke the destroy callback.
ASSERT_EQ(oakcommon_current_set_video_params(c, nullptr, nullptr),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_current_get_video_params(c, &out), OAKCOMMON_OK);
EXPECT_EQ(out, nullptr);
}
TEST(OakCommonCurrent, AudioParamsSetGetRoundTrip)
{
OakCommonCurrent *c = oakcommon_current_instance();
int value = 7; // non-owning storage, no destroy callback
ASSERT_EQ(oakcommon_current_set_audio_params(c, &value, nullptr),
OAKCOMMON_OK);
void *out = nullptr;
ASSERT_EQ(oakcommon_current_get_audio_params(c, &out), OAKCOMMON_OK);
EXPECT_EQ(out, &value);
ASSERT_EQ(oakcommon_current_set_audio_params(c, nullptr, nullptr),
OAKCOMMON_OK);
}
TEST(OakCommonCurrent, PluginHostAndCacheRoundTrip)
{
OakCommonCurrent *c = oakcommon_current_instance();
int host = 1, cache = 2;
ASSERT_EQ(oakcommon_current_set_plugin_host(c, &host, nullptr),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_current_set_plugin_cache(c, &cache, nullptr),
OAKCOMMON_OK);
void *out = nullptr;
ASSERT_EQ(oakcommon_current_get_plugin_host(c, &out), OAKCOMMON_OK);
EXPECT_EQ(out, &host);
ASSERT_EQ(oakcommon_current_get_plugin_cache(c, &out), OAKCOMMON_OK);
EXPECT_EQ(out, &cache);
ASSERT_EQ(oakcommon_current_set_plugin_host(c, nullptr, nullptr),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_current_set_plugin_cache(c, nullptr, nullptr),
OAKCOMMON_OK);
}
TEST(OakCommonCurrent, NullHandleAndOutArgs)
{
void *out = nullptr;
int flag = 0;
EXPECT_EQ(oakcommon_current_set_video_params(nullptr, &flag, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_get_video_params(nullptr, &out),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_get_video_params(
oakcommon_current_instance(), nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_is_interactive(nullptr, &flag),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_is_interactive(
oakcommon_current_instance(), nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonCurrent, IsInteractive)
{
int flag = 0;
ASSERT_EQ(oakcommon_current_is_interactive(
oakcommon_current_instance(), &flag),
OAKCOMMON_OK);
EXPECT_EQ(flag, 1);
}
+69
View File
@@ -0,0 +1,69 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include "common/debug.h"
TEST(OakCommonDebug, LogValidMessage)
{
EXPECT_EQ(oakcommon_debug_log(OAKCOMMON_DEBUG_WARNING, "hello"),
OAKCOMMON_OK);
}
TEST(OakCommonDebug, LogNullMessage)
{
EXPECT_EQ(oakcommon_debug_log(OAKCOMMON_DEBUG_WARNING, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonDebug, LogOutOfRangeLevel)
{
// Out-of-range levels are tolerated and print as UNKNOWN.
EXPECT_EQ(oakcommon_debug_log(999, "odd level"), OAKCOMMON_OK);
}
TEST(OakCommonDebug, LevelNameRoundTrip)
{
char buf[16];
int needed = oakcommon_debug_level_name(OAKCOMMON_DEBUG_WARNING,
buf, sizeof(buf));
ASSERT_GT(needed, 0);
ASSERT_LE(needed, (int)sizeof(buf));
EXPECT_STREQ(buf, "WARNING");
}
TEST(OakCommonDebug, LevelNameQuerySize)
{
int needed = oakcommon_debug_level_name(OAKCOMMON_DEBUG_DEBUG,
nullptr, 0);
EXPECT_EQ(needed, 6); // "DEBUG" + NUL
}
TEST(OakCommonDebug, LevelNameUnknownLevel)
{
char buf[16];
int needed = oakcommon_debug_level_name(-1, buf, sizeof(buf));
ASSERT_GT(needed, 0);
ASSERT_LE(needed, (int)sizeof(buf));
EXPECT_STREQ(buf, "UNKNOWN");
}
@@ -0,0 +1,76 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include "common/dropworkflowbehavior.h"
TEST(OakCommonDropWorkflowBehavior, IsValidAcceptsAllEnumerators)
{
EXPECT_EQ(oakcommon_drop_workflow_behavior_is_valid(OAKCOMMON_DWS_ASK),
1);
EXPECT_EQ(oakcommon_drop_workflow_behavior_is_valid(OAKCOMMON_DWS_AUTO),
1);
EXPECT_EQ(
oakcommon_drop_workflow_behavior_is_valid(OAKCOMMON_DWS_MANUAL),
1);
EXPECT_EQ(
oakcommon_drop_workflow_behavior_is_valid(OAKCOMMON_DWS_DISABLE),
1);
}
TEST(OakCommonDropWorkflowBehavior, IsValidRejectsOutOfRange)
{
EXPECT_EQ(oakcommon_drop_workflow_behavior_is_valid(-1), 0);
EXPECT_EQ(oakcommon_drop_workflow_behavior_is_valid(4), 0);
}
TEST(OakCommonDropWorkflowBehavior, NameRoundTrip)
{
char buf[16];
int needed = oakcommon_drop_workflow_behavior_name(OAKCOMMON_DWS_AUTO,
buf, sizeof(buf));
ASSERT_GT(needed, 0);
ASSERT_LE(needed, (int)sizeof(buf));
EXPECT_STREQ(buf, "AUTO");
}
TEST(OakCommonDropWorkflowBehavior, NameQuerySizeAndTooSmallBuffer)
{
int needed = oakcommon_drop_workflow_behavior_name(OAKCOMMON_DWS_DISABLE,
nullptr, 0);
EXPECT_EQ(needed, 8); // "DISABLE" + NUL
char buf[4];
EXPECT_EQ(oakcommon_drop_workflow_behavior_name(OAKCOMMON_DWS_DISABLE,
buf, sizeof(buf)),
needed);
}
TEST(OakCommonDropWorkflowBehavior, NameInvalidValue)
{
char buf[16];
int needed = oakcommon_drop_workflow_behavior_name(99, buf, sizeof(buf));
ASSERT_GT(needed, 0);
ASSERT_LE(needed, (int)sizeof(buf));
EXPECT_STREQ(buf, "UNKNOWN");
}
+206
View File
@@ -0,0 +1,206 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include "common/ffmpegutils.h"
#include "../src/ffmpegutils.h"
TEST(OakCommonFFmpegUtils, GetCompatiblePixelFormatMapsCorrectly)
{
int out = -2;
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_pixel_format(
olive::core::PixelFormat::u8, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::PixelFormat::u8);
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_pixel_format(
olive::core::PixelFormat::u10, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::PixelFormat::u8);
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_pixel_format(
olive::core::PixelFormat::f32, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::PixelFormat::u16);
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_pixel_format(
olive::core::PixelFormat::invalid, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::PixelFormat::invalid);
}
TEST(OakCommonFFmpegUtils, GetCompatiblePixelFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_pixel_format(
olive::core::PixelFormat::u8, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetFFmpegPixelFormatMapsCorrectly)
{
int out = -2;
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
olive::core::PixelFormat::u8,
OAKCOMMON_RGBA_CHANNEL_COUNT, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_rgba);
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
olive::core::PixelFormat::u8,
OAKCOMMON_RGB_CHANNEL_COUNT, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_rg_b24);
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
olive::core::PixelFormat::u16,
OAKCOMMON_RGBA_CHANNEL_COUNT, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_rgb_a64_le);
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
olive::core::PixelFormat::invalid,
OAKCOMMON_RGBA_CHANNEL_COUNT, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_none);
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
olive::core::PixelFormat::u8, 2, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_none);
}
TEST(OakCommonFFmpegUtils, GetFFmpegPixelFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
olive::core::PixelFormat::u8,
OAKCOMMON_RGBA_CHANNEL_COUNT, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetNativeSampleFormatMapsCorrectly)
{
int out = -2;
EXPECT_EQ(oakcommon_ffmpegutils_get_native_sample_format(
fb_sample_fmt_u8, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::SampleFormat::u8);
EXPECT_EQ(oakcommon_ffmpegutils_get_native_sample_format(
fb_sample_fmt_fltp, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::SampleFormat::f32_p);
EXPECT_EQ(oakcommon_ffmpegutils_get_native_sample_format(
fb_sample_fmt_dblp, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::SampleFormat::f64_p);
EXPECT_EQ(oakcommon_ffmpegutils_get_native_sample_format(
fb_sample_fmt_none, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, olive::core::SampleFormat::invalid);
}
TEST(OakCommonFFmpegUtils, GetNativeSampleFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_native_sample_format(
fb_sample_fmt_u8, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetFFmpegSampleFormatMapsCorrectly)
{
int out = -2;
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_sample_format(
olive::core::SampleFormat::u8, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_sample_fmt_u8);
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_sample_format(
olive::core::SampleFormat::f32_p, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_sample_fmt_fltp);
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_sample_format(
olive::core::SampleFormat::f64_p, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_sample_fmt_dblp);
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_sample_format(
olive::core::SampleFormat::invalid, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_sample_fmt_none);
}
TEST(OakCommonFFmpegUtils, GetFFmpegSampleFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_sample_format(
olive::core::SampleFormat::u8, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, ConvertJpegSpaceToRegularSpaceMapsCorrectly)
{
int out = -2;
EXPECT_EQ(oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(
fb_pix_fmt_yuv_j420_p, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_yu_v420_p);
EXPECT_EQ(oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(
fb_pix_fmt_yuv_j444_p, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_yu_v444_p);
EXPECT_EQ(oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(
fb_pix_fmt_rgba, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, fb_pix_fmt_rgba);
}
TEST(OakCommonFFmpegUtils, ConvertJpegSpaceToRegularSpaceNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(
fb_pix_fmt_rgba, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetCompatibleBridgePixelFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(
fb_pix_fmt_rgba, -1, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetCompatibleBridgePixelFormatMapsCorrectly)
{
/* Calls fb_find_best_pix_fmt_of_list() in ffmpeg_bridge, which
* requires a working FFmpeg runtime environment. */
GTEST_SKIP() << "requires FFmpeg runtime environment";
}
+421
View File
@@ -0,0 +1,421 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <filesystem>
#include <unistd.h>
#include <fstream>
#include <string>
#include <vector>
#include "common/filefunctions.h"
namespace fs = std::filesystem;
class FileFunctionsTest : public ::testing::Test {
protected:
void SetUp() override
{
handle_ = oakcommon_filefunctions_init();
ASSERT_NE(handle_, nullptr);
temp_dir_ = fs::temp_directory_path() /
fs::path("oakcommon_filefunctions_test_" +
std::to_string(::getpid()));
fs::remove_all(temp_dir_);
fs::create_directories(temp_dir_);
}
void TearDown() override
{
oakcommon_filefunctions_free(handle_);
handle_ = nullptr;
std::error_code ec;
fs::remove_all(temp_dir_, ec);
}
std::string write_file(const std::string &name,
const std::string &content)
{
fs::path p = temp_dir_ / name;
fs::create_directories(p.parent_path());
std::ofstream f(p, std::ios::binary);
f << content;
return p.string();
}
/**
* @brief Two-stage string call helper: query size, then fetch
*/
template <typename Fn>
std::string fetch_string(Fn &&fn, int *first_return)
{
int required = fn(nullptr, 0);
if (first_return != nullptr) {
*first_return = required;
}
EXPECT_GT(required, 0);
if (required <= 0) {
return std::string();
}
std::vector<char> buf(required);
int second = fn(buf.data(), required);
EXPECT_EQ(second, required);
return std::string(buf.data());
}
OakCommonFileFunctions *handle_ = nullptr;
fs::path temp_dir_;
};
TEST_F(FileFunctionsTest, FreeNullIsNoOp)
{
// Must not crash
oakcommon_filefunctions_free(nullptr);
}
TEST_F(FileFunctionsTest, NullHandleReturnsInvalid)
{
char buf[16];
int out = 0;
EXPECT_EQ(oakcommon_filefunctions_get_configuration_location(
nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_application_path(nullptr, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_temp_file_path(nullptr, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_auto_recovery_root(nullptr, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_unique_file_identifier(
nullptr, "x", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_ensure_filename_extension(
nullptr, "x", "y", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_read_file_as_string(nullptr, "x",
buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_safe_temporary_filename(
nullptr, "x", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_formatted_executable_for_platform(
nullptr, "x", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(nullptr, "x", 1,
&out),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_can_copy_directory_without_overwriting(
nullptr, "a", "b", &out),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_copy_directory(nullptr, "a", "b", 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_rename_file_allow_overwrite(
nullptr, "a", "b", &out),
OAKCOMMON_E_INVALID);
}
TEST_F(FileFunctionsTest, NullArgumentsReturnInvalid)
{
char buf[16];
int out = 0;
EXPECT_EQ(oakcommon_filefunctions_read_file_as_string(handle_, nullptr,
buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(handle_, nullptr,
1, &out),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(handle_, "x", 1,
nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_configuration_location(
handle_, nullptr, -1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_rename_file_allow_overwrite(
handle_, nullptr, "b", &out),
OAKCOMMON_E_INVALID);
}
TEST_F(FileFunctionsTest, ReadFileAsStringRoundTrip)
{
std::string content = "hello oak\nline two\n";
std::string path = write_file("read.txt", content);
std::string result = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_read_file_as_string(
handle_, path.c_str(), buf, size);
},
nullptr);
EXPECT_EQ(result, content);
// Buffer too small: required size is reported, nothing written
char tiny[2] = {'z', '\0'};
int required = oakcommon_filefunctions_read_file_as_string(
handle_, path.c_str(), tiny, sizeof(tiny));
EXPECT_EQ(required, static_cast<int>(content.size()) + 1);
}
TEST_F(FileFunctionsTest, ReadFileAsStringMissingFile)
{
std::string path = (temp_dir_ / "no_such_file.txt").string();
char buf[8] = {};
int required = oakcommon_filefunctions_read_file_as_string(
handle_, path.c_str(), buf, sizeof(buf));
// Missing file yields an empty string (required size 1)
EXPECT_EQ(required, 1);
EXPECT_STREQ(buf, "");
}
TEST_F(FileFunctionsTest, EnsureFilenameExtension)
{
std::string result = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_ensure_filename_extension(
handle_, "project", "ove", buf, size);
},
nullptr);
EXPECT_EQ(result, "project.ove");
// Already present (case-insensitive): unchanged
result = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_ensure_filename_extension(
handle_, "project.OVE", "ove", buf, size);
},
nullptr);
EXPECT_EQ(result, "project.OVE");
}
TEST_F(FileFunctionsTest, GetUniqueFileIdentifierStable)
{
std::string path = write_file("id.txt", "some data");
std::string id1 = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_unique_file_identifier(
handle_, path.c_str(), buf, size);
},
nullptr);
EXPECT_FALSE(id1.empty());
std::string id2 = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_unique_file_identifier(
handle_, path.c_str(), buf, size);
},
nullptr);
EXPECT_EQ(id1, id2);
}
TEST_F(FileFunctionsTest, GetUniqueFileIdentifierMissingFile)
{
std::string path = (temp_dir_ / "missing.bin").string();
char buf[8] = {};
int required = oakcommon_filefunctions_get_unique_file_identifier(
handle_, path.c_str(), buf, sizeof(buf));
EXPECT_EQ(required, 1);
EXPECT_STREQ(buf, "");
}
TEST_F(FileFunctionsTest, DirectoryIsValidCreateAndCheck)
{
int out = 0;
// Existing directory
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(
handle_, temp_dir_.string().c_str(), 0, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, 1);
// Non-existing directory, allowed to create
std::string sub = (temp_dir_ / "a" / "b").string();
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(
handle_, sub.c_str(), 1, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, 1);
EXPECT_TRUE(fs::is_directory(sub));
// Non-existing directory, not allowed to create
std::string sub2 = (temp_dir_ / "c").string();
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(
handle_, sub2.c_str(), 0, &out),
OAKCOMMON_OK);
EXPECT_EQ(out, 0);
EXPECT_FALSE(fs::exists(sub2));
}
TEST_F(FileFunctionsTest, GetSafeTemporaryFilenameDoesNotExist)
{
std::string base = write_file("safe.ove", "data");
std::string temp = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_safe_temporary_filename(
handle_, base.c_str(), buf, size);
},
nullptr);
EXPECT_FALSE(temp.empty());
EXPECT_FALSE(fs::exists(temp));
EXPECT_NE(temp, base);
EXPECT_TRUE(temp.find(".tmp") != std::string::npos);
}
TEST_F(FileFunctionsTest, RenameFileAllowOverwrite)
{
std::string from = write_file("from.txt", "new content");
std::string to = write_file("to.txt", "old content");
int out = 0;
EXPECT_EQ(oakcommon_filefunctions_rename_file_allow_overwrite(
handle_, from.c_str(), to.c_str(), &out),
OAKCOMMON_OK);
EXPECT_EQ(out, 1);
EXPECT_FALSE(fs::exists(from));
std::ifstream f(to, std::ios::binary);
std::string content((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
EXPECT_EQ(content, "new content");
}
TEST_F(FileFunctionsTest, RenameFileAllowOverwriteMissingSource)
{
std::string from = (temp_dir_ / "no_source.txt").string();
std::string to = (temp_dir_ / "target.txt").string();
int out = 1;
EXPECT_EQ(oakcommon_filefunctions_rename_file_allow_overwrite(
handle_, from.c_str(), to.c_str(), &out),
OAKCOMMON_OK);
EXPECT_EQ(out, 0);
}
TEST_F(FileFunctionsTest, CopyDirectoryAndOverwriteCheck)
{
write_file("src/one.txt", "one");
write_file("src/nested/two.txt", "two");
std::string src = (temp_dir_ / "src").string();
std::string dst = (temp_dir_ / "dst").string();
// Nothing at dest: safe to copy
int out = 0;
EXPECT_EQ(oakcommon_filefunctions_can_copy_directory_without_overwriting(
handle_, src.c_str(), dst.c_str(), &out),
OAKCOMMON_OK);
EXPECT_EQ(out, 1);
EXPECT_EQ(oakcommon_filefunctions_copy_directory(handle_, src.c_str(),
dst.c_str(), 0),
OAKCOMMON_OK);
EXPECT_TRUE(fs::exists(temp_dir_ / "dst" / "one.txt"));
EXPECT_TRUE(fs::exists(temp_dir_ / "dst" / "nested" / "two.txt"));
// Files exist at dest now: no longer safe
EXPECT_EQ(oakcommon_filefunctions_can_copy_directory_without_overwriting(
handle_, src.c_str(), dst.c_str(), &out),
OAKCOMMON_OK);
EXPECT_EQ(out, 0);
// Read back copied content through the C API
std::string copied = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_read_file_as_string(
handle_,
(temp_dir_ / "dst" / "nested" / "two.txt")
.string()
.c_str(),
buf, size);
},
nullptr);
EXPECT_EQ(copied, "two");
}
TEST_F(FileFunctionsTest, CopyDirectoryMissingSource)
{
std::string src = (temp_dir_ / "no_such_dir").string();
std::string dst = (temp_dir_ / "dst2").string();
// Underlying call logs and returns without copying; must not crash
EXPECT_EQ(oakcommon_filefunctions_copy_directory(handle_, src.c_str(),
dst.c_str(), 0),
OAKCOMMON_OK);
EXPECT_FALSE(fs::exists(temp_dir_ / "dst2" / "anything"));
}
TEST_F(FileFunctionsTest, LocationGettersReturnNonEmpty)
{
int first_return = 0;
std::string config = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_configuration_location(
handle_, buf, size);
},
&first_return);
EXPECT_FALSE(config.empty());
EXPECT_EQ(first_return, static_cast<int>(config.size()) + 1);
std::string temp = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_temp_file_path(
handle_, buf, size);
},
nullptr);
EXPECT_FALSE(temp.empty());
EXPECT_TRUE(fs::is_directory(temp));
std::string app = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_application_path(
handle_, buf, size);
},
nullptr);
EXPECT_FALSE(app.empty());
std::string recovery = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_auto_recovery_root(
handle_, buf, size);
},
nullptr);
EXPECT_FALSE(recovery.empty());
}
TEST_F(FileFunctionsTest, GetFormattedExecutableForPlatform)
{
std::string result = fetch_string(
[&](char *buf, int size) {
return oakcommon_filefunctions_get_formatted_executable_for_platform(
handle_, "oak", buf, size);
},
nullptr);
#ifdef _WIN32
EXPECT_EQ(result, "oak.exe");
#else
EXPECT_EQ(result, "oak");
#endif
}
+145
View File
@@ -0,0 +1,145 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <string.h>
#include "../src/memorypool.h"
namespace
{
/**
* @brief Test pool with a fixed 16-byte element size
*/
class TestPool : public olive::MemoryPool {
public:
TestPool(int element_count) : MemoryPool(element_count)
{
}
protected:
size_t get_element_size() override
{
return 16;
}
};
} // namespace
TEST(MemoryPool, AllocateReturnsWritableElement)
{
TestPool pool(4);
auto e = pool.get();
ASSERT_NE(e, nullptr);
ASSERT_NE(e->data(), nullptr);
// The full element must be writable
memset(e->data(), 0xAB, 16);
EXPECT_EQ(e->data()[0], 0xAB);
EXPECT_EQ(e->data()[15], 0xAB);
EXPECT_TRUE(pool.is_allocated());
EXPECT_EQ(pool.get_arena_count(), 1);
}
TEST(MemoryPool, ReleasedElementIsReused)
{
TestPool pool(4);
uint8_t *first = nullptr;
{
auto e = pool.get();
ASSERT_NE(e, nullptr);
first = e->data();
}
// After the ElementPtr went out of scope the chunk must be recycled
auto e2 = pool.get();
ASSERT_NE(e2, nullptr);
EXPECT_EQ(e2->data(), first);
EXPECT_EQ(pool.get_arena_count(), 1);
}
TEST(MemoryPool, FullArenaGrowsNewArena)
{
TestPool pool(2);
auto e1 = pool.get();
auto e2 = pool.get();
ASSERT_NE(e1, nullptr);
ASSERT_NE(e2, nullptr);
EXPECT_EQ(pool.get_arena_count(), 1);
// Both slots lent out, next get() must allocate a second arena
auto e3 = pool.get();
ASSERT_NE(e3, nullptr);
EXPECT_EQ(pool.get_arena_count(), 2);
}
TEST(MemoryPool, ClearFreesAllArenas)
{
TestPool pool(4);
auto e = pool.get();
ASSERT_NE(e, nullptr);
e->release();
e.reset();
pool.clear();
EXPECT_FALSE(pool.is_allocated());
EXPECT_EQ(pool.get_arena_count(), 0);
}
TEST(MemoryPool, InvalidElementCountReturnsNull)
{
TestPool pool(0);
EXPECT_EQ(pool.get(), nullptr);
TestPool negative(-1);
EXPECT_EQ(negative.get(), nullptr);
}
TEST(MemoryPool, ClearEmptyArenasKeepsRecentlyEmptiedArena)
{
TestPool pool(2);
{
auto e = pool.get();
ASSERT_NE(e, nullptr);
}
// Arena was emptied just now, well within kMaxEmptyArenaLife, so it must survive
pool.clear_empty_arenas();
EXPECT_EQ(pool.get_arena_count(), 1);
}
TEST(MemoryPool, ClearEmptyArenasKeepsArenaInUse)
{
TestPool pool(2);
auto e = pool.get();
ASSERT_NE(e, nullptr);
pool.clear_empty_arenas();
EXPECT_EQ(pool.get_arena_count(), 1);
}
+164
View File
@@ -0,0 +1,164 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <cmath>
#include <gtest/gtest.h>
#include "common/miscutils.h"
TEST(MiscUtilsDecibel, FromLinearZero)
{
double db;
ASSERT_EQ(oakcommon_decibel_from_linear(0.0, &db), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(db, OAKCOMMON_DECIBEL_MINIMUM);
}
TEST(MiscUtilsDecibel, FromLinearNegative)
{
double db;
ASSERT_EQ(oakcommon_decibel_from_linear(-1.0, &db), OAKCOMMON_OK);
// log10 of a negative value is NaN, not clamped to minimum
EXPECT_TRUE(std::isnan(db));
}
TEST(MiscUtilsDecibel, FromLinearOne)
{
double db;
ASSERT_EQ(oakcommon_decibel_from_linear(1.0, &db), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(db, 0.0);
}
TEST(MiscUtilsDecibel, FromLinearNullOut)
{
EXPECT_EQ(oakcommon_decibel_from_linear(1.0, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(MiscUtilsDecibel, ToLinearZeroDb)
{
double linear;
ASSERT_EQ(oakcommon_decibel_to_linear(0.0, &linear), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(linear, 1.0);
}
TEST(MiscUtilsDecibel, ToLinearMinimum)
{
double linear;
ASSERT_EQ(oakcommon_decibel_to_linear(OAKCOMMON_DECIBEL_MINIMUM,
&linear),
OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(linear, 0.0);
}
TEST(MiscUtilsDecibel, ToLinearNullOut)
{
EXPECT_EQ(oakcommon_decibel_to_linear(0.0, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(MiscUtilsDecibel, LinearRoundTrip)
{
double db;
double linear;
ASSERT_EQ(oakcommon_decibel_from_linear(0.5, &db), OAKCOMMON_OK);
ASSERT_EQ(oakcommon_decibel_to_linear(db, &linear), OAKCOMMON_OK);
EXPECT_NEAR(linear, 0.5, 1e-12);
}
TEST(MiscUtilsDecibel, FromLogarithmicBounds)
{
double db;
ASSERT_EQ(oakcommon_decibel_from_logarithmic(0.0, &db), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(db, OAKCOMMON_DECIBEL_MINIMUM);
ASSERT_EQ(oakcommon_decibel_from_logarithmic(1.0, &db), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(db, 0.0);
}
TEST(MiscUtilsDecibel, FromLogarithmicNullOut)
{
EXPECT_EQ(oakcommon_decibel_from_logarithmic(0.5, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(MiscUtilsDecibel, ToLogarithmicZeroDb)
{
double logarithmic;
ASSERT_EQ(oakcommon_decibel_to_logarithmic(0.0, &logarithmic),
OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(logarithmic, 1.0);
}
TEST(MiscUtilsDecibel, ToLogarithmicNullOut)
{
EXPECT_EQ(oakcommon_decibel_to_logarithmic(0.0, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(MiscUtilsDecibel, LogarithmicRoundTrip)
{
double logarithmic;
double linear;
ASSERT_EQ(oakcommon_decibel_linear_to_logarithmic(0.5, &logarithmic),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_decibel_logarithmic_to_linear(logarithmic, &linear),
OAKCOMMON_OK);
EXPECT_NEAR(linear, 0.5, 1e-12);
}
TEST(MiscUtilsDecibel, LogarithmicDirectNullOut)
{
double out;
EXPECT_EQ(oakcommon_decibel_linear_to_logarithmic(0.5, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_decibel_logarithmic_to_linear(0.5, nullptr),
OAKCOMMON_E_INVALID);
(void)out;
}
TEST(MiscUtilsLerp, Endpoints)
{
double value;
ASSERT_EQ(oakcommon_lerp(2.0, 8.0, 0.0, &value), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(value, 2.0);
ASSERT_EQ(oakcommon_lerp(2.0, 8.0, 1.0, &value), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(value, 8.0);
}
TEST(MiscUtilsLerp, Midpoint)
{
double value;
ASSERT_EQ(oakcommon_lerp(2.0, 8.0, 0.5, &value), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(value, 5.0);
}
TEST(MiscUtilsLerp, NegativeValues)
{
double value;
ASSERT_EQ(oakcommon_lerp(-10.0, 10.0, 0.25, &value), OAKCOMMON_OK);
EXPECT_DOUBLE_EQ(value, -5.0);
}
TEST(MiscUtilsLerp, NullOut)
{
EXPECT_EQ(oakcommon_lerp(0.0, 1.0, 0.5, nullptr), OAKCOMMON_E_INVALID);
}
+122
View File
@@ -0,0 +1,122 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <OpenColorIO/OpenColorIO.h>
namespace ocio = OCIO_NAMESPACE;
#include "common/ocioutils.h"
/**
* @note The whole oakcommon ocioutils family links against OpenColorIO;
* if the test binary is built without OCIO these tests cannot run at
* all. The mapping itself is pure logic and needs no OCIO config or GPU,
* so no GTEST_SKIP() is required at runtime.
*/
TEST(OCIOUtilsCApi, InitReturnsHandle)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
oakcommon_ocioutils_free(utils);
}
TEST(OCIOUtilsCApi, FreeNullIsNoOp)
{
oakcommon_ocioutils_free(NULL);
}
TEST(OCIOUtilsCApi, BitDepthMappingMatchesOCIO)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
const struct {
int pixel_format;
ocio::BitDepth expected;
} cases[] = {
{ OAKCOMMON_PIXEL_FORMAT_U8, ocio::BIT_DEPTH_UINT8 },
{ OAKCOMMON_PIXEL_FORMAT_U10, ocio::BIT_DEPTH_UINT10 },
{ OAKCOMMON_PIXEL_FORMAT_U16, ocio::BIT_DEPTH_UINT16 },
{ OAKCOMMON_PIXEL_FORMAT_F16, ocio::BIT_DEPTH_F16 },
{ OAKCOMMON_PIXEL_FORMAT_F32, ocio::BIT_DEPTH_F32 },
};
for (const auto &c : cases) {
int depth = -1;
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
utils, c.pixel_format, &depth),
OAKCOMMON_OK);
EXPECT_EQ(depth, static_cast<int>(c.expected));
}
oakcommon_ocioutils_free(utils);
}
TEST(OCIOUtilsCApi, InvalidFormatYieldsUnknownDepth)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
int depth = -1;
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
utils, OAKCOMMON_PIXEL_FORMAT_INVALID, &depth),
OAKCOMMON_OK);
EXPECT_EQ(depth, static_cast<int>(ocio::BIT_DEPTH_UNKNOWN));
oakcommon_ocioutils_free(utils);
}
TEST(OCIOUtilsCApi, NullHandleReturnsInvalid)
{
int depth = 0;
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
NULL, OAKCOMMON_PIXEL_FORMAT_U8, &depth),
OAKCOMMON_E_INVALID);
}
TEST(OCIOUtilsCApi, NullOutParamReturnsInvalid)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
utils, OAKCOMMON_PIXEL_FORMAT_U8, NULL),
OAKCOMMON_E_INVALID);
oakcommon_ocioutils_free(utils);
}
TEST(OCIOUtilsCApi, OutOfRangeFormatReturnsInvalid)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
int depth = 0;
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
utils, OAKCOMMON_PIXEL_FORMAT_COUNT, &depth),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
utils, -2, &depth),
OAKCOMMON_E_INVALID);
oakcommon_ocioutils_free(utils);
}
+191
View File
@@ -0,0 +1,191 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include "common/oiioutils.h"
/**
* @note The whole oakcommon oiioutils family links against OpenImageIO,
* but every function exposed through the C API is pure logic (enum
* mapping and double-to-rational conversion) that needs no image I/O,
* codec or GPU at runtime, so no GTEST_SKIP() is required. The
* OIIO::ImageBuf-based helpers (frame_to_buffer / buffer_to_frame) are
* intentionally not part of the C API and are not tested here.
*
* OIIO TypeDesc::BASETYPE values used below: UNKNOWN = 0, UINT8 = 2,
* UINT16 = 4, HALF = 10, FLOAT = 11, DOUBLE = 12 (LASTBASE is
* version-dependent; 100 is safely out of range).
*/
TEST(OIIOUtilsCApi, InitReturnsHandle)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
oakcommon_oiioutils_free(utils);
}
TEST(OIIOUtilsCApi, FreeNullIsNoOp)
{
oakcommon_oiioutils_free(NULL);
}
TEST(OIIOUtilsCApi, BaseTypeFromPixelFormat)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
const struct {
int pixel_format;
int expected_base_type;
} cases[] = {
{ OAKCOMMON_PIXEL_FORMAT_U8, 2 }, /* UINT8 */
{ OAKCOMMON_PIXEL_FORMAT_U16, 4 }, /* UINT16 */
{ OAKCOMMON_PIXEL_FORMAT_F16, 10 }, /* HALF */
{ OAKCOMMON_PIXEL_FORMAT_F32, 11 }, /* FLOAT */
{ OAKCOMMON_PIXEL_FORMAT_U10, 0 }, /* UNKNOWN: unmappable */
{ OAKCOMMON_PIXEL_FORMAT_INVALID, 0 }, /* UNKNOWN */
};
for (const auto &c : cases) {
int base_type = -1;
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(
utils, c.pixel_format, &base_type),
OAKCOMMON_OK);
EXPECT_EQ(base_type, c.expected_base_type);
}
oakcommon_oiioutils_free(utils);
}
TEST(OIIOUtilsCApi, BaseTypeFromPixelFormatRejectsBadArgs)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
int base_type = 0;
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(
NULL, OAKCOMMON_PIXEL_FORMAT_U8, &base_type),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(
utils, OAKCOMMON_PIXEL_FORMAT_U8, NULL),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(
utils, OAKCOMMON_PIXEL_FORMAT_COUNT, &base_type),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(
utils, -2, &base_type),
OAKCOMMON_E_INVALID);
oakcommon_oiioutils_free(utils);
}
TEST(OIIOUtilsCApi, PixelFormatFromBaseType)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
const struct {
int base_type;
int expected_pixel_format;
} cases[] = {
{ 2, OAKCOMMON_PIXEL_FORMAT_U8 }, /* UINT8 */
{ 4, OAKCOMMON_PIXEL_FORMAT_U16 }, /* UINT16 */
{ 10, OAKCOMMON_PIXEL_FORMAT_F16 }, /* HALF */
{ 11, OAKCOMMON_PIXEL_FORMAT_F32 }, /* FLOAT */
{ 0, OAKCOMMON_PIXEL_FORMAT_INVALID }, /* UNKNOWN */
{ 12, OAKCOMMON_PIXEL_FORMAT_INVALID }, /* DOUBLE: unmappable */
{ 13, OAKCOMMON_PIXEL_FORMAT_INVALID }, /* STRING: unmappable */
};
for (const auto &c : cases) {
int pixel_format = -2;
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(
utils, c.base_type, &pixel_format),
OAKCOMMON_OK);
EXPECT_EQ(pixel_format, c.expected_pixel_format);
}
oakcommon_oiioutils_free(utils);
}
TEST(OIIOUtilsCApi, PixelFormatFromBaseTypeRejectsBadArgs)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
int pixel_format = 0;
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(
NULL, 2, &pixel_format),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(
utils, 2, NULL),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(
utils, -1, &pixel_format),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(
utils, 100, &pixel_format),
OAKCOMMON_E_INVALID); /* >= LASTBASE */
oakcommon_oiioutils_free(utils);
}
TEST(OIIOUtilsCApi, PixelAspectRatioConvertsToRational)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
int num = 0;
int den = 0;
ASSERT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(utils, 1.0, &num,
&den),
OAKCOMMON_OK);
EXPECT_EQ(num, 1);
EXPECT_EQ(den, 1);
ASSERT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(utils, 1.5, &num,
&den),
OAKCOMMON_OK);
ASSERT_NE(den, 0);
EXPECT_NEAR(static_cast<double>(num) / den, 1.5, 1e-9);
oakcommon_oiioutils_free(utils);
}
TEST(OIIOUtilsCApi, PixelAspectRatioRejectsBadArgs)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
int num = 0;
int den = 0;
EXPECT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(NULL, 1.0, &num,
&den),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(utils, 1.0, NULL,
&den),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(utils, 1.0, &num,
NULL),
OAKCOMMON_E_INVALID);
oakcommon_oiioutils_free(utils);
}
+69
View File
@@ -0,0 +1,69 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include "../../include/common/power.h"
TEST(Power, CeilToPowerOf2)
{
uint32_t out = 0;
ASSERT_EQ(oakcommon_power_ceil_to_power_of_2(1, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 1u);
ASSERT_EQ(oakcommon_power_ceil_to_power_of_2(2, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 2u);
ASSERT_EQ(oakcommon_power_ceil_to_power_of_2(3, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 4u);
ASSERT_EQ(oakcommon_power_ceil_to_power_of_2(1000, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 1024u);
}
TEST(Power, CeilToPowerOf2NullOut)
{
EXPECT_EQ(oakcommon_power_ceil_to_power_of_2(3, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(Power, FloorToPowerOf2)
{
uint32_t out = 0;
ASSERT_EQ(oakcommon_power_floor_to_power_of_2(1, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 1u);
ASSERT_EQ(oakcommon_power_floor_to_power_of_2(3, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 2u);
ASSERT_EQ(oakcommon_power_floor_to_power_of_2(1024, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 1024u);
ASSERT_EQ(oakcommon_power_floor_to_power_of_2(1025, &out), OAKCOMMON_OK);
EXPECT_EQ(out, 1024u);
}
TEST(Power, FloorToPowerOf2NullOut)
{
EXPECT_EQ(oakcommon_power_floor_to_power_of_2(3, nullptr),
OAKCOMMON_E_INVALID);
}
+98
View File
@@ -0,0 +1,98 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <cstdio>
#include <fstream>
#include <gtest/gtest.h>
#include "common/qtutils.h"
TEST(QtUtils, PtrToValueRoundTrip)
{
int value = 42;
uint64_t encoded = 0;
ASSERT_EQ(oakcommon_qtutils_ptr_to_value(&value, &encoded),
OAKCOMMON_OK);
EXPECT_EQ(encoded, static_cast<uint64_t>(
reinterpret_cast<uintptr_t>(&value)));
void *decoded = nullptr;
ASSERT_EQ(oakcommon_qtutils_value_to_ptr(encoded, &decoded),
OAKCOMMON_OK);
EXPECT_EQ(decoded, static_cast<void *>(&value));
}
TEST(QtUtils, PtrToValueNullOutParam)
{
int value = 42;
EXPECT_EQ(oakcommon_qtutils_ptr_to_value(&value, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(QtUtils, ValueToPtrNullPointer)
{
void *decoded = reinterpret_cast<void *>(0xdeadbeef);
ASSERT_EQ(oakcommon_qtutils_value_to_ptr(0, &decoded), OAKCOMMON_OK);
EXPECT_EQ(decoded, nullptr);
}
TEST(QtUtils, ValueToPtrNullOutParam)
{
EXPECT_EQ(oakcommon_qtutils_value_to_ptr(0, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(QtUtils, GetCreationDateExistingFile)
{
char path[] = "/tmp/oakcommon_qtutils_test_XXXXXX";
int fd = mkstemp(path);
ASSERT_NE(fd, -1);
close(fd);
int64_t secs = 0;
ASSERT_EQ(oakcommon_qtutils_get_creation_date(path, &secs),
OAKCOMMON_OK);
EXPECT_GT(secs, 0);
std::remove(path);
}
TEST(QtUtils, GetCreationDateMissingFile)
{
int64_t secs = 0;
EXPECT_EQ(oakcommon_qtutils_get_creation_date(
"/tmp/oakcommon_qtutils_no_such_file_12345", &secs),
OAKCOMMON_E_NOT_FOUND);
}
TEST(QtUtils, GetCreationDateNullArgs)
{
int64_t secs = 0;
EXPECT_EQ(oakcommon_qtutils_get_creation_date(nullptr, &secs),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_qtutils_get_creation_date("/tmp", nullptr),
OAKCOMMON_E_INVALID);
}
+89
View File
@@ -0,0 +1,89 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <thread>
#include <vector>
#include "../src/threadsafemap.h"
TEST(ThreadSafeMap, InsertAndGet)
{
ThreadSafeMap<int, int> map;
map.insert(1, 42);
int value = 0;
ASSERT_TRUE(map.get(1, &value));
EXPECT_EQ(value, 42);
EXPECT_TRUE(map.contains(1));
EXPECT_EQ(map.size(), 1u);
}
TEST(ThreadSafeMap, InsertOverwritesExistingKey)
{
ThreadSafeMap<int, int> map;
map.insert(1, 42);
map.insert(1, 7);
int value = 0;
ASSERT_TRUE(map.get(1, &value));
EXPECT_EQ(value, 7);
EXPECT_EQ(map.size(), 1u);
}
TEST(ThreadSafeMap, MissingKeyReturnsFalse)
{
ThreadSafeMap<int, int> map;
EXPECT_FALSE(map.contains(99));
int value = 1234;
EXPECT_FALSE(map.get(99, &value));
// `out` must be left untouched on failure
EXPECT_EQ(value, 1234);
}
TEST(ThreadSafeMap, ConcurrentInserts)
{
ThreadSafeMap<int, int> map;
const int k_threads = 8;
const int k_inserts_per_thread = 500;
std::vector<std::thread> threads;
for (int t = 0; t < k_threads; t++) {
threads.emplace_back([&map, t, k_inserts_per_thread]() {
for (int i = 0; i < k_inserts_per_thread; i++) {
map.insert(t * k_inserts_per_thread + i, i);
}
});
}
for (std::thread &th : threads) {
th.join();
}
EXPECT_EQ(map.size(), size_t(k_threads * k_inserts_per_thread));
int value = 0;
ASSERT_TRUE(map.get(3 * k_inserts_per_thread + 123, &value));
EXPECT_EQ(value, 123);
}
+330
View File
@@ -0,0 +1,330 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include <vector>
#include "common/xmlutils.h"
namespace
{
std::string read_string(int (*fn)(OakCommonXmlReader *, char *, int),
OakCommonXmlReader *reader)
{
int needed = fn(reader, nullptr, 0);
EXPECT_GT(needed, 0);
std::vector<char> buf(needed);
EXPECT_EQ(fn(reader, buf.data(), needed), needed);
return std::string(buf.data());
}
} // namespace
TEST(CommonXmlUtilsCApi, ReaderInitNullData)
{
EXPECT_EQ(oakcommon_xml_reader_init(nullptr), nullptr);
}
TEST(CommonXmlUtilsCApi, ReaderFreeNull)
{
oakcommon_xml_reader_free(nullptr);
}
TEST(CommonXmlUtilsCApi, ReadNextStartElement)
{
OakCommonXmlReader *r =
oakcommon_xml_reader_init("<root><child>value</child></root>");
ASSERT_NE(r, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "root");
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "child");
oakcommon_xml_reader_free(r);
}
TEST(CommonXmlUtilsCApi, ReadNextStartElementNullHandle)
{
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(nullptr, &found),
OAKCOMMON_E_INVALID);
OakCommonXmlReader *r = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(r, nullptr);
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, nullptr),
OAKCOMMON_E_INVALID);
oakcommon_xml_reader_free(r);
}
TEST(CommonXmlUtilsCApi, ReadNextStartElementReturnsFalseAtEnd)
{
OakCommonXmlReader *r = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(r, nullptr);
int found = -1;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 0);
oakcommon_xml_reader_free(r);
}
TEST(CommonXmlUtilsCApi, NameNullHandle)
{
char buf[16];
EXPECT_EQ(oakcommon_xml_reader_name(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonXmlUtilsCApi, ReadElementText)
{
OakCommonXmlReader *r =
oakcommon_xml_reader_init("<root><child>a &amp; b</child></root>");
ASSERT_NE(r, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_read_element_text, r),
"a & b");
oakcommon_xml_reader_free(r);
}
TEST(CommonXmlUtilsCApi, ReadElementTextNullHandle)
{
char buf[16];
EXPECT_EQ(oakcommon_xml_reader_read_element_text(nullptr, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonXmlUtilsCApi, SkipCurrentElement)
{
OakCommonXmlReader *r = oakcommon_xml_reader_init(
"<root><unknown><nested/></unknown><known/></root>");
ASSERT_NE(r, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "unknown");
EXPECT_EQ(oakcommon_xml_reader_skip_current_element(r), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "known");
oakcommon_xml_reader_free(r);
}
TEST(CommonXmlUtilsCApi, SkipCurrentElementNullHandle)
{
EXPECT_EQ(oakcommon_xml_reader_skip_current_element(nullptr),
OAKCOMMON_E_INVALID);
}
TEST(CommonXmlUtilsCApi, Attributes)
{
OakCommonXmlReader *r = oakcommon_xml_reader_init(
"<root><item id=\"7\" name=\"a&quot;b\"/></root>");
ASSERT_NE(r, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
int count = 0;
EXPECT_EQ(oakcommon_xml_reader_attribute_count(r, &count), OAKCOMMON_OK);
ASSERT_EQ(count, 2);
char buf[32];
EXPECT_GT(oakcommon_xml_reader_attribute_name(r, 0, buf, sizeof(buf)), 0);
EXPECT_STREQ(buf, "id");
EXPECT_GT(oakcommon_xml_reader_attribute_value(r, 0, buf, sizeof(buf)),
0);
EXPECT_STREQ(buf, "7");
EXPECT_GT(oakcommon_xml_reader_attribute_name(r, 1, buf, sizeof(buf)), 0);
EXPECT_STREQ(buf, "name");
EXPECT_GT(oakcommon_xml_reader_attribute_value(r, 1, buf, sizeof(buf)),
0);
EXPECT_STREQ(buf, "a\"b");
oakcommon_xml_reader_free(r);
}
TEST(CommonXmlUtilsCApi, AttributeErrorPaths)
{
EXPECT_EQ(oakcommon_xml_reader_attribute_count(nullptr, nullptr),
OAKCOMMON_E_INVALID);
OakCommonXmlReader *r = oakcommon_xml_reader_init("<root a=\"1\"/>");
ASSERT_NE(r, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
char buf[16];
EXPECT_EQ(oakcommon_xml_reader_attribute_name(r, 5, buf, sizeof(buf)),
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_xml_reader_attribute_value(r, -1, buf, sizeof(buf)),
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_xml_reader_attribute_name(nullptr, 0, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
oakcommon_xml_reader_free(r);
}
TEST(CommonXmlUtilsCApi, HasError)
{
OakCommonXmlReader *bad =
oakcommon_xml_reader_init("<root><unclosed></root>");
ASSERT_NE(bad, nullptr);
int has_error = 0;
EXPECT_EQ(oakcommon_xml_reader_has_error(bad, &has_error), OAKCOMMON_OK);
EXPECT_EQ(has_error, 1);
oakcommon_xml_reader_free(bad);
OakCommonXmlReader *good = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(good, nullptr);
EXPECT_EQ(oakcommon_xml_reader_has_error(good, &has_error), OAKCOMMON_OK);
EXPECT_EQ(has_error, 0);
EXPECT_EQ(oakcommon_xml_reader_has_error(nullptr, &has_error),
OAKCOMMON_E_INVALID);
oakcommon_xml_reader_free(good);
}
TEST(CommonXmlUtilsCApi, WriterFreeNull)
{
oakcommon_xml_writer_free(nullptr);
}
TEST(CommonXmlUtilsCApi, WriterNullHandleAndArgs)
{
char buf[16];
EXPECT_EQ(oakcommon_xml_writer_write_start_element(nullptr, "a"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_end_element(nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_end_document(nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_output(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
OakCommonXmlWriter *w = oakcommon_xml_writer_init();
ASSERT_NE(w, nullptr);
EXPECT_EQ(oakcommon_xml_writer_write_start_element(w, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_attribute(w, "a", nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_characters(w, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_text_element(w, nullptr, "x"),
OAKCOMMON_E_INVALID);
oakcommon_xml_writer_free(w);
}
TEST(CommonXmlUtilsCApi, WriterRoundTrip)
{
OakCommonXmlWriter *w = oakcommon_xml_writer_init();
ASSERT_NE(w, nullptr);
EXPECT_EQ(oakcommon_xml_writer_write_start_element(w, "root"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_writer_write_attribute(w, "version", "1"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_writer_write_text_element(w, "child",
"a & b"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_writer_write_start_element(w, "empty"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_writer_write_end_element(w), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_writer_write_end_document(w), OAKCOMMON_OK);
int needed = oakcommon_xml_writer_output(w, nullptr, 0);
ASSERT_GT(needed, 0);
std::vector<char> buf(needed);
EXPECT_EQ(oakcommon_xml_writer_output(w, buf.data(), needed), needed);
oakcommon_xml_writer_free(w);
// Read the produced document back.
OakCommonXmlReader *r = oakcommon_xml_reader_init(buf.data());
ASSERT_NE(r, nullptr);
int has_error = 1;
EXPECT_EQ(oakcommon_xml_reader_has_error(r, &has_error), OAKCOMMON_OK);
EXPECT_EQ(has_error, 0);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "root");
int count = 0;
EXPECT_EQ(oakcommon_xml_reader_attribute_count(r, &count), OAKCOMMON_OK);
ASSERT_EQ(count, 1);
char abuf[32];
EXPECT_GT(oakcommon_xml_reader_attribute_value(r, 0, abuf, sizeof(abuf)),
0);
EXPECT_STREQ(abuf, "1");
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "child");
EXPECT_EQ(read_string(oakcommon_xml_reader_read_element_text, r),
"a & b");
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "empty");
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 0);
oakcommon_xml_reader_free(r);
}