refactor(node): de-Qt oaknode and wrap it in a pure C ABI
- copy engine/node (188 files) to src/node/src, de-Qt in waves: core infra (Node/Param/Value/Variant/mathtypes), project/serializer, block/output, color, effect leaves, generator, gizmo/plugins - strip QObject/signals/slots: notifications move to the facade's oakengine_event channel, ownership becomes explicit (unique_ptr, add_keyframe/add_gizmo), sender() replaced by current_gizmo - QVariant replaced by olive::Variant, Qt math types by POD mathtypes, QXmlStreamReader/Writer by oakcommon's expat-based classes - sink VideoParams/SubtitleParams/LoopMode/ColorTransform to oakcommon (M3.5); polygon/text rasterization behind backend hooks - pure C ABI in include/node + src/node/c_api (oaknode_ prefix, OAKNODE_E_* codes, undoable variants take OakUndoCommand out-params) - fix Project::clear() root_ reset + disconnect assert, Sequence TrackList leak - 96 gtest cases green in standalone build (build-oaknode) - docs: signal/slot handling strategy + M3 implementation status
This commit is contained in:
@@ -2,7 +2,11 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
# Daily at 03:17 UTC to keep ccache/dependency caches warm
|
||||
- cron: '17 3 * * *'
|
||||
|
||||
@@ -80,3 +80,95 @@ M3 阶段判据(放宽版):oaknode 目录就位、C API 实现、oaknode_g
|
||||
- 枚举序数:NodeValue::Type ⇄ oak_node_value_type 映射表(已在
|
||||
nodevaluehandle.h 钉过一次,oaknode 测试再钉一次,防两侧漂移)。
|
||||
- `oaknode_debug_alive_count()` 泄漏断言。
|
||||
|
||||
## 实施现状(2026-08-05)
|
||||
|
||||
M3 已落地并可独立构建、测试全绿(96 个用例全部通过,无 skip)。
|
||||
以下为与上文计划的实际差异。
|
||||
|
||||
### 最终目录结构
|
||||
|
||||
- `src/node/src/` — 去 Qt 化 C++ 实现(`olive::` 命名空间),target
|
||||
`oaknode`(SHARED);平铺结构,`src/node/src` 为 include 根,
|
||||
模块内 include 无前缀(`"value.h"`、`"block/block.h"`)。
|
||||
- `src/node/c_api/` — 纯 C ABI 包装(node/group/keyframe/factory/
|
||||
traverser/project/folder/footage/serializer/block/track/sequence/
|
||||
colormanager 共 13 个 .cpp + 内部头 `valueconvert.h`/`alivecount.h`),
|
||||
通过 `target_sources` 合并进 `oaknode`,不单独成库。
|
||||
- `src/node/tests/` — gtest,单一 target `oaknode-gtest`(13 个
|
||||
_test.cpp + 共享夹具 `testnode.h`),`gtest_discover_tests`
|
||||
(`DISCOVERY_MODE PRE_TEST`)。
|
||||
- `include/node/`(仓库根)— 公共 C 头:`error.h` + node/group/
|
||||
keyframe/factory/traverser/project/folder/footage/serializer/block/
|
||||
track/sequence/colormanager.h。
|
||||
- `src/node/standalone/CMakeLists.txt` — 独立构建 driver(见下)。
|
||||
- `src/node/DEQT.md` — 去Qt化替换约定与逐波次裁决记录。
|
||||
- `src/node/transition/` — 过渡 stub 头(见「实际依赖」)。
|
||||
|
||||
### 独立构建与测试
|
||||
|
||||
```sh
|
||||
cmake -S src/node/standalone -B build-oaknode
|
||||
cmake --build build-oaknode -j
|
||||
ctest --test-dir build-oaknode --output-on-failure
|
||||
```
|
||||
|
||||
driver 照 src/common/standalone 模式:EXPAT/OpenColorIO/OpenImageIO
|
||||
用 Homebrew 的 config 包(`find_package(... CONFIG)`)并映射到
|
||||
`${OCIO_LIBRARIES}` 等变量;`add_subdirectory` 引入真实 in-repo
|
||||
target(core→olivecore、ffmpeg_bridge、src/undo→oakundo、
|
||||
src/common→oakcommon,各自 BUILD_TESTS 关闭),不再链接预构建
|
||||
dylib;禁用 OpenTimelineIO(`/opt/otio` 的 `@loader_path` 问题,
|
||||
oaknode 不需要)。
|
||||
|
||||
### 实际依赖
|
||||
|
||||
- Oak 内部:oakcommon(XML/Current/工具)、oakundo(UndoCommand/
|
||||
UndoStack)、olivecore(`olive::core::Rational/Color/Bezier` 等
|
||||
C ABI 包装,真实符号)、ffmpeg_bridge(经 oakcommon 间接)。
|
||||
- 第三方:EXPAT、OpenColorIO、OpenImageIO、Imath(头)、FFmpeg
|
||||
(经 ffmpeg_bridge 间接)、GTest(仅测试)。
|
||||
- **transition stub 机制**(裁决 A):对尚未拆分的
|
||||
render/codec/timeline/audio/pluginSupport 模块的引用允许悬空——
|
||||
头文件由 `src/node/transition/` 的过渡 stub/转发头提供(engine 头
|
||||
仍是 Qt 版),符号经 `-undefined dynamic_lookup`(macOS)留到
|
||||
运行时解析。测试进程启动时必须能解析这些符号:oaknode-gtest
|
||||
链接真实 target(olivecore/oakcommon/oakundo + OCIO/OIIO/Imath
|
||||
dylib)并 `-Wl,-force_load` 预构建的
|
||||
`build/third_party/openfx/HostSupport/libOfxHost.a`(OFX 符号与
|
||||
typeinfo,否则二进制启动即崩,PRE_TEST 发现模式也会挂;路径用
|
||||
`find_library` 定位,可由 `OAKNODE_OFX_HOST_ARCHIVE` 覆盖)。
|
||||
- ColorManager 构造时需要有效 OCIO 配置(`:/ocioconf` qrc 提取是
|
||||
Qt 资源遗留、必然失败):ctest 经 `ENVIRONMENT OCIO=...` 指向
|
||||
`engine/render/ocioconf/config.ocio`,colormanager_test 另用
|
||||
`OAK_OCIO_TEST_CONFIG` 编译定义兜底。
|
||||
|
||||
### 与冻结 C API 的主要差异
|
||||
|
||||
- 函数族命名与约定照 oakcommon/oakundo 既有契约:`oaknode_<族>_<动词>`,
|
||||
int 错误码 + out 参数,字符串两段式 buffer;undoable 变体成对
|
||||
(`_undoable` 后缀,部分经 `OakUndoCommand *` 尾参)。
|
||||
- §2 冻结表中**跳过/未实现**的函数族:
|
||||
- Sequence 的 workarea/markers 族(timeline 边界类型
|
||||
TimelineMarker/TimelineWorkArea 未拆,留 M4);
|
||||
- ProjectSerializer 的落盘 save/load(按计划迁 oakstorage M10),
|
||||
本模块只实现内存形态 `save_to_xml`/`load_from_xml` + SaveData/
|
||||
LoadData 句柄;
|
||||
- Node 的 `oak_node_value` 句柄族未单独成族,输入值经
|
||||
`oaknode_node_get/set_input[_string][_undoable]` 直接收发;
|
||||
- 无任何事件订阅接口(与 §2 特殊约定 2 一致)。
|
||||
- `oaknode_debug_alive_count()` 泄漏断言已实现并在测试中使用。
|
||||
|
||||
### 已知问题
|
||||
|
||||
- `NodeGroupAddInputPassthrough` 的上游 bug 原样保留(未修,待裁决)。
|
||||
- polygon/text 光栅化后端钩子(PathFillBackend/TextMeasureBackend/
|
||||
TextRenderBackend)未安装前输出空白;footage 离线警示帧丢失文字
|
||||
叠层;track 默认高度固化为 13px——均为被迫行为差异,见
|
||||
`notes.md`「oaknode 去Qt化的删除与语义变更」。
|
||||
- 去Qt化顺带修了三个上游 bug(行为与旧版不同):
|
||||
`Project::clear()` 重置 `root_`(可重新 initialize)、clear 删除
|
||||
顺序修正(不再触发 disconnect_edge 的 parent assert)、
|
||||
`Sequence` 析构删除三个 `TrackList`(原泄漏)。详见 notes.md。
|
||||
- 库本体对 transition stub 模块的符号悬空在 M7/M9 复核前是预期
|
||||
状态;禁止新增对 render/codec/timeline/audio/pluginSupport 的引用。
|
||||
|
||||
@@ -126,3 +126,90 @@ oakcommon;后续若确认无用途可直接删除。
|
||||
转移、wrapper 被消费(不可再用/再 free);
|
||||
`oakundo_command_multi_child` 返回 borrowed wrapper(free 只释放
|
||||
wrapper)。
|
||||
|
||||
## oaknode 去Qt化的删除与语义变更(2026-08-05)
|
||||
|
||||
`src/node/`(Node 族、Project 族、Sequence/Track/Block 族、serializer、
|
||||
ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需注意
|
||||
(详细约定见 `src/node/DEQT.md` §4/§7):
|
||||
|
||||
- 信号槽整组删除:Node 的 28 个信号(label_changed、input_connected、
|
||||
keyframe_added、added_to_graph 等)、NodeKeyframe 的 5 个、
|
||||
Project/Folder/Sequence/Footage 的全部信号(name_changed、
|
||||
modified_changed、track_added、begin/end_insert_item 等),变更通知
|
||||
统一由 facade 经 `oakengine_event` 发出,oaknode 不持有上层回调。
|
||||
- QObject 父子生命周期改显式所有权:Project 持有全部节点
|
||||
(`add_node`/`remove_node`,`clear()`/析构删除);nodeundo 用
|
||||
`std::unique_ptr<Node>` 替代 `memory_manager_`;keyframe 经
|
||||
`Node::add_keyframe()/remove_keyframe()` 显式注册(自动 set_parent),
|
||||
gizmo 经 `Node::add_gizmo()/remove_gizmo()` 显式注册(构造不再自
|
||||
注册);`NodeInputImmediate::delete_all_keyframes(reclaimed)` 替代
|
||||
"reparent 续命"。
|
||||
- polygon/text 的 `generate_frame()` 光栅化(QPainter/QTextDocument)
|
||||
改后端钩子:`PathFillBackend`(geometry.h)、`TextMeasureBackend`/
|
||||
`TextRenderBackend`(textbackend.h),facade 未安装前输出空白
|
||||
(被迫行为差异)。
|
||||
- gizmo 绘制与 hit-test 归 app 层:各 gizmo 的 `draw(QPainter*)`、
|
||||
`PointGizmo::get_clicking_rect()/get_drawing_rect()` 等删除;
|
||||
`DraggableGizmo` 的拖拽信号改直调 `parent_node()->gizmo_drag_*()`;
|
||||
drag 回调里的 `sender()` 由 `Node::current_gizmo()` 替代。
|
||||
- footage 离线媒体警示帧:QImage/QPainter 改纯像素循环(深红底+斜纹),
|
||||
"Media Offline" 文字叠层与抗锯齿丢失(被迫行为差异)。
|
||||
- `:/ocioconf` Qt 资源路径不可用:ColorManager 默认配置的 qrc 提取必然
|
||||
失败,需 `OCIO` 环境变量指向磁盘 config.ocio(测试用
|
||||
`engine/render/ocioconf/config.ocio`)。
|
||||
- `NodeGroupAddInputPassthrough` 的上游 bug 原样保留(去Qt化不改行为
|
||||
逻辑,待单独裁决修复)。
|
||||
- track.h 的 `QFontMetrics` 13px 常量:轨道默认高度原依赖
|
||||
`QFontMetrics(qApp->font()).height()`,去Qt后固化为 13px 字面量,
|
||||
换字体/DPI 不再自适应。
|
||||
- `Footage::check_footage()` 的 QTimer 周期回调删除,函数本体保留为
|
||||
public 由 facade 周期调用;`qApp->activeWindow()` 门槛移到 app 层。
|
||||
- 序列化 XML 元素/属性名与读写顺序逐字节不变;`XmlStreamWriter` 输出
|
||||
紧凑 XML(无声明无缩进);OVEC 压缩段保持 qCompress 兼容
|
||||
(zlib + 4 字节大端长度)。
|
||||
- 修复三个上游真实 bug(行为因此与旧版不同,特此记录):
|
||||
`Project::clear()` 现重置 `root_`(clear 后可再次 initialize);
|
||||
clear 不再先 `set_parent(nullptr)` 再 delete(否则 ~Node 的
|
||||
disconnect_edge 触发 parent 不等的 assert);
|
||||
`Sequence` 新增析构删除三个 `TrackList`(原来构造 new 后泄漏)。
|
||||
- `sender()` 的其他替代:原 keyframe signal→Node 的 5 条失效通知链
|
||||
(invalidate_from_keyframe_*)函数本体保留为 public 成员,带
|
||||
`NodeKeyframe *key` 显式参数,待 facade 接线。
|
||||
|
||||
## 信号与槽的处理策略(2026-08-05)
|
||||
|
||||
去Qt化对 Qt 信号槽的统一处理模式,M1–M3 一致执行,后续模块照此:
|
||||
|
||||
1. **模块内通知改显式回调/直调(std::function)**:通知双方都在同一
|
||||
模块内时,去掉 connect,改为 `std::function` 回调注册或成员直调。
|
||||
例:oakundo `UndoStack::index_changed(int)` signal →
|
||||
`set_index_changed_callback(std::function<void(int)>)`;oaknode
|
||||
`Node::current_gizmo()/set_current_gizmo()` 机制替代 gizmo drag
|
||||
回调里的 `sender()`;`DraggableGizmo` 的 handle_start/handle_movement
|
||||
信号改直调 `parent_node()->gizmo_drag_start()/gizmo_drag_move()`;
|
||||
TrackList 原 track_list_changed/length_changed 发射点直调
|
||||
`sequence->update_track_cache()/verify_length()`。
|
||||
2. **跨层/跨界通知删除,由 facade 经 oakengine_event 统一发出**:
|
||||
一切指向 app/UI/其他模块的信号整组删除(Node 28 个、NodeKeyframe
|
||||
5 个、Project/Folder/Sequence/Footage 全部),oaknode 自身不持有
|
||||
任何上层回调;facade 在执行命令后经既有 `oakengine_event` 通道发
|
||||
通知(事件 id 沿用 `oakengine/events.h` 70-95 段,值不变)。依据:
|
||||
oaknode 的所有修改都经命令函数完成,调用方知道影响(M3 §2 特殊
|
||||
约定 2,04 §3)。
|
||||
3. **QObject 父子生命周期改显式所有权**:`setParent`/childEvent 机制
|
||||
删除,所有权用显式容器与注册函数表达。例:Project 持有节点
|
||||
(`add_node`/`remove_node`/`clear()`);`ColorManager` 由
|
||||
`std::unique_ptr` 持有;nodeundo 用 `std::unique_ptr<Node>` 替代
|
||||
`memory_manager_`;keyframe/gizmo 分别经 `add_keyframe()`/
|
||||
`add_gizmo()` 显式注册;UndoCommand 树由 MultiUndoCommand 析构
|
||||
删除 children。
|
||||
4. **sender() 的替代**:两种形态——拖曳场景用
|
||||
`Node::current_gizmo()`(DraggableGizmo 直调回调前后
|
||||
`set_current_gizmo(this/nullptr)` 包一层);通知链场景改显式参数,
|
||||
如 `invalidate_from_keyframe_*(NodeKeyframe *key)`。
|
||||
5. **原 slot 函数本体保留为 public 成员,待 facade 接线**:signal 删了
|
||||
但 slot 承载的业务逻辑不删,改为 public 成员函数由上层直调。清单
|
||||
见 `src/node/DEQT.md` §7(如 `Sequence::update_track_cache()`、
|
||||
`Footage::check_footage()/default_color_space_changed()/
|
||||
proxy_ready()/proxy_finished()`、5 条 invalidate_from_keyframe_*)。
|
||||
|
||||
@@ -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_COLORTRANSFORM_H
|
||||
#define OAK_EDITOR_COLORTRANSFORM_H
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a color transform description
|
||||
* (olive::ColorTransform).
|
||||
*/
|
||||
typedef struct OakCommonColorTransform OakCommonColorTransform;
|
||||
|
||||
/**
|
||||
* @brief Create a plain output-colorspace transform.
|
||||
*
|
||||
* @param output Output colorspace name. Must not be NULL.
|
||||
* @return Transform handle, or NULL on failure.
|
||||
*/
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_output(
|
||||
const char *output);
|
||||
|
||||
/**
|
||||
* @brief Create a display/view/look transform.
|
||||
*
|
||||
* All three strings must not be NULL.
|
||||
*
|
||||
* @return Transform handle, or NULL on failure.
|
||||
*/
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_display(
|
||||
const char *display, const char *view, const char *look);
|
||||
|
||||
/**
|
||||
* @brief Destroy a transform. No-op on NULL.
|
||||
*/
|
||||
void oakcommon_colortransform_free(OakCommonColorTransform *transform);
|
||||
|
||||
/**
|
||||
* @brief Query whether this is a display/view/look transform.
|
||||
*
|
||||
* @param is_display Receives the result. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
|
||||
int *is_display);
|
||||
|
||||
/**
|
||||
* @brief Get the display name (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_colortransform_get_display(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Get the output colorspace name (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_colortransform_get_output(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Get the view name (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_colortransform_get_view(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Get the look name (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_colortransform_get_look(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_COLORTRANSFORM_H
|
||||
@@ -0,0 +1,44 @@
|
||||
/***
|
||||
|
||||
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_LOOPMODE_H
|
||||
#define OAK_EDITOR_LOOPMODE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Playback loop mode, mirroring olive::LoopMode.
|
||||
*
|
||||
* The numeric values must stay in sync with src/common/src/loopmode.h.
|
||||
* Pure enum: no functions are needed.
|
||||
*/
|
||||
enum OakCommonLoopMode {
|
||||
OAKCOMMON_LOOP_MODE_OFF = 0, /**< Looping disabled. */
|
||||
OAKCOMMON_LOOP_MODE_LOOP = 1, /**< Loop playback. */
|
||||
OAKCOMMON_LOOP_MODE_CLAMP = 2 /**< Clamp at the end. */
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_LOOPMODE_H
|
||||
@@ -0,0 +1,140 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_SUBTITLEPARAMS_H
|
||||
#define OAK_EDITOR_SUBTITLEPARAMS_H
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a subtitle parameter set (olive::SubtitleParams).
|
||||
*/
|
||||
typedef struct OakCommonSubtitleParams OakCommonSubtitleParams;
|
||||
|
||||
/**
|
||||
* @brief Create an empty subtitle parameter set.
|
||||
*
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakCommonSubtitleParams *oakcommon_subtitleparams_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a subtitle parameter set. No-op on NULL.
|
||||
*/
|
||||
void oakcommon_subtitleparams_free(OakCommonSubtitleParams *params);
|
||||
|
||||
int oakcommon_subtitleparams_get_stream_index(
|
||||
OakCommonSubtitleParams *params, int *index);
|
||||
int oakcommon_subtitleparams_set_stream_index(
|
||||
OakCommonSubtitleParams *params, int index);
|
||||
int oakcommon_subtitleparams_get_enabled(OakCommonSubtitleParams *params,
|
||||
int *enabled);
|
||||
int oakcommon_subtitleparams_set_enabled(OakCommonSubtitleParams *params,
|
||||
int enabled);
|
||||
|
||||
/**
|
||||
* @brief Query whether the set contains at least one subtitle.
|
||||
*/
|
||||
int oakcommon_subtitleparams_is_valid(OakCommonSubtitleParams *params,
|
||||
int *is_valid);
|
||||
|
||||
/**
|
||||
* @brief Number of subtitle entries.
|
||||
*/
|
||||
int oakcommon_subtitleparams_count(OakCommonSubtitleParams *params,
|
||||
int *count);
|
||||
|
||||
/**
|
||||
* @brief Out time of the last subtitle (0/1 when empty).
|
||||
*/
|
||||
int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
|
||||
int *numerator, int *denominator);
|
||||
|
||||
/**
|
||||
* @brief Append a subtitle entry.
|
||||
*
|
||||
* @param text Subtitle text. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
|
||||
int in_num, int in_den, int out_num,
|
||||
int out_den, const char *text);
|
||||
|
||||
/**
|
||||
* @brief Remove all subtitle entries.
|
||||
*/
|
||||
int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params);
|
||||
|
||||
/**
|
||||
* @brief Get the time range of the subtitle at @p index.
|
||||
*
|
||||
* @return OAKCOMMON_OK, OAKCOMMON_E_NOT_FOUND if @p index is out of range,
|
||||
* or another negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
|
||||
int index, int *in_num, int *in_den,
|
||||
int *out_num, int *out_den);
|
||||
|
||||
/**
|
||||
* @brief Get the text of the subtitle at @p index (two-stage string getter).
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), OAKCOMMON_E_NOT_FOUND if @p index is out of
|
||||
* range, or another negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_get_subtitle_text(OakCommonSubtitleParams *params,
|
||||
int index, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Generate a default ASS header (static, no handle required).
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_generate_ass_header(char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Load subtitles from an XML fragment.
|
||||
*
|
||||
* @param xml NUL-terminated XML text. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
|
||||
const char *xml);
|
||||
|
||||
/**
|
||||
* @brief Save subtitles to an XML fragment (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_subtitleparams_save_xml(OakCommonSubtitleParams *params,
|
||||
char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_SUBTITLEPARAMS_H
|
||||
@@ -0,0 +1,301 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_VIDEOPARAMS_H
|
||||
#define OAK_EDITOR_VIDEOPARAMS_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/ocioutils.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a video parameter set (olive::VideoParams).
|
||||
*/
|
||||
typedef struct OakCommonVideoParams OakCommonVideoParams;
|
||||
|
||||
/**
|
||||
* @brief Interlacing modes, mirroring olive::VideoParams::Interlacing.
|
||||
*/
|
||||
enum OakCommonVideoInterlacing {
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE = 0,
|
||||
OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST = 1,
|
||||
OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Video stream types, mirroring olive::VideoParams::Type.
|
||||
*/
|
||||
enum OakCommonVideoType {
|
||||
OAKCOMMON_VIDEO_TYPE_VIDEO = 0,
|
||||
OAKCOMMON_VIDEO_TYPE_STILL = 1,
|
||||
OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Color range codes, mirroring olive::VideoParams::ColorRange.
|
||||
*/
|
||||
enum OakCommonVideoColorRange {
|
||||
OAKCOMMON_COLOR_RANGE_LIMITED = 0, /**< 16-235 */
|
||||
OAKCOMMON_COLOR_RANGE_FULL = 1 /**< 0-255 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a default (invalid) video parameter set.
|
||||
*
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakCommonVideoParams *oakcommon_videoparams_init(void);
|
||||
|
||||
/**
|
||||
* @brief Create a video parameter set without a time base.
|
||||
*
|
||||
* @param pixel_format One of the OakCommonPixelFormat values.
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_basic(
|
||||
int width, int height, int pixel_format, int nb_channels,
|
||||
int pixel_aspect_num, int pixel_aspect_den, int interlacing, int divider);
|
||||
|
||||
/**
|
||||
* @brief Create a video parameter set with a time base.
|
||||
*
|
||||
* The frame rate is derived as the flipped time base.
|
||||
*
|
||||
* @param pixel_format One of the OakCommonPixelFormat values.
|
||||
* @return Params handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_with_time_base(
|
||||
int width, int height, int time_base_num, int time_base_den,
|
||||
int pixel_format, int nb_channels, int pixel_aspect_num,
|
||||
int pixel_aspect_den, int interlacing, int divider);
|
||||
|
||||
/**
|
||||
* @brief Destroy a video parameter set. No-op on NULL.
|
||||
*/
|
||||
void oakcommon_videoparams_free(OakCommonVideoParams *params);
|
||||
|
||||
int oakcommon_videoparams_get_width(OakCommonVideoParams *params, int *width);
|
||||
int oakcommon_videoparams_set_width(OakCommonVideoParams *params, int width);
|
||||
int oakcommon_videoparams_get_height(OakCommonVideoParams *params, int *height);
|
||||
int oakcommon_videoparams_set_height(OakCommonVideoParams *params, int height);
|
||||
int oakcommon_videoparams_get_depth(OakCommonVideoParams *params, int *depth);
|
||||
int oakcommon_videoparams_set_depth(OakCommonVideoParams *params, int depth);
|
||||
int oakcommon_videoparams_get_is_3d(OakCommonVideoParams *params, int *is_3d);
|
||||
|
||||
/**
|
||||
* @brief Rational getters return the value as a numerator/denominator pair.
|
||||
*/
|
||||
int oakcommon_videoparams_get_time_base(OakCommonVideoParams *params,
|
||||
int *numerator, int *denominator);
|
||||
int oakcommon_videoparams_set_time_base(OakCommonVideoParams *params,
|
||||
int numerator, int denominator);
|
||||
int oakcommon_videoparams_get_frame_rate(OakCommonVideoParams *params,
|
||||
int *numerator, int *denominator);
|
||||
int oakcommon_videoparams_set_frame_rate(OakCommonVideoParams *params,
|
||||
int numerator, int denominator);
|
||||
int oakcommon_videoparams_frame_rate_as_time_base(OakCommonVideoParams *params,
|
||||
int *numerator,
|
||||
int *denominator);
|
||||
int oakcommon_videoparams_get_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int *numerator,
|
||||
int *denominator);
|
||||
int oakcommon_videoparams_set_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int numerator, int denominator);
|
||||
|
||||
/**
|
||||
* @brief Format getters/setters use the OakCommonPixelFormat codes.
|
||||
*/
|
||||
int oakcommon_videoparams_get_format(OakCommonVideoParams *params, int *format);
|
||||
int oakcommon_videoparams_set_format(OakCommonVideoParams *params, int format);
|
||||
int oakcommon_videoparams_get_channel_count(OakCommonVideoParams *params,
|
||||
int *count);
|
||||
int oakcommon_videoparams_set_channel_count(OakCommonVideoParams *params,
|
||||
int count);
|
||||
int oakcommon_videoparams_get_interlacing(OakCommonVideoParams *params,
|
||||
int *interlacing);
|
||||
int oakcommon_videoparams_set_interlacing(OakCommonVideoParams *params,
|
||||
int interlacing);
|
||||
int oakcommon_videoparams_get_divider(OakCommonVideoParams *params,
|
||||
int *divider);
|
||||
int oakcommon_videoparams_set_divider(OakCommonVideoParams *params,
|
||||
int divider);
|
||||
int oakcommon_videoparams_get_enabled(OakCommonVideoParams *params,
|
||||
int *enabled);
|
||||
int oakcommon_videoparams_set_enabled(OakCommonVideoParams *params,
|
||||
int enabled);
|
||||
int oakcommon_videoparams_get_x(OakCommonVideoParams *params, float *x);
|
||||
int oakcommon_videoparams_set_x(OakCommonVideoParams *params, float x);
|
||||
int oakcommon_videoparams_get_y(OakCommonVideoParams *params, float *y);
|
||||
int oakcommon_videoparams_set_y(OakCommonVideoParams *params, float y);
|
||||
int oakcommon_videoparams_get_stream_index(OakCommonVideoParams *params,
|
||||
int *index);
|
||||
int oakcommon_videoparams_set_stream_index(OakCommonVideoParams *params,
|
||||
int index);
|
||||
int oakcommon_videoparams_get_video_type(OakCommonVideoParams *params,
|
||||
int *type);
|
||||
int oakcommon_videoparams_set_video_type(OakCommonVideoParams *params,
|
||||
int type);
|
||||
int oakcommon_videoparams_get_start_time(OakCommonVideoParams *params,
|
||||
int64_t *start_time);
|
||||
int oakcommon_videoparams_set_start_time(OakCommonVideoParams *params,
|
||||
int64_t start_time);
|
||||
int oakcommon_videoparams_get_duration(OakCommonVideoParams *params,
|
||||
int64_t *duration);
|
||||
int oakcommon_videoparams_set_duration(OakCommonVideoParams *params,
|
||||
int64_t duration);
|
||||
int oakcommon_videoparams_get_premultiplied_alpha(OakCommonVideoParams *params,
|
||||
int *premultiplied);
|
||||
int oakcommon_videoparams_set_premultiplied_alpha(OakCommonVideoParams *params,
|
||||
int premultiplied);
|
||||
int oakcommon_videoparams_get_color_range(OakCommonVideoParams *params,
|
||||
int *color_range);
|
||||
int oakcommon_videoparams_set_color_range(OakCommonVideoParams *params,
|
||||
int color_range);
|
||||
int oakcommon_videoparams_get_color_primaries(OakCommonVideoParams *params,
|
||||
int *primaries);
|
||||
int oakcommon_videoparams_set_color_primaries(OakCommonVideoParams *params,
|
||||
int primaries);
|
||||
int oakcommon_videoparams_get_color_transfer(OakCommonVideoParams *params,
|
||||
int *transfer);
|
||||
int oakcommon_videoparams_set_color_transfer(OakCommonVideoParams *params,
|
||||
int transfer);
|
||||
|
||||
/**
|
||||
* @brief Get the colorspace name (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_videoparams_get_colorspace(OakCommonVideoParams *params,
|
||||
char *buf, int buf_size);
|
||||
int oakcommon_videoparams_set_colorspace(OakCommonVideoParams *params,
|
||||
const char *colorspace);
|
||||
|
||||
/**
|
||||
* @brief Width multiplied by the pixel aspect ratio.
|
||||
*/
|
||||
int oakcommon_videoparams_get_square_pixel_width(OakCommonVideoParams *params,
|
||||
int *width);
|
||||
int oakcommon_videoparams_get_effective_width(OakCommonVideoParams *params,
|
||||
int *width);
|
||||
int oakcommon_videoparams_get_effective_height(OakCommonVideoParams *params,
|
||||
int *height);
|
||||
int oakcommon_videoparams_get_effective_depth(OakCommonVideoParams *params,
|
||||
int *depth);
|
||||
int oakcommon_videoparams_get_is_valid(OakCommonVideoParams *params,
|
||||
int *is_valid);
|
||||
int oakcommon_videoparams_get_bytes_per_channel(OakCommonVideoParams *params,
|
||||
int *bytes);
|
||||
int oakcommon_videoparams_get_bytes_per_pixel(OakCommonVideoParams *params,
|
||||
int *bytes);
|
||||
int oakcommon_videoparams_get_buffer_size(OakCommonVideoParams *params,
|
||||
int *size);
|
||||
|
||||
/**
|
||||
* @brief Convert a time (in seconds, as a rational) to time base units.
|
||||
*
|
||||
* Returns INT64_MIN (AV_NOPTS_VALUE) in @p timestamp when no time base is
|
||||
* set.
|
||||
*/
|
||||
int oakcommon_videoparams_get_time_in_timebase_units(
|
||||
OakCommonVideoParams *params, int time_num, int time_den,
|
||||
int64_t *timestamp);
|
||||
|
||||
/**
|
||||
* @brief Compare two parameter sets for equality.
|
||||
*/
|
||||
int oakcommon_videoparams_equals(OakCommonVideoParams *params,
|
||||
OakCommonVideoParams *other, int *equal);
|
||||
|
||||
/**
|
||||
* @brief Load parameters from an XML fragment.
|
||||
*
|
||||
* @param xml NUL-terminated XML text. Must not be NULL.
|
||||
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
|
||||
const char *xml);
|
||||
|
||||
/**
|
||||
* @brief Save parameters to an XML fragment (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_videoparams_save_xml(OakCommonVideoParams *params, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/* Static helpers (no handle required). */
|
||||
|
||||
int oakcommon_videoparams_get_bytes_per_channel_for_format(int pixel_format);
|
||||
int oakcommon_videoparams_get_bytes_per_pixel_for_format(int pixel_format,
|
||||
int channels);
|
||||
int oakcommon_videoparams_calculate_buffer_size(int width, int height,
|
||||
int pixel_format,
|
||||
int channels);
|
||||
int oakcommon_videoparams_format_is_float(int pixel_format);
|
||||
int oakcommon_videoparams_generate_auto_divider(int64_t width, int64_t height);
|
||||
int oakcommon_videoparams_get_scaled_dimension(int dimension, int divider);
|
||||
int oakcommon_videoparams_get_divider_for_target_resolution(int src_width,
|
||||
int src_height,
|
||||
int dst_width,
|
||||
int dst_height);
|
||||
|
||||
/**
|
||||
* @brief Human-readable name for a divider ("Full", "1/2", ...).
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_videoparams_get_name_for_divider(int divider, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Human-readable name for a pixel format.
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_videoparams_get_format_name(int pixel_format, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Human-readable frame rate string ("23.976 FPS").
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKCOMMON_E_* error code.
|
||||
*/
|
||||
int oakcommon_videoparams_frame_rate_to_string(int numerator, int denominator,
|
||||
char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_VIDEOPARAMS_H
|
||||
@@ -0,0 +1,240 @@
|
||||
/***
|
||||
|
||||
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_NODE_BLOCK_H
|
||||
#define OAK_EDITOR_NODE_BLOCK_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include "node/error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a timeline block (olive::Block).
|
||||
*
|
||||
* Covers the whole Block family: ClipBlock, GapBlock and the concrete
|
||||
* TransitionBlock subclasses. The handle IS the C++ object pointer; no
|
||||
* wrapper is allocated. Concrete instances are created through the
|
||||
* oaknode_block_*_create() factories below; callers never touch C++
|
||||
* subclasses directly.
|
||||
*/
|
||||
typedef struct OakNodeBlock OakNodeBlock;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a track (olive::Track), see node/track.h.
|
||||
*
|
||||
* Re-declared here so block.h is self-contained; the typedef is identical.
|
||||
*/
|
||||
typedef struct OakNodeTrack OakNodeTrack;
|
||||
|
||||
/**
|
||||
* @brief Concrete transition kinds for oaknode_block_transition_create().
|
||||
*/
|
||||
enum OakNodeTransitionKind {
|
||||
OAKNODE_TRANSITION_CROSS_DISSOLVE = 0, /**< CrossDissolveTransition. */
|
||||
OAKNODE_TRANSITION_DIP_TO_COLOR = 1 /**< DipToColorTransition. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a ClipBlock.
|
||||
*
|
||||
* The caller owns the block until it is placed on a track that belongs to
|
||||
* a project; a block that was never placed must be released with
|
||||
* oaknode_block_free().
|
||||
*
|
||||
* @return Block handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakNodeBlock *oaknode_block_clip_create(void);
|
||||
|
||||
/**
|
||||
* @brief Create a GapBlock. Ownership as oaknode_block_clip_create().
|
||||
*
|
||||
* @return Block handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakNodeBlock *oaknode_block_gap_create(void);
|
||||
|
||||
/**
|
||||
* @brief Create a concrete TransitionBlock.
|
||||
*
|
||||
* @param kind One of the OakNodeTransitionKind values.
|
||||
* @return Block handle, or NULL on invalid kind / allocation failure.
|
||||
*/
|
||||
OakNodeBlock *oaknode_block_transition_create(int kind);
|
||||
|
||||
/**
|
||||
* @brief Destroy a block. No-op on NULL.
|
||||
*
|
||||
* The block must not be placed on a track or linked to other nodes; the
|
||||
* caller is responsible for detaching it first.
|
||||
*/
|
||||
void oaknode_block_free(OakNodeBlock *block);
|
||||
|
||||
/**
|
||||
* @brief Rational getters/setters use numerator/denominator out pairs.
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_block_get_in(OakNodeBlock *block, int *numerator, int *denominator);
|
||||
int oaknode_block_set_in(OakNodeBlock *block, int numerator, int denominator);
|
||||
int oaknode_block_get_out(OakNodeBlock *block, int *numerator, int *denominator);
|
||||
int oaknode_block_set_out(OakNodeBlock *block, int numerator, int denominator);
|
||||
int oaknode_block_get_length(OakNodeBlock *block, int *numerator,
|
||||
int *denominator);
|
||||
|
||||
/**
|
||||
* @brief Set the block length, keeping the media out/in point anchored
|
||||
* (olive::Block::set_length_and_media_out / _media_in).
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_block_set_length_and_media_out(OakNodeBlock *block, int numerator,
|
||||
int denominator);
|
||||
int oaknode_block_set_length_and_media_in(OakNodeBlock *block, int numerator,
|
||||
int denominator);
|
||||
|
||||
/**
|
||||
* @brief Enabled flag (olive::Block::is_enabled/set_enabled).
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_block_get_enabled(OakNodeBlock *block, int *enabled);
|
||||
int oaknode_block_set_enabled(OakNodeBlock *block, int enabled);
|
||||
|
||||
/**
|
||||
* @brief Adjacency accessors. `out` receives a borrowed handle (NULL when
|
||||
* there is no neighbour / the block is not on a track).
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_block_get_previous(OakNodeBlock *block, OakNodeBlock **out);
|
||||
int oaknode_block_get_next(OakNodeBlock *block, OakNodeBlock **out);
|
||||
int oaknode_block_get_track(OakNodeBlock *block, OakNodeTrack **out);
|
||||
|
||||
/**
|
||||
* @brief Link two blocks (olive::Node::link/unlink/are_linked).
|
||||
*
|
||||
* Linked blocks move together in timeline edits.
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID or OAKNODE_E_FAILED (already
|
||||
* linked / not linked).
|
||||
*/
|
||||
int oaknode_block_link(OakNodeBlock *a, OakNodeBlock *b);
|
||||
int oaknode_block_unlink(OakNodeBlock *a, OakNodeBlock *b);
|
||||
int oaknode_block_are_linked(OakNodeBlock *a, OakNodeBlock *b, int *linked);
|
||||
|
||||
/**
|
||||
* @brief Number of blocks linked to `block` (olive::Node::links()).
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_block_get_link_count(OakNodeBlock *block, int *count);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle to the linked block at `index`.
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID or OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_block_get_link_at(OakNodeBlock *block, int index,
|
||||
OakNodeBlock **out);
|
||||
|
||||
/* ---------------------------------------------------------------- Clip */
|
||||
|
||||
/**
|
||||
* @brief Media in/out accessors (olive::ClipBlock). Non-clip blocks return
|
||||
* OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_clip_get_media_in(OakNodeBlock *clip, int *numerator,
|
||||
int *denominator);
|
||||
int oaknode_clip_set_media_in(OakNodeBlock *clip, int numerator,
|
||||
int denominator);
|
||||
|
||||
/**
|
||||
* @brief Playback speed factor, 1.0 = normal (olive::ClipBlock speed input).
|
||||
*/
|
||||
int oaknode_clip_get_speed(OakNodeBlock *clip, double *speed);
|
||||
int oaknode_clip_set_speed(OakNodeBlock *clip, double speed);
|
||||
|
||||
/**
|
||||
* @brief Reverse playback flag.
|
||||
*/
|
||||
int oaknode_clip_get_reverse(OakNodeBlock *clip, int *reverse);
|
||||
int oaknode_clip_set_reverse(OakNodeBlock *clip, int reverse);
|
||||
|
||||
/**
|
||||
* @brief Maintain-audio-pitch flag.
|
||||
*/
|
||||
int oaknode_clip_get_maintain_audio_pitch(OakNodeBlock *clip, int *maintain);
|
||||
int oaknode_clip_set_maintain_audio_pitch(OakNodeBlock *clip, int maintain);
|
||||
|
||||
/**
|
||||
* @brief Loop mode, one of the OakCommonLoopMode values
|
||||
* (olive::ClipBlock::loop_mode/set_loop_mode).
|
||||
*/
|
||||
int oaknode_clip_get_loop_mode(OakNodeBlock *clip, int *loop_mode);
|
||||
int oaknode_clip_set_loop_mode(OakNodeBlock *clip, int loop_mode);
|
||||
|
||||
/**
|
||||
* @brief Type of the track the clip sits on (OakNodeTrackType values,
|
||||
* OAKNODE_TRACK_TYPE_NONE when trackless).
|
||||
*/
|
||||
int oaknode_clip_get_track_type(OakNodeBlock *clip, int *type);
|
||||
|
||||
/* ----------------------------------------------------------- Transition */
|
||||
|
||||
/**
|
||||
* @brief Transition offsets (olive::TransitionBlock). Non-transition blocks
|
||||
* return OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_transition_get_in_offset(OakNodeBlock *transition, int *numerator,
|
||||
int *denominator);
|
||||
int oaknode_transition_get_out_offset(OakNodeBlock *transition, int *numerator,
|
||||
int *denominator);
|
||||
int oaknode_transition_get_offset_center(OakNodeBlock *transition,
|
||||
int *numerator, int *denominator);
|
||||
int oaknode_transition_set_offset_center(OakNodeBlock *transition,
|
||||
int numerator, int denominator);
|
||||
int oaknode_transition_set_offsets_and_length(OakNodeBlock *transition,
|
||||
int in_num, int in_den,
|
||||
int out_num, int out_den);
|
||||
|
||||
/**
|
||||
* @brief Whether both sides of the transition are connected to clips.
|
||||
*/
|
||||
int oaknode_transition_is_dual(OakNodeBlock *transition, int *dual);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handles to the connected out/in side blocks (NULL when
|
||||
* unconnected).
|
||||
*/
|
||||
int oaknode_transition_get_connected_out_block(OakNodeBlock *transition,
|
||||
OakNodeBlock **out);
|
||||
int oaknode_transition_get_connected_in_block(OakNodeBlock *transition,
|
||||
OakNodeBlock **out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_BLOCK_H
|
||||
@@ -0,0 +1,189 @@
|
||||
/***
|
||||
|
||||
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_NODE_COLORMANAGER_H
|
||||
#define OAK_EDITOR_NODE_COLORMANAGER_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include "common/colortransform.h"
|
||||
#include "node/error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a color manager (olive::ColorManager).
|
||||
*
|
||||
* Unlike node handles this one IS a wrapper allocation (ColorManager is
|
||||
* not a Node); release with oaknode_colormanager_free().
|
||||
*/
|
||||
typedef struct OakNodeColorManager OakNodeColorManager;
|
||||
|
||||
/**
|
||||
* @brief Opaque borrowed handle to a project (olive::Project).
|
||||
*
|
||||
* Owned by the project family; re-declared here so this header is
|
||||
* self-contained.
|
||||
*/
|
||||
typedef struct OakNodeProject OakNodeProject;
|
||||
|
||||
/**
|
||||
* @brief Create a color manager bound to `project` (borrowed).
|
||||
*
|
||||
* The manager is created without a config; call
|
||||
* oaknode_colormanager_initialize() (or set a config filename and
|
||||
* oaknode_colormanager_update_config_from_filename()) before using the
|
||||
* config-dependent queries.
|
||||
*
|
||||
* @return Manager handle, or NULL on NULL project / allocation failure.
|
||||
*/
|
||||
OakNodeColorManager *oaknode_colormanager_init(OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Destroy a color manager. No-op on NULL.
|
||||
*/
|
||||
void oaknode_colormanager_free(OakNodeColorManager *manager);
|
||||
|
||||
/**
|
||||
* @brief Load the built-in default OCIO config and set the default input
|
||||
* colorspace (olive::ColorManager::init()).
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID or OAKNODE_E_FAILED (the OCIO
|
||||
* config could not be created).
|
||||
*/
|
||||
int oaknode_colormanager_initialize(OakNodeColorManager *manager);
|
||||
|
||||
/**
|
||||
* @brief (Re)build the process-wide default OCIO config
|
||||
* (olive::ColorManager::set_up_default_config()).
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_FAILED.
|
||||
*/
|
||||
int oaknode_colormanager_set_up_default_config(void);
|
||||
|
||||
/**
|
||||
* @brief Config filename stored on the project. Two-stage string getter:
|
||||
* returns the required buffer size in bytes including NUL; pass
|
||||
* buf == NULL or a too-small buffer to query the size.
|
||||
*/
|
||||
int oaknode_colormanager_get_config_filename(OakNodeColorManager *manager,
|
||||
char *buf, int buf_size);
|
||||
int oaknode_colormanager_set_config_filename(OakNodeColorManager *manager,
|
||||
const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Reload the OCIO config from the stored filename. Missing/invalid
|
||||
* files are tolerated (the previous config is kept), matching
|
||||
* olive::ColorManager::update_config_from_filename().
|
||||
*/
|
||||
int oaknode_colormanager_update_config_from_filename(
|
||||
OakNodeColorManager *manager);
|
||||
|
||||
/**
|
||||
* @brief Default input colorspace. Two-stage string accessor.
|
||||
*/
|
||||
int oaknode_colormanager_get_default_input_color_space(
|
||||
OakNodeColorManager *manager, char *buf, int buf_size);
|
||||
int oaknode_colormanager_set_default_input_color_space(
|
||||
OakNodeColorManager *manager, const char *colorspace);
|
||||
|
||||
/**
|
||||
* @brief Reference (working) colorspace. Two-stage string getter.
|
||||
*/
|
||||
int oaknode_colormanager_get_reference_color_space(
|
||||
OakNodeColorManager *manager, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Return `colorspace` when the active config lists it, otherwise the
|
||||
* default input colorspace. Two-stage string getter. Requires a config
|
||||
* (OAKNODE_E_STATE when none is loaded).
|
||||
*/
|
||||
int oaknode_colormanager_get_compliant_color_space(
|
||||
OakNodeColorManager *manager, const char *colorspace, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Map FFmpeg color primaries/transfer codes to a colorspace of the
|
||||
* active config. Two-stage string getter; an empty result (required size
|
||||
* 1) means "unknown tags, use the default". Requires a config
|
||||
* (OAKNODE_E_STATE when none is loaded).
|
||||
*/
|
||||
int oaknode_colormanager_get_colorspace_for_ffmpeg_tags(
|
||||
OakNodeColorManager *manager, int primaries, int trc, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Config listings. Count + per-index two-stage string getters.
|
||||
* All require a loaded config (OAKNODE_E_STATE otherwise); index out of
|
||||
* range yields OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_colormanager_get_display_count(OakNodeColorManager *manager,
|
||||
int *count);
|
||||
int oaknode_colormanager_get_display_at(OakNodeColorManager *manager,
|
||||
int index, char *buf, int buf_size);
|
||||
int oaknode_colormanager_get_default_display(OakNodeColorManager *manager,
|
||||
char *buf, int buf_size);
|
||||
int oaknode_colormanager_get_view_count(OakNodeColorManager *manager,
|
||||
const char *display, int *count);
|
||||
int oaknode_colormanager_get_view_at(OakNodeColorManager *manager,
|
||||
const char *display, int index, char *buf,
|
||||
int buf_size);
|
||||
int oaknode_colormanager_get_default_view(OakNodeColorManager *manager,
|
||||
const char *display, char *buf,
|
||||
int buf_size);
|
||||
int oaknode_colormanager_get_look_count(OakNodeColorManager *manager,
|
||||
int *count);
|
||||
int oaknode_colormanager_get_look_at(OakNodeColorManager *manager, int index,
|
||||
char *buf, int buf_size);
|
||||
int oaknode_colormanager_get_colorspace_count(OakNodeColorManager *manager,
|
||||
int *count);
|
||||
int oaknode_colormanager_get_colorspace_at(OakNodeColorManager *manager,
|
||||
int index, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Default luma coefficients of the active config into rgb[3].
|
||||
* Requires a loaded config (OAKNODE_E_STATE otherwise).
|
||||
*/
|
||||
int oaknode_colormanager_get_default_luma_coefs(OakNodeColorManager *manager,
|
||||
double rgb[3]);
|
||||
|
||||
/**
|
||||
* @brief Return a copy of `transform` whose display/view/look (or output
|
||||
* colorspace) is clamped to what the active config offers
|
||||
* (olive::ColorManager::get_compliant_color_space(ColorTransform, bool)).
|
||||
*
|
||||
* `out` receives a NEW handle owned by the caller (release with
|
||||
* oakcommon_colortransform_free()). Requires a loaded config
|
||||
* (OAKNODE_E_STATE otherwise).
|
||||
*/
|
||||
int oaknode_colormanager_get_compliant_color_transform(
|
||||
OakNodeColorManager *manager, const OakCommonColorTransform *transform,
|
||||
int force_display, OakCommonColorTransform **out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_COLORMANAGER_H
|
||||
@@ -0,0 +1,39 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_NODE_ERROR_H
|
||||
#define OAK_EDITOR_NODE_ERROR_H
|
||||
|
||||
/**
|
||||
* @brief Status and error codes shared by all oaknode C API families.
|
||||
*
|
||||
* Return-code convention (mirrors engine/include/oakengine/init.h):
|
||||
* 0 (OAKNODE_OK) on success, a negative OAKNODE_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 OAKNODE_OK 0 /**< Success. */
|
||||
#define OAKNODE_E_INVALID (-1) /**< NULL handle or invalid argument. */
|
||||
#define OAKNODE_E_STATE (-2) /**< Call not valid in the current state. */
|
||||
#define OAKNODE_E_FAILED (-3) /**< The underlying operation failed. */
|
||||
#define OAKNODE_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
|
||||
#define OAKNODE_E_NOMEM (-5) /**< Allocation failed. */
|
||||
|
||||
#endif //OAK_EDITOR_NODE_ERROR_H
|
||||
@@ -0,0 +1,96 @@
|
||||
/***
|
||||
|
||||
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_NODE_FACTORY_H
|
||||
#define OAK_EDITOR_NODE_FACTORY_H
|
||||
|
||||
#include "node/error.h"
|
||||
#include "node/node.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file factory.h
|
||||
* @brief C ABI for olive::NodeFactory (src/node/src/factory.h): the
|
||||
* internal node-type library.
|
||||
*
|
||||
* The library must be populated with oaknode_factory_initialize() before
|
||||
* any other call; oaknode_factory_destroy() releases it. Prototype nodes
|
||||
* from oaknode_factory_node_at() are owned by the library: read-only
|
||||
* metadata queries only, never free them or add them to a graph.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Populate the internal node library (NodeFactory::initialize()).
|
||||
* Idempotent: calling twice is a no-op.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_factory_initialize(void);
|
||||
|
||||
/**
|
||||
* @brief Release the internal node library (NodeFactory::destroy()).
|
||||
* Safe when not initialized.
|
||||
*/
|
||||
void oaknode_factory_destroy(void);
|
||||
|
||||
/**
|
||||
* @brief Number of registered node types (the library size).
|
||||
* OAKNODE_E_STATE when not initialized.
|
||||
*/
|
||||
int oaknode_factory_id_count(int *out_count);
|
||||
|
||||
/**
|
||||
* @brief The type id of the registered node at `index`. Two-stage
|
||||
* getter; OAKNODE_E_NOT_FOUND for an out-of-range index,
|
||||
* OAKNODE_E_STATE when not initialized.
|
||||
*/
|
||||
int oaknode_factory_id_at(int index, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The display name of the node type `type_id`
|
||||
* (NodeFactory::get_name_from_id()). Two-stage getter; an unknown id
|
||||
* yields an empty string (required size 1).
|
||||
*/
|
||||
int oaknode_factory_name_from_id(const char *type_id, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Create a node of `type_id` WITHOUT adding it to any graph
|
||||
* (NodeFactory::create_from_id()). The caller owns the returned node
|
||||
* and must release it with oaknode_node_free() while it is still
|
||||
* orphaned. Returns NULL when the id is unknown or not initialized.
|
||||
*/
|
||||
OakNodeNode *oaknode_factory_create_from_id(const char *type_id);
|
||||
|
||||
/**
|
||||
* @brief Borrow the prototype node at `index` in the library.
|
||||
* OAKNODE_E_NOT_FOUND for an out-of-range index, OAKNODE_E_STATE when
|
||||
* not initialized.
|
||||
*/
|
||||
int oaknode_factory_node_at(int index, OakNodeNode **out_node);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_FACTORY_H
|
||||
@@ -0,0 +1,123 @@
|
||||
/***
|
||||
|
||||
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_NODE_FOLDER_H
|
||||
#define OAK_EDITOR_NODE_FOLDER_H
|
||||
|
||||
#include "node/error.h"
|
||||
#include "node/project.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file folder.h
|
||||
* @brief C ABI for olive::Folder (oaknode)
|
||||
*
|
||||
* A folder is a project node that organizes item children (footage,
|
||||
* sequences, subfolders). Folder handles are borrowed from the owning
|
||||
* project; they become invalid when the project is freed or cleared.
|
||||
*
|
||||
* Child add/remove/move operations execute the underlying undo commands
|
||||
* live (redo_now); wiring them onto an undo stack is the oakundo /
|
||||
* facade layer's job, not this layer's.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Create a folder node owned by `project`.
|
||||
*
|
||||
* The folder is added to the project's graph (Project::add_node()) but is
|
||||
* NOT attached under any parent folder; use oaknode_folder_add_child() to
|
||||
* place it. The handle is borrowed: the project owns the folder.
|
||||
*
|
||||
* @return Folder handle, or NULL on failure.
|
||||
*/
|
||||
OakNodeFolder *oaknode_folder_create(OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Number of direct item children (Folder::item_child_count()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_folder_child_count(const OakNodeFolder *folder);
|
||||
|
||||
/**
|
||||
* @brief Borrowed node handle of the item child at `index`
|
||||
* (Folder::item_child()). NULL when out of range.
|
||||
*/
|
||||
OakNodeNode *oaknode_folder_child_at(const OakNodeFolder *folder, int index);
|
||||
|
||||
/**
|
||||
* @brief Add `child` as a direct item child of `folder` (live, non-undoable;
|
||||
* executes FolderAddChild::redo()).
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_STATE if `child` already belongs to a
|
||||
* folder, or another negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_folder_add_child(OakNodeFolder *folder, OakNodeNode *child);
|
||||
|
||||
/**
|
||||
* @brief Remove `child` from `folder` without deleting it (live,
|
||||
* non-undoable; executes Folder::RemoveElementCommand::redo()).
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_NOT_FOUND if `child` is not a direct child,
|
||||
* or another negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_folder_remove_child(OakNodeFolder *folder, OakNodeNode *child);
|
||||
|
||||
/**
|
||||
* @brief Move several nodes into `dest_folder` (live, non-undoable).
|
||||
*
|
||||
* Each node is removed from its current folder (if any) and appended to
|
||||
* `dest_folder`. Nodes already directly inside `dest_folder` are skipped.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_folder_move_children(OakNodeNode *const *nodes, int count,
|
||||
OakNodeFolder *dest_folder);
|
||||
|
||||
/**
|
||||
* @brief 1 if `folder` recursively contains `child`, 0 otherwise
|
||||
* (Folder::has_child_recursive()). Negative OAKNODE_E_* code on NULL args.
|
||||
*/
|
||||
int oaknode_folder_has_child_recursive(const OakNodeFolder *folder,
|
||||
const OakNodeNode *child);
|
||||
|
||||
/**
|
||||
* @brief Index of `child` in `folder`'s direct children
|
||||
* (Folder::index_of_child()).
|
||||
*
|
||||
* @return The index, OAKNODE_E_NOT_FOUND if not a direct child, or
|
||||
* OAKNODE_E_INVALID on NULL args.
|
||||
*/
|
||||
int oaknode_folder_index_of_child(const OakNodeFolder *folder,
|
||||
const OakNodeNode *child);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle of the folder a node currently belongs to
|
||||
* (Node::folder()), or NULL if the node is not in any folder.
|
||||
*/
|
||||
OakNodeFolder *oaknode_folder_parent_of(const OakNodeNode *node);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_FOLDER_H
|
||||
@@ -0,0 +1,199 @@
|
||||
/***
|
||||
|
||||
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_NODE_FOOTAGE_H
|
||||
#define OAK_EDITOR_NODE_FOOTAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "node/error.h"
|
||||
#include "node/project.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file footage.h
|
||||
* @brief C ABI for olive::Footage (oaknode)
|
||||
*
|
||||
* A footage node references an external media file and caches its stream
|
||||
* metadata. Footage handles are borrowed from the owning project; they
|
||||
* become invalid when the project is freed or cleared.
|
||||
*
|
||||
* NOTE: setting a filename whose file exists on disk triggers a probe,
|
||||
* which requires the codec/render modules (outside oaknode). Tests and
|
||||
* pure-graph consumers should use nonexistent paths; probing is the
|
||||
* facade layer's job.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Opaque footage handle. Borrowed from the owning project.
|
||||
*/
|
||||
typedef struct OakNodeFootage OakNodeFootage;
|
||||
|
||||
/**
|
||||
* @brief Create a footage node owned by `project` (added to the project's
|
||||
* graph, not attached to any folder).
|
||||
*
|
||||
* @param filename Initial media path, may be NULL/empty.
|
||||
*
|
||||
* @return Footage handle, or NULL on failure.
|
||||
*/
|
||||
OakNodeFootage *oaknode_footage_create(OakNodeProject *project,
|
||||
const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Current media path (Footage::filename()). Two-stage string getter.
|
||||
*
|
||||
* @return Required buffer size in bytes including the NUL, or a negative
|
||||
* OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_filename(const OakNodeFootage *footage, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Set the media path (Footage::set_filename()). Does not re-probe
|
||||
* unless the file exists (see the file comment above).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_set_filename(OakNodeFootage *footage, const char *filename);
|
||||
|
||||
/**
|
||||
* @brief 1 if the footage was successfully probed and is ready for use
|
||||
* (Footage::is_valid()), 0 otherwise. Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_footage_is_valid(const OakNodeFootage *footage);
|
||||
|
||||
/**
|
||||
* @brief Last-modified timestamp of the media file in milliseconds since the
|
||||
* epoch (Footage::timestamp()).
|
||||
*
|
||||
* @param out_timestamp Receives the timestamp. Must not be NULL.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_timestamp(const OakNodeFootage *footage,
|
||||
int64_t *out_timestamp);
|
||||
|
||||
/**
|
||||
* @brief Set the last-modified timestamp (Footage::set_timestamp()).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_set_timestamp(OakNodeFootage *footage, int64_t timestamp);
|
||||
|
||||
/**
|
||||
* @brief Decoder ID recorded when the footage was probed
|
||||
* (Footage::decoder()). Two-stage string getter.
|
||||
*/
|
||||
int oaknode_footage_decoder(const OakNodeFootage *footage, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Total number of streams (Footage::get_total_stream_count()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_footage_total_stream_count(const OakNodeFootage *footage);
|
||||
|
||||
/**
|
||||
* @brief Number of video streams (ViewerOutput::get_video_stream_count()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_footage_video_stream_count(const OakNodeFootage *footage);
|
||||
|
||||
/**
|
||||
* @brief Number of audio streams (ViewerOutput::get_audio_stream_count()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_footage_audio_stream_count(const OakNodeFootage *footage);
|
||||
|
||||
/**
|
||||
* @brief Number of subtitle streams (ViewerOutput::get_subtitle_stream_count()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_footage_subtitle_stream_count(const OakNodeFootage *footage);
|
||||
|
||||
/**
|
||||
* @brief Footage duration as a rational number of seconds
|
||||
* (ViewerOutput::get_length()).
|
||||
*
|
||||
* @param out_numerator Receives the numerator. Must not be NULL.
|
||||
* @param out_denominator Receives the denominator. Must not be NULL.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_duration(const OakNodeFootage *footage,
|
||||
int *out_numerator, int *out_denominator);
|
||||
|
||||
/**
|
||||
* @brief 1 if proxy playback is enabled (Footage::proxy_enabled()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_footage_proxy_enabled(const OakNodeFootage *footage);
|
||||
|
||||
/**
|
||||
* @brief Enable/disable proxy playback (Footage::set_proxy_enabled()).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_set_proxy_enabled(OakNodeFootage *footage, int enabled);
|
||||
|
||||
/**
|
||||
* @brief Proxy file path, or "" when none (Footage::proxy_path()).
|
||||
* Two-stage string getter.
|
||||
*/
|
||||
int oaknode_footage_proxy_path(const OakNodeFootage *footage, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Proxy state enum value (Footage::proxy_state():
|
||||
* ProxyManager::ProxyState). Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_footage_proxy_state(const OakNodeFootage *footage);
|
||||
|
||||
/**
|
||||
* @brief Set all proxy fields at once (Footage::set_proxy()).
|
||||
*
|
||||
* @param path Proxy file path, may be NULL/empty.
|
||||
* @param state ProxyManager::ProxyState enum value.
|
||||
* @param video_stream_index Proxy's video stream index (-1 when none).
|
||||
* @param preset_version Proxy preset version.
|
||||
* @param enabled Non-zero to enable proxy playback.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_set_proxy(OakNodeFootage *footage, const char *path,
|
||||
int state, int video_stream_index,
|
||||
int preset_version, int enabled);
|
||||
|
||||
/**
|
||||
* @brief Clear all proxy fields (Footage::clear_proxy()).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_footage_clear_proxy(OakNodeFootage *footage);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_FOOTAGE_H
|
||||
@@ -0,0 +1,163 @@
|
||||
/***
|
||||
|
||||
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_NODE_GROUP_H
|
||||
#define OAK_EDITOR_NODE_GROUP_H
|
||||
|
||||
#include "node/error.h"
|
||||
#include "node/node.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file group.h
|
||||
* @brief C ABI for olive::NodeGroup (src/node/src/group/group.h):
|
||||
* input passthrough management and input resolution.
|
||||
*
|
||||
* An OakNodeGroup is a reinterpreted olive::NodeGroup (a Node subclass);
|
||||
* group handles borrow the same lifetime rules as OakNodeNode.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Opaque group handle (olive::NodeGroup).
|
||||
*/
|
||||
typedef struct OakNodeGroup OakNodeGroup;
|
||||
|
||||
/**
|
||||
* @brief Create a standalone NodeGroup (owned; release with
|
||||
* oaknode_node_free() on the OakNodeNode view or oaknode_group_free()
|
||||
* while still orphaned).
|
||||
*
|
||||
* @return Group handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakNodeGroup *oaknode_group_create(void);
|
||||
|
||||
/**
|
||||
* @brief Borrow a group view of a node, or NULL when the node is not a
|
||||
* NodeGroup (dynamic_cast).
|
||||
*/
|
||||
OakNodeGroup *oaknode_group_cast(OakNodeNode *node);
|
||||
|
||||
/**
|
||||
* @brief Destroy an OWNED group (same rules as oaknode_node_free()).
|
||||
* NULL is a no-op.
|
||||
*/
|
||||
void oaknode_group_free(OakNodeGroup *group);
|
||||
|
||||
/**
|
||||
* @brief Add an input passthrough for (`node`, `input_id`, `element`)
|
||||
* (live, NodeGroup::add_input_passthrough()). The generated passthrough
|
||||
* id is returned through the two-stage string convention.
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_group_add_input_passthrough(OakNodeGroup *group,
|
||||
OakNodeNode *node,
|
||||
const char *input_id, int element,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Create an add-passthrough command
|
||||
* (olive::NodeGroupAddInputPassthrough). The generated id is NOT
|
||||
* retrievable through this call (the command computes it on redo).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_group_add_input_passthrough_undoable(OakNodeGroup *group,
|
||||
OakNodeNode *node,
|
||||
const char *input_id,
|
||||
int element,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Remove the passthrough for (`node`, `input_id`, `element`)
|
||||
* (live). OAKNODE_E_NOT_FOUND when no such passthrough exists.
|
||||
*/
|
||||
int oaknode_group_remove_input_passthrough(OakNodeGroup *group,
|
||||
OakNodeNode *node,
|
||||
const char *input_id, int element);
|
||||
|
||||
/**
|
||||
* @brief Number of registered input passthroughs.
|
||||
*/
|
||||
int oaknode_group_passthrough_count(const OakNodeGroup *group, int *out_count);
|
||||
|
||||
/**
|
||||
* @brief The passthrough id at `index`. Two-stage getter;
|
||||
* OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_group_passthrough_id_at(const OakNodeGroup *group, int index,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The inner input behind passthrough `index`: node (borrowed
|
||||
* handle), input id (two-stage string) and element.
|
||||
* OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_group_passthrough_input_at(const OakNodeGroup *group, int index,
|
||||
OakNodeNode **out_node, char *buf,
|
||||
int buf_size, int *out_element);
|
||||
|
||||
/**
|
||||
* @brief The output passthrough node (borrowed handle), or NULL when
|
||||
* unset. OAKNODE_OK is returned either way.
|
||||
*/
|
||||
int oaknode_group_get_output_passthrough(const OakNodeGroup *group,
|
||||
OakNodeNode **out_node);
|
||||
|
||||
/**
|
||||
* @brief Set the output passthrough node directly (live).
|
||||
*/
|
||||
int oaknode_group_set_output_passthrough(OakNodeGroup *group,
|
||||
OakNodeNode *node);
|
||||
|
||||
/**
|
||||
* @brief Create a set-output-passthrough command
|
||||
* (olive::NodeGroupSetOutputPassthrough).
|
||||
*/
|
||||
int oaknode_group_set_output_passthrough_undoable(
|
||||
OakNodeGroup *group, OakNodeNode *node, OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Resolve an input through group passthroughs
|
||||
* (NodeGroup::resolve_input()): follows a group's passthrough id to the
|
||||
* inner node input. Non-group inputs resolve to themselves.
|
||||
*
|
||||
* `out_node` (may be NULL) receives a borrowed handle; the resolved input
|
||||
* id uses the two-stage string convention; `out_element` (may be NULL)
|
||||
* receives the element. OAKNODE_E_NOT_FOUND when the input does not
|
||||
* resolve to a valid target.
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_group_resolve_input(OakNodeNode *node, const char *input_id,
|
||||
int element, OakNodeNode **out_node,
|
||||
char *buf, int buf_size, int *out_element);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_GROUP_H
|
||||
@@ -0,0 +1,226 @@
|
||||
/***
|
||||
|
||||
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_NODE_KEYFRAME_H
|
||||
#define OAK_EDITOR_NODE_KEYFRAME_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "node/error.h"
|
||||
#include "node/node.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file keyframe.h
|
||||
* @brief C ABI for olive::NodeKeyframe (src/node/src/keyframe.h).
|
||||
*
|
||||
* An OakNodeKeyframe is a reinterpreted olive::NodeKeyframe. Handles
|
||||
* created by oaknode_keyframe_create() are owned and must be released
|
||||
* with oaknode_keyframe_free(); keyframes attached to a node input's
|
||||
* track are owned by the node.
|
||||
*
|
||||
* Every setter comes in a live variant and an undoable variant (suffix
|
||||
* _undoable) returning an owned, un-executed OakUndoCommand.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Interpolation type of a keyframe (olive::NodeKeyframe::Type).
|
||||
*/
|
||||
typedef enum oaknode_keyframe_type {
|
||||
OAKNODE_KEYFRAME_INVALID = -1,
|
||||
OAKNODE_KEYFRAME_LINEAR = 0,
|
||||
OAKNODE_KEYFRAME_HOLD = 1,
|
||||
OAKNODE_KEYFRAME_BEZIER = 2
|
||||
} oaknode_keyframe_type;
|
||||
|
||||
/**
|
||||
* @brief Bezier handle selector (olive::NodeKeyframe::BezierType).
|
||||
*/
|
||||
typedef enum oaknode_keyframe_bezier {
|
||||
OAKNODE_KEYFRAME_IN_HANDLE = 0,
|
||||
OAKNODE_KEYFRAME_OUT_HANDLE = 1
|
||||
} oaknode_keyframe_bezier;
|
||||
|
||||
/**
|
||||
* @brief Opaque keyframe handle (olive::NodeKeyframe).
|
||||
*/
|
||||
typedef struct OakNodeKeyframe OakNodeKeyframe;
|
||||
|
||||
/**
|
||||
* @brief Create a standalone keyframe (owned; release with
|
||||
* oaknode_keyframe_free()).
|
||||
*
|
||||
* `value` may be NULL (null variant); OAKNODE_VALUE_STRING is rejected
|
||||
* (use oaknode_keyframe_set_value_string() after creation). `type` is an
|
||||
* oaknode_keyframe_type. `parent_or_null` may be NULL.
|
||||
*
|
||||
* @return Keyframe handle, or NULL on invalid argument or allocation
|
||||
* failure.
|
||||
*/
|
||||
OakNodeKeyframe *oaknode_keyframe_create(int64_t time_num, int64_t time_den,
|
||||
const oaknode_value *value, int type,
|
||||
int track, int element,
|
||||
const char *input_id,
|
||||
OakNodeNode *parent_or_null);
|
||||
|
||||
/**
|
||||
* @brief Destroy an OWNED keyframe. NULL is a no-op. Never free a
|
||||
* keyframe that is attached to a node's track.
|
||||
*/
|
||||
void oaknode_keyframe_free(OakNodeKeyframe *keyframe);
|
||||
|
||||
/**
|
||||
* @brief The keyframe's time as a rational (numerator/denominator).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_keyframe_get_time(const OakNodeKeyframe *keyframe,
|
||||
int64_t *out_num, int64_t *out_den);
|
||||
|
||||
/**
|
||||
* @brief Set the keyframe's time directly (live).
|
||||
*/
|
||||
int oaknode_keyframe_set_time(OakNodeKeyframe *keyframe, int64_t time_num,
|
||||
int64_t time_den);
|
||||
|
||||
/**
|
||||
* @brief Create a set-time command (olive::NodeParamSetKeyframeTimeCommand).
|
||||
*/
|
||||
int oaknode_keyframe_set_time_undoable(OakNodeKeyframe *keyframe,
|
||||
int64_t time_num, int64_t time_den,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Read the keyframe's value mapped into `out`. Values without a
|
||||
* POD representation fail with OAKNODE_E_FAILED.
|
||||
*/
|
||||
int oaknode_keyframe_get_value(const OakNodeKeyframe *keyframe,
|
||||
oaknode_value *out);
|
||||
|
||||
/**
|
||||
* @brief Set the keyframe's value directly (live).
|
||||
* OAKNODE_VALUE_STRING is rejected (use
|
||||
* oaknode_keyframe_set_value_string()).
|
||||
*/
|
||||
int oaknode_keyframe_set_value(OakNodeKeyframe *keyframe,
|
||||
const oaknode_value *v);
|
||||
|
||||
/**
|
||||
* @brief Create a set-value command
|
||||
* (olive::NodeParamSetKeyframeValueCommand).
|
||||
*/
|
||||
int oaknode_keyframe_set_value_undoable(OakNodeKeyframe *keyframe,
|
||||
const oaknode_value *v,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Read a string value. Two-stage getter.
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_keyframe_get_value_string(const OakNodeKeyframe *keyframe,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Set a string value directly (live).
|
||||
*/
|
||||
int oaknode_keyframe_set_value_string(OakNodeKeyframe *keyframe,
|
||||
const char *value);
|
||||
|
||||
/**
|
||||
* @brief Create a set-string-value command.
|
||||
*/
|
||||
int oaknode_keyframe_set_value_string_undoable(OakNodeKeyframe *keyframe,
|
||||
const char *value,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief The keyframe's interpolation type (oaknode_keyframe_type).
|
||||
*/
|
||||
int oaknode_keyframe_get_type(const OakNodeKeyframe *keyframe, int *out_type);
|
||||
|
||||
/**
|
||||
* @brief Set the interpolation type directly (live,
|
||||
* NodeKeyframe::set_type(), which adjusts neighbouring bezier handles).
|
||||
*/
|
||||
int oaknode_keyframe_set_type(OakNodeKeyframe *keyframe, int type);
|
||||
|
||||
/**
|
||||
* @brief Create a set-type command (same semantics as the live variant).
|
||||
*/
|
||||
int oaknode_keyframe_set_type_undoable(OakNodeKeyframe *keyframe, int type,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief A bezier control point (`handle` is an
|
||||
* oaknode_keyframe_bezier).
|
||||
*/
|
||||
int oaknode_keyframe_get_bezier_control(const OakNodeKeyframe *keyframe,
|
||||
int handle, double *out_x,
|
||||
double *out_y);
|
||||
|
||||
/**
|
||||
* @brief Set a bezier control point directly (live).
|
||||
*/
|
||||
int oaknode_keyframe_set_bezier_control(OakNodeKeyframe *keyframe, int handle,
|
||||
double x, double y);
|
||||
|
||||
/**
|
||||
* @brief Create a set-bezier-control command.
|
||||
*/
|
||||
int oaknode_keyframe_set_bezier_control_undoable(OakNodeKeyframe *keyframe,
|
||||
int handle, double x, double y,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief The keyframe's track index.
|
||||
*/
|
||||
int oaknode_keyframe_get_track(const OakNodeKeyframe *keyframe,
|
||||
int *out_track);
|
||||
|
||||
/**
|
||||
* @brief The keyframe's element index.
|
||||
*/
|
||||
int oaknode_keyframe_get_element(const OakNodeKeyframe *keyframe,
|
||||
int *out_element);
|
||||
|
||||
/**
|
||||
* @brief The id of the input this keyframe belongs to. Two-stage getter.
|
||||
*/
|
||||
int oaknode_keyframe_get_input(const OakNodeKeyframe *keyframe, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The node this keyframe belongs to (borrowed handle), or NULL
|
||||
* when orphaned. OAKNODE_OK either way.
|
||||
*/
|
||||
int oaknode_keyframe_get_parent(const OakNodeKeyframe *keyframe,
|
||||
OakNodeNode **out_node);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_KEYFRAME_H
|
||||
@@ -0,0 +1,487 @@
|
||||
/***
|
||||
|
||||
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_NODE_NODE_H
|
||||
#define OAK_EDITOR_NODE_NODE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "node/error.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file node.h
|
||||
* @brief C ABI for olive::Node (src/node/src/node.h).
|
||||
*
|
||||
* The handle IS the C++ object pointer (M3 handle convention 3): an
|
||||
* OakNodeNode is a reinterpreted olive::Node, no wrapper allocation.
|
||||
* Handles borrowed from a graph become invalid when the owning project or
|
||||
* node is destroyed. Owned handles (from oaknode_factory_create_from_id()
|
||||
* or oaknode_node_create_copy()) must be released with
|
||||
* oaknode_node_free() while still orphaned; once a node lives in a project
|
||||
* graph its lifetime belongs to the graph.
|
||||
*
|
||||
* Parameter values cross the boundary as the POD oaknode_value; the
|
||||
* meaningful fields depend on its type (oaknode_value_type). String-typed
|
||||
* inputs (NodeValue::k_file/k_text/k_font/k_str_combo) do not fit the POD
|
||||
* and use the dedicated *_input_string() pair (two-stage buf/size getters
|
||||
* return the required size including the terminating NUL).
|
||||
*
|
||||
* Every mutating function comes in a live variant (applies immediately)
|
||||
* and an undoable variant (suffix _undoable) that creates an
|
||||
* olive::UndoCommand without executing it and returns it as an owned
|
||||
* OakUndoCommand handle. Execute it with oakundo_command_redo_now(),
|
||||
* push it onto an OakUndoStack, or release it with
|
||||
* oakundo_command_free().
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Value type of an oaknode_value / a node input.
|
||||
*
|
||||
* Pinned mapping to olive::NodeValue::Type (src/node/src/value.h):
|
||||
* NONE -> k_none, INT -> k_int, FLOAT -> k_float, BOOL -> k_boolean,
|
||||
* RATIONAL -> k_rational, COLOR -> k_color, VEC2 -> k_vec2,
|
||||
* VEC3 -> k_vec3, VEC4 -> k_vec4, COMBO -> k_combo,
|
||||
* STRING -> k_file (string-family inputs: k_file/k_text/k_font/
|
||||
* k_str_combo, handled by the dedicated string functions). Types without
|
||||
* a POD representation (texture, samples, matrix, params, bezier, binary,
|
||||
* ...) report as OAKNODE_VALUE_NONE.
|
||||
*/
|
||||
typedef enum oaknode_value_type {
|
||||
OAKNODE_VALUE_NONE = 0,
|
||||
OAKNODE_VALUE_INT, /**< num (olive k_int, int64_t) */
|
||||
OAKNODE_VALUE_FLOAT, /**< f[0] (olive k_float, double) */
|
||||
OAKNODE_VALUE_BOOL, /**< num 0/1 (olive k_boolean) */
|
||||
OAKNODE_VALUE_RATIONAL, /**< num/den (olive k_rational) */
|
||||
OAKNODE_VALUE_COLOR, /**< f[0..3] = r,g,b,a (olive k_color) */
|
||||
OAKNODE_VALUE_VEC2, /**< f[0..1] (olive k_vec2) */
|
||||
OAKNODE_VALUE_VEC3, /**< f[0..2] (olive k_vec3) */
|
||||
OAKNODE_VALUE_VEC4, /**< f[0..3] (olive k_vec4) */
|
||||
OAKNODE_VALUE_COMBO, /**< num = selected index (olive k_combo) */
|
||||
OAKNODE_VALUE_STRING, /**< k_file string family; string APIs only */
|
||||
OAKNODE_VALUE_COUNT
|
||||
} oaknode_value_type;
|
||||
|
||||
/**
|
||||
* @brief POD parameter value. Only the fields documented for the value's
|
||||
* `type` are meaningful.
|
||||
*/
|
||||
typedef struct oaknode_value {
|
||||
int type; /**< oaknode_value_type. */
|
||||
int64_t num; /**< INT/COMBO value, BOOL 0/1, RATIONAL numerator. */
|
||||
int64_t den; /**< RATIONAL denominator. */
|
||||
double f[4]; /**< FLOAT f[0]; VEC2/3/4 f[0..n-1]; COLOR r,g,b,a. */
|
||||
} oaknode_value;
|
||||
|
||||
/**
|
||||
* @brief Opaque node handle (olive::Node).
|
||||
*/
|
||||
typedef struct OakNodeNode OakNodeNode;
|
||||
|
||||
/**
|
||||
* @brief Number of live owned objects created through this API
|
||||
* (nodes from oaknode_factory_create_from_id()/oaknode_node_create_copy(),
|
||||
* keyframes, groups, traversers, traverser databases). Debug aid for
|
||||
* leak checking; thread-unsafe, test/diagnostic use only.
|
||||
*/
|
||||
int oaknode_debug_alive_count(void);
|
||||
|
||||
/* ---- Metadata --------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief The node's unique type id (Node::id(), e.g.
|
||||
* "org.olivevideoeditor.Olive.solidgenerator"). Two-stage getter.
|
||||
*
|
||||
* @return Required buffer size in bytes including the terminating NUL
|
||||
* (non-negative), or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_get_id(const OakNodeNode *node, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The node's display name (Node::name()). Two-stage getter,
|
||||
* same return convention as oaknode_node_get_id().
|
||||
*/
|
||||
int oaknode_node_get_name(const OakNodeNode *node, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The node's user label (Node::get_label()). Two-stage getter,
|
||||
* same return convention as oaknode_node_get_id().
|
||||
*/
|
||||
int oaknode_node_get_label(const OakNodeNode *node, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Set the node's user label directly (Node::set_label(), live).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_set_label(OakNodeNode *node, const char *label);
|
||||
|
||||
/**
|
||||
* @brief Create a label-change command (olive::NodeRenameCommand).
|
||||
*
|
||||
* The command is NOT executed; `out_command` receives an owned command
|
||||
* handle.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_set_label_undoable(OakNodeNode *node, const char *label,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief The node's override color index (Node::get_override_color();
|
||||
* -1 = none).
|
||||
*
|
||||
* @param out_value Receives the result. Must not be NULL.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_get_override_color(const OakNodeNode *node, int *out_value);
|
||||
|
||||
/**
|
||||
* @brief Set the override color index directly (-1 = none; live).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_set_override_color(OakNodeNode *node, int index);
|
||||
|
||||
/**
|
||||
* @brief Create an override-color command (olive::NodeOverrideColorCommand).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_set_override_color_undoable(OakNodeNode *node, int index,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief 1 if the node is enabled (the boolean "enabled_in" input's
|
||||
* standard value).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_is_enabled(const OakNodeNode *node, int *out_value);
|
||||
|
||||
/**
|
||||
* @brief Set the node's enabled state directly (live).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_set_enabled(OakNodeNode *node, int enabled);
|
||||
|
||||
/**
|
||||
* @brief Create an enabled-state command
|
||||
* (olive::NodeParamSetStandardValueCommand on "enabled_in").
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_set_enabled_undoable(OakNodeNode *node, int enabled,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/* ---- Input introspection ------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* @brief Number of declared inputs (Node::inputs(); array elements are not
|
||||
* counted separately).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_node_input_count(const OakNodeNode *node, int *out_count);
|
||||
|
||||
/**
|
||||
* @brief The input id at `index` (Node::inputs()). Two-stage getter;
|
||||
* returns OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_node_input_id(const OakNodeNode *node, int index, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The input's value type mapped to oaknode_value_type (see the
|
||||
* pinned mapping on oaknode_value_type). OAKNODE_E_NOT_FOUND for an
|
||||
* unknown input id.
|
||||
*/
|
||||
int oaknode_node_input_get_type(const OakNodeNode *node, const char *input_id,
|
||||
int *out_type);
|
||||
|
||||
/**
|
||||
* @brief 1 if the input currently has a connected edge
|
||||
* (Node::is_input_connected()). OAKNODE_E_NOT_FOUND for an unknown id.
|
||||
*/
|
||||
int oaknode_node_input_is_connected(const OakNodeNode *node,
|
||||
const char *input_id, int *out_value);
|
||||
|
||||
/**
|
||||
* @brief 1 if the input accepts connections (Node::is_input_connectable()).
|
||||
* OAKNODE_E_NOT_FOUND for an unknown id.
|
||||
*/
|
||||
int oaknode_node_input_is_connectable(const OakNodeNode *node,
|
||||
const char *input_id, int *out_value);
|
||||
|
||||
/**
|
||||
* @brief The human-readable name of the input (Node::get_input_name()).
|
||||
* Two-stage getter; OAKNODE_E_NOT_FOUND for an unknown id.
|
||||
*/
|
||||
int oaknode_node_get_input_name(const OakNodeNode *node, const char *input_id,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The node feeding this input, or NULL when not connected
|
||||
* (Node::get_connected_output(), element -1). `out_node` receives a
|
||||
* borrowed handle. OAKNODE_E_NOT_FOUND for an unknown input id.
|
||||
*/
|
||||
int oaknode_node_input_get_connected_node(const OakNodeNode *node,
|
||||
const char *input_id,
|
||||
OakNodeNode **out_node);
|
||||
|
||||
/* ---- Parameter access ----------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Read an input's standard value (Node::get_standard_value())
|
||||
* mapped into `out`.
|
||||
*
|
||||
* String-family inputs fail with OAKNODE_E_INVALID (use
|
||||
* oaknode_node_get_input_string()); types without a POD representation
|
||||
* fail with OAKNODE_E_FAILED; an unknown input id fails with
|
||||
* OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_node_get_input(const OakNodeNode *node, const char *input_id,
|
||||
oaknode_value *out);
|
||||
|
||||
/**
|
||||
* @brief Write an input's standard value directly (live,
|
||||
* Node::set_standard_value()).
|
||||
*
|
||||
* `v->type` must match the input's declared type; OAKNODE_VALUE_STRING is
|
||||
* rejected (use oaknode_node_set_input_string()).
|
||||
*/
|
||||
int oaknode_node_set_input(OakNodeNode *node, const char *input_id,
|
||||
const oaknode_value *v);
|
||||
|
||||
/**
|
||||
* @brief Create a set-standard-value command
|
||||
* (olive::NodeParamSetStandardValueCommand, track -1 semantics via the
|
||||
* whole-value reference on track 0).
|
||||
*
|
||||
* Same type rules as oaknode_node_set_input().
|
||||
*/
|
||||
int oaknode_node_set_input_undoable(OakNodeNode *node, const char *input_id,
|
||||
const oaknode_value *v,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Read a string-family input's standard value. Two-stage getter.
|
||||
*/
|
||||
int oaknode_node_get_input_string(const OakNodeNode *node,
|
||||
const char *input_id, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Write a string-family input's standard value directly (live).
|
||||
*/
|
||||
int oaknode_node_set_input_string(OakNodeNode *node, const char *input_id,
|
||||
const char *value);
|
||||
|
||||
/**
|
||||
* @brief Create a set-standard-value command for a string-family input.
|
||||
*/
|
||||
int oaknode_node_set_input_string_undoable(OakNodeNode *node,
|
||||
const char *input_id,
|
||||
const char *value,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/* ---- Graph editing -------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Connect `output_node`'s output into `input_node`'s `input_id`
|
||||
* directly (live, Node::connect_edge(), element -1).
|
||||
*
|
||||
* Fails with OAKNODE_E_NOT_FOUND for an unknown input id,
|
||||
* OAKNODE_E_INVALID when the input is not connectable, and
|
||||
* OAKNODE_E_STATE when the input is already connected or the nodes belong
|
||||
* to different graphs.
|
||||
*/
|
||||
int oaknode_node_connect(OakNodeNode *output_node, OakNodeNode *input_node,
|
||||
const char *input_id);
|
||||
|
||||
/**
|
||||
* @brief Create an edge-add command (olive::NodeEdgeAddCommand,
|
||||
* element -1). Same validation as oaknode_node_connect() except the
|
||||
* different-graph check (the command may legitimately be redone after
|
||||
* graph changes).
|
||||
*/
|
||||
int oaknode_node_connect_undoable(OakNodeNode *output_node,
|
||||
OakNodeNode *input_node,
|
||||
const char *input_id,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Remove the edge feeding `input_node`'s `input_id` directly
|
||||
* (live, Node::disconnect_edge(), element -1). OAKNODE_E_NOT_FOUND when
|
||||
* the input is unknown or not connected.
|
||||
*/
|
||||
int oaknode_node_disconnect(OakNodeNode *input_node, const char *input_id);
|
||||
|
||||
/**
|
||||
* @brief Create an edge-remove command (olive::NodeEdgeRemoveCommand,
|
||||
* element -1). OAKNODE_E_NOT_FOUND when not connected.
|
||||
*/
|
||||
int oaknode_node_disconnect_undoable(OakNodeNode *input_node,
|
||||
const char *input_id,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Number of outgoing edges (Node::output_connections()).
|
||||
*/
|
||||
int oaknode_node_output_connection_count(const OakNodeNode *node,
|
||||
int *out_count);
|
||||
|
||||
/**
|
||||
* @brief The node at the input end of outgoing edge `index`
|
||||
* (borrowed handle). OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_node_output_connection_node_at(const OakNodeNode *node, int index,
|
||||
OakNodeNode **out_node);
|
||||
|
||||
/**
|
||||
* @brief The input id at the input end of outgoing edge `index`.
|
||||
* Two-stage getter; OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_node_output_connection_input_id_at(const OakNodeNode *node,
|
||||
int index, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief The input element at the input end of outgoing edge `index`
|
||||
* (-1 for non-array inputs). OAKNODE_E_NOT_FOUND for an out-of-range
|
||||
* index.
|
||||
*/
|
||||
int oaknode_node_output_connection_element_at(const OakNodeNode *node,
|
||||
int index, int *out_element);
|
||||
|
||||
/* ---- Links --------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Link two nodes directly (live, Node::link()). `out_linked`
|
||||
* receives 1 on success, 0 when the link was rejected (e.g. either node
|
||||
* rejects links). `out_linked` may be NULL.
|
||||
*/
|
||||
int oaknode_node_link(OakNodeNode *a, OakNodeNode *b, int *out_linked);
|
||||
|
||||
/**
|
||||
* @brief Unlink two nodes directly (live, Node::unlink()).
|
||||
* `out_unlinked` receives 1 on success, 0 otherwise; may be NULL.
|
||||
*/
|
||||
int oaknode_node_unlink(OakNodeNode *a, OakNodeNode *b, int *out_unlinked);
|
||||
|
||||
/**
|
||||
* @brief Create a link/unlink command (olive::NodeLinkCommand;
|
||||
* `link` != 0 links, 0 unlinks).
|
||||
*/
|
||||
int oaknode_node_link_undoable(OakNodeNode *a, OakNodeNode *b, int link,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief 1 if the two nodes are linked (Node::are_linked()).
|
||||
*/
|
||||
int oaknode_node_are_linked(const OakNodeNode *a, const OakNodeNode *b,
|
||||
int *out_value);
|
||||
|
||||
/**
|
||||
* @brief Number of linked nodes (Node::links()).
|
||||
*/
|
||||
int oaknode_node_link_count(const OakNodeNode *node, int *out_count);
|
||||
|
||||
/**
|
||||
* @brief The linked node at `index` (borrowed handle).
|
||||
* OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_node_link_at(const OakNodeNode *node, int index,
|
||||
OakNodeNode **out_node);
|
||||
|
||||
/* ---- Context positions ---------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Number of context entries (Node::get_context_positions()).
|
||||
*/
|
||||
int oaknode_node_context_count(const OakNodeNode *node, int *out_count);
|
||||
|
||||
/**
|
||||
* @brief The context node at `index` (borrowed handle).
|
||||
* OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_node_context_node_at(const OakNodeNode *node, int index,
|
||||
OakNodeNode **out_node);
|
||||
|
||||
/**
|
||||
* @brief The node's position in `context` (any out pointer may be NULL).
|
||||
* OAKNODE_E_NOT_FOUND when the context does not contain this node.
|
||||
*/
|
||||
int oaknode_node_get_context_position(const OakNodeNode *node,
|
||||
OakNodeNode *context, double *out_x,
|
||||
double *out_y, int *out_expanded);
|
||||
|
||||
/**
|
||||
* @brief Set the node's position in `context` directly (live,
|
||||
* Node::set_node_position_in_context() + set_node_expanded_in_context()).
|
||||
*/
|
||||
int oaknode_node_set_context_position(OakNodeNode *node, OakNodeNode *context,
|
||||
double x, double y, int expanded);
|
||||
|
||||
/**
|
||||
* @brief Create a set-position command (olive::NodeSetPositionCommand).
|
||||
*/
|
||||
int oaknode_node_set_context_position_undoable(OakNodeNode *node,
|
||||
OakNodeNode *context, double x,
|
||||
double y, int expanded,
|
||||
OakUndoCommand **out_command);
|
||||
|
||||
/**
|
||||
* @brief Remove the node from `context` directly (live).
|
||||
* OAKNODE_E_NOT_FOUND when not contained.
|
||||
*/
|
||||
int oaknode_node_remove_from_context(OakNodeNode *node, OakNodeNode *context);
|
||||
|
||||
/* ---- Lifetime --------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Create a standalone copy of the node (Node::copy()). The copy is
|
||||
* NOT added to any graph; the caller owns it and must release it with
|
||||
* oaknode_node_free() while it is still orphaned. Returns NULL for NULL.
|
||||
*/
|
||||
OakNodeNode *oaknode_node_create_copy(const OakNodeNode *node);
|
||||
|
||||
/**
|
||||
* @brief Destroy an OWNED node immediately (C++ delete). NULL is a no-op.
|
||||
*
|
||||
* ONLY valid for owned handles that were never added to a graph: the
|
||||
* products of oaknode_factory_create_from_id(),
|
||||
* oaknode_node_create_copy() and oaknode_group_create() while still
|
||||
* orphaned. Freeing a graph-owned node, or freeing twice, is a
|
||||
* use-after-free.
|
||||
*/
|
||||
void oaknode_node_free(OakNodeNode *node);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_NODE_H
|
||||
@@ -0,0 +1,237 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_NODE_PROJECT_H
|
||||
#define OAK_EDITOR_NODE_PROJECT_H
|
||||
|
||||
#include "node/error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file project.h
|
||||
* @brief C ABI for olive::Project (oaknode)
|
||||
*
|
||||
* An OakNodeProject owns its whole node graph: nodes added with
|
||||
* oaknode_project_add_node() (directly, or indirectly through the folder and
|
||||
* footage families) are deleted by oaknode_project_free(). Handles to nodes,
|
||||
* folders and footage obtained from a project are borrowed views and must not
|
||||
* be freed.
|
||||
*
|
||||
* Conventions (shared by all oaknode C API families):
|
||||
* - Return codes: 0 (OAKNODE_OK) on success, a negative OAKNODE_E_* code on
|
||||
* failure.
|
||||
* - String getters are two-stage: pass buf == NULL (or a short buffer) to
|
||||
* query the required size; the return value is the required buffer size in
|
||||
* bytes INCLUDING the terminating NUL. The output is NUL-terminated
|
||||
* whenever buf_size > 0.
|
||||
* - NULL handles yield OAKNODE_E_INVALID (or a no-op for free()).
|
||||
* - Disk save/load of project files is NOT part of this layer; it belongs to
|
||||
* oakstorage (milestone M10).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Opaque project handle. Owned by the caller; release with
|
||||
* oaknode_project_free().
|
||||
*/
|
||||
typedef struct OakNodeProject OakNodeProject;
|
||||
|
||||
/**
|
||||
* @brief Opaque node handle (defined by the node family; forward-declared
|
||||
* here so the headers can be included in any order).
|
||||
*/
|
||||
typedef struct OakNodeNode OakNodeNode;
|
||||
|
||||
/**
|
||||
* @brief Opaque folder handle (defined in node/folder.h; forward-declared
|
||||
* here so the headers can be included in any order). Borrowed from the
|
||||
* owning project.
|
||||
*/
|
||||
typedef struct OakNodeFolder OakNodeFolder;
|
||||
|
||||
/**
|
||||
* @brief Create an empty project shell.
|
||||
*
|
||||
* The project has no root folder until oaknode_project_initialize() is
|
||||
* called (mirrors Project::initialize()).
|
||||
*
|
||||
* @return Project handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakNodeProject *oaknode_project_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a project and every node it owns. NULL is a no-op.
|
||||
*/
|
||||
void oaknode_project_free(OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Initialize the project: create the root folder (Project::initialize()).
|
||||
*
|
||||
* @return OAKNODE_OK, or OAKNODE_E_STATE if already initialized.
|
||||
*/
|
||||
int oaknode_project_initialize(OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Destructively destroy all nodes in the graph (Project::clear()).
|
||||
*
|
||||
* The project shell stays usable; oaknode_project_initialize() may be called
|
||||
* again afterwards.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_clear(OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle of the project's root folder (Project::root()).
|
||||
*
|
||||
* NULL if the project has not been initialized.
|
||||
*/
|
||||
OakNodeFolder *oaknode_project_root(OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Project display name (Project::name(): the filename's base name, or
|
||||
* "(untitled)"). Two-stage string getter.
|
||||
*
|
||||
* @return Required buffer size in bytes including the NUL, or a negative
|
||||
* OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_name(const OakNodeProject *project, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Full path the project was saved as, or "" if untitled
|
||||
* (Project::filename()). Two-stage string getter.
|
||||
*/
|
||||
int oaknode_project_filename(const OakNodeProject *project, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Display name safe for window titles (Project::pretty_filename()).
|
||||
* Two-stage string getter.
|
||||
*/
|
||||
int oaknode_project_pretty_filename(const OakNodeProject *project, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Set the project's filename (Project::set_filename()).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_set_filename(OakNodeProject *project, const char *filename);
|
||||
|
||||
/**
|
||||
* @brief 1 if the project has unsaved changes, 0 otherwise
|
||||
* (Project::is_modified()). Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_project_is_modified(const OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Set the modified flag (Project::set_modified()).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_set_modified(OakNodeProject *project, int modified);
|
||||
|
||||
/**
|
||||
* @brief 1 if the project is new (untitled and unmodified, Project::is_new()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_project_is_new(const OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Effective cache directory (Project::cache_path(), honoring the cache
|
||||
* location setting). Two-stage string getter.
|
||||
*/
|
||||
int oaknode_project_cache_path(const OakNodeProject *project, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Cache location setting enum value
|
||||
* (Project::get_cache_location_setting(): 0 = default location,
|
||||
* 1 = alongside project, 2 = custom path). Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_project_get_cache_location_setting(const OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Set the cache location setting (0/1/2, see
|
||||
* oaknode_project_get_cache_location_setting()).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_set_cache_location_setting(OakNodeProject *project,
|
||||
int setting);
|
||||
|
||||
/**
|
||||
* @brief Custom cache directory, or "" when none is set
|
||||
* (Project::get_custom_cache_path()). Two-stage string getter.
|
||||
*/
|
||||
int oaknode_project_get_custom_cache_path(const OakNodeProject *project,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Set a custom cache directory (Project::set_custom_cache_path()).
|
||||
* NULL clears it.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_set_custom_cache_path(OakNodeProject *project,
|
||||
const char *path);
|
||||
|
||||
/**
|
||||
* @brief Project UUID string (Project::get_uuid()). Two-stage string getter.
|
||||
*/
|
||||
int oaknode_project_get_uuid(const OakNodeProject *project, char *buf,
|
||||
int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Add a node to the graph; the project takes ownership
|
||||
* (Project::add_node()).
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_add_node(OakNodeProject *project, OakNodeNode *node);
|
||||
|
||||
/**
|
||||
* @brief Detach a node from the graph without deleting it
|
||||
* (Project::remove_node()).
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_NOT_FOUND if the node is not in the graph, or
|
||||
* another negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_project_remove_node(OakNodeProject *project, OakNodeNode *node);
|
||||
|
||||
/**
|
||||
* @brief Number of nodes belonging to the graph (Project::nodes().size()).
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_project_node_count(const OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle of the graph node at `index`, or NULL when out of
|
||||
* range.
|
||||
*/
|
||||
OakNodeNode *oaknode_project_node_at(const OakNodeProject *project, int index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_PROJECT_H
|
||||
@@ -0,0 +1,163 @@
|
||||
/***
|
||||
|
||||
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_NODE_SEQUENCE_H
|
||||
#define OAK_EDITOR_NODE_SEQUENCE_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include "common/videoparams.h"
|
||||
#include "node/error.h"
|
||||
#include "olive/core/oakcore/audioparams.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a sequence (olive::Sequence).
|
||||
*
|
||||
* The handle IS the C++ object pointer; no wrapper is allocated.
|
||||
*/
|
||||
typedef struct OakNodeSequence OakNodeSequence;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a track list (olive::TrackList), see node/track.h.
|
||||
*/
|
||||
typedef struct OakNodeTrackList OakNodeTrackList;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a track (olive::Track), see node/track.h.
|
||||
*/
|
||||
typedef struct OakNodeTrack OakNodeTrack;
|
||||
|
||||
/**
|
||||
* @brief Create an empty sequence with zero tracks.
|
||||
*
|
||||
* @return Sequence handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakNodeSequence *oaknode_sequence_create(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a sequence and its track lists. No-op on NULL.
|
||||
*
|
||||
* Tracks and blocks connected to the sequence are owned by the graph and
|
||||
* are not deleted here; the caller must have torn them down first.
|
||||
*/
|
||||
void oaknode_sequence_free(OakNodeSequence *sequence);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle to the per-type track list.
|
||||
*
|
||||
* @param type One of OAKNODE_TRACK_TYPE_VIDEO / _AUDIO / _SUBTITLE.
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID or OAKNODE_E_NOT_FOUND (bad type).
|
||||
*/
|
||||
int oaknode_sequence_get_track_list(OakNodeSequence *sequence, int type,
|
||||
OakNodeTrackList **out);
|
||||
|
||||
/**
|
||||
* @brief Number of connected tracks of the given type.
|
||||
*/
|
||||
int oaknode_sequence_get_track_count(OakNodeSequence *sequence, int type,
|
||||
int *count);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle to the track of `type` at `index`.
|
||||
*/
|
||||
int oaknode_sequence_get_track_at(OakNodeSequence *sequence, int type,
|
||||
int index, OakNodeTrack **out);
|
||||
|
||||
/**
|
||||
* @brief Flat track cache across all types (olive::Sequence::get_tracks()).
|
||||
*/
|
||||
int oaknode_sequence_get_all_track_count(OakNodeSequence *sequence, int *count);
|
||||
int oaknode_sequence_get_all_track_at(OakNodeSequence *sequence, int index,
|
||||
OakNodeTrack **out);
|
||||
|
||||
/**
|
||||
* @brief Playhead position in sequence time.
|
||||
*/
|
||||
int oaknode_sequence_get_playhead(OakNodeSequence *sequence, int *numerator,
|
||||
int *denominator);
|
||||
int oaknode_sequence_set_playhead(OakNodeSequence *sequence, int numerator,
|
||||
int denominator);
|
||||
|
||||
/**
|
||||
* @brief Cached overall/video/audio lengths (olive::ViewerOutput).
|
||||
*/
|
||||
int oaknode_sequence_get_length(OakNodeSequence *sequence, int *numerator,
|
||||
int *denominator);
|
||||
int oaknode_sequence_get_video_length(OakNodeSequence *sequence,
|
||||
int *numerator, int *denominator);
|
||||
int oaknode_sequence_get_audio_length(OakNodeSequence *sequence,
|
||||
int *numerator, int *denominator);
|
||||
|
||||
/**
|
||||
* @brief Recompute the cached lengths from the track lists
|
||||
* (olive::ViewerOutput::verify_length()).
|
||||
*/
|
||||
int oaknode_sequence_verify_length(OakNodeSequence *sequence);
|
||||
|
||||
/* --------------------------------------------------- Video/audio params */
|
||||
|
||||
/**
|
||||
* @brief Number of video/audio parameter slots.
|
||||
*/
|
||||
int oaknode_sequence_get_video_stream_count(OakNodeSequence *sequence,
|
||||
int *count);
|
||||
int oaknode_sequence_get_audio_stream_count(OakNodeSequence *sequence,
|
||||
int *count);
|
||||
|
||||
/**
|
||||
* @brief Video parameters at `index` as a NEW handle owned by the caller
|
||||
* (release with oakcommon_videoparams_free()).
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID, OAKNODE_E_NOT_FOUND or
|
||||
* OAKNODE_E_NOMEM.
|
||||
*/
|
||||
int oaknode_sequence_get_video_params(OakNodeSequence *sequence, int index,
|
||||
OakCommonVideoParams **out);
|
||||
|
||||
/**
|
||||
* @brief Replace the video parameters at `index` with a copy of `params`.
|
||||
*/
|
||||
int oaknode_sequence_set_video_params(OakNodeSequence *sequence, int index,
|
||||
const OakCommonVideoParams *params);
|
||||
|
||||
/**
|
||||
* @brief Audio parameters at `index` as a NEW handle owned by the caller
|
||||
* (release with oakcore_audioparams_free()).
|
||||
*/
|
||||
int oaknode_sequence_get_audio_params(OakNodeSequence *sequence, int index,
|
||||
OakAudioParams **out);
|
||||
|
||||
/**
|
||||
* @brief Replace the audio parameters at `index` with a copy of `params`.
|
||||
*/
|
||||
int oaknode_sequence_set_audio_params(OakNodeSequence *sequence, int index,
|
||||
const OakAudioParams *params);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_SEQUENCE_H
|
||||
@@ -0,0 +1,244 @@
|
||||
/***
|
||||
|
||||
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_NODE_SERIALIZER_H
|
||||
#define OAK_EDITOR_NODE_SERIALIZER_H
|
||||
|
||||
#include "node/error.h"
|
||||
#include "node/project.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file serializer.h
|
||||
* @brief C ABI for olive::ProjectSerializer (oaknode), in-memory form
|
||||
*
|
||||
* Clipboard copy/paste and node-graph XML round trips without touching the
|
||||
* filesystem: "copy" is oaknode_serializer_save_to_xml() (serialize a
|
||||
* SaveData to an XML string), "paste" is oaknode_serializer_load_from_xml()
|
||||
* (parse an XML string into a project, exposing the resulting LoadData).
|
||||
* System-clipboard integration and on-disk .ove save/load live in the
|
||||
* facade / oakstorage layers (M9/M10), not here.
|
||||
*
|
||||
* oaknode_serializer_initialize() must be called before any save/load; it
|
||||
* registers the versioned serializers and the node factory the loaders use
|
||||
* to instantiate nodes by id.
|
||||
*/
|
||||
|
||||
/** @brief Load type: a whole project. */
|
||||
#define OAKNODE_SERIALIZER_LOAD_PROJECT 0
|
||||
/** @brief Load type: only nodes (clipboard node-graph paste). */
|
||||
#define OAKNODE_SERIALIZER_LOAD_ONLY_NODES 1
|
||||
/** @brief Load type: only clips (timeline family). */
|
||||
#define OAKNODE_SERIALIZER_LOAD_ONLY_CLIPS 2
|
||||
/** @brief Load type: only markers (timeline family). */
|
||||
#define OAKNODE_SERIALIZER_LOAD_ONLY_MARKERS 3
|
||||
/** @brief Load type: only keyframes (keyframe family). */
|
||||
#define OAKNODE_SERIALIZER_LOAD_ONLY_KEYFRAMES 4
|
||||
|
||||
/** @brief Serializer result code: success. */
|
||||
#define OAKNODE_SERIALIZER_OK 0
|
||||
/** @brief Serializer result code: data written by a too-old format. */
|
||||
#define OAKNODE_SERIALIZER_TOO_OLD 1
|
||||
/** @brief Serializer result code: data written by a too-new format. */
|
||||
#define OAKNODE_SERIALIZER_TOO_NEW 2
|
||||
/** @brief Serializer result code: unrecognizable format version. */
|
||||
#define OAKNODE_SERIALIZER_UNKNOWN_VERSION 3
|
||||
/** @brief Serializer result code: file I/O error (unused in-memory). */
|
||||
#define OAKNODE_SERIALIZER_FILE_ERROR 4
|
||||
/** @brief Serializer result code: XML parse error. */
|
||||
#define OAKNODE_SERIALIZER_XML_ERROR 5
|
||||
/** @brief Serializer result code: overwrite error (unused in-memory). */
|
||||
#define OAKNODE_SERIALIZER_OVERWRITE_ERROR 6
|
||||
/** @brief Serializer result code: no data to load. */
|
||||
#define OAKNODE_SERIALIZER_NO_DATA 7
|
||||
|
||||
/**
|
||||
* @brief Opaque save descriptor (wraps ProjectSerializer::SaveData).
|
||||
* Owned by the caller; release with oaknode_serializer_savedata_free().
|
||||
*/
|
||||
typedef struct OakNodeSerializerSaveData OakNodeSerializerSaveData;
|
||||
|
||||
/**
|
||||
* @brief Opaque load result (wraps ProjectSerializer::LoadData).
|
||||
* Owned by the caller; release with oaknode_serializer_loaddata_free().
|
||||
* Node handles obtained from it are borrowed from the target project.
|
||||
*/
|
||||
typedef struct OakNodeSerializerLoadData OakNodeSerializerLoadData;
|
||||
|
||||
/**
|
||||
* @brief Register the versioned serializers and initialize the node factory.
|
||||
* Idempotent. Must be called before any save/load.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_serializer_initialize(void);
|
||||
|
||||
/**
|
||||
* @brief Tear down the serializers and the node factory registered by
|
||||
* oaknode_serializer_initialize(). Safe to call when not initialized.
|
||||
*/
|
||||
void oaknode_serializer_shutdown(void);
|
||||
|
||||
/**
|
||||
* @brief Create a save descriptor.
|
||||
*
|
||||
* @param load_type One of OAKNODE_SERIALIZER_LOAD_*; use
|
||||
* OAKNODE_SERIALIZER_LOAD_ONLY_NODES for clipboard-style node copies.
|
||||
* @param project Context project (borrowed), may be NULL for load types
|
||||
* that do not require it.
|
||||
*
|
||||
* @return Save-data handle, or NULL on failure.
|
||||
*/
|
||||
OakNodeSerializerSaveData *oaknode_serializer_savedata_create(
|
||||
int load_type, OakNodeProject *project);
|
||||
|
||||
/**
|
||||
* @brief Destroy a save descriptor. NULL is a no-op.
|
||||
*/
|
||||
void oaknode_serializer_savedata_free(OakNodeSerializerSaveData *save_data);
|
||||
|
||||
/**
|
||||
* @brief Restrict serialization to the given nodes
|
||||
* (SaveData::set_only_serialize_nodes()). `nodes` is an array of `count`
|
||||
* borrowed node handles.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_serializer_savedata_set_nodes(
|
||||
OakNodeSerializerSaveData *save_data, OakNodeNode *const *nodes, int count);
|
||||
|
||||
/**
|
||||
* @brief Attach a free-form (key, value) property to a node in the
|
||||
* serialized output (SaveData::set_properties()); used for graph positions
|
||||
* and clip metadata. Replaces the value if the (node, key) pair exists.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_serializer_savedata_set_property(
|
||||
OakNodeSerializerSaveData *save_data, OakNodeNode *node, const char *key,
|
||||
const char *value);
|
||||
|
||||
/**
|
||||
* @brief Serialize to an in-memory XML document ("copy"). Two-stage string
|
||||
* getter: pass buf == NULL to query the size.
|
||||
*
|
||||
* @return Required buffer size in bytes including the NUL, or a negative
|
||||
* OAKNODE_E_* error code (OAKNODE_E_STATE if the serializers have
|
||||
* not been initialized).
|
||||
*/
|
||||
int oaknode_serializer_save_to_xml(OakNodeSerializerSaveData *save_data,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Parse an in-memory XML document into `project` ("paste").
|
||||
*
|
||||
* @param project Target project (borrowed), may be NULL for load types that
|
||||
* do not attach nodes to a project.
|
||||
* @param xml Complete XML document text. Must not be NULL.
|
||||
* @param load_type One of OAKNODE_SERIALIZER_LOAD_*.
|
||||
* @param out_result Receives one of the OAKNODE_SERIALIZER_* result codes.
|
||||
* Must not be NULL.
|
||||
* @param out_load_data Receives the load result on OAKNODE_SERIALIZER_OK
|
||||
* (caller-owned, may be NULL if the caller does not need it;
|
||||
* receives NULL on failure).
|
||||
* @param details_buf Optional human-readable error detail buffer
|
||||
* (two-stage convention is NOT used; truncation is silent). May be
|
||||
* NULL.
|
||||
* @param details_buf_size Size of details_buf.
|
||||
*
|
||||
* @return OAKNODE_OK if the call itself succeeded (inspect *out_result for
|
||||
* the serializer outcome), or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_serializer_load_from_xml(OakNodeProject *project, const char *xml,
|
||||
int load_type, int *out_result,
|
||||
OakNodeSerializerLoadData **out_load_data,
|
||||
char *details_buf, int details_buf_size);
|
||||
|
||||
/**
|
||||
* @brief Destroy a load result. NULL is a no-op.
|
||||
*
|
||||
* Does not delete the loaded nodes: they are newly created objects owned by
|
||||
* the CALLER until adopted into a project with oaknode_project_add_node()
|
||||
* (or attached under a folder); otherwise they leak.
|
||||
*/
|
||||
void oaknode_serializer_loaddata_free(OakNodeSerializerLoadData *load_data);
|
||||
|
||||
/**
|
||||
* @brief Number of nodes created by the load. Negative OAKNODE_E_* code on
|
||||
* NULL.
|
||||
*/
|
||||
int oaknode_serializer_loaddata_node_count(
|
||||
const OakNodeSerializerLoadData *load_data);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle of the loaded node at `index`, or NULL when out of
|
||||
* range.
|
||||
*/
|
||||
OakNodeNode *oaknode_serializer_loaddata_node_at(
|
||||
const OakNodeSerializerLoadData *load_data, int index);
|
||||
|
||||
/**
|
||||
* @brief Look up a serialized property attached to a loaded node.
|
||||
* Two-stage string getter.
|
||||
*
|
||||
* @return Required buffer size in bytes including the NUL,
|
||||
* OAKNODE_E_NOT_FOUND if the (node, key) pair is absent, or another
|
||||
* negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_serializer_loaddata_get_property(
|
||||
const OakNodeSerializerLoadData *load_data, OakNodeNode *node,
|
||||
const char *key, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Number of promised (deferred) connections in the load result.
|
||||
* Negative OAKNODE_E_* code on NULL.
|
||||
*/
|
||||
int oaknode_serializer_loaddata_connection_count(
|
||||
const OakNodeSerializerLoadData *load_data);
|
||||
|
||||
/**
|
||||
* @brief Read the promised connection at `index`.
|
||||
*
|
||||
* All output parameters except the input-id buffer are required;
|
||||
* `input_id_buf` follows the two-stage string convention inside a
|
||||
* fixed call: pass NULL/0 to skip copying the id.
|
||||
*
|
||||
* @param out_output_node Receives the output (source) node.
|
||||
* @param out_input_node Receives the input (destination) node.
|
||||
* @param input_id_buf Receives the input id string, may be NULL.
|
||||
* @param input_id_buf_size Size of input_id_buf.
|
||||
* @param out_element Receives the input element index.
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_NOT_FOUND when out of range, or another
|
||||
* negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_serializer_loaddata_connection_at(
|
||||
const OakNodeSerializerLoadData *load_data, int index,
|
||||
OakNodeNode **out_output_node, OakNodeNode **out_input_node,
|
||||
char *input_id_buf, int input_id_buf_size, int *out_element);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_SERIALIZER_H
|
||||
@@ -0,0 +1,279 @@
|
||||
/***
|
||||
|
||||
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_NODE_TRACK_H
|
||||
#define OAK_EDITOR_NODE_TRACK_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include "node/error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a track (olive::Track).
|
||||
*
|
||||
* The handle IS the C++ object pointer; no wrapper is allocated.
|
||||
*/
|
||||
typedef struct OakNodeTrack OakNodeTrack;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a per-type track container (olive::TrackList).
|
||||
*
|
||||
* Borrowed from oaknode_sequence_get_track_list(); invalidated when the
|
||||
* owning sequence is destroyed.
|
||||
*/
|
||||
typedef struct OakNodeTrackList OakNodeTrackList;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a block (olive::Block), see node/block.h.
|
||||
*/
|
||||
typedef struct OakNodeBlock OakNodeBlock;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a sequence (olive::Sequence), see node/sequence.h.
|
||||
*/
|
||||
typedef struct OakNodeSequence OakNodeSequence;
|
||||
|
||||
/**
|
||||
* @brief Track types, matching olive::Track::Type.
|
||||
*/
|
||||
enum OakNodeTrackType {
|
||||
OAKNODE_TRACK_TYPE_NONE = -1,
|
||||
OAKNODE_TRACK_TYPE_VIDEO = 0,
|
||||
OAKNODE_TRACK_TYPE_AUDIO = 1,
|
||||
OAKNODE_TRACK_TYPE_SUBTITLE = 2,
|
||||
OAKNODE_TRACK_TYPE_COUNT = 3
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------- Track */
|
||||
|
||||
/**
|
||||
* @brief Create a track of the given type (OakNodeTrackType value).
|
||||
*
|
||||
* The caller owns the track until it is added to a track list; a track
|
||||
* that was never added must be released with oaknode_track_free().
|
||||
*
|
||||
* @return Track handle, or NULL on invalid type / allocation failure.
|
||||
*/
|
||||
OakNodeTrack *oaknode_track_create(int type);
|
||||
|
||||
/**
|
||||
* @brief Destroy a track. No-op on NULL.
|
||||
*
|
||||
* The track must have been removed from its track list first.
|
||||
*/
|
||||
void oaknode_track_free(OakNodeTrack *track);
|
||||
|
||||
/**
|
||||
* @brief Track type (OakNodeTrackType values).
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_track_get_type(OakNodeTrack *track, int *type);
|
||||
int oaknode_track_set_type(OakNodeTrack *track, int type);
|
||||
|
||||
/**
|
||||
* @brief Track height in internal units (olive::Track::get/set_track_height).
|
||||
*/
|
||||
int oaknode_track_get_height(OakNodeTrack *track, double *height);
|
||||
int oaknode_track_set_height(OakNodeTrack *track, double height);
|
||||
|
||||
/**
|
||||
* @brief Track height in pixels (converted through the default font height).
|
||||
*/
|
||||
int oaknode_track_get_height_in_pixels(OakNodeTrack *track, int *height);
|
||||
int oaknode_track_set_height_in_pixels(OakNodeTrack *track, int height);
|
||||
|
||||
/**
|
||||
* @brief Default / minimum track heights in pixels (static).
|
||||
*/
|
||||
int oaknode_track_get_default_height_in_pixels(void);
|
||||
int oaknode_track_get_minimum_height_in_pixels(void);
|
||||
|
||||
/**
|
||||
* @brief Index of the track inside its track list.
|
||||
*/
|
||||
int oaknode_track_get_index(OakNodeTrack *track, int *index);
|
||||
int oaknode_track_set_index(OakNodeTrack *track, int index);
|
||||
|
||||
/**
|
||||
* @brief Mute / lock flags.
|
||||
*/
|
||||
int oaknode_track_get_muted(OakNodeTrack *track, int *muted);
|
||||
int oaknode_track_set_muted(OakNodeTrack *track, int muted);
|
||||
int oaknode_track_get_locked(OakNodeTrack *track, int *locked);
|
||||
int oaknode_track_set_locked(OakNodeTrack *track, int locked);
|
||||
|
||||
/**
|
||||
* @brief Track reference as a (type, index) pair (olive::Track::Reference).
|
||||
*/
|
||||
int oaknode_track_get_reference(OakNodeTrack *track, int *type, int *index);
|
||||
|
||||
/**
|
||||
* @brief Total length of the track (end of the last block).
|
||||
*/
|
||||
int oaknode_track_get_length(OakNodeTrack *track, int *numerator,
|
||||
int *denominator);
|
||||
|
||||
/**
|
||||
* @brief Owning sequence as a borrowed handle (NULL when trackless).
|
||||
*/
|
||||
int oaknode_track_get_sequence(OakNodeTrack *track, OakNodeSequence **out);
|
||||
|
||||
/* ------------------------------------------------------- Track blocks */
|
||||
|
||||
/**
|
||||
* @brief Number of blocks on the track.
|
||||
*/
|
||||
int oaknode_track_get_block_count(OakNodeTrack *track, int *count);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle to the block at `index`.
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID or OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_track_get_block_at(OakNodeTrack *track, int index,
|
||||
OakNodeBlock **out);
|
||||
|
||||
/**
|
||||
* @brief Append/prepend/insert primitives (olive::Track::*_block).
|
||||
*
|
||||
* The track takes over graph membership of the block; the block must have
|
||||
* a valid length before insertion.
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_track_append_block(OakNodeTrack *track, OakNodeBlock *block);
|
||||
int oaknode_track_prepend_block(OakNodeTrack *track, OakNodeBlock *block);
|
||||
int oaknode_track_insert_block_at_index(OakNodeTrack *track,
|
||||
OakNodeBlock *block, int index);
|
||||
int oaknode_track_insert_block_after(OakNodeTrack *track, OakNodeBlock *block,
|
||||
OakNodeBlock *before);
|
||||
int oaknode_track_insert_block_before(OakNodeTrack *track, OakNodeBlock *block,
|
||||
OakNodeBlock *after);
|
||||
|
||||
/**
|
||||
* @brief Remove `block` and shift all subsequent blocks earlier
|
||||
* (olive::Track::ripple_remove_block). The block is NOT deleted; ownership
|
||||
* returns to the caller.
|
||||
*/
|
||||
int oaknode_track_ripple_remove_block(OakNodeTrack *track, OakNodeBlock *block);
|
||||
|
||||
/**
|
||||
* @brief Replace `old_block` with `new_block`; both must have equal lengths.
|
||||
*/
|
||||
int oaknode_track_replace_block(OakNodeTrack *track, OakNodeBlock *old_block,
|
||||
OakNodeBlock *new_block);
|
||||
|
||||
/**
|
||||
* @brief Index of `block` in the track's block array, or OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_track_get_block_index(OakNodeTrack *track, OakNodeBlock *block,
|
||||
int *index);
|
||||
|
||||
/**
|
||||
* @brief Block strictly containing `time` (in < time < out), or
|
||||
* OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_track_get_block_containing_time(OakNodeTrack *track, int numerator,
|
||||
int denominator,
|
||||
OakNodeBlock **out);
|
||||
|
||||
/**
|
||||
* @brief Block visible at `time` (in <= time < out), or OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_track_get_visible_block_at_time(OakNodeTrack *track, int numerator,
|
||||
int denominator,
|
||||
OakNodeBlock **out);
|
||||
|
||||
/**
|
||||
* @brief Whether the [in, out) range holds no block or only a gap
|
||||
* (olive::Track::is_range_free). `is_free` receives 1/0.
|
||||
*/
|
||||
int oaknode_track_is_range_free(OakNodeTrack *track, int in_num, int in_den,
|
||||
int out_num, int out_den, int *is_free);
|
||||
|
||||
/* ------------------------------------------------------------ TrackList */
|
||||
|
||||
/**
|
||||
* @brief Track list type (OakNodeTrackType values).
|
||||
*/
|
||||
int oaknode_tracklist_get_type(OakNodeTrackList *list, int *type);
|
||||
|
||||
/**
|
||||
* @brief Number of connected tracks.
|
||||
*/
|
||||
int oaknode_tracklist_get_track_count(OakNodeTrackList *list, int *count);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle to the track at `index`.
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID or OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_tracklist_get_track_at(OakNodeTrackList *list, int index,
|
||||
OakNodeTrack **out);
|
||||
|
||||
/**
|
||||
* @brief Combined length of the longest track in the list.
|
||||
*/
|
||||
int oaknode_tracklist_get_total_length(OakNodeTrackList *list, int *numerator,
|
||||
int *denominator);
|
||||
|
||||
/**
|
||||
* @brief Size of the underlying input array (>= track count; may contain
|
||||
* disconnected slots).
|
||||
*/
|
||||
int oaknode_tracklist_get_array_size(OakNodeTrackList *list, int *size);
|
||||
|
||||
/**
|
||||
* @brief Add `track` to the list (non-undoable primitive).
|
||||
*
|
||||
* Mirrors the graph steps of TimelineAddTrackCommand::redo() minus the
|
||||
* auto-merge: the track is parented to the list's graph (when any),
|
||||
* inherits the previous track's height, a new array slot is appended and
|
||||
* the track is connected to it. The sequence's flat track cache and
|
||||
* lengths are refreshed before returning.
|
||||
*
|
||||
* @return OAKNODE_OK or OAKNODE_E_INVALID.
|
||||
*/
|
||||
int oaknode_tracklist_add_track(OakNodeTrackList *list, OakNodeTrack *track);
|
||||
|
||||
/**
|
||||
* @brief Remove `track` from the list (non-undoable primitive).
|
||||
*
|
||||
* Disconnects the track from its array slot and removes the slot
|
||||
* (Node::input_array_remove). The track is NOT deleted; ownership returns
|
||||
* to the caller.
|
||||
*
|
||||
* @return OAKNODE_OK, OAKNODE_E_INVALID or OAKNODE_E_NOT_FOUND.
|
||||
*/
|
||||
int oaknode_tracklist_remove_track(OakNodeTrackList *list,
|
||||
OakNodeTrack *track);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_TRACK_H
|
||||
@@ -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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_NODE_TRAVERSER_H
|
||||
#define OAK_EDITOR_NODE_TRAVERSER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "node/error.h"
|
||||
#include "node/node.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file traverser.h
|
||||
* @brief C ABI for olive::NodeTraverser (src/node/src/traverser.h),
|
||||
* limited to database generation: generating the value database of a node
|
||||
* over a time range and enumerating its rows.
|
||||
*
|
||||
* The base NodeTraverser resolves no render jobs (textures/samples stay
|
||||
* dummy); only value-producing nodes are meaningful here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Opaque traverser handle (olive::NodeTraverser).
|
||||
*/
|
||||
typedef struct OakNodeTraverser OakNodeTraverser;
|
||||
|
||||
/**
|
||||
* @brief Opaque value-database handle (an owned copy of an
|
||||
* olive::NodeValueDatabase). Release with
|
||||
* oaknode_traverser_database_free().
|
||||
*/
|
||||
typedef struct OakNodeValueDatabase OakNodeValueDatabase;
|
||||
|
||||
/**
|
||||
* @brief Create a traverser.
|
||||
*
|
||||
* @return Traverser handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakNodeTraverser *oaknode_traverser_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a traverser. NULL is a no-op.
|
||||
*/
|
||||
void oaknode_traverser_free(OakNodeTraverser *traverser);
|
||||
|
||||
/**
|
||||
* @brief Generate the value database of `node` over the time range
|
||||
* [`in_num`/`in_den`, `out_num`/`out_den`) seconds
|
||||
* (NodeTraverser::generate_database()).
|
||||
*
|
||||
* `out_db` receives an owned database handle.
|
||||
*
|
||||
* @return OAKNODE_OK or a negative OAKNODE_E_* error code.
|
||||
*/
|
||||
int oaknode_traverser_generate_database(OakNodeTraverser *traverser,
|
||||
OakNodeNode *node, int64_t in_num,
|
||||
int64_t in_den, int64_t out_num,
|
||||
int64_t out_den,
|
||||
OakNodeValueDatabase **out_db);
|
||||
|
||||
/**
|
||||
* @brief Destroy a database handle. NULL is a no-op.
|
||||
*/
|
||||
void oaknode_traverser_database_free(OakNodeValueDatabase *db);
|
||||
|
||||
/**
|
||||
* @brief Number of rows (input tables) in the database.
|
||||
*/
|
||||
int oaknode_traverser_database_row_count(const OakNodeValueDatabase *db,
|
||||
int *out_count);
|
||||
|
||||
/**
|
||||
* @brief The input id (key) of the row at `index`. Two-stage getter;
|
||||
* OAKNODE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
int oaknode_traverser_database_row_key_at(const OakNodeValueDatabase *db,
|
||||
int index, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Number of values in the row named `key`.
|
||||
* OAKNODE_E_NOT_FOUND for an unknown key.
|
||||
*/
|
||||
int oaknode_traverser_database_row_value_count(const OakNodeValueDatabase *db,
|
||||
const char *key,
|
||||
int *out_count);
|
||||
|
||||
/**
|
||||
* @brief Read the value at `index` of row `key` mapped into `out`.
|
||||
* Values without a POD representation fail with OAKNODE_E_FAILED;
|
||||
* OAKNODE_E_NOT_FOUND for an unknown key or out-of-range index.
|
||||
*/
|
||||
int oaknode_traverser_database_value_at(const OakNodeValueDatabase *db,
|
||||
const char *key, int index,
|
||||
oaknode_value *out);
|
||||
|
||||
/**
|
||||
* @brief Read the value at `index` of row `key` as a string
|
||||
* (NodeValue::value_to_string()). Two-stage getter;
|
||||
* OAKNODE_E_NOT_FOUND for an unknown key or out-of-range index.
|
||||
*/
|
||||
int oaknode_traverser_database_value_string_at(const OakNodeValueDatabase *db,
|
||||
const char *key, int index,
|
||||
char *buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_NODE_TRAVERSER_H
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(undo)
|
||||
add_subdirectory(undo)add_subdirectory(node)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
target_sources(oakcommon PRIVATE
|
||||
colortransform.cpp
|
||||
commandlineparser.cpp
|
||||
current.cpp
|
||||
subtitleparams.cpp
|
||||
videoparams.cpp
|
||||
debug.cpp
|
||||
dropworkflowbehavior.cpp
|
||||
ffmpegutils.cpp
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/***
|
||||
|
||||
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/colortransform.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/colortransform.h"
|
||||
|
||||
struct OakCommonColorTransform {
|
||||
olive::ColorTransform impl;
|
||||
};
|
||||
|
||||
static int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
{
|
||||
int needed = (int)value.size() + 1;
|
||||
if (buf && buf_size >= needed)
|
||||
memcpy(buf, value.c_str(), needed);
|
||||
return needed;
|
||||
}
|
||||
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_output(
|
||||
const char *output)
|
||||
{
|
||||
if (!output)
|
||||
return nullptr;
|
||||
try {
|
||||
return new OakCommonColorTransform{
|
||||
olive::ColorTransform(std::string(output))};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakCommonColorTransform *oakcommon_colortransform_init_display(
|
||||
const char *display, const char *view, const char *look)
|
||||
{
|
||||
if (!display || !view || !look)
|
||||
return nullptr;
|
||||
try {
|
||||
return new OakCommonColorTransform{olive::ColorTransform(
|
||||
std::string(display), std::string(view), std::string(look))};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_colortransform_free(OakCommonColorTransform *transform)
|
||||
{
|
||||
delete transform;
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
|
||||
int *is_display)
|
||||
{
|
||||
if (!transform || !is_display)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*is_display = transform->impl.is_display() ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.display(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.view(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_colortransform_get_look(OakCommonColorTransform *transform,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!transform)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(transform->impl.look(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/***
|
||||
|
||||
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/subtitleparams.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/subtitleparams.h"
|
||||
|
||||
struct OakCommonSubtitleParams {
|
||||
olive::SubtitleParams impl;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
{
|
||||
int needed = (int)value.size() + 1;
|
||||
if (buf && buf_size >= needed)
|
||||
memcpy(buf, value.c_str(), needed);
|
||||
return needed;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCommonSubtitleParams *oakcommon_subtitleparams_init(void)
|
||||
{
|
||||
try {
|
||||
return new OakCommonSubtitleParams{olive::SubtitleParams()};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_subtitleparams_free(OakCommonSubtitleParams *params)
|
||||
{
|
||||
delete params;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_stream_index(
|
||||
OakCommonSubtitleParams *params, int *index)
|
||||
{
|
||||
if (!params || !index)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*index = params->impl.stream_index();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_set_stream_index(
|
||||
OakCommonSubtitleParams *params, int index)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_stream_index(index);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_enabled(OakCommonSubtitleParams *params,
|
||||
int *enabled)
|
||||
{
|
||||
if (!params || !enabled)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*enabled = params->impl.enabled() ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_set_enabled(OakCommonSubtitleParams *params,
|
||||
int enabled)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_enabled(enabled != 0);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_is_valid(OakCommonSubtitleParams *params,
|
||||
int *is_valid)
|
||||
{
|
||||
if (!params || !is_valid)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*is_valid = params->impl.is_valid() ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_count(OakCommonSubtitleParams *params, int *count)
|
||||
{
|
||||
if (!params || !count)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*count = (int)params->impl.size();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
|
||||
int *numerator, int *denominator)
|
||||
{
|
||||
if (!params || !numerator || !denominator)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
olive::core::Rational d = params->impl.duration();
|
||||
*numerator = d.numerator();
|
||||
*denominator = d.denominator();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
|
||||
int in_num, int in_den, int out_num,
|
||||
int out_den, const char *text)
|
||||
{
|
||||
if (!params || !text)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
params->impl.push_back(olive::Subtitle(
|
||||
olive::core::TimeRange(olive::core::Rational(in_num, in_den),
|
||||
olive::core::Rational(out_num, out_den)),
|
||||
text));
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.clear();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
|
||||
int index, int *in_num, int *in_den,
|
||||
int *out_num, int *out_den)
|
||||
{
|
||||
if (!params || !in_num || !in_den || !out_num || !out_den)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
if (index < 0 || index >= (int)params->impl.size())
|
||||
return OAKCOMMON_E_NOT_FOUND;
|
||||
const olive::Subtitle &s = params->impl.at(index);
|
||||
olive::core::Rational in = s.time().in();
|
||||
olive::core::Rational out = s.time().out();
|
||||
*in_num = in.numerator();
|
||||
*in_den = in.denominator();
|
||||
*out_num = out.numerator();
|
||||
*out_den = out.denominator();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_get_subtitle_text(OakCommonSubtitleParams *params,
|
||||
int index, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
if (index < 0 || index >= (int)params->impl.size())
|
||||
return OAKCOMMON_E_NOT_FOUND;
|
||||
try {
|
||||
return copy_string(params->impl.at(index).text(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_generate_ass_header(char *buf, int buf_size)
|
||||
{
|
||||
try {
|
||||
return copy_string(olive::SubtitleParams::generate_ass_header(), buf,
|
||||
buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
|
||||
const char *xml)
|
||||
{
|
||||
if (!params || !xml)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamReader reader(xml);
|
||||
if (reader.has_error())
|
||||
return OAKCOMMON_E_FAILED;
|
||||
// Position on the root element; load() consumes its children.
|
||||
if (!olive::xml_read_next_start_element(&reader))
|
||||
return OAKCOMMON_E_FAILED;
|
||||
params->impl.load(&reader);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_subtitleparams_save_xml(OakCommonSubtitleParams *params,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamWriter writer;
|
||||
writer.write_start_element("subtitleparams");
|
||||
params->impl.save(&writer);
|
||||
writer.write_end_element();
|
||||
return copy_string(writer.output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
/***
|
||||
|
||||
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/videoparams.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/videoparams.h"
|
||||
|
||||
struct OakCommonVideoParams {
|
||||
olive::VideoParams impl;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
{
|
||||
int needed = (int)value.size() + 1;
|
||||
if (buf && buf_size >= needed)
|
||||
memcpy(buf, value.c_str(), needed);
|
||||
return needed;
|
||||
}
|
||||
|
||||
int get_rational(const olive::core::Rational &r, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!numerator || !denominator)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*numerator = r.numerator();
|
||||
*denominator = r.denominator();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCommonVideoParams *oakcommon_videoparams_init(void)
|
||||
{
|
||||
try {
|
||||
return new OakCommonVideoParams{olive::VideoParams()};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_basic(
|
||||
int width, int height, int pixel_format, int nb_channels,
|
||||
int pixel_aspect_num, int pixel_aspect_den, int interlacing, int divider)
|
||||
{
|
||||
try {
|
||||
return new OakCommonVideoParams{olive::VideoParams(
|
||||
width, height,
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format),
|
||||
nb_channels,
|
||||
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(interlacing),
|
||||
divider)};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakCommonVideoParams *oakcommon_videoparams_init_with_time_base(
|
||||
int width, int height, int time_base_num, int time_base_den,
|
||||
int pixel_format, int nb_channels, int pixel_aspect_num,
|
||||
int pixel_aspect_den, int interlacing, int divider)
|
||||
{
|
||||
try {
|
||||
return new OakCommonVideoParams{olive::VideoParams(
|
||||
width, height,
|
||||
olive::core::Rational(time_base_num, time_base_den),
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format),
|
||||
nb_channels,
|
||||
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(interlacing),
|
||||
divider)};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oakcommon_videoparams_free(OakCommonVideoParams *params)
|
||||
{
|
||||
delete params;
|
||||
}
|
||||
|
||||
#define OAKCOMMON_VIDEOPARAMS_INT_GETTER(name, expr) \
|
||||
int oakcommon_videoparams_get_##name(OakCommonVideoParams *params, \
|
||||
int *out) \
|
||||
{ \
|
||||
if (!params || !out) \
|
||||
return OAKCOMMON_E_INVALID; \
|
||||
*out = (expr); \
|
||||
return OAKCOMMON_OK; \
|
||||
}
|
||||
|
||||
#define OAKCOMMON_VIDEOPARAMS_INT_SETTER(name, stmt) \
|
||||
int oakcommon_videoparams_set_##name(OakCommonVideoParams *params, \
|
||||
int value) \
|
||||
{ \
|
||||
if (!params) \
|
||||
return OAKCOMMON_E_INVALID; \
|
||||
stmt; \
|
||||
return OAKCOMMON_OK; \
|
||||
}
|
||||
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(width, params->impl.width())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(width, params->impl.set_width(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(height, params->impl.height())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(height, params->impl.set_height(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(depth, params->impl.depth())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(depth, params->impl.set_depth(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_3d, params->impl.is_3d() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(format, static_cast<int>(params->impl.format()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
format, params->impl.set_format(
|
||||
static_cast<olive::core::PixelFormat::Format>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(channel_count, params->impl.channel_count())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(channel_count,
|
||||
params->impl.set_channel_count(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(interlacing,
|
||||
static_cast<int>(params->impl.interlacing()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
interlacing,
|
||||
params->impl.set_interlacing(
|
||||
static_cast<olive::VideoParams::Interlacing>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(divider, params->impl.divider())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(divider, params->impl.set_divider(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(enabled, params->impl.enabled() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(enabled, params->impl.set_enabled(value != 0))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(stream_index, params->impl.stream_index())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(stream_index,
|
||||
params->impl.set_stream_index(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(video_type,
|
||||
static_cast<int>(params->impl.video_type()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
video_type,
|
||||
params->impl.set_video_type(static_cast<olive::VideoParams::Type>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(premultiplied_alpha,
|
||||
params->impl.premultiplied_alpha() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(premultiplied_alpha,
|
||||
params->impl.set_premultiplied_alpha(
|
||||
value != 0))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_range,
|
||||
static_cast<int>(params->impl.color_range()))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
|
||||
color_range, params->impl.set_color_range(
|
||||
static_cast<olive::VideoParams::ColorRange>(value)))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_primaries,
|
||||
params->impl.color_primaries())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_primaries,
|
||||
params->impl.set_color_primaries(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_transfer, params->impl.color_transfer())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_transfer,
|
||||
params->impl.set_color_transfer(value))
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(square_pixel_width,
|
||||
params->impl.square_pixel_width())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_width,
|
||||
params->impl.effective_width())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_height,
|
||||
params->impl.effective_height())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_depth,
|
||||
params->impl.effective_depth())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_valid, params->impl.is_valid() ? 1 : 0)
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_channel,
|
||||
params->impl.get_bytes_per_channel())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_pixel,
|
||||
params->impl.get_bytes_per_pixel())
|
||||
OAKCOMMON_VIDEOPARAMS_INT_GETTER(buffer_size, params->impl.get_buffer_size())
|
||||
|
||||
int oakcommon_videoparams_get_x(OakCommonVideoParams *params, float *x)
|
||||
{
|
||||
if (!params || !x)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*x = params->impl.x();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_x(OakCommonVideoParams *params, float x)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_x(x);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_y(OakCommonVideoParams *params, float *y)
|
||||
{
|
||||
if (!params || !y)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*y = params->impl.y();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_y(OakCommonVideoParams *params, float y)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_y(y);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_start_time(OakCommonVideoParams *params,
|
||||
int64_t *start_time)
|
||||
{
|
||||
if (!params || !start_time)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*start_time = params->impl.start_time();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_start_time(OakCommonVideoParams *params,
|
||||
int64_t start_time)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_start_time(start_time);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_duration(OakCommonVideoParams *params,
|
||||
int64_t *duration)
|
||||
{
|
||||
if (!params || !duration)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*duration = params->impl.duration();
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_duration(OakCommonVideoParams *params,
|
||||
int64_t duration)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_duration(duration);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_time_base(OakCommonVideoParams *params,
|
||||
int *numerator, int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.time_base(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_time_base(OakCommonVideoParams *params,
|
||||
int numerator, int denominator)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_time_base(olive::core::Rational(numerator, denominator));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_frame_rate(OakCommonVideoParams *params,
|
||||
int *numerator, int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.frame_rate(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_frame_rate(OakCommonVideoParams *params,
|
||||
int numerator, int denominator)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_frame_rate(olive::core::Rational(numerator, denominator));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_frame_rate_as_time_base(OakCommonVideoParams *params,
|
||||
int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.frame_rate_as_time_base(), numerator,
|
||||
denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
return get_rational(params->impl.pixel_aspect_ratio(), numerator,
|
||||
denominator);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_pixel_aspect_ratio(OakCommonVideoParams *params,
|
||||
int numerator, int denominator)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_pixel_aspect_ratio(
|
||||
olive::core::Rational(numerator, denominator));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_colorspace(OakCommonVideoParams *params,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
return copy_string(params->impl.colorspace(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_set_colorspace(OakCommonVideoParams *params,
|
||||
const char *colorspace)
|
||||
{
|
||||
if (!params || !colorspace)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
params->impl.set_colorspace(colorspace);
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_time_in_timebase_units(
|
||||
OakCommonVideoParams *params, int time_num, int time_den,
|
||||
int64_t *timestamp)
|
||||
{
|
||||
if (!params || !timestamp)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*timestamp = params->impl.get_time_in_timebase_units(
|
||||
olive::core::Rational(time_num, time_den));
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_equals(OakCommonVideoParams *params,
|
||||
OakCommonVideoParams *other, int *equal)
|
||||
{
|
||||
if (!params || !other || !equal)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
*equal = (params->impl == other->impl) ? 1 : 0;
|
||||
return OAKCOMMON_OK;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
|
||||
const char *xml)
|
||||
{
|
||||
if (!params || !xml)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamReader reader(xml);
|
||||
if (reader.has_error())
|
||||
return OAKCOMMON_E_FAILED;
|
||||
// Position on the root element; load() consumes its children.
|
||||
if (!olive::xml_read_next_start_element(&reader))
|
||||
return OAKCOMMON_E_FAILED;
|
||||
params->impl.load(&reader);
|
||||
return OAKCOMMON_OK;
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_save_xml(OakCommonVideoParams *params, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!params)
|
||||
return OAKCOMMON_E_INVALID;
|
||||
try {
|
||||
olive::XmlStreamWriter writer;
|
||||
writer.write_start_element("videoparams");
|
||||
params->impl.save(&writer);
|
||||
writer.write_end_element();
|
||||
return copy_string(writer.output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_bytes_per_channel_for_format(int pixel_format)
|
||||
{
|
||||
return olive::VideoParams::get_bytes_per_channel(
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format));
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_bytes_per_pixel_for_format(int pixel_format,
|
||||
int channels)
|
||||
{
|
||||
return olive::VideoParams::get_bytes_per_pixel(
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format), channels);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_calculate_buffer_size(int width, int height,
|
||||
int pixel_format, int channels)
|
||||
{
|
||||
return olive::VideoParams::get_buffer_size(
|
||||
width, height,
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format), channels);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_format_is_float(int pixel_format)
|
||||
{
|
||||
return olive::VideoParams::format_is_float(
|
||||
static_cast<olive::core::PixelFormat::Format>(pixel_format)) ?
|
||||
1 :
|
||||
0;
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_generate_auto_divider(int64_t width, int64_t height)
|
||||
{
|
||||
return olive::VideoParams::generate_auto_divider(width, height);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_scaled_dimension(int dimension, int divider)
|
||||
{
|
||||
return olive::VideoParams::get_scaled_dimension(dimension, divider);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_divider_for_target_resolution(int src_width,
|
||||
int src_height,
|
||||
int dst_width,
|
||||
int dst_height)
|
||||
{
|
||||
return olive::VideoParams::get_divider_for_target_resolution(
|
||||
src_width, src_height, dst_width, dst_height);
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_name_for_divider(int divider, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
try {
|
||||
return copy_string(olive::VideoParams::get_name_for_divider(divider),
|
||||
buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_get_format_name(int pixel_format, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
try {
|
||||
return copy_string(olive::VideoParams::get_format_name(
|
||||
static_cast<olive::core::PixelFormat::Format>(
|
||||
pixel_format)),
|
||||
buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakcommon_videoparams_frame_rate_to_string(int numerator, int denominator,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
try {
|
||||
return copy_string(olive::VideoParams::frame_rate_to_string(
|
||||
olive::core::Rational(numerator, denominator)),
|
||||
buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKCOMMON_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
add_library(oakcommon SHARED
|
||||
commandlineparser.cpp
|
||||
commandlineparser.h
|
||||
colortransform.h
|
||||
current.cpp
|
||||
current.h
|
||||
debug.cpp
|
||||
@@ -25,6 +26,11 @@ add_library(oakcommon SHARED
|
||||
decibel.h
|
||||
define.h
|
||||
dropworkflowbehavior.h
|
||||
loopmode.h
|
||||
subtitleparams.cpp
|
||||
subtitleparams.h
|
||||
videoparams.cpp
|
||||
videoparams.h
|
||||
ffmpegutils.cpp
|
||||
ffmpegutils.h
|
||||
filefunctions.cpp
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/***
|
||||
|
||||
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_COLORTRANSFORM_H
|
||||
#define OAK_COLORTRANSFORM_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Describes a color transform: either a plain output colorspace or a
|
||||
* display/view/look triplet (sunk from engine/render/colortransform.h,
|
||||
* QString replaced with std::string).
|
||||
*/
|
||||
class ColorTransform {
|
||||
public:
|
||||
ColorTransform()
|
||||
{
|
||||
is_display_ = false;
|
||||
}
|
||||
|
||||
ColorTransform(const std::string &output)
|
||||
{
|
||||
is_display_ = false;
|
||||
output_ = output;
|
||||
}
|
||||
|
||||
ColorTransform(const std::string &display, const std::string &view,
|
||||
const std::string &look)
|
||||
{
|
||||
is_display_ = true;
|
||||
output_ = display;
|
||||
view_ = view;
|
||||
look_ = look;
|
||||
}
|
||||
|
||||
bool is_display() const
|
||||
{
|
||||
return is_display_;
|
||||
}
|
||||
|
||||
const std::string &display() const
|
||||
{
|
||||
return output_;
|
||||
}
|
||||
|
||||
const std::string &output() const
|
||||
{
|
||||
return output_;
|
||||
}
|
||||
|
||||
const std::string &view() const
|
||||
{
|
||||
return view_;
|
||||
}
|
||||
|
||||
const std::string &look() const
|
||||
{
|
||||
return look_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string output_;
|
||||
|
||||
bool is_display_;
|
||||
std::string view_;
|
||||
std::string look_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_COLORTRANSFORM_H
|
||||
@@ -0,0 +1,34 @@
|
||||
/***
|
||||
|
||||
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_LOOPMODE_H
|
||||
#define OAK_LOOPMODE_H
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Playback loop mode (sunk from engine/render/loopmode.h).
|
||||
*/
|
||||
enum class LoopMode { k_loop_mode_off, k_loop_mode_loop, k_loop_mode_clamp };
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_LOOPMODE_H
|
||||
@@ -0,0 +1,172 @@
|
||||
/***
|
||||
|
||||
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 "subtitleparams.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
std::string SubtitleParams::generate_ass_header()
|
||||
{
|
||||
// NOTE: We'll probably implement more customization as we support ASS better. Right now, we only
|
||||
// natively support SRT and only make this header because FFmpeg requires it.
|
||||
static const int k_ass_default_play_res_x = 384;
|
||||
static const int k_ass_default_play_res_y = 288;
|
||||
static const char *k_ass_default_font = "Arial";
|
||||
static const int k_ass_default_font_size = 16;
|
||||
static const int k_ass_default_primary_color = 0xFFFFFF; // White
|
||||
static const int k_ass_default_secondary_color = 0xFFFFFF; // White
|
||||
static const int k_ass_default_outline_color = 0x000000; // Black
|
||||
static const int k_ass_default_back_color = 0x000000; // Black
|
||||
static const int k_ass_bold = 0;
|
||||
static const int k_ass_italic = 0;
|
||||
static const int k_ass_underline = 0;
|
||||
static const int k_ass_strike = 0;
|
||||
static const int k_ass_border_style = 1;
|
||||
static const int k_ass_alignment = 2;
|
||||
|
||||
std::string ass_code;
|
||||
char buf[256];
|
||||
|
||||
// Header info
|
||||
// De-Qt note: the original used QCoreApplication::applicationName()/
|
||||
// applicationVersion(); oakcommon has no application object, so a fixed
|
||||
// generator name is emitted instead.
|
||||
ass_code.append("[Script Info]\r\n");
|
||||
ass_code.append("; Script generated by Oak\r\n");
|
||||
ass_code.append("ScriptType: v4.00+\r\n");
|
||||
std::snprintf(buf, sizeof(buf), "PlayResX: %d\r\n", k_ass_default_play_res_x);
|
||||
ass_code.append(buf);
|
||||
std::snprintf(buf, sizeof(buf), "PlayResY: %d\r\n", k_ass_default_play_res_y);
|
||||
ass_code.append(buf);
|
||||
ass_code.append("ScaledBorderAndShadow: yes\r\n");
|
||||
ass_code.append("\r\n");
|
||||
|
||||
// ASSv4 header
|
||||
ass_code.append("[V4+ Styles]\r\n");
|
||||
ass_code.append("Format: Name, ");
|
||||
ass_code.append("Fontname, Fontsize, ");
|
||||
ass_code.append("PrimaryColour, SecondaryColour, OutlineColour, BackColour, ");
|
||||
ass_code.append("Bold, Italic, Underline, StrikeOut, ");
|
||||
ass_code.append("ScaleX, ScaleY, ");
|
||||
ass_code.append("Spacing, Angle, ");
|
||||
ass_code.append("BorderStyle, Outline, Shadow, ");
|
||||
ass_code.append("Alignment, MarginL, MarginR, MarginV, ");
|
||||
ass_code.append("Encoding\r\n");
|
||||
ass_code.append("Style: ");
|
||||
|
||||
// Name
|
||||
ass_code.append("Default,");
|
||||
|
||||
// Font{name,size}
|
||||
std::snprintf(buf, sizeof(buf), "%s,%d,", k_ass_default_font,
|
||||
k_ass_default_font_size);
|
||||
ass_code.append(buf);
|
||||
|
||||
// {Primary,Secondary,Outline,Back}Colour
|
||||
std::snprintf(buf, sizeof(buf), "&H%X,&H%X,&H%X,&H%X,",
|
||||
k_ass_default_primary_color, k_ass_default_secondary_color,
|
||||
k_ass_default_outline_color, k_ass_default_back_color);
|
||||
ass_code.append(buf);
|
||||
|
||||
// Bold, Italic, Underline, StrikeOut
|
||||
std::snprintf(buf, sizeof(buf), "%d,%d,%d,%d,", k_ass_bold, k_ass_italic,
|
||||
k_ass_underline, k_ass_strike);
|
||||
ass_code.append(buf);
|
||||
|
||||
// Scale{X,Y}
|
||||
ass_code.append("100,100,");
|
||||
|
||||
// Spacing, Angle
|
||||
ass_code.append("0,0,");
|
||||
|
||||
// BorderStyle, Outline, Shadow
|
||||
std::snprintf(buf, sizeof(buf), "%d,1,0,", k_ass_border_style);
|
||||
ass_code.append(buf);
|
||||
|
||||
// Alignment, Margin[LRV]
|
||||
std::snprintf(buf, sizeof(buf), "%d,10,10,10,", k_ass_alignment);
|
||||
ass_code.append(buf);
|
||||
|
||||
// Encoding
|
||||
ass_code.append("0\r\n");
|
||||
ass_code.append("\r\n");
|
||||
ass_code.append("[Events]\r\n");
|
||||
ass_code.append(
|
||||
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n");
|
||||
|
||||
return ass_code;
|
||||
}
|
||||
|
||||
void SubtitleParams::load(XmlStreamReader *reader)
|
||||
{
|
||||
this->clear();
|
||||
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
const std::string &name = reader->name();
|
||||
if (name == "streamindex") {
|
||||
set_stream_index(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "enabled") {
|
||||
set_enabled(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "subtitles") {
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
if (reader->name() == "subtitle") {
|
||||
Rational in, out;
|
||||
|
||||
for (const XmlStreamAttribute &attr : reader->attributes()) {
|
||||
if (attr.name == "in") {
|
||||
in = Rational::from_string(attr.value);
|
||||
} else if (attr.name == "out") {
|
||||
out = Rational::from_string(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
std::string text = reader->read_element_text();
|
||||
|
||||
this->push_back(Subtitle(TimeRange(in, out), text));
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SubtitleParams::save(XmlStreamWriter *writer) const
|
||||
{
|
||||
writer->write_text_element("streamindex", std::to_string(stream_index_));
|
||||
writer->write_text_element("enabled", std::to_string(enabled_));
|
||||
|
||||
writer->write_start_element("subtitles");
|
||||
for (auto it = this->cbegin(); it != this->cend(); it++) {
|
||||
writer->write_start_element("subtitle");
|
||||
writer->write_attribute("in", it->time().in().to_string());
|
||||
writer->write_attribute("out", it->time().out().to_string());
|
||||
writer->write_characters(it->text());
|
||||
writer->write_end_element(); // subtitle
|
||||
}
|
||||
writer->write_end_element(); // subtitles
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SUBTITLEPARAMS_H
|
||||
#define OAK_SUBTITLEPARAMS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <olive/core/util/timerange.h>
|
||||
|
||||
#include "xmlutils.h"
|
||||
|
||||
using namespace olive::core;
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A single subtitle entry (sunk from engine/render/subtitleparams.h,
|
||||
* QString replaced with std::string).
|
||||
*/
|
||||
class Subtitle {
|
||||
public:
|
||||
Subtitle() = default;
|
||||
|
||||
Subtitle(const TimeRange &time, const std::string &text)
|
||||
: range_(time)
|
||||
, text_(text)
|
||||
{
|
||||
}
|
||||
|
||||
const TimeRange &time() const
|
||||
{
|
||||
return range_;
|
||||
}
|
||||
void set_time(const TimeRange &t)
|
||||
{
|
||||
range_ = t;
|
||||
}
|
||||
|
||||
const std::string &text() const
|
||||
{
|
||||
return text_;
|
||||
}
|
||||
void set_text(const std::string &t)
|
||||
{
|
||||
text_ = t;
|
||||
}
|
||||
|
||||
// Value equality (time range + text). Required by olive::Variant's
|
||||
// custom-type operator== (SubtitleParams is stored as a Variant value).
|
||||
bool operator==(const Subtitle &rhs) const
|
||||
{
|
||||
return range_ == rhs.range_ && text_ == rhs.text_;
|
||||
}
|
||||
bool operator!=(const Subtitle &rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
TimeRange range_;
|
||||
|
||||
std::string text_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Subtitle track parameter set (sunk from engine/render/subtitleparams.h).
|
||||
*/
|
||||
class SubtitleParams : public std::vector<Subtitle> {
|
||||
public:
|
||||
SubtitleParams()
|
||||
{
|
||||
stream_index_ = 0;
|
||||
enabled_ = true;
|
||||
}
|
||||
|
||||
static std::string generate_ass_header();
|
||||
|
||||
void load(XmlStreamReader *reader);
|
||||
|
||||
void save(XmlStreamWriter *writer) const;
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return !this->empty();
|
||||
}
|
||||
|
||||
Rational duration() const
|
||||
{
|
||||
if (this->empty()) {
|
||||
return 0;
|
||||
} else {
|
||||
return back().time().out();
|
||||
}
|
||||
}
|
||||
|
||||
int stream_index() const
|
||||
{
|
||||
return stream_index_;
|
||||
}
|
||||
void set_stream_index(int i)
|
||||
{
|
||||
stream_index_ = i;
|
||||
}
|
||||
|
||||
bool enabled() const
|
||||
{
|
||||
return enabled_;
|
||||
}
|
||||
void set_enabled(bool e)
|
||||
{
|
||||
enabled_ = e;
|
||||
}
|
||||
|
||||
private:
|
||||
int stream_index_;
|
||||
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SUBTITLEPARAMS_H
|
||||
@@ -0,0 +1,466 @@
|
||||
/***
|
||||
|
||||
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 "videoparams.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
#include <olive/core/util/timecodefunctions.h>
|
||||
|
||||
#include "ofxImageEffect.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const int VideoParams::k_internal_channel_count = k_rgba_channel_count;
|
||||
|
||||
const Rational VideoParams::k_pixel_aspect_square(1);
|
||||
const Rational VideoParams::k_pixel_aspect_ntsc_standard(8, 9);
|
||||
const Rational VideoParams::k_pixel_aspect_ntsc_widescreen(32, 27);
|
||||
const Rational VideoParams::k_pixel_aspect_pal_standard(16, 15);
|
||||
const Rational VideoParams::k_pixel_aspect_pal_widescreen(64, 45);
|
||||
const Rational VideoParams::k_pixel_aspect1080_anamorphic(4, 3);
|
||||
|
||||
const std::vector<Rational> VideoParams::k_supported_frame_rates = {
|
||||
Rational(10, 1), // 10 FPS
|
||||
Rational(15, 1), // 15 FPS
|
||||
Rational(24000, 1001), // 23.976 FPS
|
||||
Rational(24, 1), // 24 FPS
|
||||
Rational(25, 1), // 25 FPS
|
||||
Rational(30000, 1001), // 29.97 FPS
|
||||
Rational(30, 1), // 30 FPS
|
||||
Rational(48000, 1001), // 47.952 FPS
|
||||
Rational(48, 1), // 48 FPS
|
||||
Rational(50, 1), // 50 FPS
|
||||
Rational(60000, 1001), // 59.94 FPS
|
||||
Rational(60, 1) // 60 FPS
|
||||
};
|
||||
|
||||
const std::vector<int> VideoParams::k_supported_dividers = {
|
||||
1, 2, 3, 4, 6, 8, 12, 16
|
||||
};
|
||||
|
||||
const std::vector<Rational> VideoParams::k_standard_pixel_aspects = {
|
||||
VideoParams::k_pixel_aspect_square,
|
||||
VideoParams::k_pixel_aspect_ntsc_standard,
|
||||
VideoParams::k_pixel_aspect_ntsc_widescreen,
|
||||
VideoParams::k_pixel_aspect_pal_standard,
|
||||
VideoParams::k_pixel_aspect_pal_widescreen,
|
||||
VideoParams::k_pixel_aspect1080_anamorphic
|
||||
};
|
||||
|
||||
VideoParams::VideoParams()
|
||||
: width_(0)
|
||||
, height_(0)
|
||||
, depth_(0)
|
||||
, time_base_(0)
|
||||
, format_(PixelFormat::invalid)
|
||||
, channel_count_(0)
|
||||
, pixel_aspect_ratio_(1)
|
||||
, interlacing_(Interlacing::k_interlace_none)
|
||||
, divider_(1)
|
||||
{
|
||||
calculate_effective_size();
|
||||
validate_pixel_aspect_ratio();
|
||||
set_defaults_for_footage();
|
||||
}
|
||||
|
||||
VideoParams::VideoParams(int width, int height, PixelFormat format,
|
||||
int nb_channels, const Rational &pixel_aspect_ratio,
|
||||
Interlacing interlacing, int divider)
|
||||
: width_(width)
|
||||
, height_(height)
|
||||
, depth_(1)
|
||||
, format_(format)
|
||||
, channel_count_(nb_channels)
|
||||
, pixel_aspect_ratio_(pixel_aspect_ratio)
|
||||
, interlacing_(interlacing)
|
||||
, divider_(divider)
|
||||
{
|
||||
calculate_effective_size();
|
||||
validate_pixel_aspect_ratio();
|
||||
set_defaults_for_footage();
|
||||
}
|
||||
|
||||
VideoParams::VideoParams(int width, int height, int depth, PixelFormat format,
|
||||
int nb_channels, const Rational &pixel_aspect_ratio,
|
||||
VideoParams::Interlacing interlacing, int divider)
|
||||
: width_(width)
|
||||
, height_(height)
|
||||
, depth_(depth)
|
||||
, format_(format)
|
||||
, channel_count_(nb_channels)
|
||||
, pixel_aspect_ratio_(pixel_aspect_ratio)
|
||||
, interlacing_(interlacing)
|
||||
, divider_(divider)
|
||||
{
|
||||
calculate_effective_size();
|
||||
validate_pixel_aspect_ratio();
|
||||
set_defaults_for_footage();
|
||||
}
|
||||
|
||||
void VideoParams::set_channel_count(const std::string &ofx_component)
|
||||
{
|
||||
if (ofx_component == kOfxImageComponentAlpha) {
|
||||
channel_count_ = 1;
|
||||
} else if (ofx_component == kOfxImageComponentRGB) {
|
||||
channel_count_ = k_rgb_channel_count;
|
||||
} else if (ofx_component == kOfxImageComponentRGBA) {
|
||||
channel_count_ = k_rgba_channel_count;
|
||||
}
|
||||
}
|
||||
|
||||
VideoParams::VideoParams(int width, int height, const Rational &time_base,
|
||||
PixelFormat format, int nb_channels,
|
||||
const Rational &pixel_aspect_ratio,
|
||||
Interlacing interlacing, int divider)
|
||||
: width_(width)
|
||||
, height_(height)
|
||||
, depth_(1)
|
||||
, time_base_(time_base)
|
||||
, format_(format)
|
||||
, channel_count_(nb_channels)
|
||||
, pixel_aspect_ratio_(pixel_aspect_ratio)
|
||||
, interlacing_(interlacing)
|
||||
, divider_(divider)
|
||||
, frame_rate_(time_base.flipped())
|
||||
{
|
||||
calculate_effective_size();
|
||||
validate_pixel_aspect_ratio();
|
||||
set_defaults_for_footage();
|
||||
}
|
||||
|
||||
int VideoParams::generate_auto_divider(int64_t width, int64_t height)
|
||||
{
|
||||
const int target_res = 1280 * 720;
|
||||
|
||||
int64_t megapixels = width * height;
|
||||
|
||||
double squared_divider = double(megapixels) / double(target_res);
|
||||
double divider = std::sqrt(squared_divider);
|
||||
|
||||
if (divider <= k_supported_dividers.front()) {
|
||||
return k_supported_dividers.front();
|
||||
} else if (divider >= k_supported_dividers.back()) {
|
||||
return k_supported_dividers.back();
|
||||
} else {
|
||||
for (size_t i = 1; i < k_supported_dividers.size(); i++) {
|
||||
int prev_divider = k_supported_dividers.at(i - 1);
|
||||
int next_divider = k_supported_dividers.at(i);
|
||||
|
||||
if (divider >= prev_divider && divider <= next_divider) {
|
||||
double prev_diff = std::fabs(prev_divider - divider);
|
||||
double next_diff = std::fabs(next_divider - divider);
|
||||
|
||||
if (prev_diff < next_diff) {
|
||||
return prev_divider;
|
||||
} else {
|
||||
return next_divider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoParams::operator==(const VideoParams &rhs) const
|
||||
{
|
||||
return width() == rhs.width() && height() == rhs.height() &&
|
||||
depth() == rhs.depth() && interlacing() == rhs.interlacing() &&
|
||||
time_base() == rhs.time_base() && format() == rhs.format() &&
|
||||
pixel_aspect_ratio() == rhs.pixel_aspect_ratio() &&
|
||||
divider() == rhs.divider() && channel_count() == rhs.channel_count();
|
||||
}
|
||||
|
||||
bool VideoParams::operator!=(const VideoParams &rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
int VideoParams::get_bytes_per_channel(PixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case PixelFormat::invalid:
|
||||
case PixelFormat::count:
|
||||
break;
|
||||
case PixelFormat::u8:
|
||||
return 1;
|
||||
case PixelFormat::u10:
|
||||
return 0; // packed format, use get_bytes_per_pixel instead
|
||||
case PixelFormat::u16:
|
||||
case PixelFormat::f16:
|
||||
return 2;
|
||||
case PixelFormat::f32:
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VideoParams::get_bytes_per_pixel(PixelFormat format, int channels)
|
||||
{
|
||||
if (format == PixelFormat::u10) {
|
||||
// Packed 10-bit RGBA10A2: 4 bytes per RGBA pixel regardless of channel count
|
||||
return channels == VideoParams::k_rgba_channel_count ? 4 : 0;
|
||||
}
|
||||
return get_bytes_per_channel(format) * channels;
|
||||
}
|
||||
|
||||
std::string VideoParams::get_name_for_divider(int div)
|
||||
{
|
||||
if (div == 1) {
|
||||
return std::string("Full");
|
||||
} else {
|
||||
return std::string("1/") + std::to_string(div);
|
||||
}
|
||||
}
|
||||
|
||||
std::string VideoParams::get_format_name(PixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case PixelFormat::u8:
|
||||
return "8-bit";
|
||||
case PixelFormat::u10:
|
||||
return "10-bit Packed";
|
||||
case PixelFormat::u16:
|
||||
return "16-bit Integer";
|
||||
case PixelFormat::f16:
|
||||
return "Half-Float (16-bit)";
|
||||
case PixelFormat::f32:
|
||||
return "Full-Float (32-bit)";
|
||||
case PixelFormat::invalid:
|
||||
case PixelFormat::count:
|
||||
break;
|
||||
}
|
||||
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof(buf), "Unknown (0x%X)",
|
||||
static_cast<int>(format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
int VideoParams::get_divider_for_target_resolution(int src_width, int src_height,
|
||||
int dst_width, int dst_height)
|
||||
{
|
||||
int divider = 0;
|
||||
int test_width, test_height;
|
||||
|
||||
do {
|
||||
divider++;
|
||||
|
||||
test_width = VideoParams::get_scaled_dimension(src_width, divider);
|
||||
test_height = VideoParams::get_scaled_dimension(src_height, divider);
|
||||
} while (test_width > dst_width || test_height > dst_height);
|
||||
|
||||
return divider;
|
||||
}
|
||||
|
||||
void VideoParams::calculate_effective_size()
|
||||
{
|
||||
effective_width_ = get_scaled_dimension(width(), divider_);
|
||||
effective_height_ = get_scaled_dimension(height(), divider_);
|
||||
effective_depth_ = (depth() == 1) ? depth() :
|
||||
get_scaled_dimension(depth(), divider_);
|
||||
calculate_square_pixel_width();
|
||||
}
|
||||
|
||||
void VideoParams::validate_pixel_aspect_ratio()
|
||||
{
|
||||
if (pixel_aspect_ratio_.isNull()) {
|
||||
pixel_aspect_ratio_ = 1;
|
||||
}
|
||||
calculate_square_pixel_width();
|
||||
}
|
||||
|
||||
void VideoParams::set_defaults_for_footage()
|
||||
{
|
||||
enabled_ = true;
|
||||
stream_index_ = 0;
|
||||
video_type_ = k_video_type_video;
|
||||
start_time_ = 0;
|
||||
duration_ = 0;
|
||||
premultiplied_alpha_ = false;
|
||||
x_ = 0;
|
||||
y_ = 0;
|
||||
color_range_ = k_color_range_default;
|
||||
}
|
||||
|
||||
void VideoParams::calculate_square_pixel_width()
|
||||
{
|
||||
if (pixel_aspect_ratio_.denominator() != 0) {
|
||||
par_width_ = int(std::lround(width_ * pixel_aspect_ratio_.to_double()));
|
||||
} else {
|
||||
par_width_ = width_;
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoParams::is_valid() const
|
||||
{
|
||||
return (width() > 0 && height() > 0 && !pixel_aspect_ratio_.isNull() &&
|
||||
format_ > PixelFormat::invalid && format_ < PixelFormat::count &&
|
||||
channel_count_ > 0);
|
||||
}
|
||||
|
||||
std::string VideoParams::frame_rate_to_string(const Rational &frame_rate)
|
||||
{
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof(buf), "%g FPS", frame_rate.to_double());
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::vector<std::string> VideoParams::get_standard_pixel_aspect_ratio_names()
|
||||
{
|
||||
std::vector<std::string> strings = {
|
||||
"Square Pixels (%1)",
|
||||
"NTSC Standard (%1)",
|
||||
"NTSC Widescreen (%1)",
|
||||
"PAL Standard (%1)",
|
||||
"PAL Widescreen (%1)",
|
||||
"HD Anamorphic 1080 (%1)"
|
||||
};
|
||||
|
||||
// Format each
|
||||
for (size_t i = 0; i < strings.size(); i++) {
|
||||
strings.at(i) = format_pixel_aspect_ratio_string(
|
||||
strings.at(i), k_standard_pixel_aspects.at(i));
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
std::string VideoParams::format_pixel_aspect_ratio_string(
|
||||
const std::string &format, const Rational &ratio)
|
||||
{
|
||||
char number[64];
|
||||
std::snprintf(number, sizeof(number), "%.4f", ratio.to_double());
|
||||
|
||||
std::string result = format;
|
||||
std::string::size_type pos = result.find("%1");
|
||||
if (pos != std::string::npos) {
|
||||
result.replace(pos, 2, number);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int VideoParams::get_scaled_dimension(int dim, int divider)
|
||||
{
|
||||
return dim / divider;
|
||||
}
|
||||
|
||||
int64_t VideoParams::get_time_in_timebase_units(const Rational &time) const
|
||||
{
|
||||
if (time_base_.isNull()) {
|
||||
return INT64_MIN; // AV_NOPTS_VALUE
|
||||
}
|
||||
|
||||
return Timecode::time_to_timestamp(time, time_base_) + start_time_;
|
||||
}
|
||||
|
||||
void VideoParams::load(XmlStreamReader *reader)
|
||||
{
|
||||
while (xml_read_next_start_element(reader)) {
|
||||
const std::string &name = reader->name();
|
||||
if (name == "width") {
|
||||
set_width(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "height") {
|
||||
set_height(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "depth") {
|
||||
set_depth(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "timebase") {
|
||||
set_time_base(Rational::from_string(reader->read_element_text()));
|
||||
} else if (name == "format") {
|
||||
set_format(static_cast<PixelFormat::Format>(
|
||||
std::stoi(reader->read_element_text())));
|
||||
} else if (name == "channelcount") {
|
||||
set_channel_count(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "pixelaspectratio") {
|
||||
set_pixel_aspect_ratio(
|
||||
Rational::from_string(reader->read_element_text()));
|
||||
} else if (name == "interlacing") {
|
||||
set_interlacing(static_cast<VideoParams::Interlacing>(
|
||||
std::stoi(reader->read_element_text())));
|
||||
} else if (name == "divider") {
|
||||
set_divider(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "enabled") {
|
||||
set_enabled(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "x") {
|
||||
set_x(std::stof(reader->read_element_text()));
|
||||
} else if (name == "y") {
|
||||
set_y(std::stof(reader->read_element_text()));
|
||||
} else if (name == "streamindex") {
|
||||
set_stream_index(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "videotype") {
|
||||
set_video_type(static_cast<VideoParams::Type>(
|
||||
std::stoi(reader->read_element_text())));
|
||||
} else if (name == "framerate") {
|
||||
set_frame_rate(Rational::from_string(reader->read_element_text()));
|
||||
} else if (name == "starttime") {
|
||||
set_start_time(std::stoll(reader->read_element_text()));
|
||||
} else if (name == "duration") {
|
||||
set_duration(std::stoll(reader->read_element_text()));
|
||||
} else if (name == "premultipliedalpha") {
|
||||
set_premultiplied_alpha(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "colorspace") {
|
||||
set_colorspace(reader->read_element_text());
|
||||
} else if (name == "colorrange") {
|
||||
set_color_range(
|
||||
static_cast<ColorRange>(std::stoi(reader->read_element_text())));
|
||||
} else if (name == "colorprimaries") {
|
||||
set_color_primaries(std::stoi(reader->read_element_text()));
|
||||
} else if (name == "colortransfer") {
|
||||
set_color_transfer(std::stoi(reader->read_element_text()));
|
||||
} else {
|
||||
reader->skip_current_element();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoParams::save(XmlStreamWriter *writer) const
|
||||
{
|
||||
writer->write_text_element("width", std::to_string(width_));
|
||||
writer->write_text_element("height", std::to_string(height_));
|
||||
writer->write_text_element("depth", std::to_string(depth_));
|
||||
writer->write_text_element("timebase", time_base_.to_string());
|
||||
writer->write_text_element("format", std::to_string(format_));
|
||||
writer->write_text_element("channelcount", std::to_string(channel_count_));
|
||||
writer->write_text_element("pixelaspectratio",
|
||||
pixel_aspect_ratio_.to_string());
|
||||
writer->write_text_element("interlacing", std::to_string(interlacing_));
|
||||
writer->write_text_element("divider", std::to_string(divider_));
|
||||
writer->write_text_element("enabled", std::to_string(enabled_));
|
||||
writer->write_text_element("x", std::to_string(x_));
|
||||
writer->write_text_element("y", std::to_string(y_));
|
||||
writer->write_text_element("streamindex", std::to_string(stream_index_));
|
||||
writer->write_text_element("videotype", std::to_string(video_type_));
|
||||
writer->write_text_element("framerate", frame_rate_.to_string());
|
||||
writer->write_text_element("starttime", std::to_string(start_time_));
|
||||
writer->write_text_element("duration", std::to_string(duration_));
|
||||
writer->write_text_element("premultipliedalpha",
|
||||
std::to_string(premultiplied_alpha_));
|
||||
writer->write_text_element("colorspace", colorspace_);
|
||||
writer->write_text_element("colorrange", std::to_string(color_range_));
|
||||
writer->write_text_element("colorprimaries", std::to_string(color_primaries_));
|
||||
writer->write_text_element("colortransfer", std::to_string(color_transfer_));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
/***
|
||||
|
||||
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_VIDEOPARAMS_H
|
||||
#define OAK_VIDEOPARAMS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <olive/core/render/pixelformat.h>
|
||||
#include <olive/core/util/rational.h>
|
||||
|
||||
#include "xmlutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using namespace core;
|
||||
|
||||
/**
|
||||
* @brief Pure-data video parameter set (sunk from engine/render/videoparams.h).
|
||||
*
|
||||
* De-Qt port: QString -> std::string, QVector -> std::vector,
|
||||
* QXmlStreamReader/Writer -> olive::XmlStreamReader/Writer. The QVector2D
|
||||
* convenience getters (resolution/square_resolution/offset) were dropped;
|
||||
* use width()/height()/x()/y() directly. Translated UI strings are returned
|
||||
* as plain English text.
|
||||
*/
|
||||
class VideoParams {
|
||||
public:
|
||||
enum Interlacing {
|
||||
k_interlace_none,
|
||||
k_interlaced_top_first,
|
||||
k_interlaced_bottom_first
|
||||
};
|
||||
|
||||
enum Type { k_video_type_video, k_video_type_still, k_video_type_image_sequence };
|
||||
enum ColorRange {
|
||||
k_color_range_limited, // 16-235
|
||||
k_color_range_full, // 0-255
|
||||
|
||||
k_color_range_default = k_color_range_limited
|
||||
};
|
||||
|
||||
VideoParams();
|
||||
VideoParams(int width, int height, PixelFormat format, int nb_channels,
|
||||
const Rational &pixel_aspect_ratio = 1,
|
||||
Interlacing interlacing = k_interlace_none, int divider = 1);
|
||||
VideoParams(int width, int height, int depth, PixelFormat format,
|
||||
int nb_channels, const Rational &pixel_aspect_ratio = 1,
|
||||
Interlacing interlacing = k_interlace_none, int divider = 1);
|
||||
VideoParams(int width, int height, const Rational &time_base,
|
||||
PixelFormat format, int nb_channels,
|
||||
const Rational &pixel_aspect_ratio = 1,
|
||||
Interlacing interlacing = k_interlace_none, int divider = 1);
|
||||
|
||||
int width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
void set_width(int width)
|
||||
{
|
||||
width_ = width;
|
||||
calculate_effective_size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns width multiplied by pixel aspect ratio where applicable
|
||||
*/
|
||||
int square_pixel_width() const
|
||||
{
|
||||
return par_width_;
|
||||
}
|
||||
|
||||
int height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
void set_height(int height)
|
||||
{
|
||||
height_ = height;
|
||||
calculate_effective_size();
|
||||
}
|
||||
|
||||
int depth() const
|
||||
{
|
||||
return depth_;
|
||||
}
|
||||
|
||||
void set_depth(int depth)
|
||||
{
|
||||
depth_ = depth;
|
||||
calculate_effective_size();
|
||||
}
|
||||
|
||||
bool is_3d() const
|
||||
{
|
||||
return depth_ > 1;
|
||||
}
|
||||
|
||||
const Rational &time_base() const
|
||||
{
|
||||
return time_base_;
|
||||
}
|
||||
|
||||
void set_time_base(const Rational &r)
|
||||
{
|
||||
time_base_ = r;
|
||||
}
|
||||
|
||||
Rational frame_rate_as_time_base() const
|
||||
{
|
||||
return frame_rate_.flipped();
|
||||
}
|
||||
|
||||
int divider() const
|
||||
{
|
||||
return divider_;
|
||||
}
|
||||
|
||||
void set_divider(int d)
|
||||
{
|
||||
divider_ = d;
|
||||
calculate_effective_size();
|
||||
}
|
||||
|
||||
int effective_width() const
|
||||
{
|
||||
return effective_width_;
|
||||
}
|
||||
|
||||
int effective_height() const
|
||||
{
|
||||
return effective_height_;
|
||||
}
|
||||
|
||||
int effective_depth() const
|
||||
{
|
||||
return effective_depth_;
|
||||
}
|
||||
|
||||
PixelFormat format() const
|
||||
{
|
||||
return format_;
|
||||
}
|
||||
|
||||
void set_format(PixelFormat f)
|
||||
{
|
||||
format_ = f;
|
||||
}
|
||||
|
||||
int channel_count() const
|
||||
{
|
||||
return channel_count_;
|
||||
}
|
||||
|
||||
void set_channel_count(int c)
|
||||
{
|
||||
channel_count_ = c;
|
||||
}
|
||||
void set_channel_count(const std::string &ofx_component);
|
||||
const Rational &pixel_aspect_ratio() const
|
||||
{
|
||||
return pixel_aspect_ratio_;
|
||||
}
|
||||
|
||||
void set_pixel_aspect_ratio(const Rational &r)
|
||||
{
|
||||
pixel_aspect_ratio_ = r;
|
||||
validate_pixel_aspect_ratio();
|
||||
}
|
||||
|
||||
Interlacing interlacing() const
|
||||
{
|
||||
return interlacing_;
|
||||
}
|
||||
|
||||
void set_interlacing(Interlacing i)
|
||||
{
|
||||
interlacing_ = i;
|
||||
}
|
||||
|
||||
static int generate_auto_divider(int64_t width, int64_t height);
|
||||
|
||||
bool is_valid() const;
|
||||
|
||||
bool operator==(const VideoParams &rhs) const;
|
||||
bool operator!=(const VideoParams &rhs) const;
|
||||
|
||||
static int get_bytes_per_channel(PixelFormat format);
|
||||
int get_bytes_per_channel() const
|
||||
{
|
||||
return get_bytes_per_channel(format_);
|
||||
}
|
||||
|
||||
static int get_bytes_per_pixel(PixelFormat format, int channels);
|
||||
int get_bytes_per_pixel() const
|
||||
{
|
||||
return get_bytes_per_pixel(format_, channel_count_);
|
||||
}
|
||||
|
||||
static int get_buffer_size(int width, int height, PixelFormat format,
|
||||
int channels)
|
||||
{
|
||||
return width * height * get_bytes_per_pixel(format, channels);
|
||||
}
|
||||
int get_buffer_size() const
|
||||
{
|
||||
return get_buffer_size(width_, height_, format_, channel_count_);
|
||||
}
|
||||
|
||||
static std::string get_name_for_divider(int div);
|
||||
|
||||
static bool format_is_float(PixelFormat format)
|
||||
{
|
||||
return format.is_float();
|
||||
}
|
||||
|
||||
static std::string get_format_name(PixelFormat format);
|
||||
|
||||
static int get_divider_for_target_resolution(int src_width, int src_height,
|
||||
int dst_width, int dst_height);
|
||||
|
||||
static const int k_internal_channel_count;
|
||||
|
||||
static const Rational k_pixel_aspect_square;
|
||||
static const Rational k_pixel_aspect_ntsc_standard;
|
||||
static const Rational k_pixel_aspect_ntsc_widescreen;
|
||||
static const Rational k_pixel_aspect_pal_standard;
|
||||
static const Rational k_pixel_aspect_pal_widescreen;
|
||||
static const Rational k_pixel_aspect1080_anamorphic;
|
||||
|
||||
static const std::vector<Rational> k_supported_frame_rates;
|
||||
static const std::vector<Rational> k_standard_pixel_aspects;
|
||||
static const std::vector<int> k_supported_dividers;
|
||||
|
||||
static const int k_hsv_channel_count = 3;
|
||||
static const int k_rgb_channel_count = 3;
|
||||
static const int k_rgba_channel_count = 4;
|
||||
|
||||
/**
|
||||
* @brief Convert Rational frame rate (i.e. flipped timebase) to a user-friendly string
|
||||
*/
|
||||
static std::string frame_rate_to_string(const Rational &frame_rate);
|
||||
|
||||
static std::vector<std::string> get_standard_pixel_aspect_ratio_names();
|
||||
static std::string format_pixel_aspect_ratio_string(const std::string &format,
|
||||
const Rational &ratio);
|
||||
|
||||
static int get_scaled_dimension(int dim, int divider);
|
||||
|
||||
bool enabled() const
|
||||
{
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
void set_enabled(bool e)
|
||||
{
|
||||
enabled_ = e;
|
||||
}
|
||||
|
||||
float x() const
|
||||
{
|
||||
return x_;
|
||||
}
|
||||
void set_x(float x)
|
||||
{
|
||||
x_ = x;
|
||||
}
|
||||
float y() const
|
||||
{
|
||||
return y_;
|
||||
}
|
||||
void set_y(float y)
|
||||
{
|
||||
y_ = y;
|
||||
}
|
||||
|
||||
int stream_index() const
|
||||
{
|
||||
return stream_index_;
|
||||
}
|
||||
|
||||
void set_stream_index(int s)
|
||||
{
|
||||
stream_index_ = s;
|
||||
}
|
||||
|
||||
Type video_type() const
|
||||
{
|
||||
return video_type_;
|
||||
}
|
||||
|
||||
void set_video_type(Type t)
|
||||
{
|
||||
video_type_ = t;
|
||||
}
|
||||
|
||||
const Rational &frame_rate() const
|
||||
{
|
||||
return frame_rate_;
|
||||
}
|
||||
|
||||
void set_frame_rate(const Rational &frame_rate)
|
||||
{
|
||||
frame_rate_ = frame_rate;
|
||||
}
|
||||
|
||||
int64_t start_time() const
|
||||
{
|
||||
return start_time_;
|
||||
}
|
||||
|
||||
void set_start_time(int64_t start_time)
|
||||
{
|
||||
start_time_ = start_time;
|
||||
}
|
||||
|
||||
int64_t duration() const
|
||||
{
|
||||
return duration_;
|
||||
}
|
||||
|
||||
void set_duration(int64_t duration)
|
||||
{
|
||||
duration_ = duration;
|
||||
}
|
||||
|
||||
bool premultiplied_alpha() const
|
||||
{
|
||||
return premultiplied_alpha_;
|
||||
}
|
||||
|
||||
void set_premultiplied_alpha(bool premultiplied_alpha)
|
||||
{
|
||||
premultiplied_alpha_ = premultiplied_alpha;
|
||||
}
|
||||
|
||||
const std::string &colorspace() const
|
||||
{
|
||||
return colorspace_;
|
||||
}
|
||||
|
||||
void set_colorspace(const std::string &c)
|
||||
{
|
||||
colorspace_ = c;
|
||||
}
|
||||
|
||||
const ColorRange &color_range() const
|
||||
{
|
||||
return color_range_;
|
||||
}
|
||||
void set_color_range(const ColorRange &color_range)
|
||||
{
|
||||
color_range_ = color_range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Color primaries/transfer as reported by the media
|
||||
*
|
||||
* Raw FFmpeg AVColorPrimaries/AVColorTransferCharacteristic values
|
||||
* (0 = unset, 2 = unspecified). Used to auto-detect the input
|
||||
* colorspace when no explicit colorspace has been set.
|
||||
*/
|
||||
int color_primaries() const
|
||||
{
|
||||
return color_primaries_;
|
||||
}
|
||||
|
||||
void set_color_primaries(int p)
|
||||
{
|
||||
color_primaries_ = p;
|
||||
}
|
||||
|
||||
int color_transfer() const
|
||||
{
|
||||
return color_transfer_;
|
||||
}
|
||||
|
||||
void set_color_transfer(int t)
|
||||
{
|
||||
color_transfer_ = t;
|
||||
}
|
||||
|
||||
int64_t get_time_in_timebase_units(const Rational &time) const;
|
||||
|
||||
void load(XmlStreamReader *reader);
|
||||
|
||||
void save(XmlStreamWriter *writer) const;
|
||||
|
||||
private:
|
||||
void calculate_effective_size();
|
||||
|
||||
void validate_pixel_aspect_ratio();
|
||||
|
||||
void set_defaults_for_footage();
|
||||
|
||||
void calculate_square_pixel_width();
|
||||
|
||||
int width_;
|
||||
int height_;
|
||||
int depth_;
|
||||
Rational time_base_;
|
||||
|
||||
PixelFormat format_;
|
||||
|
||||
int channel_count_;
|
||||
|
||||
Rational pixel_aspect_ratio_;
|
||||
|
||||
Interlacing interlacing_;
|
||||
|
||||
int divider_;
|
||||
|
||||
// Cached values
|
||||
int effective_width_;
|
||||
int effective_height_;
|
||||
int effective_depth_;
|
||||
int par_width_;
|
||||
|
||||
bool enabled_;
|
||||
int stream_index_;
|
||||
Type video_type_;
|
||||
Rational frame_rate_;
|
||||
int64_t start_time_;
|
||||
int64_t duration_;
|
||||
bool premultiplied_alpha_;
|
||||
std::string colorspace_;
|
||||
float x_;
|
||||
float y_;
|
||||
ColorRange color_range_;
|
||||
int color_primaries_ = 0;
|
||||
int color_transfer_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_VIDEOPARAMS_H
|
||||
@@ -19,6 +19,7 @@ find_package(GTest REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
add_executable(oakcommon-gtest
|
||||
colortransform_test.cpp
|
||||
commandlineparser_test.cpp
|
||||
current_test.cpp
|
||||
debug_test.cpp
|
||||
@@ -31,7 +32,9 @@ add_executable(oakcommon-gtest
|
||||
oiioutils_test.cpp
|
||||
power_test.cpp
|
||||
qtutils_test.cpp
|
||||
subtitleparams_test.cpp
|
||||
threadsafemap_test.cpp
|
||||
videoparams_test.cpp
|
||||
xmlutils_test.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/***
|
||||
|
||||
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/colortransform.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string read_string(int (*fn)(OakCommonColorTransform *, char *, int),
|
||||
OakCommonColorTransform *t)
|
||||
{
|
||||
int needed = fn(t, nullptr, 0);
|
||||
EXPECT_GT(needed, 0);
|
||||
std::vector<char> buf(needed);
|
||||
EXPECT_EQ(fn(t, buf.data(), needed), needed);
|
||||
return std::string(buf.data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(CommonColorTransformCApi, InitOutput)
|
||||
{
|
||||
OakCommonColorTransform *t =
|
||||
oakcommon_colortransform_init_output("sRGB");
|
||||
ASSERT_NE(t, nullptr);
|
||||
|
||||
int is_display = -1;
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(is_display, 0);
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_output, t), "sRGB");
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_display, t), "sRGB");
|
||||
|
||||
oakcommon_colortransform_free(t);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, InitOutputNullString)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_colortransform_init_output(nullptr), nullptr);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, InitDisplay)
|
||||
{
|
||||
OakCommonColorTransform *t =
|
||||
oakcommon_colortransform_init_display("sRGB", "Studio", "None");
|
||||
ASSERT_NE(t, nullptr);
|
||||
|
||||
int is_display = 0;
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(is_display, 1);
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_display, t), "sRGB");
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_view, t), "Studio");
|
||||
EXPECT_EQ(read_string(oakcommon_colortransform_get_look, t), "None");
|
||||
|
||||
oakcommon_colortransform_free(t);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, InitDisplayNullString)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display(nullptr, "v", "l"),
|
||||
nullptr);
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display("d", nullptr, "l"),
|
||||
nullptr);
|
||||
EXPECT_EQ(oakcommon_colortransform_init_display("d", "v", nullptr),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, FreeNull)
|
||||
{
|
||||
oakcommon_colortransform_free(nullptr);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, NullHandleErrors)
|
||||
{
|
||||
int i = 0;
|
||||
char buf[16];
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_display(nullptr, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_output(nullptr, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_view(nullptr, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_colortransform_get_look(nullptr, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(CommonColorTransformCApi, NullOutParam)
|
||||
{
|
||||
OakCommonColorTransform *t =
|
||||
oakcommon_colortransform_init_output("sRGB");
|
||||
ASSERT_NE(t, nullptr);
|
||||
EXPECT_EQ(oakcommon_colortransform_is_display(t, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
oakcommon_colortransform_free(t);
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/***
|
||||
|
||||
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/subtitleparams.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string read_indexed_string(
|
||||
int (*fn)(OakCommonSubtitleParams *, int, char *, int),
|
||||
OakCommonSubtitleParams *p, int index)
|
||||
{
|
||||
int needed = fn(p, index, nullptr, 0);
|
||||
EXPECT_GT(needed, 0);
|
||||
std::vector<char> buf(needed);
|
||||
EXPECT_EQ(fn(p, index, buf.data(), needed), needed);
|
||||
return std::string(buf.data());
|
||||
}
|
||||
|
||||
std::string read_static_string(int (*fn)(char *, int))
|
||||
{
|
||||
int needed = fn(nullptr, 0);
|
||||
EXPECT_GT(needed, 0);
|
||||
std::vector<char> buf(needed);
|
||||
EXPECT_EQ(fn(buf.data(), needed), needed);
|
||||
return std::string(buf.data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, InitFree)
|
||||
{
|
||||
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int index = -1;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(p, &index),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(index, 0);
|
||||
|
||||
int enabled = 0;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(p, &enabled),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(enabled, 1);
|
||||
|
||||
oakcommon_subtitleparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, FreeNull)
|
||||
{
|
||||
oakcommon_subtitleparams_free(nullptr);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, SetStreamIndex)
|
||||
{
|
||||
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(p, 3), OAKCOMMON_OK);
|
||||
int index = 0;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(p, &index),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(index, 3);
|
||||
oakcommon_subtitleparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, SetStreamIndexNullHandle)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(nullptr, 3),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(nullptr, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, SetEnabled)
|
||||
{
|
||||
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_set_enabled(p, 0), OAKCOMMON_OK);
|
||||
int enabled = 1;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(p, &enabled),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(enabled, 0);
|
||||
oakcommon_subtitleparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, SetEnabledNullHandle)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_subtitleparams_set_enabled(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(nullptr, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, AddAndReadSubtitles)
|
||||
{
|
||||
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int is_valid = 1;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_is_valid(p, &is_valid), OAKCOMMON_OK);
|
||||
EXPECT_EQ(is_valid, 0);
|
||||
|
||||
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 1, 2, 3, 2, "Hello"),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 2, 1, 3, 1, "World"),
|
||||
OAKCOMMON_OK);
|
||||
|
||||
EXPECT_EQ(oakcommon_subtitleparams_is_valid(p, &is_valid), OAKCOMMON_OK);
|
||||
EXPECT_EQ(is_valid, 1);
|
||||
|
||||
int count = 0;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_count(p, &count), OAKCOMMON_OK);
|
||||
EXPECT_EQ(count, 2);
|
||||
|
||||
int in_num, in_den, out_num, out_den;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(p, 0, &in_num, &in_den,
|
||||
&out_num, &out_den),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(in_num, 1);
|
||||
EXPECT_EQ(in_den, 2);
|
||||
EXPECT_EQ(out_num, 3);
|
||||
EXPECT_EQ(out_den, 2);
|
||||
EXPECT_EQ(read_indexed_string(
|
||||
oakcommon_subtitleparams_get_subtitle_text, p, 0),
|
||||
"Hello");
|
||||
|
||||
int num, den;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_duration(p, &num, &den), OAKCOMMON_OK);
|
||||
EXPECT_EQ(num, 3);
|
||||
EXPECT_EQ(den, 1);
|
||||
|
||||
EXPECT_EQ(oakcommon_subtitleparams_clear(p), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_count(p, &count), OAKCOMMON_OK);
|
||||
EXPECT_EQ(count, 0);
|
||||
|
||||
oakcommon_subtitleparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, SubtitleErrorPaths)
|
||||
{
|
||||
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int i = 0;
|
||||
char buf[16];
|
||||
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(nullptr, 0, 1, 1, 1, "x"),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 0, 1, 1, 1, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(p, 0, &i, &i, &i, &i),
|
||||
OAKCOMMON_E_NOT_FOUND);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(nullptr, 0, &i, &i, &i,
|
||||
&i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle_text(p, 5, buf,
|
||||
sizeof(buf)),
|
||||
OAKCOMMON_E_NOT_FOUND);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle_text(nullptr, 0, buf,
|
||||
sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_is_valid(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_count(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_duration(nullptr, &i, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_clear(nullptr), OAKCOMMON_E_INVALID);
|
||||
|
||||
oakcommon_subtitleparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, GenerateAssHeader)
|
||||
{
|
||||
std::string header =
|
||||
read_static_string(oakcommon_subtitleparams_generate_ass_header);
|
||||
EXPECT_NE(header.find("[Script Info]"), std::string::npos);
|
||||
EXPECT_NE(header.find("[V4+ Styles]"), std::string::npos);
|
||||
EXPECT_NE(header.find("[Events]"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, XmlRoundTrip)
|
||||
{
|
||||
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(p, 2), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 1, 25, 2, 25,
|
||||
"First <line>"),
|
||||
OAKCOMMON_OK);
|
||||
|
||||
int needed = oakcommon_subtitleparams_save_xml(p, nullptr, 0);
|
||||
ASSERT_GT(needed, 0);
|
||||
std::vector<char> buf(needed);
|
||||
ASSERT_EQ(oakcommon_subtitleparams_save_xml(p, buf.data(), needed), needed);
|
||||
|
||||
OakCommonSubtitleParams *q = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(q, nullptr);
|
||||
ASSERT_EQ(oakcommon_subtitleparams_load_xml(q, buf.data()), OAKCOMMON_OK);
|
||||
|
||||
int index = 0;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(q, &index),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(index, 2);
|
||||
int count = 0;
|
||||
EXPECT_EQ(oakcommon_subtitleparams_count(q, &count), OAKCOMMON_OK);
|
||||
EXPECT_EQ(count, 1);
|
||||
EXPECT_EQ(read_indexed_string(
|
||||
oakcommon_subtitleparams_get_subtitle_text, q, 0),
|
||||
"First <line>");
|
||||
|
||||
oakcommon_subtitleparams_free(p);
|
||||
oakcommon_subtitleparams_free(q);
|
||||
}
|
||||
|
||||
TEST(CommonSubtitleParamsCApi, XmlErrorPaths)
|
||||
{
|
||||
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
char buf[16];
|
||||
EXPECT_EQ(oakcommon_subtitleparams_load_xml(nullptr, "<a/>"),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_load_xml(p, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_load_xml(p, "not xml"),
|
||||
OAKCOMMON_E_FAILED);
|
||||
EXPECT_EQ(oakcommon_subtitleparams_save_xml(nullptr, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
oakcommon_subtitleparams_free(p);
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
/***
|
||||
|
||||
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 <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/videoparams.h"
|
||||
|
||||
TEST(CommonVideoParamsCApi, InitDefaults)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int v = -1;
|
||||
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 0);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_is_valid(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 0);
|
||||
|
||||
oakcommon_videoparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, FreeNull)
|
||||
{
|
||||
oakcommon_videoparams_free(nullptr);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, InitBasic)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init_basic(
|
||||
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8,
|
||||
4, 1, 1, OAKCOMMON_VIDEO_INTERLACE_NONE, 2);
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int v;
|
||||
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1920);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_height(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1080);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_depth(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_is_3d(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 0);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_format(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, OAKCOMMON_PIXEL_FORMAT_U8);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_channel_count(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 4);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_divider(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 2);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_interlacing(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, OAKCOMMON_VIDEO_INTERLACE_NONE);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_is_valid(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1);
|
||||
|
||||
// Effective size is divided by the divider
|
||||
EXPECT_EQ(oakcommon_videoparams_get_effective_width(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 960);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_effective_height(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 540);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(p, &v),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1920);
|
||||
|
||||
// u8 RGBA: 4 bytes per pixel
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel(p, &v),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 4);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_buffer_size(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1920 * 1080 * 4);
|
||||
|
||||
oakcommon_videoparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, InitWithTimeBase)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
|
||||
1280, 720, 1001, 30000, OAKCOMMON_PIXEL_FORMAT_F32, 4, 1, 1,
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int num, den;
|
||||
EXPECT_EQ(oakcommon_videoparams_get_time_base(p, &num, &den),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(num, 1001);
|
||||
EXPECT_EQ(den, 30000);
|
||||
|
||||
// Frame rate is the flipped time base
|
||||
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(p, &num, &den),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(num, 30000);
|
||||
EXPECT_EQ(den, 1001);
|
||||
EXPECT_EQ(oakcommon_videoparams_frame_rate_as_time_base(p, &num, &den),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(num, 1001);
|
||||
EXPECT_EQ(den, 30000);
|
||||
|
||||
oakcommon_videoparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, ScalarSetters)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int v;
|
||||
EXPECT_EQ(oakcommon_videoparams_set_width(p, 640), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_height(p, 480), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_depth(p, 4), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_is_3d(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_format(p, OAKCOMMON_PIXEL_FORMAT_U16),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_channel_count(p, 3), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_interlacing(
|
||||
p, OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_divider(p, 4), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_enabled(p, 0), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_stream_index(p, 5), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_video_type(
|
||||
p, OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_premultiplied_alpha(p, 1),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_color_range(
|
||||
p, OAKCOMMON_COLOR_RANGE_FULL),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(p, 9), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_color_transfer(p, 16), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_time_base(p, 1, 25), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_frame_rate(p, 25, 1), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_pixel_aspect_ratio(p, 4, 3),
|
||||
OAKCOMMON_OK);
|
||||
|
||||
float f;
|
||||
EXPECT_EQ(oakcommon_videoparams_set_x(p, 1.5f), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_y(p, -2.5f), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_x(p, &f), OAKCOMMON_OK);
|
||||
EXPECT_FLOAT_EQ(f, 1.5f);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_y(p, &f), OAKCOMMON_OK);
|
||||
EXPECT_FLOAT_EQ(f, -2.5f);
|
||||
|
||||
int64_t i64;
|
||||
EXPECT_EQ(oakcommon_videoparams_set_start_time(p, 100), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_duration(p, 250), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_start_time(p, &i64), OAKCOMMON_OK);
|
||||
EXPECT_EQ(i64, 100);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_duration(p, &i64), OAKCOMMON_OK);
|
||||
EXPECT_EQ(i64, 250);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 640);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_effective_width(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 160);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_interlacing(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_enabled(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 0);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_stream_index(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 5);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_video_type(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_premultiplied_alpha(p, &v),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_color_range(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, OAKCOMMON_COLOR_RANGE_FULL);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 9);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_color_transfer(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 16);
|
||||
|
||||
int num, den;
|
||||
EXPECT_EQ(oakcommon_videoparams_get_pixel_aspect_ratio(p, &num, &den),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(num, 4);
|
||||
EXPECT_EQ(den, 3);
|
||||
// Square pixel width = 640 * 4/3 = 853.33 -> 853
|
||||
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(p, &v),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 853);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_effective_depth(p, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 1);
|
||||
|
||||
oakcommon_videoparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, NullHandleErrors)
|
||||
{
|
||||
int i;
|
||||
float f;
|
||||
int64_t i64;
|
||||
char buf[16];
|
||||
EXPECT_EQ(oakcommon_videoparams_get_width(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_width(nullptr, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_height(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_height(nullptr, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_depth(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_depth(nullptr, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_is_3d(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_time_base(nullptr, &i, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_time_base(nullptr, 1, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(nullptr, &i, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_frame_rate(nullptr, 1, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_frame_rate_as_time_base(nullptr, &i, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_pixel_aspect_ratio(nullptr, &i, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_pixel_aspect_ratio(nullptr, 1, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_format(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_format(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_channel_count(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_channel_count(nullptr, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_interlacing(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_interlacing(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_divider(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_divider(nullptr, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_enabled(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_enabled(nullptr, 1),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_x(nullptr, &f), OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_x(nullptr, 0.0f), OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_y(nullptr, &f), OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_y(nullptr, 0.0f), OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_stream_index(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_stream_index(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_video_type(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_video_type(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_start_time(nullptr, &i64),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_start_time(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_duration(nullptr, &i64),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_duration(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_premultiplied_alpha(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_premultiplied_alpha(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_color_range(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_color_range(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_color_transfer(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_color_transfer(nullptr, 0),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_colorspace(nullptr, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_colorspace(nullptr, "x"),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_effective_width(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_effective_height(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_effective_depth(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_is_valid(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_buffer_size(nullptr, &i),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(nullptr, 0, 1,
|
||||
&i64),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_load_xml(nullptr, "<a/>"),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_save_xml(nullptr, buf, sizeof(buf)),
|
||||
OAKCOMMON_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, NullOutParamErrors)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_width(p, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_time_base(p, nullptr, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_load_xml(p, nullptr), OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_load_xml(p, "not xml"),
|
||||
OAKCOMMON_E_FAILED);
|
||||
oakcommon_videoparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, Colorspace)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init();
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, "rec709"),
|
||||
OAKCOMMON_OK);
|
||||
int needed = oakcommon_videoparams_get_colorspace(p, nullptr, 0);
|
||||
ASSERT_GT(needed, 0);
|
||||
std::vector<char> buf(needed);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_colorspace(p, buf.data(), needed),
|
||||
needed);
|
||||
EXPECT_STREQ(buf.data(), "rec709");
|
||||
oakcommon_videoparams_free(p);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, TimeInTimebaseUnits)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
|
||||
1920, 1080, 1, 25, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
|
||||
ASSERT_NE(p, nullptr);
|
||||
|
||||
int64_t ts = -1;
|
||||
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(p, 2, 1, &ts),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(ts, 50); // 2 seconds at 25 fps
|
||||
|
||||
// Without a time base the result is AV_NOPTS_VALUE
|
||||
OakCommonVideoParams *q = oakcommon_videoparams_init();
|
||||
ASSERT_NE(q, nullptr);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(q, 2, 1, &ts),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(ts, INT64_MIN);
|
||||
|
||||
oakcommon_videoparams_free(p);
|
||||
oakcommon_videoparams_free(q);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, Equals)
|
||||
{
|
||||
OakCommonVideoParams *a = oakcommon_videoparams_init_basic(
|
||||
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
|
||||
OakCommonVideoParams *b = oakcommon_videoparams_init_basic(
|
||||
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
|
||||
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
|
||||
ASSERT_NE(a, nullptr);
|
||||
ASSERT_NE(b, nullptr);
|
||||
|
||||
int equal = 0;
|
||||
EXPECT_EQ(oakcommon_videoparams_equals(a, b, &equal), OAKCOMMON_OK);
|
||||
EXPECT_EQ(equal, 1);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_set_divider(b, 2), OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_equals(a, b, &equal), OAKCOMMON_OK);
|
||||
EXPECT_EQ(equal, 0);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_equals(nullptr, b, &equal),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_equals(a, nullptr, &equal),
|
||||
OAKCOMMON_E_INVALID);
|
||||
EXPECT_EQ(oakcommon_videoparams_equals(a, b, nullptr),
|
||||
OAKCOMMON_E_INVALID);
|
||||
|
||||
oakcommon_videoparams_free(a);
|
||||
oakcommon_videoparams_free(b);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, XmlRoundTrip)
|
||||
{
|
||||
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
|
||||
1920, 1080, 1, 25, OAKCOMMON_PIXEL_FORMAT_F16, 4, 4, 3,
|
||||
OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST, 2);
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, "rec709"),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(p, 9), OAKCOMMON_OK);
|
||||
|
||||
int needed = oakcommon_videoparams_save_xml(p, nullptr, 0);
|
||||
ASSERT_GT(needed, 0);
|
||||
std::vector<char> buf(needed);
|
||||
ASSERT_EQ(oakcommon_videoparams_save_xml(p, buf.data(), needed), needed);
|
||||
|
||||
OakCommonVideoParams *q = oakcommon_videoparams_init();
|
||||
ASSERT_NE(q, nullptr);
|
||||
ASSERT_EQ(oakcommon_videoparams_load_xml(q, buf.data()), OAKCOMMON_OK);
|
||||
|
||||
int equal = 0;
|
||||
EXPECT_EQ(oakcommon_videoparams_equals(p, q, &equal), OAKCOMMON_OK);
|
||||
EXPECT_EQ(equal, 1);
|
||||
|
||||
int num, den;
|
||||
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(q, &num, &den),
|
||||
OAKCOMMON_OK);
|
||||
EXPECT_EQ(num, 25);
|
||||
EXPECT_EQ(den, 1);
|
||||
|
||||
int cneeded = oakcommon_videoparams_get_colorspace(q, nullptr, 0);
|
||||
std::vector<char> cbuf(cneeded);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_colorspace(q, cbuf.data(), cneeded),
|
||||
cneeded);
|
||||
EXPECT_STREQ(cbuf.data(), "rec709");
|
||||
|
||||
int v;
|
||||
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(q, &v), OAKCOMMON_OK);
|
||||
EXPECT_EQ(v, 9);
|
||||
|
||||
oakcommon_videoparams_free(p);
|
||||
oakcommon_videoparams_free(q);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, StaticHelpers)
|
||||
{
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel_for_format(
|
||||
OAKCOMMON_PIXEL_FORMAT_U8),
|
||||
1);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel_for_format(
|
||||
OAKCOMMON_PIXEL_FORMAT_F32),
|
||||
4);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel_for_format(
|
||||
OAKCOMMON_PIXEL_FORMAT_INVALID),
|
||||
0);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel_for_format(
|
||||
OAKCOMMON_PIXEL_FORMAT_U10, 4),
|
||||
4);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel_for_format(
|
||||
OAKCOMMON_PIXEL_FORMAT_U10, 3),
|
||||
0);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel_for_format(
|
||||
OAKCOMMON_PIXEL_FORMAT_U16, 3),
|
||||
6);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_calculate_buffer_size(
|
||||
100, 50, OAKCOMMON_PIXEL_FORMAT_U8, 4),
|
||||
20000);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_format_is_float(
|
||||
OAKCOMMON_PIXEL_FORMAT_F16),
|
||||
1);
|
||||
EXPECT_EQ(oakcommon_videoparams_format_is_float(OAKCOMMON_PIXEL_FORMAT_U8),
|
||||
0);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_generate_auto_divider(1920, 1080), 2);
|
||||
EXPECT_EQ(oakcommon_videoparams_generate_auto_divider(640, 480), 1);
|
||||
EXPECT_EQ(oakcommon_videoparams_generate_auto_divider(16000, 16000), 16);
|
||||
|
||||
EXPECT_EQ(oakcommon_videoparams_get_scaled_dimension(1920, 2), 960);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_divider_for_target_resolution(
|
||||
3840, 2160, 1920, 1080),
|
||||
2);
|
||||
}
|
||||
|
||||
TEST(CommonVideoParamsCApi, StaticStrings)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
int needed = oakcommon_videoparams_get_name_for_divider(1, nullptr, 0);
|
||||
ASSERT_GT(needed, 0);
|
||||
EXPECT_EQ(oakcommon_videoparams_get_name_for_divider(1, buf, sizeof(buf)),
|
||||
needed);
|
||||
EXPECT_STREQ(buf, "Full");
|
||||
EXPECT_GT(oakcommon_videoparams_get_name_for_divider(4, buf, sizeof(buf)),
|
||||
0);
|
||||
EXPECT_STREQ(buf, "1/4");
|
||||
|
||||
EXPECT_GT(oakcommon_videoparams_get_format_name(OAKCOMMON_PIXEL_FORMAT_U8,
|
||||
buf, sizeof(buf)),
|
||||
0);
|
||||
EXPECT_STREQ(buf, "8-bit");
|
||||
EXPECT_GT(oakcommon_videoparams_get_format_name(
|
||||
OAKCOMMON_PIXEL_FORMAT_INVALID, buf, sizeof(buf)),
|
||||
0);
|
||||
EXPECT_NE(std::string(buf).find("Unknown"), std::string::npos);
|
||||
|
||||
EXPECT_GT(oakcommon_videoparams_frame_rate_to_string(25, 1, buf,
|
||||
sizeof(buf)),
|
||||
0);
|
||||
EXPECT_STREQ(buf, "25 FPS");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(c_api)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +1,313 @@
|
||||
# oaknode 去 Qt 化约定(DEQT)
|
||||
|
||||
> 第一波(核心基建)确立的替换规则。后续波次处理叶子节点文件时**严格照此机械替换,不改任何行为逻辑**。
|
||||
> 有疑问先查本文件;本文件没覆盖的 Qt 类型,在 `src/node/DEQT.md` 补一条规则再动手。
|
||||
|
||||
## 1. 替换映射表
|
||||
|
||||
| Qt | 替代 | 说明 |
|
||||
|---|---|---|
|
||||
| `QString` | `std::string` | 默认参数 `QString()` → `std::string()`;`isEmpty()` → `empty()`;`==/!=` 直接用 |
|
||||
| `QStringList` | `olive::StringList`(= `std::vector<std::string>`,定义在 `node/variant.h`) | |
|
||||
| `QByteArray` | `olive::ByteArray`(= `std::vector<char>`) | base64 用 `olive::byte_array_to_base64()` / `byte_array_from_base64()` |
|
||||
| `QVector<T>` | `std::vector<T>` | `append`→`push_back`,`prepend`→`insert(begin(), x)`,`takeAt(i)`→取值后 `erase(begin()+i)`,`removeAt`→`erase`,`contains`→`std::find(...)!=end()`,`indexOf`→`std::find`-`begin()`(无则 -1),`first/last`→`front/back`,`count/size`→`size()`(必要时 `int(...)`),`isEmpty`→`empty` |
|
||||
| `QList<T>` | `std::vector<T>` | 同上 |
|
||||
| `QStringList::split` / `s.split(':')` | `olive::core::StringUtils::split(s, ':')`(`olive/core/util/stringutils.h`) | |
|
||||
| `QStringLiteral("x")` | `"x"` | |
|
||||
| `QString::number(x)` | `std::to_string(x)`(整数);double/float 用 `snprintf("%g")`(Qt 默认 'g' 6 位有效数字,value.cpp 里有 `number_to_string()` 静态函数可复制) | |
|
||||
| `QString("%1...").arg(a,b)` | 字符串拼接 | 保持参数顺序与原文一致 |
|
||||
| `s.toStdString()` | 直接用(已是 std::string) | |
|
||||
| `s.toFloat()/toDouble()/toLongLong()` | `strtof/strtod/strtoll(s.c_str(), nullptr, 10)` | 失败返回 0 的语义一致 |
|
||||
| `QVariant` | `olive::Variant`(`node/variant.h`) | 见 §2 |
|
||||
| `QVector2D/3D/4D` | `olive::Vector2D/3D/4D`(`node/mathtypes.h`) | float 存储,API 同名(`x()`、`set_x()` … snake_case) |
|
||||
| `QMatrix4x4` | `olive::Matrix4x4`(`node/mathtypes.h`) | 行主序 `m[row][col]`,`inverted()`、`transposed()`、`operator()(r,c)` |
|
||||
| `QPointF` | `olive::PointF`(`node/mathtypes.h`) | `x()/y()/set_x()/set_y()`,运算符齐全 |
|
||||
| `QTransform` | `olive::Matrix4x4` | `translate(x,y)`、`scale(x,y)`、`rotate(deg)` 成员已提供(后乘语义,同 QTransform) |
|
||||
| `QHash<K,V>` | `std::map<K,V>` 或 `std::unordered_map<K,V>` | 需要键排序/迭代稳定用 `std::map`;`value(k)`→查找后返回默认值,`contains`→`count(k)` |
|
||||
| `QMap<K,V>` | `std::map<K,V>` | |
|
||||
| `QMutex` + `QMutexLocker` | `std::mutex` + `std::lock_guard<std::mutex>` | |
|
||||
| `QXmlStreamReader/Writer` | `olive::XmlStreamReader/XmlStreamWriter`(`xmlutils.h`,oakcommon) | API 形状对齐 Qt:`read_next()`→`is_start_element()` 循环;`attributes()` 返回 `std::vector<XmlStreamAttribute>`(`.name`/`.value` 都是 std::string);`readElementText()`→`read_element_text()`;`skipCurrentElement()`→`skip_current_element()`;`xml_read_next_start_element(reader)` 自由函数同 Qt 版辅助 |
|
||||
| `tr("...")` / `QCoreApplication::translate("Ctx","...")` | 直接留原文字符串字面量 `"..."` | 翻译由 app 层负责 |
|
||||
| `Q_OBJECT` / `signals:` / `slots:` / `emit x(...)` | 全部删除 | 见 §4 |
|
||||
| `Q_DECLARE_METATYPE(T)` | 删除 | Variant 不需要注册 |
|
||||
| `qHash(...)` | 删除(改用 `std::map`/`std::unordered_map`,unordered 需要时写 `std::hash` 特化) | |
|
||||
| `QObject::connect/disconnect/sender()` | 删除(事件订阅移出 oaknode) | 连接信号语句整体删除,无对应逻辑保留 |
|
||||
| `foreach (const T &x, list)` | `for (const T &x : list)` | |
|
||||
| `qWarning() << ...` | `fprintf(stderr, ...)` | |
|
||||
| `QFont` | `std::string`(family 名) | k_font 类型值直接存 family 字符串 |
|
||||
| `QDateTime::fromMSecsSinceEpoch(ms, tz).toString(fmt)` | `std::tm`(`gmtime_r`/`localtime_r`)+ 按 Qt 格式 token(`yyyy/MM/dd/hh/mm/ss/zzz` 等)展开的本地静态函数 | 仅 timeformat 节点用;UTC ↔ `QTimeZone::utc()`,local ↔ `QTimeZone::systemTimeZone()` |
|
||||
| `QDateTime::currentMSecsSinceEpoch()` | `std::chrono::system_clock::now()` 转毫秒 | |
|
||||
| `Q_UNUSED(x)` | `(void) x;` | |
|
||||
| `Q_ASSERT(x)` | `assert(x)`(`<cassert>`) | |
|
||||
| `qMax/qMin` | `std::max/std::min`(`<algorithm>`) | |
|
||||
| `qFuzzyCompare(a,b)` | 内联展开 Qt 语义:`std::abs(a-b)*100000.0f <= std::min(std::abs(a),std::abs(b))` | |
|
||||
| `qIsNull(f)` | `f == 0.0f` | |
|
||||
| `Q_PROCESSOR_X86/ARM` | `OLIVE_PROCESSOR_X86/ARM` + `#include "olive/core/util/cpuoptimize.h"` | ARM 走内置 sse2neon |
|
||||
| `QUuid`(render cache uuid 边界) | `std::string`:`set_uuid(text)` / `get_uuid()` 直接收发字符串 | M7 定型 cache 类时遵循 |
|
||||
| `Qt::KeyboardModifiers` | `int`(gizmo_drag_move 参数) | gizmo 波次对齐 |
|
||||
| `qEnvironmentVariableIsSet("X")` | `std::getenv("X") != nullptr` | |
|
||||
| `quintptr` | `uintptr_t` | |
|
||||
| `qAbs(x)` | `std::abs(x)`(`<cstdlib>`/`<cmath>`,按参数类型选头) | |
|
||||
| `QPolygonF` | `std::vector<olive::PointF>` | `translate(d)` → 循环 `p += d`;`QPolygonF(QRectF(l,t,w,h))` 按 Qt 语义展开为 5 点闭包 `(l,t),(l+w,t),(l+w,t+h),(l,t+h),(l,t)` |
|
||||
| `QTransform::map(QPointF)` / `QMatrix4x4::map(QPointF)` / `m.toTransform().map(p)` | `Matrix4x4::map(p)`(mathtypes.h 新增,`PointF map(const PointF&) const`) | 三者对 2D 点语义一致(z=0 透视除法);`toTransform()` 直接去掉 |
|
||||
| `QVector2D::toPointF()` | `Vector2D::to_point_f()`(mathtypes.h 新增) | |
|
||||
| gizmo drag 回调里的 `sender()` | `Node::current_gizmo()`(node.h 新增;返回 `NodeGizmo*`,用法 `static_cast<DraggableGizmo*>(current_gizmo())`) | DraggableGizmo 由 gizmo 波次在直接调用回调前后 `set_current_gizmo()`;回调外为 nullptr |
|
||||
| `QPainter/QBrush/QLinearGradient/QColor` 等 UI 绘制 | 删除,属 app 层 | 在 §4 清单记录 |
|
||||
| `QPainterPath` | `olive::PainterPath`(`node/geometry.h`,最小记录型 POD:`move_to/line_to/cubic_to/translated/elements`) | 填充光栅化走 facade 安装的 `PathFillBackend` 钩子(同文件,inline 变量,默认 nullptr=不绘制),见 §7.10 |
|
||||
| `QRectF` | `olive::RectF`(`node/gizmo/text.h`,gizmo 波次落地:仅 x/y/width/height 数据载体;translate/bounding 在使用点展开) | |
|
||||
| `QPolygonF` | `std::vector<olive::PointF>` | |
|
||||
| `QLineF` | `olive::LineF`(`node/gizmo/line.h`,gizmo 波次落地:两点 POD,`p1()/p2()`) | |
|
||||
| `QTextDocument`/`QTextOption`/`QAbstractTextDocumentLayout`/`QFont`(文本节点排版+栅格化) | `TextLayoutRequest`/`TextLayoutSize`/`TextRenderTarget`/`TextRenderTransform` POD(`node/generator/text/textbackend.h`)+ `TextMeasureBackend`/`TextRenderBackend` 钩子 | k_font 输入值仍按上表存 family 字符串;钩子默认 nullptr=量测返回 0/不绘制,见 §7.10 |
|
||||
| `Qt::Alignment`(gizmo 边界的对齐标志) | `int`,取值按 `TextGizmo::VerticalAlignment`(0=top/1=bottom/2=vcenter,gizmo 波次与 oakengine facade 对齐) | |
|
||||
| `Qt::AltModifier`/`Qt::ShiftModifier`(gizmo_drag_move 位测试) | `int` 位测试,常量值同 Qt(`0x08000000`/`0x02000000`),用文件内 constexpr | gizmo 波次统一 |
|
||||
| render 边界的字符串参数(`ShaderJob::insert`/`set_shader_id`、`AcceleratedJob::get`、`ShaderCode` 构造、`ShaderRequest::id` 等) | 直接传 `std::string`/字面量 | render 头当前仍是 Qt QString 版(M7 波次按 §6 stub 契约转 std::string),语法自查以 stub 为准 |
|
||||
| `QLineF` | `olive::LineF`(`node/gizmo/line.h`,仅 p1()/p2() 数据载体) | 仅 gizmo 用 |
|
||||
| `QRectF` | `olive::RectF`(`node/gizmo/text.h`,x()/y()/width()/height() 数据载体) | 仅文本 gizmo 矩形用 |
|
||||
| `QPolygonF` | `std::vector<olive::PointF>` | `boundingRect()`/`containsPoint()` 由调用方(app/facade)自行实现 |
|
||||
| `QPainterPath` | 删除 | 绘制图元,属 app 层;PathGizmo 只留类壳(层级/类型标识用) |
|
||||
| `Qt::Alignment` | `int`(TextGizmo 垂直对齐:0=Top 1=Bottom 2=VCenter,与 oakengine facade 一致) | gizmo 波次对齐 |
|
||||
| `QUuid`(Project/node uuid) | `std::string`,保留 QUuid 文本格式(带花括号 `{8-4-4-4-12}` 小写 hex);`QUuid::createUuid()` → 本地随机生成同格式字符串(v4/variant 位照设) | 读写均按原文本,工程文件兼容 |
|
||||
| `QFileInfo::completeBaseName()` | `std::filesystem::path(p).filename()` 截取到第一个 `.` 为止 | |
|
||||
| `QFileInfo::exists(p)` | `std::filesystem::exists(p, ec)` | |
|
||||
| `QFileInfo(f).lastModified().toMSecsSinceEpoch()` | `std::filesystem::last_write_time` + file clock→system_clock 换算(`t - file_clock::now() + system_clock::now()`) | |
|
||||
| `QFile` 整文件读/写 | `std::ifstream`/`std::ofstream`(binary)+ `std::stringstream`;`XmlStreamReader` 直接吃 `std::string` | |
|
||||
| `qCompress`/`qUncompress` | zlib `compress2`/`uncompress` + 4 字节大端未压缩长度头(Qt 格式原样) | `.ove` 的 OVEC 段逐字节兼容,见 §7.11 |
|
||||
| `QStandardPaths::CacheLocation` | macOS `$HOME/Library/Caches/oak`,否则 `FileFunctions::get_configuration_location()+"/cache"` | 仅 footage 探针缓存目录;位置变化只导致重新探针 |
|
||||
| `QTimer` 周期回调(footage `check_footage`) | 删除定时器,函数本体保留为 public,由 facade 周期调用 | `qApp->activeWindow()` 门槛一并移到 app 层 |
|
||||
|
||||
### 虚函数命名约定
|
||||
|
||||
Node 的虚函数(含事件钩子 `InputValueChangedEvent`/`InputConnectedEvent`/`InputDisconnectedEvent`/
|
||||
`OutputConnectedEvent`/`LoadFinishedEvent`/`AddedToGraphEvent` 等)**保持原 CamelCase 名字不变**
|
||||
(虚函数 API 形状保持,仅换参数/返回类型)。子类 override 同理,不要 snake_case 化。
|
||||
|
||||
### NodeValueRow 访问
|
||||
|
||||
`NodeValueRow = std::map<std::string, NodeValue>`,const 引用无 `operator[]`:
|
||||
`value[k]` → `value.at(k)`(键必须存在,语义同 const `QHash::operator[]`)。
|
||||
|
||||
### QObject 父子机制的替代(keyframe / node 生命周期)
|
||||
|
||||
- `NodeKeyframe` 持有 `Node *parent_`:构造参数传入,或 `set_parent(Node*)`。
|
||||
`Node::add_keyframe(key)` / `remove_keyframe(key)` 会自动 `set_parent(this)` / `set_parent(nullptr)`——
|
||||
替代原 childEvent 的 ChildAdded/ChildRemoved 分支。**不要再对 keyframe 调 setParent()**。
|
||||
- `NodeInputImmediate::delete_all_keyframes(std::vector<NodeKeyframe*> *reclaimed = nullptr)`:
|
||||
传 nullptr 即删除;传指针则把 keyframe 收回向量(替代原"reparent 到 memory_manager 续命")。
|
||||
- nodeundo 的命令用 `std::unique_ptr<Node>` / `std::vector<std::unique_ptr<Node>>` 替代
|
||||
`QObject memory_manager_`(析构即删未交出的节点,undo 重新入图前 `release()`)。
|
||||
- 节点入图/出图:`graph_->add_node(node)` / `graph_->remove_node(node)`(Project 波次提供;
|
||||
remove 是"摘出不删除")。
|
||||
- gizmo:`Node::add_gizmo()/remove_gizmo()` 替代 childEvent 的 gizmo 分支(gizmo 波次对齐)。
|
||||
|
||||
### include 路径写法(src/node/src 为根)
|
||||
|
||||
- 本模块内:`"node/value.h"`、`"node/keyframe.h"` 等(CMake include root = `src/node/src`)。
|
||||
- oakcommon:`"xmlutils.h"`、`"debug.h"`、`"define.h"`(include dir = `src/common/src`,**没有** `common/` 前缀)。
|
||||
- oakundo:`"undocommand.h"`、`"undostack.h"`(include dir = `src/undo/src`)。
|
||||
- oakcore C++ 封装:`"olive/core/util/color.h"`、`"olive/core/util/bezier.h"`、`"olive/core/util/rational.h"`、`"olive/core/util/timerange.h"`、`"olive/core/util/stringutils.h"`。
|
||||
- `render/...`、`codec/...`、`pluginSupport/...`(OpenFX)include **原样保留**(M7/M9 处理),禁止新增。
|
||||
|
||||
### olive 命名空间别名(value.h 已建立,直接可用)
|
||||
|
||||
```cpp
|
||||
namespace olive {
|
||||
using core::Bezier; // olive::core::Bezier
|
||||
using core::Color; // olive::core::Color (float RGBA, red()/green()/blue()/alpha())
|
||||
using core::Rational; // olive::core::Rational (to_string()/from_string() 是 std::string)
|
||||
}
|
||||
```
|
||||
|
||||
`olive::core::TimeRange` 在 node.h 以 `using core::TimeRange;` 引入(见 node.h)。
|
||||
|
||||
## 2. Variant(QVariant 替代)速查
|
||||
|
||||
```cpp
|
||||
#include "node/variant.h" // olive::Variant, olive::StringList, olive::ByteArray
|
||||
|
||||
Variant v; // null,v.is_null() == true
|
||||
Variant a = 42; // int(有符号统一存 int64_t)
|
||||
Variant b = 3.14; // double(float 也存 double)
|
||||
Variant c = std::string("x");
|
||||
Variant d = Vector2D(1, 2); // 任意可复制类型,类型擦除存储(原 Q_DECLARE_METATYPE 场景)
|
||||
|
||||
v.value<int64_t>(); // 取数(QVariant::value<T>())
|
||||
v.value<Vector2D>(); // 自定义类型必须类型精确匹配,否则返回 T()
|
||||
v.to_double(); v.to_float(); // QVariant 风格转换(数值互通、字符串解析)
|
||||
v.to_int(); v.to_uint();
|
||||
v.to_long_long(); v.to_u_long_long();
|
||||
v.to_bool();
|
||||
v.to_string(); // double 按 %g 格式化(同 QString::number)
|
||||
v.to_string_list(); // QStringList
|
||||
v.to_byte_array(); // QByteArray
|
||||
v.can_convert<Vector2D>(); // QVariant::canConvert<T>()
|
||||
Variant::from_value(x); // QVariant::fromValue
|
||||
v == w; // 数值跨 kind 按值比较;自定义类型用其 operator==
|
||||
```
|
||||
|
||||
- `value<QString>()` → `value<std::string>()` 或 `to_string()`。
|
||||
- 原来 `QVariant::fromValue(Color(...))` → `Variant::from_value(Color(...))`。
|
||||
- 函数返回 `QVariant` 的(如 `Node::get_standard_value()`)→ 返回 `Variant`,调用处照旧 `Variant v = ...; v.to_double()`。
|
||||
|
||||
## 3. 核心类新 API 形态
|
||||
|
||||
### NodeValue(`node/value.h`)
|
||||
|
||||
```cpp
|
||||
NodeValue v(NodeValue::k_float, 1.5, from_node); // 构造(模板,不变)
|
||||
v.type(); // NodeValue::Type
|
||||
v.value<double>(); v.to_double(); v.to_string(); // 取数
|
||||
v.data(); // const Variant &
|
||||
v.set_value(x);
|
||||
NodeValue::value_to_string(type, variant, is_key_track); // std::string
|
||||
NodeValue::string_to_value(type, str, is_key_track); // Variant
|
||||
NodeValue::split_normal_value_into_track_values(type, v); // std::vector<Variant>
|
||||
NodeValue::combine_track_values_into_normal_value(type, split); // Variant
|
||||
v.to_split_value(); // SplitValue = std::vector<Variant>
|
||||
```
|
||||
|
||||
### NodeValueTable
|
||||
|
||||
`push/prepend/at/take_at/count/has/remove/clear/is_empty/get(type, tag)/merge(std::vector<NodeValueTable>)`。
|
||||
`NodeValueRow = std::map<std::string, NodeValue>`。
|
||||
|
||||
### Node(`node/node.h`)
|
||||
|
||||
- 不再继承 QObject;纯虚 `name()/id()` 返回 `std::string`,`category()` 返回 `std::vector<CategoryID>`,
|
||||
`description()` 返回 `std::string`,`sub_category()` 返回 `std::string`。
|
||||
- 输入遍历:`for (const std::string &id : node->inputs())`。
|
||||
- `Position`:`PointF position; bool expanded;`,`load(XmlStreamReader*)` / `save(XmlStreamWriter*)`。
|
||||
- 序列化:`load(XmlStreamReader*, SerializedData*)` / `save(XmlStreamWriter*)`。
|
||||
- gizmo/undo 等签名里的 `MultiUndoCommand` 来自 oakundo(`undocommand.h`)。
|
||||
|
||||
### NodeKeyframe(`node/keyframe.h`)
|
||||
|
||||
- `bezier_control_in()/out()` 返回 `const PointF &`;不再有任何 signal。
|
||||
- 值类型:`Variant value()` / `set_value(const Variant&)`。
|
||||
|
||||
## 4. 被删除的东西(第一波)
|
||||
|
||||
### Node 的 signals(整组删除,facade 层经 oakengine_event 发通知)
|
||||
|
||||
label_changed、color_changed、value_changed、input_connected、input_disconnected、
|
||||
output_connected、output_disconnected、input_value_hint_changed、input_property_changed、
|
||||
links_changed、input_array_size_changed、keyframe_added、keyframe_removed、
|
||||
keyframe_time_changed、message_count_changed、keyframe_type_changed、
|
||||
keyframe_value_changed、keyframe_enable_changed、input_added、input_removed、
|
||||
input_name_changed、input_data_type_changed、added_to_graph、removed_from_graph、
|
||||
node_added_to_context、node_position_in_context_changed、node_removed_from_context、
|
||||
input_flags_changed。
|
||||
|
||||
### NodeKeyframe 的 signals
|
||||
|
||||
value_changed、time_changed、type_changed、bezier_control_in_changed、
|
||||
bezier_control_out_changed(删除理由同上)。
|
||||
|
||||
### UI 绘制(属 app 层)
|
||||
|
||||
`Node::gradient_color()`、`Node::brush()`(QLinearGradient/QBrush);`Node::color()` 保留(返回 olive::core::Color 数据)。
|
||||
`Node::gizmo_transformation()` 的 QTransform 改 Matrix4x4(数据类型,不是绘制)。
|
||||
|
||||
### 其他
|
||||
|
||||
- `childEvent(QChildEvent*)`(QObject 事件机制)——keyframe 分支变 `Node::add_keyframe()/remove_keyframe()`,
|
||||
gizmo 分支变 `Node::add_gizmo()/remove_gizmo()`。
|
||||
- `Q_DECLARE_METATYPE`、`qHash()` 重载。
|
||||
- nodeundo 的 23 处 `get_relevant_project()` override(modified 语义由 oakundo 回调承担)。
|
||||
- `NodeAddCommand::push_to_thread(QThread*)`(QObject 线程亲和)。
|
||||
- render cache 的 `QUuid` uuid:改 `std::string`(见映射表)。
|
||||
- keyframe 失效通知链:原 keyframe signal→Node slot 的 5 条(invalidate_from_keyframe_*)随 signal 删除,
|
||||
函数本体保留为 public 成员(带 `NodeKeyframe *key` 参数替代 sender()),**调用方由 facade/keyframe 波次接**。
|
||||
|
||||
## 5. undo(oakundo)适配
|
||||
|
||||
- `#include "undocommand.h"`,`olive::UndoCommand` / `olive::MultiUndoCommand`。
|
||||
- **没有** `get_relevant_project()`:原来 `get_relevant_project()->set_modified(true)` 的语义由
|
||||
`UndoCommand::set_modified_callbacks(is_modified, set_modified)` +
|
||||
`redo_and_set_modified()/undo_and_set_modified()` 承担。nodeundo 的命令类不再碰 Project 的
|
||||
modified 标记,回调由 facade 层装配。
|
||||
- `prepare()` 仍是 protected virtual;`UndoStack::push(cmd)` 语义不变(见 oakundo undostack.h)。
|
||||
|
||||
## 6. 语法自检
|
||||
|
||||
整库编译本波必然失败(叶子未改),但每个改过的 .cpp 必须过 `-fsyntax-only`(缺失的 render/ 等头用 stub 垫):
|
||||
|
||||
```bash
|
||||
# stub 头在 /tmp/oakstub(render/texture.h 等,仅语法检查用,不进仓库)
|
||||
cd src/node/src/node && c++ -std=c++17 -fsyntax-only -Wall \
|
||||
-I. -I.. -I/tmp/oakstub \
|
||||
-I$OAK/src/common/src -I$OAK/core/include -I$OAK/src/undo/src \
|
||||
-I/opt/homebrew/include -I/opt/homebrew/include/Imath \
|
||||
-I$OAK/third_party/openfx/include -I$OAK/third_party/openfx/HostSupport/include \
|
||||
<file.cpp>
|
||||
```
|
||||
|
||||
(`$OAK` = /Users/sunyu/Projects/oak)
|
||||
|
||||
## 7. 已知行为注意点与后续波次依赖
|
||||
|
||||
1. `NodeValueDatabase::merge()`:原 `QHash::values()` 无序,现按 key 字典序收集后合并——
|
||||
merge 是按优先级覆盖语义,若调用方依赖原哈希序需注意。
|
||||
2. `NodeValueTable` 新增了 `operator==/!=`(Variant 存 `NodeValueTableArray` 需要)。
|
||||
3. `FrameHashCache(this)` 等 cache 构造仍传 `this`(现在是 `Node *` 而非 `QObject *`)——
|
||||
M7 定型 render cache 类时构造参数需接受 `Node *`(或届时改 nullptr,需裁决)。
|
||||
4. `traverser.h` 新增 `TimeRangeLess` 比较器(`QHash<TimeRange,…>`→`std::map` 需要严格弱序,
|
||||
按 `(in(), out())` 排序)。
|
||||
5. nodeundo 假定 `Project::add_node/remove_node/is_being_cleared/
|
||||
get_number_of_contexts_node_is_in(node,bool)` 存在(project 波次提供,M3 手册 §2 已冻结
|
||||
add/remove_node 函数族)。
|
||||
6. `factory.cpp` 的 `pluginSupport/` include、`traverser.h` 的 `"common/cancelableobject.h"`
|
||||
原样保留(后者 oakcommon 没有,留 M 系列裁决)。
|
||||
7. `node.h` 仍 include `config/config.h`、`ui/colorcoding.h`(engine Qt 头,`color()` 用到
|
||||
`OAK_CONFIG_STR(...)`)——config 波次处理。
|
||||
8. `Node::gizmo_drag_move` 的 modifiers 参数为 `int`(原 `Qt::KeyboardModifiers`)。
|
||||
9. timeformat 的 `format_date_time()` 不实现 `MMMM`(月名)/`dddd`(星期名)本地化 token
|
||||
(原默认格式 `hh:mm:ss` 用不到);负 epoch 毫秒的 `zzz` 取模与 Qt 有边界差异(实际输入不会触发)。
|
||||
10. generator 波次(matrix/noise/polygon/shape/solid/text/multicam):
|
||||
- `gizmo_drag_move` 原 slot 用 `sender()` 取被拖 gizmo;gizmo 波次定型为
|
||||
`Node::current_gizmo()`(DraggableGizmo 直调 3 参虚函数期间设置),本波照此收口。
|
||||
- `new PathGizmo(this)`/`new TextGizmo(this)` 后必须显式 `add_gizmo(...)`
|
||||
(NodeGizmo 构造不再自注册;`add_draggable_gizmo<>()` 内部已含)。
|
||||
- PathGizmo 不再存储路径(gizmo 波次:绘制数据归 app 层),polygon 原
|
||||
`poly_gizmo_->set_path(...)` 调用删除;`generate_path()` 仍供 generate_frame 使用。
|
||||
- polygon/text 的 `generate_frame()` 光栅化(QPainter/QTextDocument)委托给
|
||||
`PathFillBackend`/`TextMeasureBackend`+`TextRenderBackend` 钩子(geometry.h /
|
||||
textbackend.h);facade 未安装后端前输出为空白(被迫行为差异,facade 波次恢复)。
|
||||
像素缓冲清零、alpha 移植循环等纯数据逻辑原样保留。
|
||||
- textv2 非 SSE 死分支里的 `VideoParams::kRGBAChannelCount` 拼写在任何 Qt 头中都不存在
|
||||
(原代码靠 Q_PROCESSOR_X86/ARM 宏永远不编译该分支),改为 `k_rgba_channel_count`。
|
||||
10. 叶子效果波次(audio/distort/effect/filter/keying)新增约定(见映射表新增行):
|
||||
`Matrix4x4::map(PointF)`、`Vector2D::to_point_f()` 为 mathtypes.h 增量补充;
|
||||
`Node::current_gizmo()`/`set_current_gizmo()` 为 node.h 增量补充,替代 gizmo 回调中的
|
||||
`sender()`;gizmo 波次须在 DraggableGizmo 直接调用 drag 回调时包一层
|
||||
`set_current_gizmo(this)` / `set_current_gizmo(nullptr)`。
|
||||
gizmo 头(`node/gizmo/polygon.h` 的 `set_polygon(QPolygonF)`、`point.h`/`text.h` 的
|
||||
QRectF/QTransform 接口)仍属 gizmo 波次,叶子文件按新类型(`std::vector<PointF>`)调用,
|
||||
签名对齐由 gizmo 波次完成。
|
||||
10. gizmo 波次:`NodeGizmo` 构造**不自动**向 parent 注册(`Node::add_draggable_gizmo()` 已显式
|
||||
`add_gizmo()`);析构时若持有 parent 则 `remove_gizmo()`(对应原 dtor 的 `setParent(nullptr)`)。
|
||||
直接 `new XGizmo(node)` 的旧调用点(polygon/textv3 等)改完信号后需补 `add_gizmo()`。
|
||||
`DraggableGizmo` 的 handle_start/handle_movement 信号改为直调
|
||||
`parent_node()->gizmo_drag_start()/gizmo_drag_move()`。各 gizmo 的 `draw(QPainter*)`、
|
||||
`PointGizmo::get_clicking_rect()/get_drawing_rect()/get_standard_radius()`
|
||||
(依赖 `QFontMetrics(qApp->font())`)删除,归 app 层(facade 的 hit-test 需在 app 侧重实现)。
|
||||
`TextGizmo` 的 4 个信号(activated/deactivated/rect_changed/vertical_alignment_changed)删除,
|
||||
由 oakengine 事件机制承担。
|
||||
11. project 波次(project/folder/footage/sequence/serializer):
|
||||
- `Project` 不再继承 QObject:`childEvent` 的 ChildAdded/ChildRemoved 分支变
|
||||
`Project::add_node(node)` / `remove_node(node)`(remove 摘出不删除);节点所有权归
|
||||
Project(`clear()`/析构删除)。`ColorManager` 由 `std::unique_ptr` 持有(原 QObject 父子)。
|
||||
- `Project`/`Folder`/`Sequence`/`Footage` 的信号整组删除(含 Folder 的
|
||||
begin/end_insert/remove_item、Sequence 的 track_added/removed/subtitles_changed、
|
||||
Footage 的 proxy_settings_changed、Project 的 name_changed/modified_changed/
|
||||
setting_changed/node_added/node_removed 等),通知由 facade 层承担。
|
||||
- `Sequence::update_track_cache()` 变 public 成员;TrackList 波次须在原
|
||||
track_list_changed/length_changed 发射点直调 `sequence->update_track_cache()` /
|
||||
`verify_length()`。`Footage::check_footage()/default_color_space_changed()/
|
||||
proxy_ready()/proxy_finished()` 同理保留为 public 待 facade/ProxyManager 波次接线。
|
||||
- 序列化 XML 元素/属性名与读写顺序逐字节保持不变;`XmlStreamWriter` 不再输出
|
||||
XML 声明与自动缩进(紧凑 XML),新旧 reader 均兼容。OVEC 压缩段为
|
||||
qCompress 兼容格式(zlib + 4 字节大端长度)。
|
||||
- `Footage::generate_frame()` 的离线媒体警示帧:QImage/QPainter 光栅化改为纯像素
|
||||
循环(深红底+斜纹),文字叠层("Media Offline")与抗锯齿丢失(被迫行为差异)。
|
||||
- timeline 边界(M4):`TimelineMarker`/`TimelineWorkArea`/`TimelineMarkerList` 调用按
|
||||
去 Qt 形态书写(std::string、XmlStreamReader/Writer),签名对齐由 M4 完成。
|
||||
@@ -0,0 +1,15 @@
|
||||
target_sources(oaknode PRIVATE
|
||||
block.cpp
|
||||
colormanager.cpp
|
||||
factory.cpp
|
||||
folder.cpp
|
||||
footage.cpp
|
||||
group.cpp
|
||||
keyframe.cpp
|
||||
node.cpp
|
||||
project.cpp
|
||||
sequence.cpp
|
||||
serializer.cpp
|
||||
track.cpp
|
||||
traverser.cpp
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_NODE_ALIVECOUNT_H
|
||||
#define OAK_EDITOR_NODE_ALIVECOUNT_H
|
||||
|
||||
/**
|
||||
* @brief Shared live-object counter hooks (internal, not installed).
|
||||
*
|
||||
* The counter itself and the public oaknode_debug_alive_count() live in
|
||||
* the node family (src/node/c_api/node.cpp); these hooks have external
|
||||
* linkage so the other families' create/free functions can participate.
|
||||
*/
|
||||
namespace oaknode_c_api
|
||||
{
|
||||
|
||||
void alive_inc();
|
||||
void alive_dec();
|
||||
|
||||
}
|
||||
|
||||
#endif //OAK_EDITOR_NODE_ALIVECOUNT_H
|
||||
@@ -0,0 +1,477 @@
|
||||
/***
|
||||
|
||||
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 "node/block.h"
|
||||
|
||||
#include "alivecount.h"
|
||||
|
||||
#include "block/block.h"
|
||||
#include "block/clip/clip.h"
|
||||
#include "block/gap/gap.h"
|
||||
#include "block/transition/crossdissolve/crossdissolvetransition.h"
|
||||
#include "block/transition/diptocolor/diptocolortransition.h"
|
||||
#include "block/transition/transition.h"
|
||||
#include "output/track/track.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::Block *impl(OakNodeBlock *h)
|
||||
{
|
||||
return reinterpret_cast<olive::Block *>(h);
|
||||
}
|
||||
|
||||
olive::ClipBlock *clip_impl(OakNodeBlock *h)
|
||||
{
|
||||
return h ? dynamic_cast<olive::ClipBlock *>(impl(h)) : nullptr;
|
||||
}
|
||||
|
||||
olive::TransitionBlock *transition_impl(OakNodeBlock *h)
|
||||
{
|
||||
return h ? dynamic_cast<olive::TransitionBlock *>(impl(h)) : nullptr;
|
||||
}
|
||||
|
||||
OakNodeBlock *wrap(olive::Block *b)
|
||||
{
|
||||
return reinterpret_cast<OakNodeBlock *>(b);
|
||||
}
|
||||
|
||||
int get_rational(const olive::core::Rational &r, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!numerator || !denominator) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*numerator = r.numerator();
|
||||
*denominator = r.denominator();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
OakNodeBlock *create_block(Args &&...args)
|
||||
{
|
||||
try {
|
||||
T *b = new T(std::forward<Args>(args)...);
|
||||
oaknode_c_api::alive_inc();
|
||||
return wrap(b);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakNodeBlock *oaknode_block_clip_create(void)
|
||||
{
|
||||
return create_block<olive::ClipBlock>();
|
||||
}
|
||||
|
||||
OakNodeBlock *oaknode_block_gap_create(void)
|
||||
{
|
||||
return create_block<olive::GapBlock>();
|
||||
}
|
||||
|
||||
OakNodeBlock *oaknode_block_transition_create(int kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case OAKNODE_TRANSITION_CROSS_DISSOLVE:
|
||||
return create_block<olive::CrossDissolveTransition>();
|
||||
case OAKNODE_TRANSITION_DIP_TO_COLOR:
|
||||
return create_block<olive::DipToColorTransition>();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_block_free(OakNodeBlock *block)
|
||||
{
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
delete impl(block);
|
||||
oaknode_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oaknode_block_get_in(OakNodeBlock *block, int *numerator, int *denominator)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(block)->in(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_block_set_in(OakNodeBlock *block, int numerator, int denominator)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(block)->set_in(olive::core::Rational(numerator, denominator));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_out(OakNodeBlock *block, int *numerator, int *denominator)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(block)->out(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_block_set_out(OakNodeBlock *block, int numerator, int denominator)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(block)->set_out(olive::core::Rational(numerator, denominator));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_length(OakNodeBlock *block, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(block)->length(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_block_set_length_and_media_out(OakNodeBlock *block, int numerator,
|
||||
int denominator)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(block)->set_length_and_media_out(
|
||||
olive::core::Rational(numerator, denominator));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_set_length_and_media_in(OakNodeBlock *block, int numerator,
|
||||
int denominator)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(block)->set_length_and_media_in(
|
||||
olive::core::Rational(numerator, denominator));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_enabled(OakNodeBlock *block, int *enabled)
|
||||
{
|
||||
if (!block || !enabled) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*enabled = impl(block)->is_enabled() ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_set_enabled(OakNodeBlock *block, int enabled)
|
||||
{
|
||||
if (!block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(block)->set_enabled(enabled != 0);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_previous(OakNodeBlock *block, OakNodeBlock **out)
|
||||
{
|
||||
if (!block || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*out = wrap(impl(block)->previous());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_next(OakNodeBlock *block, OakNodeBlock **out)
|
||||
{
|
||||
if (!block || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*out = wrap(impl(block)->next());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_track(OakNodeBlock *block, OakNodeTrack **out)
|
||||
{
|
||||
if (!block || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*out = reinterpret_cast<OakNodeTrack *>(impl(block)->track());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_link(OakNodeBlock *a, OakNodeBlock *b)
|
||||
{
|
||||
if (!a || !b) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return olive::Node::link(impl(a), impl(b)) ? OAKNODE_OK : OAKNODE_E_FAILED;
|
||||
}
|
||||
|
||||
int oaknode_block_unlink(OakNodeBlock *a, OakNodeBlock *b)
|
||||
{
|
||||
if (!a || !b) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return olive::Node::unlink(impl(a), impl(b)) ? OAKNODE_OK : OAKNODE_E_FAILED;
|
||||
}
|
||||
|
||||
int oaknode_block_are_linked(OakNodeBlock *a, OakNodeBlock *b, int *linked)
|
||||
{
|
||||
if (!a || !b || !linked) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*linked = olive::Node::are_linked(impl(a), impl(b)) ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_link_count(OakNodeBlock *block, int *count)
|
||||
{
|
||||
if (!block || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*count = int(impl(block)->links().size());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_block_get_link_at(OakNodeBlock *block, int index,
|
||||
OakNodeBlock **out)
|
||||
{
|
||||
if (!block || !out || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
const auto &links = impl(block)->links();
|
||||
if (index >= int(links.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = wrap(static_cast<olive::Block *>(links.at(index)));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- Clip */
|
||||
|
||||
int oaknode_clip_get_media_in(OakNodeBlock *clip, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(c->media_in(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_clip_set_media_in(OakNodeBlock *clip, int numerator,
|
||||
int denominator)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
c->set_media_in(olive::core::Rational(numerator, denominator));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_get_speed(OakNodeBlock *clip, double *speed)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c || !speed) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*speed = c->speed();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_set_speed(OakNodeBlock *clip, double speed)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
c->set_standard_value(olive::ClipBlock::k_speed_input, speed);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_get_reverse(OakNodeBlock *clip, int *reverse)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c || !reverse) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*reverse = c->reverse() ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_set_reverse(OakNodeBlock *clip, int reverse)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
c->set_reverse(reverse != 0);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_get_maintain_audio_pitch(OakNodeBlock *clip, int *maintain)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c || !maintain) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*maintain = c->maintain_audio_pitch() ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_set_maintain_audio_pitch(OakNodeBlock *clip, int maintain)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
c->set_maintain_audio_pitch(maintain != 0);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_get_loop_mode(OakNodeBlock *clip, int *loop_mode)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c || !loop_mode) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*loop_mode = int(c->loop_mode());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_set_loop_mode(OakNodeBlock *clip, int loop_mode)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
c->set_loop_mode(static_cast<olive::LoopMode>(loop_mode));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_clip_get_track_type(OakNodeBlock *clip, int *type)
|
||||
{
|
||||
olive::ClipBlock *c = clip_impl(clip);
|
||||
if (!c || !type) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*type = int(c->get_track_type());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------- Transition */
|
||||
|
||||
int oaknode_transition_get_in_offset(OakNodeBlock *transition, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(t->in_offset(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_transition_get_out_offset(OakNodeBlock *transition, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(t->out_offset(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_transition_get_offset_center(OakNodeBlock *transition,
|
||||
int *numerator, int *denominator)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(t->offset_center(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_transition_set_offset_center(OakNodeBlock *transition,
|
||||
int numerator, int denominator)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
t->set_offset_center(olive::core::Rational(numerator, denominator));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_transition_set_offsets_and_length(OakNodeBlock *transition,
|
||||
int in_num, int in_den,
|
||||
int out_num, int out_den)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
t->set_offsets_and_length(olive::core::Rational(in_num, in_den),
|
||||
olive::core::Rational(out_num, out_den));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_transition_is_dual(OakNodeBlock *transition, int *dual)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t || !dual) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*dual = t->is_dual_transition() ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_transition_get_connected_out_block(OakNodeBlock *transition,
|
||||
OakNodeBlock **out)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*out = wrap(t->connected_out_block());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_transition_get_connected_in_block(OakNodeBlock *transition,
|
||||
OakNodeBlock **out)
|
||||
{
|
||||
olive::TransitionBlock *t = transition_impl(transition);
|
||||
if (!t || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*out = wrap(t->connected_in_block());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/***
|
||||
|
||||
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 "node/colormanager.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "alivecount.h"
|
||||
|
||||
#include "color/colormanager/colormanager.h"
|
||||
#include "colortransform.h"
|
||||
#include "project.h"
|
||||
|
||||
// Same handle-echo pattern as sequence.cpp: oakcommon defines
|
||||
// `struct OakCommonColorTransform { olive::ColorTransform impl; }`
|
||||
// (src/common/c_api/colortransform.cpp) without exporting the definition.
|
||||
struct OakCommonColorTransform {
|
||||
olive::ColorTransform impl;
|
||||
};
|
||||
|
||||
struct OakNodeColorManager {
|
||||
olive::ColorManager impl;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int copy_string(const std::string &value, char *buf, int buf_size)
|
||||
{
|
||||
int needed = int(value.size()) + 1;
|
||||
if (buf && buf_size >= needed) {
|
||||
memcpy(buf, value.c_str(), needed);
|
||||
}
|
||||
return needed;
|
||||
}
|
||||
|
||||
bool has_config(olive::ColorManager *cm)
|
||||
{
|
||||
return cm && cm->get_config() != nullptr;
|
||||
}
|
||||
|
||||
int list_at(const olive::StringList &list, int index, char *buf, int buf_size)
|
||||
{
|
||||
if (index < 0 || index >= int(list.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
return copy_string(list.at(index), buf, buf_size);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakNodeColorManager *oaknode_colormanager_init(OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
auto *m = new OakNodeColorManager{
|
||||
olive::ColorManager(reinterpret_cast<olive::Project *>(project))};
|
||||
oaknode_c_api::alive_inc();
|
||||
return m;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_colormanager_free(OakNodeColorManager *manager)
|
||||
{
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
delete manager;
|
||||
oaknode_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oaknode_colormanager_initialize(OakNodeColorManager *manager)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
manager->impl.init();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_set_up_default_config(void)
|
||||
{
|
||||
try {
|
||||
olive::ColorManager::set_up_default_config();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_config_filename(OakNodeColorManager *manager,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return copy_string(manager->impl.get_config_filename(), buf, buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_set_config_filename(OakNodeColorManager *manager,
|
||||
const char *filename)
|
||||
{
|
||||
if (!manager || !filename) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
manager->impl.set_config_filename(filename);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_update_config_from_filename(
|
||||
OakNodeColorManager *manager)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
manager->impl.update_config_from_filename();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_default_input_color_space(
|
||||
OakNodeColorManager *manager, char *buf, int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return copy_string(manager->impl.get_default_input_color_space(), buf,
|
||||
buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_set_default_input_color_space(
|
||||
OakNodeColorManager *manager, const char *colorspace)
|
||||
{
|
||||
if (!manager || !colorspace) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
manager->impl.set_default_input_color_space(colorspace);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_reference_color_space(
|
||||
OakNodeColorManager *manager, char *buf, int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return copy_string(manager->impl.get_reference_color_space(), buf,
|
||||
buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_compliant_color_space(
|
||||
OakNodeColorManager *manager, const char *colorspace, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!manager || !colorspace) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return copy_string(manager->impl.get_compliant_color_space(colorspace), buf,
|
||||
buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_colorspace_for_ffmpeg_tags(
|
||||
OakNodeColorManager *manager, int primaries, int trc, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return copy_string(
|
||||
manager->impl.get_colorspace_for_ffmpeg_tags(primaries, trc), buf,
|
||||
buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_display_count(OakNodeColorManager *manager,
|
||||
int *count)
|
||||
{
|
||||
if (!manager || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
*count = int(manager->impl.list_available_displays().size());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_display_at(OakNodeColorManager *manager,
|
||||
int index, char *buf, int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return list_at(manager->impl.list_available_displays(), index, buf,
|
||||
buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_default_display(OakNodeColorManager *manager,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return copy_string(manager->impl.get_default_display(), buf, buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_view_count(OakNodeColorManager *manager,
|
||||
const char *display, int *count)
|
||||
{
|
||||
if (!manager || !display || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
*count = int(manager->impl.list_available_views(display).size());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_view_at(OakNodeColorManager *manager,
|
||||
const char *display, int index, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!manager || !display) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return list_at(manager->impl.list_available_views(display), index, buf,
|
||||
buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_default_view(OakNodeColorManager *manager,
|
||||
const char *display, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!manager || !display) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return copy_string(manager->impl.get_default_view(display), buf, buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_look_count(OakNodeColorManager *manager,
|
||||
int *count)
|
||||
{
|
||||
if (!manager || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
*count = int(manager->impl.list_available_looks().size());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_look_at(OakNodeColorManager *manager, int index,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return list_at(manager->impl.list_available_looks(), index, buf, buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_colorspace_count(OakNodeColorManager *manager,
|
||||
int *count)
|
||||
{
|
||||
if (!manager || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
*count = int(manager->impl.list_available_colorspaces().size());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_colorspace_at(OakNodeColorManager *manager,
|
||||
int index, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!manager) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
return list_at(manager->impl.list_available_colorspaces(), index, buf,
|
||||
buf_size);
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_default_luma_coefs(OakNodeColorManager *manager,
|
||||
double rgb[3])
|
||||
{
|
||||
if (!manager || !rgb) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
manager->impl.get_default_luma_coefs(rgb);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_colormanager_get_compliant_color_transform(
|
||||
OakNodeColorManager *manager, const OakCommonColorTransform *transform,
|
||||
int force_display, OakCommonColorTransform **out)
|
||||
{
|
||||
if (!manager || !transform || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!has_config(&manager->impl)) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
try {
|
||||
*out = new OakCommonColorTransform{
|
||||
manager->impl.get_compliant_color_space(transform->impl,
|
||||
force_display != 0)};
|
||||
} catch (...) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/***
|
||||
|
||||
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 "node/factory.h"
|
||||
|
||||
#include "factory.h"
|
||||
|
||||
#include "valueconvert.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
inline OakNodeNode *from_node(olive::Node *node)
|
||||
{
|
||||
return reinterpret_cast<OakNodeNode *>(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int oaknode_factory_initialize(void)
|
||||
{
|
||||
try {
|
||||
if (olive::NodeFactory::get_library().empty()) {
|
||||
olive::NodeFactory::initialize();
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_factory_destroy(void)
|
||||
{
|
||||
try {
|
||||
olive::NodeFactory::destroy();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_factory_id_count(int *out_count)
|
||||
{
|
||||
if (!out_count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
if (olive::NodeFactory::get_library().empty()) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
*out_count = static_cast<int>(olive::NodeFactory::get_library().size());
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_factory_id_at(int index, char *buf, int buf_size)
|
||||
{
|
||||
try {
|
||||
const std::vector<olive::Node *> &library =
|
||||
olive::NodeFactory::get_library();
|
||||
if (library.empty()) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
if (index < 0 || index >= static_cast<int>(library.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
return oaknode_c_api::copy_string(library[size_t(index)]->id(), buf,
|
||||
buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_factory_name_from_id(const char *type_id, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!type_id) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return oaknode_c_api::copy_string(
|
||||
olive::NodeFactory::get_name_from_id(type_id), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeNode *oaknode_factory_create_from_id(const char *type_id)
|
||||
{
|
||||
if (!type_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Node *node = olive::NodeFactory::create_from_id(type_id);
|
||||
if (node) {
|
||||
oaknode_c_api::alive_inc();
|
||||
}
|
||||
return from_node(node);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_factory_node_at(int index, OakNodeNode **out_node)
|
||||
{
|
||||
if (!out_node) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::vector<olive::Node *> &library =
|
||||
olive::NodeFactory::get_library();
|
||||
if (library.empty()) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
if (index < 0 || index >= static_cast<int>(library.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out_node = from_node(library[size_t(index)]);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/***
|
||||
|
||||
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 "node/folder.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
#include "../src/project.h"
|
||||
#include "../src/project/folder/folder.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::Folder *to_cpp(OakNodeFolder *folder)
|
||||
{
|
||||
return reinterpret_cast<olive::Folder *>(folder);
|
||||
}
|
||||
|
||||
const olive::Folder *to_cpp(const OakNodeFolder *folder)
|
||||
{
|
||||
return reinterpret_cast<const olive::Folder *>(folder);
|
||||
}
|
||||
|
||||
olive::Node *to_cpp(OakNodeNode *node)
|
||||
{
|
||||
return reinterpret_cast<olive::Node *>(node);
|
||||
}
|
||||
|
||||
const olive::Node *to_cpp(const OakNodeNode *node)
|
||||
{
|
||||
return reinterpret_cast<const olive::Node *>(node);
|
||||
}
|
||||
|
||||
OakNodeNode *to_c(olive::Node *node)
|
||||
{
|
||||
return reinterpret_cast<OakNodeNode *>(node);
|
||||
}
|
||||
|
||||
olive::Project *to_cpp(OakNodeProject *project)
|
||||
{
|
||||
return reinterpret_cast<olive::Project *>(project);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakNodeFolder *oaknode_folder_create(OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
auto *folder = new (std::nothrow) olive::Folder();
|
||||
if (!folder) {
|
||||
return NULL;
|
||||
}
|
||||
to_cpp(project)->add_node(folder);
|
||||
return reinterpret_cast<OakNodeFolder *>(folder);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_folder_child_count(const OakNodeFolder *folder)
|
||||
{
|
||||
if (!folder) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_cpp(folder)->item_child_count();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeNode *oaknode_folder_child_at(const OakNodeFolder *folder, int index)
|
||||
{
|
||||
if (!folder || index < 0 || index >= to_cpp(folder)->item_child_count()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_c(to_cpp(folder)->item_child(index));
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_folder_add_child(OakNodeFolder *folder, OakNodeNode *child)
|
||||
{
|
||||
if (!folder || !child) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Folder *f = to_cpp(folder);
|
||||
olive::Node *c = to_cpp(child);
|
||||
|
||||
if (c->folder()) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
|
||||
olive::FolderAddChild cmd(f, c);
|
||||
cmd.redo_now();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_folder_remove_child(OakNodeFolder *folder, OakNodeNode *child)
|
||||
{
|
||||
if (!folder || !child) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Folder *f = to_cpp(folder);
|
||||
olive::Node *c = to_cpp(child);
|
||||
|
||||
if (f->index_of_child(c) == -1) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
olive::Folder::RemoveElementCommand cmd(f, c);
|
||||
cmd.redo_now();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_folder_move_children(OakNodeNode *const *nodes, int count,
|
||||
OakNodeFolder *dest_folder)
|
||||
{
|
||||
if (!nodes || count < 0 || !dest_folder) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Folder *dest = to_cpp(dest_folder);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!nodes[i]) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
olive::Node *node = to_cpp(nodes[i]);
|
||||
olive::Folder *old_folder = node->folder();
|
||||
|
||||
if (old_folder == dest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (old_folder) {
|
||||
olive::Folder::RemoveElementCommand remove_cmd(old_folder, node);
|
||||
remove_cmd.redo_now();
|
||||
}
|
||||
|
||||
olive::FolderAddChild add_cmd(dest, node);
|
||||
add_cmd.redo_now();
|
||||
}
|
||||
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_folder_has_child_recursive(const OakNodeFolder *folder,
|
||||
const OakNodeNode *child)
|
||||
{
|
||||
if (!folder || !child) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_cpp(folder)->has_child_recursive(
|
||||
const_cast<olive::Node *>(to_cpp(child)))
|
||||
? 1
|
||||
: 0;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_folder_index_of_child(const OakNodeFolder *folder,
|
||||
const OakNodeNode *child)
|
||||
{
|
||||
if (!folder || !child) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
int index = to_cpp(folder)->index_of_child(
|
||||
const_cast<olive::Node *>(to_cpp(child)));
|
||||
return index == -1 ? OAKNODE_E_NOT_FOUND : index;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeFolder *oaknode_folder_parent_of(const OakNodeNode *node)
|
||||
{
|
||||
if (!node) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
return reinterpret_cast<OakNodeFolder *>(to_cpp(node)->folder());
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/***
|
||||
|
||||
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 "node/footage.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "../src/project.h"
|
||||
#include "../src/project/footage/footage.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::Footage *to_cpp(OakNodeFootage *footage)
|
||||
{
|
||||
return reinterpret_cast<olive::Footage *>(footage);
|
||||
}
|
||||
|
||||
const olive::Footage *to_cpp(const OakNodeFootage *footage)
|
||||
{
|
||||
return reinterpret_cast<const olive::Footage *>(footage);
|
||||
}
|
||||
|
||||
olive::Project *to_cpp(OakNodeProject *project)
|
||||
{
|
||||
return reinterpret_cast<olive::Project *>(project);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shared two-stage string getter.
|
||||
*
|
||||
* Returns the required buffer size in bytes (including the terminating
|
||||
* NUL) as a non-negative value.
|
||||
*/
|
||||
int copy_string(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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakNodeFootage *oaknode_footage_create(OakNodeProject *project,
|
||||
const char *filename)
|
||||
{
|
||||
if (!project) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
auto *footage = new (std::nothrow)
|
||||
olive::Footage(filename ? filename : "");
|
||||
if (!footage) {
|
||||
return NULL;
|
||||
}
|
||||
to_cpp(project)->add_node(footage);
|
||||
return reinterpret_cast<OakNodeFootage *>(footage);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_filename(const OakNodeFootage *footage, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(footage)->filename(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_set_filename(OakNodeFootage *footage, const char *filename)
|
||||
{
|
||||
if (!footage || !filename) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(footage)->set_filename(filename);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_is_valid(const OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
return to_cpp(footage)->is_valid() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oaknode_footage_timestamp(const OakNodeFootage *footage,
|
||||
int64_t *out_timestamp)
|
||||
{
|
||||
if (!footage || !out_timestamp) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*out_timestamp = to_cpp(footage)->timestamp();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_set_timestamp(OakNodeFootage *footage, int64_t timestamp)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(footage)->set_timestamp(timestamp);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_decoder(const OakNodeFootage *footage, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(footage)->decoder(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_total_stream_count(const OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_cpp(footage)->get_total_stream_count();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_video_stream_count(const OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_cpp(footage)->get_video_stream_count();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_audio_stream_count(const OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_cpp(footage)->get_audio_stream_count();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_subtitle_stream_count(const OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_cpp(footage)->get_subtitle_stream_count();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_duration(const OakNodeFootage *footage, int *out_numerator,
|
||||
int *out_denominator)
|
||||
{
|
||||
if (!footage || !out_numerator || !out_denominator) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::Rational &length = to_cpp(footage)->get_length();
|
||||
*out_numerator = length.numerator();
|
||||
*out_denominator = length.denominator();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_proxy_enabled(const OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
return to_cpp(footage)->proxy_enabled() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oaknode_footage_set_proxy_enabled(OakNodeFootage *footage, int enabled)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(footage)->set_proxy_enabled(enabled != 0);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_proxy_path(const OakNodeFootage *footage, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(footage)->proxy_path(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_proxy_state(const OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return static_cast<int>(to_cpp(footage)->proxy_state());
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_set_proxy(OakNodeFootage *footage, const char *path,
|
||||
int state, int video_stream_index,
|
||||
int preset_version, int enabled)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(footage)->set_proxy(
|
||||
path ? path : "",
|
||||
static_cast<olive::ProxyManager::ProxyState>(state),
|
||||
video_stream_index, preset_version, enabled != 0);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_footage_clear_proxy(OakNodeFootage *footage)
|
||||
{
|
||||
if (!footage) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(footage)->clear_proxy();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -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 "node/group.h"
|
||||
|
||||
#include "group/group.h"
|
||||
|
||||
#include "valueconvert.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
inline olive::NodeGroup *to_group(OakNodeGroup *group)
|
||||
{
|
||||
return reinterpret_cast<olive::NodeGroup *>(group);
|
||||
}
|
||||
|
||||
inline const olive::NodeGroup *to_group(const OakNodeGroup *group)
|
||||
{
|
||||
return reinterpret_cast<const olive::NodeGroup *>(group);
|
||||
}
|
||||
|
||||
inline olive::Node *to_node(OakNodeNode *node)
|
||||
{
|
||||
return reinterpret_cast<olive::Node *>(node);
|
||||
}
|
||||
|
||||
inline OakNodeNode *from_node(olive::Node *node)
|
||||
{
|
||||
return reinterpret_cast<OakNodeNode *>(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OakNodeGroup *oaknode_group_create(void)
|
||||
{
|
||||
try {
|
||||
olive::NodeGroup *group = new (std::nothrow) olive::NodeGroup();
|
||||
if (group) {
|
||||
oaknode_c_api::alive_inc();
|
||||
}
|
||||
return reinterpret_cast<OakNodeGroup *>(group);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeGroup *oaknode_group_cast(OakNodeNode *node)
|
||||
{
|
||||
if (!node) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
return reinterpret_cast<OakNodeGroup *>(
|
||||
dynamic_cast<olive::NodeGroup *>(to_node(node)));
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_group_free(OakNodeGroup *group)
|
||||
{
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
delete to_group(group);
|
||||
oaknode_c_api::alive_dec();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_add_input_passthrough(OakNodeGroup *group,
|
||||
OakNodeNode *node,
|
||||
const char *input_id, int element,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!group || !node || !input_id) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
std::string id = to_group(group)->add_input_passthrough(
|
||||
olive::NodeInput(to_node(node), input_id, element));
|
||||
return oaknode_c_api::copy_string(id, buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_add_input_passthrough_undoable(OakNodeGroup *group,
|
||||
OakNodeNode *node,
|
||||
const char *input_id,
|
||||
int element,
|
||||
OakUndoCommand **out_command)
|
||||
{
|
||||
if (!group || !node || !input_id || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
OakUndoCommand *handle = oaknode_c_api::wrap_command(
|
||||
new olive::NodeGroupAddInputPassthrough(
|
||||
to_group(group),
|
||||
olive::NodeInput(to_node(node), input_id, element)));
|
||||
if (!handle) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out_command = handle;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_remove_input_passthrough(OakNodeGroup *group,
|
||||
OakNodeNode *node,
|
||||
const char *input_id, int element)
|
||||
{
|
||||
if (!group || !node || !input_id) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::NodeInput input(to_node(node), input_id, element);
|
||||
if (!to_group(group)->contains_input_passthrough(input)) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
to_group(group)->remove_input_passthrough(input);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_passthrough_count(const OakNodeGroup *group, int *out_count)
|
||||
{
|
||||
if (!group || !out_count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*out_count =
|
||||
static_cast<int>(to_group(group)->get_input_passthroughs().size());
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_passthrough_id_at(const OakNodeGroup *group, int index,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!group) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::NodeGroup::InputPassthroughs &passthroughs =
|
||||
to_group(group)->get_input_passthroughs();
|
||||
if (index < 0 || index >= static_cast<int>(passthroughs.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
return oaknode_c_api::copy_string(passthroughs[size_t(index)].first, buf,
|
||||
buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_passthrough_input_at(const OakNodeGroup *group, int index,
|
||||
OakNodeNode **out_node, char *buf,
|
||||
int buf_size, int *out_element)
|
||||
{
|
||||
if (!group) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::NodeGroup::InputPassthroughs &passthroughs =
|
||||
to_group(group)->get_input_passthroughs();
|
||||
if (index < 0 || index >= static_cast<int>(passthroughs.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
const olive::NodeInput &input = passthroughs[size_t(index)].second;
|
||||
if (out_node) {
|
||||
*out_node = from_node(input.node());
|
||||
}
|
||||
if (out_element) {
|
||||
*out_element = input.element();
|
||||
}
|
||||
return oaknode_c_api::copy_string(input.input(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_get_output_passthrough(const OakNodeGroup *group,
|
||||
OakNodeNode **out_node)
|
||||
{
|
||||
if (!group || !out_node) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*out_node = from_node(to_group(group)->get_output_passthrough());
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_set_output_passthrough(OakNodeGroup *group,
|
||||
OakNodeNode *node)
|
||||
{
|
||||
if (!group) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_group(group)->set_output_passthrough(to_node(node));
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_set_output_passthrough_undoable(
|
||||
OakNodeGroup *group, OakNodeNode *node, OakUndoCommand **out_command)
|
||||
{
|
||||
if (!group || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
OakUndoCommand *handle = oaknode_c_api::wrap_command(
|
||||
new olive::NodeGroupSetOutputPassthrough(to_group(group),
|
||||
to_node(node)));
|
||||
if (!handle) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out_command = handle;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_group_resolve_input(OakNodeNode *node, const char *input_id,
|
||||
int element, OakNodeNode **out_node,
|
||||
char *buf, int buf_size, int *out_element)
|
||||
{
|
||||
if (!node || !input_id) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::NodeInput resolved = olive::NodeGroup::resolve_input(
|
||||
olive::NodeInput(to_node(node), input_id, element));
|
||||
if (!resolved.is_valid()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (out_node) {
|
||||
*out_node = from_node(resolved.node());
|
||||
}
|
||||
if (out_element) {
|
||||
*out_element = resolved.element();
|
||||
}
|
||||
return oaknode_c_api::copy_string(resolved.input(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
/***
|
||||
|
||||
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 "node/keyframe.h"
|
||||
|
||||
#include "keyframe.h"
|
||||
#include "node.h"
|
||||
#include "nodeundo.h"
|
||||
|
||||
#include "valueconvert.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
inline olive::NodeKeyframe *to_key(OakNodeKeyframe *keyframe)
|
||||
{
|
||||
return reinterpret_cast<olive::NodeKeyframe *>(keyframe);
|
||||
}
|
||||
|
||||
inline const olive::NodeKeyframe *to_key(const OakNodeKeyframe *keyframe)
|
||||
{
|
||||
return reinterpret_cast<const olive::NodeKeyframe *>(keyframe);
|
||||
}
|
||||
|
||||
inline olive::Node *to_node(OakNodeNode *node)
|
||||
{
|
||||
return reinterpret_cast<olive::Node *>(node);
|
||||
}
|
||||
|
||||
inline OakNodeNode *from_node(olive::Node *node)
|
||||
{
|
||||
return reinterpret_cast<OakNodeNode *>(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert an oaknode_keyframe_type to olive::NodeKeyframe::Type.
|
||||
* The oaknode enum mirrors the olive ordinals exactly (invalid = -1,
|
||||
* linear = 0, hold = 1, bezier = 2).
|
||||
*/
|
||||
bool keyframe_type_from_oak(int type, olive::NodeKeyframe::Type *out)
|
||||
{
|
||||
if (type < OAKNODE_KEYFRAME_LINEAR || type > OAKNODE_KEYFRAME_BEZIER) {
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<olive::NodeKeyframe::Type>(type);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Undoable set-type command (no olive command class exists for
|
||||
* this; defined locally, mirroring NodeOverrideColorCommand's
|
||||
* capture-on-redo pattern).
|
||||
*/
|
||||
class KeyframeSetTypeCommand : public olive::UndoCommand {
|
||||
public:
|
||||
KeyframeSetTypeCommand(olive::NodeKeyframe *key,
|
||||
olive::NodeKeyframe::Type type)
|
||||
: key_(key)
|
||||
, new_type_(type)
|
||||
, old_type_(olive::NodeKeyframe::k_invalid)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
old_type_ = key_->type();
|
||||
key_->set_type(new_type_);
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
key_->set_type(old_type_);
|
||||
}
|
||||
|
||||
private:
|
||||
olive::NodeKeyframe *key_;
|
||||
olive::NodeKeyframe::Type new_type_;
|
||||
olive::NodeKeyframe::Type old_type_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Undoable set-bezier-control command (no olive command class
|
||||
* exists for this; defined locally).
|
||||
*/
|
||||
class KeyframeSetBezierControlCommand : public olive::UndoCommand {
|
||||
public:
|
||||
KeyframeSetBezierControlCommand(olive::NodeKeyframe *key,
|
||||
olive::NodeKeyframe::BezierType handle,
|
||||
const olive::PointF &point)
|
||||
: key_(key)
|
||||
, handle_(handle)
|
||||
, new_point_(point)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
old_point_ = key_->bezier_control(handle_);
|
||||
key_->set_bezier_control(handle_, new_point_);
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
key_->set_bezier_control(handle_, old_point_);
|
||||
}
|
||||
|
||||
private:
|
||||
olive::NodeKeyframe *key_;
|
||||
olive::NodeKeyframe::BezierType handle_;
|
||||
olive::PointF new_point_;
|
||||
olive::PointF old_point_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
OakNodeKeyframe *oaknode_keyframe_create(int64_t time_num, int64_t time_den,
|
||||
const oaknode_value *value, int type,
|
||||
int track, int element,
|
||||
const char *input_id,
|
||||
OakNodeNode *parent_or_null)
|
||||
{
|
||||
olive::NodeKeyframe::Type keyframe_type;
|
||||
if (!keyframe_type_from_oak(type, &keyframe_type)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Variant variant;
|
||||
if (value) {
|
||||
if (!oaknode_c_api::variant_from_value(value, &variant)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
olive::core::Rational time(static_cast<int>(time_num),
|
||||
static_cast<int>(time_den));
|
||||
olive::NodeKeyframe *key = new (std::nothrow) olive::NodeKeyframe(
|
||||
time, variant, keyframe_type, track, element,
|
||||
input_id ? input_id : "", to_node(parent_or_null));
|
||||
if (key) {
|
||||
oaknode_c_api::alive_inc();
|
||||
}
|
||||
return reinterpret_cast<OakNodeKeyframe *>(key);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_keyframe_free(OakNodeKeyframe *keyframe)
|
||||
{
|
||||
if (!keyframe) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
delete to_key(keyframe);
|
||||
oaknode_c_api::alive_dec();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_time(const OakNodeKeyframe *keyframe,
|
||||
int64_t *out_num, int64_t *out_den)
|
||||
{
|
||||
if (!keyframe || !out_num || !out_den) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::core::Rational &time = to_key(keyframe)->time();
|
||||
*out_num = time.numerator();
|
||||
*out_den = time.denominator();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_time(OakNodeKeyframe *keyframe, int64_t time_num,
|
||||
int64_t time_den)
|
||||
{
|
||||
if (!keyframe) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_key(keyframe)->set_time(olive::core::Rational(
|
||||
static_cast<int>(time_num), static_cast<int>(time_den)));
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_time_undoable(OakNodeKeyframe *keyframe,
|
||||
int64_t time_num, int64_t time_den,
|
||||
OakUndoCommand **out_command)
|
||||
{
|
||||
if (!keyframe || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
OakUndoCommand *handle = oaknode_c_api::wrap_command(
|
||||
new olive::NodeParamSetKeyframeTimeCommand(
|
||||
to_key(keyframe),
|
||||
olive::core::Rational(static_cast<int>(time_num),
|
||||
static_cast<int>(time_den))));
|
||||
if (!handle) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out_command = handle;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_value(const OakNodeKeyframe *keyframe,
|
||||
oaknode_value *out)
|
||||
{
|
||||
if (!keyframe || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::NodeKeyframe *key = to_key(keyframe);
|
||||
const olive::Variant &variant = key->value();
|
||||
|
||||
// Preferred path: the parent node's declared input type pins the
|
||||
// mapping.
|
||||
olive::Node *parent = key->parent();
|
||||
if (parent && !key->input().empty() &&
|
||||
parent->has_input_with_id(key->input())) {
|
||||
return oaknode_c_api::value_from_variant(
|
||||
parent->get_input_data_type(key->input()), variant, out);
|
||||
}
|
||||
|
||||
// Orphan fallback: infer the POD type from the stored variant
|
||||
// content. Numeric kinds are reported as FLOAT (the Variant kind is
|
||||
// not recoverable across the POD).
|
||||
if (variant.can_convert<olive::core::Rational>()) {
|
||||
olive::core::Rational r = variant.value<olive::core::Rational>();
|
||||
*out = oaknode_value();
|
||||
out->type = OAKNODE_VALUE_RATIONAL;
|
||||
out->num = r.numerator();
|
||||
out->den = r.denominator();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
if (variant.can_convert<olive::core::Color>()) {
|
||||
return oaknode_c_api::value_from_variant(olive::NodeValue::k_color,
|
||||
variant, out);
|
||||
}
|
||||
if (variant.can_convert<olive::Vector2D>()) {
|
||||
return oaknode_c_api::value_from_variant(olive::NodeValue::k_vec2,
|
||||
variant, out);
|
||||
}
|
||||
if (variant.can_convert<olive::Vector3D>()) {
|
||||
return oaknode_c_api::value_from_variant(olive::NodeValue::k_vec3,
|
||||
variant, out);
|
||||
}
|
||||
if (variant.can_convert<olive::Vector4D>()) {
|
||||
return oaknode_c_api::value_from_variant(olive::NodeValue::k_vec4,
|
||||
variant, out);
|
||||
}
|
||||
if (variant.can_convert<double>()) {
|
||||
*out = oaknode_value();
|
||||
out->type = OAKNODE_VALUE_FLOAT;
|
||||
out->f[0] = variant.to_double();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
return OAKNODE_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_value(OakNodeKeyframe *keyframe,
|
||||
const oaknode_value *v)
|
||||
{
|
||||
if (!keyframe || !v) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Variant variant;
|
||||
if (!oaknode_c_api::variant_from_value(v, &variant)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
to_key(keyframe)->set_value(variant);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_value_undoable(OakNodeKeyframe *keyframe,
|
||||
const oaknode_value *v,
|
||||
OakUndoCommand **out_command)
|
||||
{
|
||||
if (!keyframe || !v || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Variant variant;
|
||||
if (!oaknode_c_api::variant_from_value(v, &variant)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
OakUndoCommand *handle = oaknode_c_api::wrap_command(
|
||||
new olive::NodeParamSetKeyframeValueCommand(to_key(keyframe),
|
||||
variant));
|
||||
if (!handle) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out_command = handle;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_value_string(const OakNodeKeyframe *keyframe,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!keyframe) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return oaknode_c_api::copy_string(to_key(keyframe)->value().to_string(),
|
||||
buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_value_string(OakNodeKeyframe *keyframe,
|
||||
const char *value)
|
||||
{
|
||||
if (!keyframe || !value) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_key(keyframe)->set_value(olive::Variant(value));
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_value_string_undoable(OakNodeKeyframe *keyframe,
|
||||
const char *value,
|
||||
OakUndoCommand **out_command)
|
||||
{
|
||||
if (!keyframe || !value || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
OakUndoCommand *handle = oaknode_c_api::wrap_command(
|
||||
new olive::NodeParamSetKeyframeValueCommand(
|
||||
to_key(keyframe), olive::Variant(value)));
|
||||
if (!handle) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out_command = handle;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_type(const OakNodeKeyframe *keyframe, int *out_type)
|
||||
{
|
||||
if (!keyframe || !out_type) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*out_type = static_cast<int>(to_key(keyframe)->type());
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_type(OakNodeKeyframe *keyframe, int type)
|
||||
{
|
||||
if (!keyframe) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::NodeKeyframe::Type keyframe_type;
|
||||
if (!keyframe_type_from_oak(type, &keyframe_type)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
to_key(keyframe)->set_type(keyframe_type);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_type_undoable(OakNodeKeyframe *keyframe, int type,
|
||||
OakUndoCommand **out_command)
|
||||
{
|
||||
if (!keyframe || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::NodeKeyframe::Type keyframe_type;
|
||||
if (!keyframe_type_from_oak(type, &keyframe_type)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
OakUndoCommand *handle = oaknode_c_api::wrap_command(
|
||||
new KeyframeSetTypeCommand(to_key(keyframe), keyframe_type));
|
||||
if (!handle) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out_command = handle;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_bezier_control(const OakNodeKeyframe *keyframe,
|
||||
int handle, double *out_x,
|
||||
double *out_y)
|
||||
{
|
||||
if (!keyframe || !out_x || !out_y) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::PointF point;
|
||||
if (handle == OAKNODE_KEYFRAME_IN_HANDLE) {
|
||||
point = to_key(keyframe)->bezier_control_in();
|
||||
} else if (handle == OAKNODE_KEYFRAME_OUT_HANDLE) {
|
||||
point = to_key(keyframe)->bezier_control_out();
|
||||
} else {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*out_x = point.x();
|
||||
*out_y = point.y();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_bezier_control(OakNodeKeyframe *keyframe, int handle,
|
||||
double x, double y)
|
||||
{
|
||||
if (!keyframe) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
if (handle == OAKNODE_KEYFRAME_IN_HANDLE) {
|
||||
to_key(keyframe)->set_bezier_control_in(olive::PointF(x, y));
|
||||
} else if (handle == OAKNODE_KEYFRAME_OUT_HANDLE) {
|
||||
to_key(keyframe)->set_bezier_control_out(olive::PointF(x, y));
|
||||
} else {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_set_bezier_control_undoable(OakNodeKeyframe *keyframe,
|
||||
int handle, double x, double y,
|
||||
OakUndoCommand **out_command)
|
||||
{
|
||||
if (!keyframe || !out_command) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::NodeKeyframe::BezierType bezier_handle;
|
||||
if (handle == OAKNODE_KEYFRAME_IN_HANDLE) {
|
||||
bezier_handle = olive::NodeKeyframe::k_in_handle;
|
||||
} else if (handle == OAKNODE_KEYFRAME_OUT_HANDLE) {
|
||||
bezier_handle = olive::NodeKeyframe::k_out_handle;
|
||||
} else {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
OakUndoCommand *handle_ptr = oaknode_c_api::wrap_command(
|
||||
new KeyframeSetBezierControlCommand(to_key(keyframe), bezier_handle,
|
||||
olive::PointF(x, y)));
|
||||
if (!handle_ptr) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out_command = handle_ptr;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_track(const OakNodeKeyframe *keyframe,
|
||||
int *out_track)
|
||||
{
|
||||
if (!keyframe || !out_track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*out_track = to_key(keyframe)->track();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_element(const OakNodeKeyframe *keyframe,
|
||||
int *out_element)
|
||||
{
|
||||
if (!keyframe || !out_element) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*out_element = to_key(keyframe)->element();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_input(const OakNodeKeyframe *keyframe, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!keyframe) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return oaknode_c_api::copy_string(to_key(keyframe)->input(), buf,
|
||||
buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_keyframe_get_parent(const OakNodeKeyframe *keyframe,
|
||||
OakNodeNode **out_node)
|
||||
{
|
||||
if (!keyframe || !out_node) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
*out_node = from_node(to_key(keyframe)->parent());
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,373 @@
|
||||
/***
|
||||
|
||||
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 "node/project.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "../src/project.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::Project *to_cpp(OakNodeProject *project)
|
||||
{
|
||||
return reinterpret_cast<olive::Project *>(project);
|
||||
}
|
||||
|
||||
const olive::Project *to_cpp(const OakNodeProject *project)
|
||||
{
|
||||
return reinterpret_cast<const olive::Project *>(project);
|
||||
}
|
||||
|
||||
olive::Node *to_cpp(OakNodeNode *node)
|
||||
{
|
||||
return reinterpret_cast<olive::Node *>(node);
|
||||
}
|
||||
|
||||
OakNodeNode *to_c(olive::Node *node)
|
||||
{
|
||||
return reinterpret_cast<OakNodeNode *>(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shared two-stage string getter.
|
||||
*
|
||||
* Returns the required buffer size in bytes (including the terminating
|
||||
* NUL) as a non-negative value.
|
||||
*/
|
||||
int copy_string(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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakNodeProject *oaknode_project_init(void)
|
||||
{
|
||||
try {
|
||||
return reinterpret_cast<OakNodeProject *>(
|
||||
new (std::nothrow) olive::Project());
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_project_free(OakNodeProject *project)
|
||||
{
|
||||
delete to_cpp(project);
|
||||
}
|
||||
|
||||
int oaknode_project_initialize(OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
if (to_cpp(project)->root()) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
to_cpp(project)->initialize();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_clear(OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(project)->clear();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeFolder *oaknode_project_root(OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
return reinterpret_cast<OakNodeFolder *>(to_cpp(project)->root());
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_name(const OakNodeProject *project, char *buf, int buf_size)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(project)->name(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_filename(const OakNodeProject *project, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(project)->filename(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_pretty_filename(const OakNodeProject *project, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(project)->pretty_filename(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_set_filename(OakNodeProject *project, const char *filename)
|
||||
{
|
||||
if (!project || !filename) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(project)->set_filename(filename);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_is_modified(const OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
return to_cpp(project)->is_modified() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oaknode_project_set_modified(OakNodeProject *project, int modified)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(project)->set_modified(modified != 0);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_is_new(const OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
return to_cpp(project)->is_new() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oaknode_project_cache_path(const OakNodeProject *project, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(project)->cache_path(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_get_cache_location_setting(const OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return static_cast<int>(to_cpp(project)->get_cache_location_setting());
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_set_cache_location_setting(OakNodeProject *project,
|
||||
int setting)
|
||||
{
|
||||
if (!project || setting < 0 ||
|
||||
setting > static_cast<int>(olive::Project::k_cache_custom_path)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(project)->set_cache_location_setting(
|
||||
static_cast<olive::Project::CacheSetting>(setting));
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_get_custom_cache_path(const OakNodeProject *project,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(project)->get_custom_cache_path(), buf,
|
||||
buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_set_custom_cache_path(OakNodeProject *project,
|
||||
const char *path)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(project)->set_custom_cache_path(path ? path : "");
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_get_uuid(const OakNodeProject *project, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return copy_string(to_cpp(project)->get_uuid(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_add_node(OakNodeProject *project, OakNodeNode *node)
|
||||
{
|
||||
if (!project || !node) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
to_cpp(project)->add_node(to_cpp(node));
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_remove_node(OakNodeProject *project, OakNodeNode *node)
|
||||
{
|
||||
if (!project || !node) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::Project *p = to_cpp(project);
|
||||
olive::Node *n = to_cpp(node);
|
||||
const auto &nodes = p->nodes();
|
||||
if (std::find(nodes.begin(), nodes.end(), n) == nodes.end()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
p->remove_node(n);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_project_node_count(const OakNodeProject *project)
|
||||
{
|
||||
if (!project) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return static_cast<int>(to_cpp(project)->nodes().size());
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeNode *oaknode_project_node_at(const OakNodeProject *project, int index)
|
||||
{
|
||||
if (!project || index < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
const auto &nodes = to_cpp(project)->nodes();
|
||||
if (static_cast<size_t>(index) >= nodes.size()) {
|
||||
return NULL;
|
||||
}
|
||||
return to_c(nodes[static_cast<size_t>(index)]);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/***
|
||||
|
||||
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 "node/sequence.h"
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "node/track.h"
|
||||
|
||||
#include "globals.h"
|
||||
#include "output/track/tracklist.h"
|
||||
#include "project/sequence/sequence.h"
|
||||
#include "videoparams.h"
|
||||
|
||||
// oakcommon defines its handle as `struct OakCommonVideoParams {
|
||||
// olive::VideoParams impl; }` (src/common/c_api/videoparams.cpp) without
|
||||
// exporting the definition. Echoing the identical layout here is the only
|
||||
// way to hand native VideoParams values across without a field-by-field
|
||||
// copy; keep in sync with oakcommon (flagged in the family-C report).
|
||||
struct OakCommonVideoParams {
|
||||
olive::VideoParams impl;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::Sequence *impl(OakNodeSequence *h)
|
||||
{
|
||||
return reinterpret_cast<olive::Sequence *>(h);
|
||||
}
|
||||
|
||||
int get_rational(const olive::core::Rational &r, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!numerator || !denominator) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*numerator = r.numerator();
|
||||
*denominator = r.denominator();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
bool valid_track_type(int type)
|
||||
{
|
||||
return type >= OAKNODE_TRACK_TYPE_VIDEO && type < OAKNODE_TRACK_TYPE_COUNT;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakNodeSequence *oaknode_sequence_create(void)
|
||||
{
|
||||
try {
|
||||
olive::Sequence *s = new olive::Sequence();
|
||||
oaknode_c_api::alive_inc();
|
||||
return reinterpret_cast<OakNodeSequence *>(s);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_sequence_free(OakNodeSequence *sequence)
|
||||
{
|
||||
if (!sequence) {
|
||||
return;
|
||||
}
|
||||
olive::Sequence *s = impl(sequence);
|
||||
// ~Sequence() deletes the owned TrackLists
|
||||
delete s;
|
||||
oaknode_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_track_list(OakNodeSequence *sequence, int type,
|
||||
OakNodeTrackList **out)
|
||||
{
|
||||
if (!sequence || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!valid_track_type(type)) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = reinterpret_cast<OakNodeTrackList *>(
|
||||
impl(sequence)->track_list(static_cast<olive::Track::Type>(type)));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_track_count(OakNodeSequence *sequence, int type,
|
||||
int *count)
|
||||
{
|
||||
if (!sequence || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!valid_track_type(type)) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*count = impl(sequence)
|
||||
->track_list(static_cast<olive::Track::Type>(type))
|
||||
->get_track_count();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_track_at(OakNodeSequence *sequence, int type,
|
||||
int index, OakNodeTrack **out)
|
||||
{
|
||||
if (!sequence || !out || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!valid_track_type(type)) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
olive::TrackList *list =
|
||||
impl(sequence)->track_list(static_cast<olive::Track::Type>(type));
|
||||
if (index >= list->get_track_count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = reinterpret_cast<OakNodeTrack *>(list->get_track_at(index));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_all_track_count(OakNodeSequence *sequence, int *count)
|
||||
{
|
||||
if (!sequence || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*count = int(impl(sequence)->get_tracks().size());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_all_track_at(OakNodeSequence *sequence, int index,
|
||||
OakNodeTrack **out)
|
||||
{
|
||||
if (!sequence || !out || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
const auto &tracks = impl(sequence)->get_tracks();
|
||||
if (index >= int(tracks.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = reinterpret_cast<OakNodeTrack *>(tracks.at(index));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_playhead(OakNodeSequence *sequence, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(sequence)->get_playhead(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_sequence_set_playhead(OakNodeSequence *sequence, int numerator,
|
||||
int denominator)
|
||||
{
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(sequence)->set_playhead(olive::core::Rational(numerator,
|
||||
denominator));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_length(OakNodeSequence *sequence, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(sequence)->get_length(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_video_length(OakNodeSequence *sequence, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(sequence)->get_video_length(), numerator,
|
||||
denominator);
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_audio_length(OakNodeSequence *sequence, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(sequence)->get_audio_length(), numerator,
|
||||
denominator);
|
||||
}
|
||||
|
||||
int oaknode_sequence_verify_length(OakNodeSequence *sequence)
|
||||
{
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(sequence)->verify_length();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------- Video/audio params */
|
||||
|
||||
int oaknode_sequence_get_video_stream_count(OakNodeSequence *sequence,
|
||||
int *count)
|
||||
{
|
||||
if (!sequence || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*count = impl(sequence)->get_video_stream_count();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_audio_stream_count(OakNodeSequence *sequence,
|
||||
int *count)
|
||||
{
|
||||
if (!sequence || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*count = impl(sequence)->get_audio_stream_count();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_video_params(OakNodeSequence *sequence, int index,
|
||||
OakCommonVideoParams **out)
|
||||
{
|
||||
if (!sequence || !out || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (index >= impl(sequence)->get_video_stream_count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
try {
|
||||
*out = new OakCommonVideoParams{impl(sequence)->get_video_params(index)};
|
||||
} catch (...) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_set_video_params(OakNodeSequence *sequence, int index,
|
||||
const OakCommonVideoParams *params)
|
||||
{
|
||||
if (!sequence || !params || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (index >= impl(sequence)->get_video_stream_count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
try {
|
||||
impl(sequence)->set_video_params(params->impl, index);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_get_audio_params(OakNodeSequence *sequence, int index,
|
||||
OakAudioParams **out)
|
||||
{
|
||||
if (!sequence || !out || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (index >= impl(sequence)->get_audio_stream_count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
OakAudioParams *copy =
|
||||
oakcore_audioparams_copy(impl(sequence)->get_audio_params(index).handle());
|
||||
if (!copy) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
*out = copy;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_sequence_set_audio_params(OakNodeSequence *sequence, int index,
|
||||
const OakAudioParams *params)
|
||||
{
|
||||
if (!sequence || !params || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (index >= impl(sequence)->get_audio_stream_count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
OakAudioParams *copy = oakcore_audioparams_copy(params);
|
||||
if (!copy) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
try {
|
||||
impl(sequence)->set_audio_params(
|
||||
olive::core::AudioParams::from_handle(copy), index);
|
||||
} catch (...) {
|
||||
oakcore_audioparams_free(copy);
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
/***
|
||||
|
||||
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 "node/serializer.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../src/factory.h"
|
||||
#include "../src/project.h"
|
||||
#include "../src/project/serializer/serializer.h"
|
||||
#include "xmlutils.h"
|
||||
|
||||
struct OakNodeSerializerSaveData {
|
||||
olive::ProjectSerializer::SaveData impl;
|
||||
|
||||
OakNodeSerializerSaveData(olive::ProjectSerializer::LoadType type,
|
||||
olive::Project *project)
|
||||
: impl(type, project)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct OakNodeSerializerLoadData {
|
||||
olive::ProjectSerializer::LoadData impl;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool g_initialized = false;
|
||||
|
||||
olive::Project *to_cpp(OakNodeProject *project)
|
||||
{
|
||||
return reinterpret_cast<olive::Project *>(project);
|
||||
}
|
||||
|
||||
olive::Node *to_cpp(OakNodeNode *node)
|
||||
{
|
||||
return reinterpret_cast<olive::Node *>(node);
|
||||
}
|
||||
|
||||
OakNodeNode *to_c(olive::Node *node)
|
||||
{
|
||||
return reinterpret_cast<OakNodeNode *>(node);
|
||||
}
|
||||
|
||||
bool is_valid_load_type(int load_type)
|
||||
{
|
||||
return load_type >= static_cast<int>(olive::ProjectSerializer::k_project) &&
|
||||
load_type <=
|
||||
static_cast<int>(olive::ProjectSerializer::k_only_keyframes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shared two-stage string getter.
|
||||
*
|
||||
* Returns the required buffer size in bytes (including the terminating
|
||||
* NUL) as a non-negative value.
|
||||
*/
|
||||
int copy_string(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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int oaknode_serializer_initialize(void)
|
||||
{
|
||||
if (g_initialized) {
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
try {
|
||||
// The loaders instantiate nodes by id through the factory.
|
||||
olive::NodeFactory::initialize();
|
||||
olive::ProjectSerializer::initialize();
|
||||
g_initialized = true;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_serializer_shutdown(void)
|
||||
{
|
||||
if (!g_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::ProjectSerializer::destroy();
|
||||
olive::NodeFactory::destroy();
|
||||
} catch (...) {
|
||||
}
|
||||
g_initialized = false;
|
||||
}
|
||||
|
||||
OakNodeSerializerSaveData *oaknode_serializer_savedata_create(
|
||||
int load_type, OakNodeProject *project)
|
||||
{
|
||||
if (!is_valid_load_type(load_type)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
return new (std::nothrow) OakNodeSerializerSaveData(
|
||||
static_cast<olive::ProjectSerializer::LoadType>(load_type),
|
||||
to_cpp(project));
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_serializer_savedata_free(OakNodeSerializerSaveData *save_data)
|
||||
{
|
||||
delete save_data;
|
||||
}
|
||||
|
||||
int oaknode_serializer_savedata_set_nodes(
|
||||
OakNodeSerializerSaveData *save_data, OakNodeNode *const *nodes, int count)
|
||||
{
|
||||
if (!save_data || !nodes || count < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
std::vector<olive::Node *> cpp_nodes;
|
||||
cpp_nodes.reserve(static_cast<size_t>(count));
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!nodes[i]) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
cpp_nodes.push_back(to_cpp(nodes[i]));
|
||||
}
|
||||
|
||||
save_data->impl.set_only_serialize_nodes(cpp_nodes);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_serializer_savedata_set_property(
|
||||
OakNodeSerializerSaveData *save_data, OakNodeNode *node, const char *key,
|
||||
const char *value)
|
||||
{
|
||||
if (!save_data || !node || !key || !value) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::ProjectSerializer::SerializedProperties properties =
|
||||
save_data->impl.get_properties();
|
||||
properties[to_cpp(node)][key] = value;
|
||||
save_data->impl.set_properties(properties);
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_serializer_save_to_xml(OakNodeSerializerSaveData *save_data,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!save_data) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!g_initialized) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::XmlStreamWriter writer;
|
||||
olive::ProjectSerializer::Result result =
|
||||
olive::ProjectSerializer::save(&writer, save_data->impl);
|
||||
if (result != olive::ProjectSerializer::k_success) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return copy_string(writer.output(), buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_serializer_load_from_xml(OakNodeProject *project, const char *xml,
|
||||
int load_type, int *out_result,
|
||||
OakNodeSerializerLoadData **out_load_data,
|
||||
char *details_buf, int details_buf_size)
|
||||
{
|
||||
if (!xml || !out_result || !is_valid_load_type(load_type)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (!g_initialized) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
|
||||
if (out_load_data) {
|
||||
*out_load_data = NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::XmlStreamReader reader(xml);
|
||||
olive::ProjectSerializer::Result result = olive::ProjectSerializer::load(
|
||||
to_cpp(project), &reader,
|
||||
static_cast<olive::ProjectSerializer::LoadType>(load_type));
|
||||
|
||||
*out_result = static_cast<int>(result.code());
|
||||
|
||||
if (details_buf && details_buf_size > 0) {
|
||||
copy_string(result.get_details(), details_buf, details_buf_size);
|
||||
}
|
||||
|
||||
if (result == olive::ProjectSerializer::k_success && out_load_data) {
|
||||
auto *load_data = new (std::nothrow) OakNodeSerializerLoadData();
|
||||
if (!load_data) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
load_data->impl = result.get_load_data();
|
||||
*out_load_data = load_data;
|
||||
}
|
||||
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_serializer_loaddata_free(OakNodeSerializerLoadData *load_data)
|
||||
{
|
||||
delete load_data;
|
||||
}
|
||||
|
||||
int oaknode_serializer_loaddata_node_count(
|
||||
const OakNodeSerializerLoadData *load_data)
|
||||
{
|
||||
if (!load_data) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return static_cast<int>(load_data->impl.nodes.size());
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
OakNodeNode *oaknode_serializer_loaddata_node_at(
|
||||
const OakNodeSerializerLoadData *load_data, int index)
|
||||
{
|
||||
if (!load_data || index < 0 ||
|
||||
static_cast<size_t>(index) >= load_data->impl.nodes.size()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
return to_c(load_data->impl.nodes[static_cast<size_t>(index)]);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_serializer_loaddata_get_property(
|
||||
const OakNodeSerializerLoadData *load_data, OakNodeNode *node,
|
||||
const char *key, char *buf, int buf_size)
|
||||
{
|
||||
if (!load_data || !node || !key) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
auto node_it = load_data->impl.properties.find(to_cpp(node));
|
||||
if (node_it == load_data->impl.properties.end()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
auto key_it = node_it->second.find(key);
|
||||
if (key_it == node_it->second.end()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
return copy_string(key_it->second, buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_serializer_loaddata_connection_count(
|
||||
const OakNodeSerializerLoadData *load_data)
|
||||
{
|
||||
if (!load_data) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return static_cast<int>(load_data->impl.promised_connections.size());
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_serializer_loaddata_connection_at(
|
||||
const OakNodeSerializerLoadData *load_data, int index,
|
||||
OakNodeNode **out_output_node, OakNodeNode **out_input_node,
|
||||
char *input_id_buf, int input_id_buf_size, int *out_element)
|
||||
{
|
||||
if (!load_data || !out_output_node || !out_input_node || !out_element) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (index < 0 || static_cast<size_t>(index) >=
|
||||
load_data->impl.promised_connections.size()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::Node::OutputConnection &connection =
|
||||
load_data->impl.promised_connections[static_cast<size_t>(index)];
|
||||
*out_output_node = to_c(connection.first);
|
||||
*out_input_node = to_c(connection.second.node());
|
||||
if (input_id_buf && input_id_buf_size > 0) {
|
||||
copy_string(connection.second.input(), input_id_buf,
|
||||
input_id_buf_size);
|
||||
}
|
||||
*out_element = connection.second.element();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
/***
|
||||
|
||||
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 "node/track.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "alivecount.h"
|
||||
|
||||
#include "block/block.h"
|
||||
#include "output/track/track.h"
|
||||
#include "output/track/tracklist.h"
|
||||
#include "project/sequence/sequence.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::Track *impl(OakNodeTrack *h)
|
||||
{
|
||||
return reinterpret_cast<olive::Track *>(h);
|
||||
}
|
||||
|
||||
olive::TrackList *list_impl(OakNodeTrackList *h)
|
||||
{
|
||||
return reinterpret_cast<olive::TrackList *>(h);
|
||||
}
|
||||
|
||||
olive::Block *block_impl(OakNodeBlock *h)
|
||||
{
|
||||
return reinterpret_cast<olive::Block *>(h);
|
||||
}
|
||||
|
||||
OakNodeTrack *wrap(olive::Track *t)
|
||||
{
|
||||
return reinterpret_cast<OakNodeTrack *>(t);
|
||||
}
|
||||
|
||||
OakNodeBlock *wrap_block(olive::Block *b)
|
||||
{
|
||||
return reinterpret_cast<OakNodeBlock *>(b);
|
||||
}
|
||||
|
||||
bool valid_type(int type)
|
||||
{
|
||||
return type >= OAKNODE_TRACK_TYPE_VIDEO && type < OAKNODE_TRACK_TYPE_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Refresh the cached lengths after a block mutation
|
||||
*
|
||||
* De-Qt wave: TrackList::update_total_length / Sequence::verify_length
|
||||
* were signal-driven; the C API performs the refresh synchronously so
|
||||
* that state read back right after a mutation is consistent.
|
||||
*/
|
||||
void refresh_lengths(olive::Track *t)
|
||||
{
|
||||
olive::Sequence *s = t->sequence();
|
||||
if (s && valid_type(int(t->type()))) {
|
||||
olive::TrackList *l = s->track_list(t->type());
|
||||
if (l) {
|
||||
l->update_total_length();
|
||||
}
|
||||
s->verify_length();
|
||||
}
|
||||
}
|
||||
|
||||
int get_rational(const olive::core::Rational &r, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!numerator || !denominator) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*numerator = r.numerator();
|
||||
*denominator = r.denominator();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---------------------------------------------------------------- Track */
|
||||
|
||||
OakNodeTrack *oaknode_track_create(int type)
|
||||
{
|
||||
if (!valid_type(type)) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
olive::Track *t = new olive::Track();
|
||||
t->set_type(static_cast<olive::Track::Type>(type));
|
||||
oaknode_c_api::alive_inc();
|
||||
return wrap(t);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_track_free(OakNodeTrack *track)
|
||||
{
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
delete impl(track);
|
||||
oaknode_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oaknode_track_get_type(OakNodeTrack *track, int *type)
|
||||
{
|
||||
if (!track || !type) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*type = int(impl(track)->type());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_set_type(OakNodeTrack *track, int type)
|
||||
{
|
||||
if (!track || !valid_type(type)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(track)->set_type(static_cast<olive::Track::Type>(type));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_height(OakNodeTrack *track, double *height)
|
||||
{
|
||||
if (!track || !height) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*height = impl(track)->get_track_height();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_set_height(OakNodeTrack *track, double height)
|
||||
{
|
||||
if (!track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(track)->set_track_height(height);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_height_in_pixels(OakNodeTrack *track, int *height)
|
||||
{
|
||||
if (!track || !height) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*height = impl(track)->get_track_height_in_pixels();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_set_height_in_pixels(OakNodeTrack *track, int height)
|
||||
{
|
||||
if (!track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(track)->set_track_height_in_pixels(height);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_default_height_in_pixels(void)
|
||||
{
|
||||
return olive::Track::get_default_track_height_in_pixels();
|
||||
}
|
||||
|
||||
int oaknode_track_get_minimum_height_in_pixels(void)
|
||||
{
|
||||
return olive::Track::get_minimum_track_height_in_pixels();
|
||||
}
|
||||
|
||||
int oaknode_track_get_index(OakNodeTrack *track, int *index)
|
||||
{
|
||||
if (!track || !index) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*index = impl(track)->index();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_set_index(OakNodeTrack *track, int index)
|
||||
{
|
||||
if (!track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(track)->set_index(index);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_muted(OakNodeTrack *track, int *muted)
|
||||
{
|
||||
if (!track || !muted) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*muted = impl(track)->is_muted() ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_set_muted(OakNodeTrack *track, int muted)
|
||||
{
|
||||
if (!track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(track)->set_muted(muted != 0);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_locked(OakNodeTrack *track, int *locked)
|
||||
{
|
||||
if (!track || !locked) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*locked = impl(track)->is_locked() ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_set_locked(OakNodeTrack *track, int locked)
|
||||
{
|
||||
if (!track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
impl(track)->set_locked(locked != 0);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_reference(OakNodeTrack *track, int *type, int *index)
|
||||
{
|
||||
if (!track || !type || !index) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
olive::Track::Reference ref = impl(track)->to_reference();
|
||||
*type = int(ref.type());
|
||||
*index = ref.index();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_length(OakNodeTrack *track, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(impl(track)->track_length(), numerator, denominator);
|
||||
}
|
||||
|
||||
int oaknode_track_get_sequence(OakNodeTrack *track, OakNodeSequence **out)
|
||||
{
|
||||
if (!track || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*out = reinterpret_cast<OakNodeSequence *>(impl(track)->sequence());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------- Track blocks */
|
||||
|
||||
int oaknode_track_get_block_count(OakNodeTrack *track, int *count)
|
||||
{
|
||||
if (!track || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*count = int(impl(track)->blocks().size());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_block_at(OakNodeTrack *track, int index,
|
||||
OakNodeBlock **out)
|
||||
{
|
||||
if (!track || !out || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
const auto &blocks = impl(track)->blocks();
|
||||
if (index >= int(blocks.size())) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = wrap_block(blocks.at(index));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_append_block(OakNodeTrack *track, OakNodeBlock *block)
|
||||
{
|
||||
if (!track || !block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(track)->append_block(block_impl(block));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
refresh_lengths(impl(track));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_prepend_block(OakNodeTrack *track, OakNodeBlock *block)
|
||||
{
|
||||
if (!track || !block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(track)->prepend_block(block_impl(block));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
refresh_lengths(impl(track));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_insert_block_at_index(OakNodeTrack *track,
|
||||
OakNodeBlock *block, int index)
|
||||
{
|
||||
if (!track || !block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(track)->insert_block_at_index(block_impl(block), index);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
refresh_lengths(impl(track));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_insert_block_after(OakNodeTrack *track, OakNodeBlock *block,
|
||||
OakNodeBlock *before)
|
||||
{
|
||||
if (!track || !block || !before) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(track)->insert_block_after(block_impl(block), block_impl(before));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
refresh_lengths(impl(track));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_insert_block_before(OakNodeTrack *track, OakNodeBlock *block,
|
||||
OakNodeBlock *after)
|
||||
{
|
||||
if (!track || !block || !after) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(track)->insert_block_before(block_impl(block), block_impl(after));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
refresh_lengths(impl(track));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_ripple_remove_block(OakNodeTrack *track, OakNodeBlock *block)
|
||||
{
|
||||
if (!track || !block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(track)->ripple_remove_block(block_impl(block));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
refresh_lengths(impl(track));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_replace_block(OakNodeTrack *track, OakNodeBlock *old_block,
|
||||
OakNodeBlock *new_block)
|
||||
{
|
||||
if (!track || !old_block || !new_block) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
try {
|
||||
impl(track)->replace_block(block_impl(old_block), block_impl(new_block));
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
refresh_lengths(impl(track));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_block_index(OakNodeTrack *track, OakNodeBlock *block,
|
||||
int *index)
|
||||
{
|
||||
if (!track || !block || !index) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
int i = impl(track)->get_array_index_from_block(block_impl(block));
|
||||
if (i < 0) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*index = i;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_block_containing_time(OakNodeTrack *track, int numerator,
|
||||
int denominator,
|
||||
OakNodeBlock **out)
|
||||
{
|
||||
if (!track || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
olive::Block *b = impl(track)->block_containing_time(
|
||||
olive::core::Rational(numerator, denominator));
|
||||
if (!b) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = wrap_block(b);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_get_visible_block_at_time(OakNodeTrack *track, int numerator,
|
||||
int denominator,
|
||||
OakNodeBlock **out)
|
||||
{
|
||||
if (!track || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
olive::Block *b = impl(track)->visible_block_at_time(
|
||||
olive::core::Rational(numerator, denominator));
|
||||
if (!b) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = wrap_block(b);
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_track_is_range_free(OakNodeTrack *track, int in_num, int in_den,
|
||||
int out_num, int out_den, int *is_free)
|
||||
{
|
||||
if (!track || !is_free) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*is_free = impl(track)->is_range_free(
|
||||
olive::core::TimeRange(
|
||||
olive::core::Rational(in_num, in_den),
|
||||
olive::core::Rational(out_num, out_den))) ?
|
||||
1 :
|
||||
0;
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ TrackList */
|
||||
|
||||
int oaknode_tracklist_get_type(OakNodeTrackList *list, int *type)
|
||||
{
|
||||
if (!list || !type) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*type = int(list_impl(list)->type());
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_tracklist_get_track_count(OakNodeTrackList *list, int *count)
|
||||
{
|
||||
if (!list || !count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*count = list_impl(list)->get_track_count();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_tracklist_get_track_at(OakNodeTrackList *list, int index,
|
||||
OakNodeTrack **out)
|
||||
{
|
||||
if (!list || !out || index < 0) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
if (index >= list_impl(list)->get_track_count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out = wrap(list_impl(list)->get_track_at(index));
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_tracklist_get_total_length(OakNodeTrackList *list, int *numerator,
|
||||
int *denominator)
|
||||
{
|
||||
if (!list) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
return get_rational(list_impl(list)->get_total_length(), numerator,
|
||||
denominator);
|
||||
}
|
||||
|
||||
int oaknode_tracklist_get_array_size(OakNodeTrackList *list, int *size)
|
||||
{
|
||||
if (!list || !size) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
*size = list_impl(list)->array_size();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_tracklist_add_track(OakNodeTrackList *list, OakNodeTrack *track)
|
||||
{
|
||||
if (!list || !track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
olive::TrackList *l = list_impl(list);
|
||||
olive::Track *t = impl(track);
|
||||
olive::Sequence *sequence = l->parent();
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
try {
|
||||
// Graph steps of TimelineAddTrackCommand::redo() minus auto-merge
|
||||
t->set_parent(l->get_parent_graph());
|
||||
if (l->get_track_count() > 0) {
|
||||
t->set_track_height(
|
||||
l->get_track_at(l->get_track_count() - 1)->get_track_height());
|
||||
}
|
||||
l->array_append();
|
||||
olive::Node::connect_edge(t, l->track_input(l->array_size() - 1));
|
||||
|
||||
// De-Qt wave: the former signal emissions are the caller's job
|
||||
sequence->update_track_cache();
|
||||
l->update_total_length();
|
||||
sequence->verify_length();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
|
||||
int oaknode_tracklist_remove_track(OakNodeTrackList *list, OakNodeTrack *track)
|
||||
{
|
||||
if (!list || !track) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
olive::TrackList *l = list_impl(list);
|
||||
olive::Track *t = impl(track);
|
||||
olive::Sequence *sequence = l->parent();
|
||||
if (!sequence) {
|
||||
return OAKNODE_E_STATE;
|
||||
}
|
||||
|
||||
const auto &tracks = l->get_tracks();
|
||||
auto it = std::find(tracks.begin(), tracks.end(), t);
|
||||
if (it == tracks.end()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
try {
|
||||
int cache_index = int(it - tracks.begin());
|
||||
int array_index = l->get_array_index_from_cache_index(cache_index);
|
||||
|
||||
olive::Node::disconnect_edge(t, l->track_input(array_index));
|
||||
sequence->input_array_remove(l->track_input(), array_index);
|
||||
|
||||
// De-Qt wave: the former signal emissions are the caller's job
|
||||
sequence->update_track_cache();
|
||||
l->update_total_length();
|
||||
sequence->verify_length();
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/***
|
||||
|
||||
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 "node/traverser.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
#include "traverser.h"
|
||||
#include "valuedatabase.h"
|
||||
|
||||
#include "valueconvert.h"
|
||||
|
||||
struct OakNodeValueDatabase {
|
||||
olive::NodeValueDatabase impl;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
inline olive::NodeTraverser *to_traverser(OakNodeTraverser *traverser)
|
||||
{
|
||||
return reinterpret_cast<olive::NodeTraverser *>(traverser);
|
||||
}
|
||||
|
||||
inline olive::Node *to_node(OakNodeNode *node)
|
||||
{
|
||||
return reinterpret_cast<olive::Node *>(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the table named `key`, or NULL when absent.
|
||||
*/
|
||||
const olive::NodeValueTable *find_table(const OakNodeValueDatabase *db,
|
||||
const char *key)
|
||||
{
|
||||
for (auto it = db->impl.cbegin(); it != db->impl.cend(); ++it) {
|
||||
if (it->first == key) {
|
||||
return &it->second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OakNodeTraverser *oaknode_traverser_init(void)
|
||||
{
|
||||
try {
|
||||
olive::NodeTraverser *traverser = new (std::nothrow) olive::NodeTraverser();
|
||||
if (traverser) {
|
||||
oaknode_c_api::alive_inc();
|
||||
}
|
||||
return reinterpret_cast<OakNodeTraverser *>(traverser);
|
||||
} catch (...) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_traverser_free(OakNodeTraverser *traverser)
|
||||
{
|
||||
if (!traverser) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
delete to_traverser(traverser);
|
||||
oaknode_c_api::alive_dec();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_traverser_generate_database(OakNodeTraverser *traverser,
|
||||
OakNodeNode *node, int64_t in_num,
|
||||
int64_t in_den, int64_t out_num,
|
||||
int64_t out_den,
|
||||
OakNodeValueDatabase **out_db)
|
||||
{
|
||||
if (!traverser || !node || !out_db) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
olive::core::Rational in(static_cast<int>(in_num),
|
||||
static_cast<int>(in_den));
|
||||
olive::core::Rational out(static_cast<int>(out_num),
|
||||
static_cast<int>(out_den));
|
||||
|
||||
OakNodeValueDatabase *db = new (std::nothrow) OakNodeValueDatabase();
|
||||
if (!db) {
|
||||
return OAKNODE_E_NOMEM;
|
||||
}
|
||||
|
||||
db->impl = to_traverser(traverser)->generate_database(
|
||||
to_node(node), olive::core::TimeRange(in, out));
|
||||
|
||||
*out_db = db;
|
||||
oaknode_c_api::alive_inc();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oaknode_traverser_database_free(OakNodeValueDatabase *db)
|
||||
{
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete db;
|
||||
oaknode_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oaknode_traverser_database_row_count(const OakNodeValueDatabase *db,
|
||||
int *out_count)
|
||||
{
|
||||
if (!db || !out_count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
int count = 0;
|
||||
for (auto it = db->impl.cbegin(); it != db->impl.cend(); ++it) {
|
||||
count++;
|
||||
}
|
||||
*out_count = count;
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_traverser_database_row_key_at(const OakNodeValueDatabase *db,
|
||||
int index, char *buf, int buf_size)
|
||||
{
|
||||
if (!db) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
if (index < 0) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
auto it = db->impl.cbegin();
|
||||
for (int i = 0; i < index && it != db->impl.cend(); i++, ++it) {
|
||||
}
|
||||
if (it == db->impl.cend()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
return oaknode_c_api::copy_string(it->first, buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_traverser_database_row_value_count(const OakNodeValueDatabase *db,
|
||||
const char *key,
|
||||
int *out_count)
|
||||
{
|
||||
if (!db || !key || !out_count) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::NodeValueTable *table = find_table(db, key);
|
||||
if (!table) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
*out_count = table->count();
|
||||
return OAKNODE_OK;
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_traverser_database_value_at(const OakNodeValueDatabase *db,
|
||||
const char *key, int index,
|
||||
oaknode_value *out)
|
||||
{
|
||||
if (!db || !key || !out) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::NodeValueTable *table = find_table(db, key);
|
||||
if (!table || index < 0 || index >= table->count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
const olive::NodeValue &value = table->at(index);
|
||||
return oaknode_c_api::value_from_variant(value.type(), value.data(),
|
||||
out);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oaknode_traverser_database_value_string_at(const OakNodeValueDatabase *db,
|
||||
const char *key, int index,
|
||||
char *buf, int buf_size)
|
||||
{
|
||||
if (!db || !key) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
const olive::NodeValueTable *table = find_table(db, key);
|
||||
if (!table || index < 0 || index >= table->count()) {
|
||||
return OAKNODE_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
const olive::NodeValue &value = table->at(index);
|
||||
return oaknode_c_api::copy_string(
|
||||
olive::NodeValue::value_to_string(value.type(), value.data(), false),
|
||||
buf, buf_size);
|
||||
} catch (...) {
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/***
|
||||
|
||||
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_NODE_C_API_VALUECONVERT_H
|
||||
#define OAK_NODE_C_API_VALUECONVERT_H
|
||||
|
||||
// Internal helpers shared by the oaknode c_api translation units:
|
||||
// oaknode_value <-> olive::Variant mapping, the pinned
|
||||
// oaknode_value_type <-> olive::NodeValue::Type mapping, two-stage string
|
||||
// copy, OakUndoCommand wrapping and the debug alive counter.
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "value.h"
|
||||
|
||||
// Internal layout of the OakUndoCommand handle, shared with the oakundo
|
||||
// module (src/undo/c_api/commandhandle.h). Included so undoable variants
|
||||
// can hand out owned handles wrapping freshly created olive commands.
|
||||
#include "../../undo/c_api/commandhandle.h"
|
||||
|
||||
namespace oaknode_c_api
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Bump/release the debug alive counter (defined in node.cpp).
|
||||
*/
|
||||
void alive_inc();
|
||||
void alive_dec();
|
||||
|
||||
/**
|
||||
* @brief Shared two-stage string getter.
|
||||
*
|
||||
* Returns the required buffer size in bytes (including the terminating
|
||||
* NUL) as a non-negative value.
|
||||
*/
|
||||
inline int copy_string(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pinned mapping olive::NodeValue::Type -> oaknode_value_type
|
||||
* (see the table on oaknode_value_type in node/node.h). Types without a
|
||||
* POD representation map to OAKNODE_VALUE_NONE.
|
||||
*/
|
||||
inline int value_type_to_oak(olive::NodeValue::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case olive::NodeValue::k_int:
|
||||
return OAKNODE_VALUE_INT;
|
||||
case olive::NodeValue::k_float:
|
||||
return OAKNODE_VALUE_FLOAT;
|
||||
case olive::NodeValue::k_boolean:
|
||||
return OAKNODE_VALUE_BOOL;
|
||||
case olive::NodeValue::k_rational:
|
||||
return OAKNODE_VALUE_RATIONAL;
|
||||
case olive::NodeValue::k_color:
|
||||
return OAKNODE_VALUE_COLOR;
|
||||
case olive::NodeValue::k_vec2:
|
||||
return OAKNODE_VALUE_VEC2;
|
||||
case olive::NodeValue::k_vec3:
|
||||
return OAKNODE_VALUE_VEC3;
|
||||
case olive::NodeValue::k_vec4:
|
||||
return OAKNODE_VALUE_VEC4;
|
||||
case olive::NodeValue::k_combo:
|
||||
return OAKNODE_VALUE_COMBO;
|
||||
case olive::NodeValue::k_file:
|
||||
case olive::NodeValue::k_text:
|
||||
case olive::NodeValue::k_font:
|
||||
case olive::NodeValue::k_str_combo:
|
||||
return OAKNODE_VALUE_STRING;
|
||||
default:
|
||||
return OAKNODE_VALUE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 1 if the olive type is string-carried (no POD representation,
|
||||
* handled by the dedicated string functions).
|
||||
*/
|
||||
inline bool value_type_is_string(olive::NodeValue::Type type)
|
||||
{
|
||||
return type == olive::NodeValue::k_file || type == olive::NodeValue::k_text ||
|
||||
type == olive::NodeValue::k_font || type == olive::NodeValue::k_str_combo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build an olive::Variant from an oaknode_value POD.
|
||||
*
|
||||
* `value->type` must be one of the POD-carrying oaknode_value_type
|
||||
* values (STRING is rejected: no string data fits the POD).
|
||||
*/
|
||||
inline bool variant_from_value(const oaknode_value *value, olive::Variant *out)
|
||||
{
|
||||
using olive::core::Color;
|
||||
using olive::core::Rational;
|
||||
using olive::Vector2D;
|
||||
using olive::Vector3D;
|
||||
using olive::Vector4D;
|
||||
|
||||
switch (value->type) {
|
||||
case OAKNODE_VALUE_INT:
|
||||
case OAKNODE_VALUE_COMBO:
|
||||
*out = olive::Variant(value->num);
|
||||
return true;
|
||||
case OAKNODE_VALUE_FLOAT:
|
||||
*out = olive::Variant(value->f[0]);
|
||||
return true;
|
||||
case OAKNODE_VALUE_BOOL:
|
||||
*out = olive::Variant(value->num != 0);
|
||||
return true;
|
||||
case OAKNODE_VALUE_RATIONAL:
|
||||
*out = olive::Variant::from_value(
|
||||
Rational(static_cast<int>(value->num), static_cast<int>(value->den)));
|
||||
return true;
|
||||
case OAKNODE_VALUE_COLOR:
|
||||
*out = olive::Variant::from_value(
|
||||
Color(static_cast<float>(value->f[0]), static_cast<float>(value->f[1]),
|
||||
static_cast<float>(value->f[2]), static_cast<float>(value->f[3])));
|
||||
return true;
|
||||
case OAKNODE_VALUE_VEC2:
|
||||
*out = olive::Variant::from_value(Vector2D(static_cast<float>(value->f[0]),
|
||||
static_cast<float>(value->f[1])));
|
||||
return true;
|
||||
case OAKNODE_VALUE_VEC3:
|
||||
*out = olive::Variant::from_value(Vector3D(static_cast<float>(value->f[0]),
|
||||
static_cast<float>(value->f[1]),
|
||||
static_cast<float>(value->f[2])));
|
||||
return true;
|
||||
case OAKNODE_VALUE_VEC4:
|
||||
*out = olive::Variant::from_value(Vector4D(static_cast<float>(value->f[0]),
|
||||
static_cast<float>(value->f[1]),
|
||||
static_cast<float>(value->f[2]),
|
||||
static_cast<float>(value->f[3])));
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Map an olive::Variant of declared type `type` into an
|
||||
* oaknode_value POD.
|
||||
*
|
||||
* Returns OAKNODE_OK, OAKNODE_E_INVALID for string-family types (use the
|
||||
* string getters), or OAKNODE_E_FAILED for types without a POD
|
||||
* representation.
|
||||
*/
|
||||
inline int value_from_variant(olive::NodeValue::Type type, const olive::Variant &v,
|
||||
oaknode_value *out)
|
||||
{
|
||||
using olive::core::Color;
|
||||
using olive::core::Rational;
|
||||
using olive::Vector2D;
|
||||
using olive::Vector3D;
|
||||
using olive::Vector4D;
|
||||
|
||||
if (value_type_is_string(type)) {
|
||||
return OAKNODE_E_INVALID;
|
||||
}
|
||||
|
||||
*out = oaknode_value();
|
||||
out->type = value_type_to_oak(type);
|
||||
|
||||
switch (type) {
|
||||
case olive::NodeValue::k_none:
|
||||
return OAKNODE_OK;
|
||||
case olive::NodeValue::k_int:
|
||||
case olive::NodeValue::k_combo:
|
||||
out->num = v.to_long_long();
|
||||
return OAKNODE_OK;
|
||||
case olive::NodeValue::k_float:
|
||||
out->f[0] = v.to_double();
|
||||
return OAKNODE_OK;
|
||||
case olive::NodeValue::k_boolean:
|
||||
out->num = v.to_bool() ? 1 : 0;
|
||||
return OAKNODE_OK;
|
||||
case olive::NodeValue::k_rational: {
|
||||
Rational r = v.value<Rational>();
|
||||
out->num = r.numerator();
|
||||
out->den = r.denominator();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
case olive::NodeValue::k_color: {
|
||||
Color c = v.value<Color>();
|
||||
out->f[0] = c.red();
|
||||
out->f[1] = c.green();
|
||||
out->f[2] = c.blue();
|
||||
out->f[3] = c.alpha();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
case olive::NodeValue::k_vec2: {
|
||||
Vector2D vec = v.value<Vector2D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
case olive::NodeValue::k_vec3: {
|
||||
Vector3D vec = v.value<Vector3D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
out->f[2] = vec.z();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
case olive::NodeValue::k_vec4: {
|
||||
Vector4D vec = v.value<Vector4D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
out->f[2] = vec.z();
|
||||
out->f[3] = vec.w();
|
||||
return OAKNODE_OK;
|
||||
}
|
||||
default:
|
||||
out->type = OAKNODE_VALUE_NONE;
|
||||
return OAKNODE_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wrap a freshly created olive::UndoCommand in an owned
|
||||
* OakUndoCommand handle. Returns NULL on allocation failure.
|
||||
*/
|
||||
inline OakUndoCommand *wrap_command(olive::UndoCommand *command)
|
||||
{
|
||||
if (!command) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
OakUndoCommand *handle = new (std::nothrow) OakUndoCommand();
|
||||
if (!handle) {
|
||||
delete command;
|
||||
return NULL;
|
||||
}
|
||||
handle->command = command;
|
||||
handle->owned = true;
|
||||
return handle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_NODE_C_API_VALUECONVERT_H
|
||||
@@ -0,0 +1,36 @@
|
||||
# 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/>.
|
||||
|
||||
# oaknode library. The tree is flat: src/node/src is the include root and all
|
||||
# in-module includes use the unprefixed form ("value.h", "block/block.h").
|
||||
# The per-subdirectory CMakeLists.txt files are inert legacy (they only append
|
||||
# to OLIVE_SOURCES) and are intentionally not added.
|
||||
file(GLOB_RECURSE OAKNODE_SOURCES CONFIGURE_DEPENDS *.cpp)
|
||||
|
||||
add_library(oaknode SHARED ${OAKNODE_SOURCES})
|
||||
|
||||
target_include_directories(oaknode PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${OAK_REPO_ROOT}/include
|
||||
${OAK_REPO_ROOT}/src/common/src
|
||||
${OAK_REPO_ROOT}/src/undo/src
|
||||
${OAK_REPO_ROOT}/core/include
|
||||
${OAK_REPO_ROOT}/ffmpeg_bridge/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/include
|
||||
${OCIO_INCLUDE_DIRS}
|
||||
${OIIO_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(pan)
|
||||
add_subdirectory(volume)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/audio/pan/pan.h
|
||||
node/audio/pan/pan.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,132 @@
|
||||
/***
|
||||
|
||||
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 "pan.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const std::string PanNode::k_samples_input = "samples_in";
|
||||
const std::string PanNode::k_panning_input = "panning_in";
|
||||
|
||||
#define super Node
|
||||
|
||||
PanNode::PanNode()
|
||||
{
|
||||
add_input(k_samples_input, NodeValue::k_samples,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_panning_input, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_panning_input, "min", -1.0);
|
||||
set_input_property(k_panning_input, "max", 1.0);
|
||||
set_input_property(k_panning_input, "view",
|
||||
slider::k_percentage);
|
||||
|
||||
set_flag(k_audio_effect);
|
||||
set_effect_input(k_samples_input);
|
||||
}
|
||||
|
||||
std::string PanNode::name() const
|
||||
{
|
||||
return "Pan";
|
||||
}
|
||||
|
||||
std::string PanNode::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.pan";
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> PanNode::category() const
|
||||
{
|
||||
return { k_category_filter };
|
||||
}
|
||||
|
||||
std::string PanNode::description() const
|
||||
{
|
||||
return "Adjust the stereo panning of an audio source.";
|
||||
}
|
||||
|
||||
void PanNode::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
// Create a sample job
|
||||
SampleBuffer samples = value.at(k_samples_input).to_samples();
|
||||
if (samples.is_allocated()) {
|
||||
// This node is only compatible with stereo audio
|
||||
if (samples.audio_params().channel_count() == 2) {
|
||||
// If the input is static, we can just do it now which will be faster
|
||||
if (is_input_static(k_panning_input)) {
|
||||
float pan_volume = value.at(k_panning_input).to_double();
|
||||
if (pan_volume != 0.0f) {
|
||||
if (pan_volume > 0) {
|
||||
samples.transform_volume_for_channel(0,
|
||||
1.0f - pan_volume);
|
||||
} else {
|
||||
samples.transform_volume_for_channel(1,
|
||||
1.0f + pan_volume);
|
||||
}
|
||||
}
|
||||
|
||||
table->push(NodeValue(NodeValue::k_samples, samples, this));
|
||||
} else {
|
||||
// Requires job
|
||||
SampleJob job(globals.time(), k_samples_input, value);
|
||||
job.insert(k_panning_input, value);
|
||||
table->push(NodeValue::k_samples, Variant::from_value(job),
|
||||
this);
|
||||
}
|
||||
} else {
|
||||
// Pass right through
|
||||
table->push(value.at(k_samples_input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PanNode::process_samples(const NodeValueRow &values,
|
||||
const SampleBuffer &input, SampleBuffer &output,
|
||||
int index) const
|
||||
{
|
||||
float pan_val = values.at(k_panning_input).to_double();
|
||||
|
||||
for (int i = 0; i < input.audio_params().channel_count(); i++) {
|
||||
output.data(i)[index] = input.data(i)[index];
|
||||
}
|
||||
|
||||
if (pan_val > 0) {
|
||||
output.data(0)[index] *= (1.0F - pan_val);
|
||||
} else if (pan_val < 0) {
|
||||
output.data(1)[index] *= (1.0F - std::abs(pan_val));
|
||||
}
|
||||
}
|
||||
|
||||
void PanNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_samples_input, "Samples");
|
||||
set_input_name(k_panning_input, "Pan");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/***
|
||||
|
||||
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_PANNODE_H
|
||||
#define OAK_PANNODE_H
|
||||
|
||||
#include "node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class PanNode : public Node {
|
||||
public:
|
||||
PanNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(PanNode)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void process_samples(const NodeValueRow &values,
|
||||
const SampleBuffer &input, SampleBuffer &output,
|
||||
int index) const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
static const std::string k_samples_input;
|
||||
static const std::string k_panning_input;
|
||||
|
||||
private:
|
||||
NodeInput *samples_input_;
|
||||
NodeInput *panning_input_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_PANNODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/audio/volume/volume.h
|
||||
node/audio/volume/volume.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,114 @@
|
||||
/***
|
||||
|
||||
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 "volume.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const std::string VolumeNode::k_samples_input = "samples_in";
|
||||
const std::string VolumeNode::k_volume_input = "volume_in";
|
||||
|
||||
#define super MathNodeBase
|
||||
|
||||
VolumeNode::VolumeNode()
|
||||
{
|
||||
add_input(k_samples_input, NodeValue::k_samples,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_volume_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_volume_input, "min", 0.0);
|
||||
set_input_property(k_volume_input, "view",
|
||||
slider::k_decibel);
|
||||
|
||||
set_flag(k_audio_effect);
|
||||
set_effect_input(k_samples_input);
|
||||
}
|
||||
|
||||
std::string VolumeNode::name() const
|
||||
{
|
||||
return "Volume";
|
||||
}
|
||||
|
||||
std::string VolumeNode::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.volume";
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> VolumeNode::category() const
|
||||
{
|
||||
return { k_category_filter };
|
||||
}
|
||||
|
||||
std::string VolumeNode::description() const
|
||||
{
|
||||
return "Adjusts the volume of an audio source.";
|
||||
}
|
||||
|
||||
void VolumeNode::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
// Create a sample job
|
||||
SampleBuffer buffer = value.at(k_samples_input).to_samples();
|
||||
|
||||
if (buffer.is_allocated()) {
|
||||
// If the input is static, we can just do it now which will be faster
|
||||
if (is_input_static(k_volume_input)) {
|
||||
auto volume = value.at(k_volume_input).to_double();
|
||||
|
||||
// Same semantics as !qFuzzyCompare(volume, 1.0) (double overload)
|
||||
if (std::abs(volume - 1.0) * 1000000000000.0 >
|
||||
std::min(std::abs(volume), 1.0)) {
|
||||
buffer.transform_volume(volume);
|
||||
}
|
||||
|
||||
table->push(NodeValue::k_samples, Variant::from_value(buffer), this);
|
||||
} else {
|
||||
// Requires job
|
||||
SampleJob job(globals.time(), k_samples_input, value);
|
||||
job.insert(k_volume_input, value);
|
||||
table->push(NodeValue::k_samples, Variant::from_value(job), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeNode::process_samples(const NodeValueRow &values,
|
||||
const SampleBuffer &input, SampleBuffer &output,
|
||||
int index) const
|
||||
{
|
||||
return process_samples_internal(values, k_op_multiply, k_samples_input,
|
||||
k_volume_input, input, output, index);
|
||||
}
|
||||
|
||||
void VolumeNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_samples_input, "Samples");
|
||||
set_input_name(k_volume_input, "Volume");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/***
|
||||
|
||||
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_VOLUMENODE_H
|
||||
#define OAK_VOLUMENODE_H
|
||||
|
||||
#include "math/math/mathbase.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class VolumeNode : public MathNodeBase {
|
||||
public:
|
||||
VolumeNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(VolumeNode)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void process_samples(const NodeValueRow &values,
|
||||
const SampleBuffer &input, SampleBuffer &output,
|
||||
int index) const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
static const std::string k_samples_input;
|
||||
static const std::string k_volume_input;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_VOLUMENODE_H
|
||||
@@ -0,0 +1,27 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(clip)
|
||||
add_subdirectory(gap)
|
||||
add_subdirectory(subtitle)
|
||||
add_subdirectory(transition)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/block.h
|
||||
node/block/block.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,144 @@
|
||||
/***
|
||||
|
||||
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 "block.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "inputdragger.h"
|
||||
#include "sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Node
|
||||
|
||||
const std::string Block::k_length_input = "length_in";
|
||||
|
||||
Block::Block()
|
||||
: previous_(nullptr)
|
||||
, next_(nullptr)
|
||||
, track_(nullptr)
|
||||
{
|
||||
add_input(k_length_input, NodeValue::k_rational,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable |
|
||||
k_input_flag_hidden));
|
||||
set_input_property(k_length_input, "min",
|
||||
Variant::from_value(Rational(0, 1)));
|
||||
set_input_property(k_length_input, "view",
|
||||
slider::k_time);
|
||||
set_input_property(k_length_input, "viewlock", true);
|
||||
|
||||
set_input_flag(k_enabled_input, k_input_flag_not_connectable);
|
||||
set_input_flag(k_enabled_input, k_input_flag_not_keyframable);
|
||||
|
||||
set_flag(k_dont_show_in_param_view);
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> Block::category() const
|
||||
{
|
||||
return { k_category_timeline };
|
||||
}
|
||||
|
||||
Rational Block::length() const
|
||||
{
|
||||
return get_standard_value(k_length_input).value<Rational>();
|
||||
}
|
||||
|
||||
void Block::set_length_and_media_out(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_length_internal(length);
|
||||
}
|
||||
|
||||
void Block::set_length_and_media_in(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the length without setting media out
|
||||
set_length_internal(length);
|
||||
}
|
||||
|
||||
bool Block::is_enabled() const
|
||||
{
|
||||
return get_standard_value(k_enabled_input).to_bool();
|
||||
}
|
||||
|
||||
void Block::set_enabled(bool e)
|
||||
{
|
||||
set_standard_value(k_enabled_input, e);
|
||||
}
|
||||
|
||||
void Block::InputValueChangedEvent(const std::string &input, int element)
|
||||
{
|
||||
super::InputValueChangedEvent(input, element);
|
||||
}
|
||||
|
||||
void Block::set_length_internal(const Rational &length)
|
||||
{
|
||||
set_standard_value(k_length_input, Variant::from_value(length));
|
||||
}
|
||||
|
||||
void Block::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_length_input, "Length");
|
||||
set_input_name(k_enabled_input, "Enabled");
|
||||
}
|
||||
|
||||
void Block::invalidate_cache(const TimeRange &range, const std::string &from,
|
||||
int element, InvalidateCacheOptions options)
|
||||
{
|
||||
TimeRange r;
|
||||
|
||||
if (from == k_length_input) {
|
||||
// We must intercept the signal here
|
||||
r = TimeRange(std::min(length(), last_length_), RATIONAL_MAX);
|
||||
|
||||
if (!NodeInputDragger::is_input_being_dragged()) {
|
||||
last_length_ = length();
|
||||
}
|
||||
|
||||
options["lengthevent"] = true;
|
||||
} else {
|
||||
r = range;
|
||||
}
|
||||
|
||||
super::invalidate_cache(r, from, element, options);
|
||||
}
|
||||
|
||||
void Block::set_previous_next(Block *previous, Block *next)
|
||||
{
|
||||
if (previous) {
|
||||
previous->set_next(next);
|
||||
}
|
||||
if (next) {
|
||||
next->set_previous(previous);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/***
|
||||
|
||||
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_BLOCK_H
|
||||
#define OAK_BLOCK_H
|
||||
|
||||
#include "node.h"
|
||||
#include "timeline/timelinecommon.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class TransitionBlock;
|
||||
|
||||
/**
|
||||
* @brief A Node that represents a block of time, also displayable on a Timeline
|
||||
*/
|
||||
class Block : public Node {
|
||||
public:
|
||||
Block();
|
||||
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
|
||||
const Rational &in() const
|
||||
{
|
||||
return in_point_;
|
||||
}
|
||||
|
||||
const Rational &out() const
|
||||
{
|
||||
return out_point_;
|
||||
}
|
||||
|
||||
void set_in(const Rational &in)
|
||||
{
|
||||
in_point_ = in;
|
||||
}
|
||||
|
||||
void set_out(const Rational &out)
|
||||
{
|
||||
out_point_ = out;
|
||||
}
|
||||
|
||||
Rational length() const;
|
||||
virtual void set_length_and_media_out(const Rational &length);
|
||||
virtual void set_length_and_media_in(const Rational &length);
|
||||
|
||||
TimeRange range() const
|
||||
{
|
||||
return TimeRange(in(), out());
|
||||
}
|
||||
|
||||
Block *previous() const
|
||||
{
|
||||
return previous_;
|
||||
}
|
||||
|
||||
Block *next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
void set_previous(Block *previous)
|
||||
{
|
||||
previous_ = previous;
|
||||
}
|
||||
|
||||
void set_next(Block *next)
|
||||
{
|
||||
next_ = next;
|
||||
}
|
||||
|
||||
Track *track() const
|
||||
{
|
||||
return track_;
|
||||
}
|
||||
|
||||
void set_track(Track *track)
|
||||
{
|
||||
track_ = track;
|
||||
}
|
||||
|
||||
bool is_enabled() const;
|
||||
void set_enabled(bool e);
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
virtual void invalidate_cache(
|
||||
const TimeRange &range, const std::string &from, int element = -1,
|
||||
InvalidateCacheOptions options = InvalidateCacheOptions()) override;
|
||||
|
||||
static const std::string k_length_input;
|
||||
|
||||
static void set_previous_next(Block *previous, Block *next);
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const std::string &input,
|
||||
int element) override;
|
||||
|
||||
Block *previous_;
|
||||
Block *next_;
|
||||
|
||||
private:
|
||||
void set_length_internal(const Rational &length);
|
||||
|
||||
Rational in_point_;
|
||||
Rational out_point_;
|
||||
Track *track_;
|
||||
|
||||
Rational last_length_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_BLOCK_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/clip/clip.h
|
||||
node/block/clip/clip.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,591 @@
|
||||
/***
|
||||
|
||||
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 "clip.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "block/transition/transition.h"
|
||||
#include "output/track/track.h"
|
||||
#include "output/viewer/viewer.h"
|
||||
#include "project/sequence/sequence.h"
|
||||
#include "sliderdisplaytype.h"
|
||||
#include "sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Block
|
||||
|
||||
const std::string ClipBlock::k_buffer_in = "buffer_in";
|
||||
const std::string ClipBlock::k_media_in_input = "media_in_in";
|
||||
const std::string ClipBlock::k_speed_input = "speed_in";
|
||||
const std::string ClipBlock::k_reverse_input = "reverse_in";
|
||||
const std::string ClipBlock::k_maintain_audio_pitch_input =
|
||||
"maintain_audio_pitch_in";
|
||||
const std::string ClipBlock::k_auto_cache_input = "autocache_in";
|
||||
const std::string ClipBlock::k_loop_mode_input = "loop_in";
|
||||
|
||||
ClipBlock::ClipBlock()
|
||||
: in_transition_(nullptr)
|
||||
, out_transition_(nullptr)
|
||||
, connected_viewer_(nullptr)
|
||||
{
|
||||
add_input(k_media_in_input, NodeValue::k_rational,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
set_input_property(k_media_in_input, "view",
|
||||
slider::k_time);
|
||||
set_input_property(k_media_in_input, "viewlock", true);
|
||||
|
||||
add_input(k_speed_input, NodeValue::k_float, 1.0,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
set_input_property(k_speed_input, "view",
|
||||
slider::k_percentage);
|
||||
set_input_property(k_speed_input, "min", 0.0);
|
||||
|
||||
add_input(k_reverse_input, NodeValue::k_boolean, false,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_maintain_audio_pitch_input, NodeValue::k_boolean, false,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_auto_cache_input, NodeValue::k_boolean, false,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
prepend_input(k_buffer_in, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
//SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
|
||||
|
||||
set_effect_input(k_buffer_in);
|
||||
|
||||
add_input(k_loop_mode_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
}
|
||||
|
||||
std::string ClipBlock::name() const
|
||||
{
|
||||
if (connected_viewer_ && !connected_viewer_->get_label().empty()) {
|
||||
return connected_viewer_->get_label();
|
||||
} else if (track()) {
|
||||
if (track()->type() == Track::k_video) {
|
||||
return "Video Clip";
|
||||
} else if (track()->type() == Track::k_audio) {
|
||||
return "Audio Clip";
|
||||
}
|
||||
}
|
||||
|
||||
return "Clip";
|
||||
}
|
||||
|
||||
std::string ClipBlock::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.clip";
|
||||
}
|
||||
|
||||
std::string ClipBlock::description() const
|
||||
{
|
||||
return "A time-based node that represents a media source.";
|
||||
}
|
||||
|
||||
void ClipBlock::set_length_and_media_out(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
Rational proposed_media_in = sequence_to_media_time(
|
||||
this->length() - length, k_stm_ignore_reverse | k_stm_ignore_loop);
|
||||
set_media_in(proposed_media_in);
|
||||
}
|
||||
|
||||
super::set_length_and_media_out(length);
|
||||
}
|
||||
|
||||
void ClipBlock::set_length_and_media_in(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rational old_length = this->length();
|
||||
|
||||
super::set_length_and_media_in(length);
|
||||
|
||||
if (!reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
set_media_in(sequence_to_media_time(old_length - length, k_stm_ignore_loop));
|
||||
}
|
||||
}
|
||||
|
||||
Rational ClipBlock::media_in() const
|
||||
{
|
||||
return get_standard_value(k_media_in_input).value<Rational>();
|
||||
}
|
||||
|
||||
Node::ValueHint ClipBlock::get_value_hint_for_input(const std::string &input,
|
||||
int element) const
|
||||
{
|
||||
if (input == k_buffer_in) {
|
||||
// The buffer input takes whatever the connected node provides, so it
|
||||
// is declared as kNone and carries no stored hint. When the connected
|
||||
// node pushes more than one value type (a footage pushes both a
|
||||
// kTexture job and a kSamples job), a typeless lookup falls back to
|
||||
// the last value in the table, which may feed audio samples into a
|
||||
// video clip and produce a black frame. Prefer the value type that
|
||||
// matches this clip's track.
|
||||
switch (get_track_type()) {
|
||||
case Track::k_video:
|
||||
return ValueHint(std::vector<NodeValue::Type>{ NodeValue::k_texture });
|
||||
case Track::k_audio:
|
||||
return ValueHint(std::vector<NodeValue::Type>{ NodeValue::k_samples });
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super::get_value_hint_for_input(input, element);
|
||||
}
|
||||
|
||||
void ClipBlock::set_media_in(const Rational &media_in)
|
||||
{
|
||||
set_standard_value(k_media_in_input, Variant::from_value(media_in));
|
||||
|
||||
request_invalidated_from_connected();
|
||||
}
|
||||
|
||||
void ClipBlock::set_autocache(bool e)
|
||||
{
|
||||
set_standard_value(k_auto_cache_input, e);
|
||||
}
|
||||
|
||||
void ClipBlock::discard_cache()
|
||||
{
|
||||
if (Node *connected = get_connected_output(k_buffer_in)) {
|
||||
Track::Type type = get_track_type();
|
||||
if (type == Track::k_video) {
|
||||
connected->video_frame_cache()->invalidate(
|
||||
TimeRange(RATIONAL_MIN, RATIONAL_MAX));
|
||||
} else if (type == Track::k_audio) {
|
||||
connected->audio_playback_cache()->invalidate(
|
||||
TimeRange(RATIONAL_MIN, RATIONAL_MAX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rational ClipBlock::sequence_to_media_time(const Rational &sequence_time,
|
||||
uint64_t flags) const
|
||||
{
|
||||
// These constants are not considered "values" per se, so we don't modify them
|
||||
if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) {
|
||||
return sequence_time;
|
||||
}
|
||||
|
||||
Rational media_time = sequence_time;
|
||||
|
||||
if (reverse() && !(flags & k_stm_ignore_reverse)) {
|
||||
media_time = length() - media_time;
|
||||
}
|
||||
|
||||
if (!(flags & k_stm_ignore_speed)) {
|
||||
double speed_value = speed();
|
||||
if (speed_value == 0.0) {
|
||||
// Effectively holds the frame at the in point
|
||||
media_time = 0;
|
||||
} else if (!(std::abs(speed_value - 1.0) * 1000000000000.0 <=
|
||||
std::min(std::abs(speed_value), std::abs(1.0)))) {
|
||||
// Multiply time
|
||||
media_time =
|
||||
Rational::from_double(media_time.to_double() * speed_value);
|
||||
}
|
||||
}
|
||||
|
||||
media_time += media_in();
|
||||
|
||||
/*if (!(flags & kSTMIgnoreLoop)
|
||||
&& this->loop_mode() != kLoopModeOff
|
||||
&& connected_viewer_
|
||||
&& !connected_viewer_->GetLength().isNull()
|
||||
&& (media_time < 0 || media_time >= connected_viewer_->GetLength())) {
|
||||
if (loop_mode() == kLoopModeLoop) {
|
||||
while (media_time < 0) {
|
||||
media_time += connected_viewer_->GetLength();
|
||||
}
|
||||
while (media_time >= connected_viewer_->GetLength()) {
|
||||
media_time -= connected_viewer_->GetLength();
|
||||
}
|
||||
} else if (loop_mode() == kLoopModeClamp) {
|
||||
media_time = std::clamp(media_time, Rational(0), connected_viewer_->GetLength()-connected_viewer_->GetVideoParams().frame_rate_as_time_base());
|
||||
}
|
||||
}*/
|
||||
|
||||
return media_time;
|
||||
}
|
||||
|
||||
Rational ClipBlock::media_to_sequence_time(const Rational &media_time) const
|
||||
{
|
||||
// These constants are not considered "values" per se, so we don't modify them
|
||||
if (media_time == RATIONAL_MIN || media_time == RATIONAL_MAX) {
|
||||
return media_time;
|
||||
}
|
||||
|
||||
Rational sequence_time = media_time - media_in();
|
||||
|
||||
double speed_value = speed();
|
||||
if (speed_value == 0.0) {
|
||||
// Speed zero holds the frame at the in point, so map to that frame
|
||||
sequence_time = media_in();
|
||||
} else if (!(std::abs(speed_value - 1.0) * 1000000000000.0 <=
|
||||
std::min(std::abs(speed_value), std::abs(1.0)))) {
|
||||
// Divide time
|
||||
sequence_time =
|
||||
Rational::from_double(sequence_time.to_double() / speed_value);
|
||||
}
|
||||
|
||||
if (reverse()) {
|
||||
sequence_time = length() - sequence_time;
|
||||
}
|
||||
|
||||
return sequence_time;
|
||||
}
|
||||
|
||||
void ClipBlock::request_range_from_connected(const TimeRange &range)
|
||||
{
|
||||
Track::Type type = get_track_type();
|
||||
|
||||
if (type == Track::k_video || type == Track::k_audio) {
|
||||
if (Node *connected = get_connected_output(k_buffer_in)) {
|
||||
TimeRange max_range = media_range();
|
||||
if (type == Track::k_video) {
|
||||
// Handle thumbnails
|
||||
request_range_for_cache(connected->thumbnail_cache(), max_range,
|
||||
range, true, false);
|
||||
{
|
||||
TimeRange thumb_range = range.intersected(max_range);
|
||||
if (get_adjusted_thumbnail_range(&thumb_range)) {
|
||||
connected->thumbnail_cache()->request(
|
||||
this->track()->sequence(), thumb_range);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle video cache
|
||||
request_range_for_cache(connected->video_frame_cache(), max_range,
|
||||
range, true, is_autocaching());
|
||||
} else if (type == Track::k_audio) {
|
||||
// Handle waveforms
|
||||
request_range_for_cache(
|
||||
connected->waveform_cache(), max_range, range, true,
|
||||
(OAK_CONFIG("TimelineWaveformMode").to_int() ==
|
||||
Timeline::k_waveforms_enabled));
|
||||
|
||||
// Handle audio cache
|
||||
request_range_for_cache(connected->audio_playback_cache(),
|
||||
max_range, range, true, is_autocaching());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::request_invalidated_from_connected(bool force_all,
|
||||
const TimeRange &intersect)
|
||||
{
|
||||
Track::Type type = get_track_type();
|
||||
|
||||
if (type == Track::k_video || type == Track::k_audio) {
|
||||
if (Node *connected = get_connected_output(k_buffer_in)) {
|
||||
TimeRange max_range = media_range();
|
||||
|
||||
if (!intersect.length().isNull()) {
|
||||
max_range = max_range.intersected(intersect);
|
||||
}
|
||||
|
||||
if (type == Track::k_video) {
|
||||
// Handle thumbnails
|
||||
TimeRange thumb_range = max_range;
|
||||
if (get_adjusted_thumbnail_range(&thumb_range)) {
|
||||
request_invalidated_for_cache(connected->thumbnail_cache(),
|
||||
thumb_range);
|
||||
}
|
||||
|
||||
// Handle video cache
|
||||
if (is_autocaching() || force_all) {
|
||||
request_invalidated_for_cache(connected->video_frame_cache(),
|
||||
max_range);
|
||||
}
|
||||
} else if (type == Track::k_audio) {
|
||||
// Handle waveforms
|
||||
if (OAK_CONFIG("TimelineWaveformMode").to_int() ==
|
||||
Timeline::k_waveforms_enabled) {
|
||||
request_invalidated_for_cache(connected->waveform_cache(),
|
||||
max_range);
|
||||
}
|
||||
|
||||
// Handle audio cache
|
||||
if (is_autocaching() || force_all) {
|
||||
request_invalidated_for_cache(
|
||||
connected->audio_playback_cache(), max_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::request_range_for_cache(PlaybackCache *cache,
|
||||
const TimeRange &max_range,
|
||||
const TimeRange &range, bool invalidate,
|
||||
bool request)
|
||||
{
|
||||
TimeRange r = range.intersected(max_range);
|
||||
|
||||
if (invalidate) {
|
||||
cache->invalidate(r);
|
||||
}
|
||||
|
||||
if (request) {
|
||||
cache->request(this->track()->sequence(), r);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::request_invalidated_for_cache(PlaybackCache *cache,
|
||||
const TimeRange &max_range)
|
||||
{
|
||||
core::TimeRangeList invalid = cache->get_invalidated_ranges(max_range);
|
||||
|
||||
for (const PlaybackCache::Passthrough &p : cache->get_passthroughs()) {
|
||||
invalid.remove(p);
|
||||
}
|
||||
|
||||
for (const TimeRange &r : invalid) {
|
||||
request_range_for_cache(cache, max_range, r, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClipBlock::get_adjusted_thumbnail_range(TimeRange *r) const
|
||||
{
|
||||
switch (static_cast<Timeline::ThumbnailMode>(
|
||||
OAK_CONFIG("TimelineThumbnailMode").to_int())) {
|
||||
case Timeline::k_thumbnail_off:
|
||||
// Don't cache any range
|
||||
return false;
|
||||
case Timeline::k_thumbnail_in_out: {
|
||||
// Only cache in point
|
||||
Rational in = this->media_range().in();
|
||||
if (r->contains(in)) {
|
||||
// Cache only the in point
|
||||
*r = TimeRange(in, in + thumbnail_cache()->get_timebase());
|
||||
return true;
|
||||
} else {
|
||||
// Cache nothing
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case Timeline::k_thumbnail_on:
|
||||
// Cache entire range
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClipBlock::invalidate_cache(const TimeRange &range, const std::string &from,
|
||||
int element, InvalidateCacheOptions options)
|
||||
{
|
||||
(void) element;
|
||||
|
||||
// If signal is from texture input, transform all times from media time to sequence time
|
||||
if (from == k_buffer_in) {
|
||||
// Render caches where necessary
|
||||
if (are_caches_enabled()) {
|
||||
request_range_from_connected(range);
|
||||
}
|
||||
|
||||
// Adjust range from media time to sequence time
|
||||
TimeRange adj;
|
||||
double speed_value = speed();
|
||||
|
||||
if (speed_value == 0.0) {
|
||||
// Handle 0 speed by invalidating the whole clip
|
||||
adj = TimeRange(RATIONAL_MIN, RATIONAL_MAX);
|
||||
} else {
|
||||
adj = TimeRange(media_to_sequence_time(range.in()),
|
||||
media_to_sequence_time(range.out()));
|
||||
}
|
||||
|
||||
// Find connected viewer node
|
||||
auto viewers = find_input_nodes_connected_to_input<ViewerOutput>(
|
||||
NodeInput(this, k_buffer_in), 1);
|
||||
ViewerOutput *new_connected_viewer =
|
||||
viewers.empty() ? nullptr : viewers.front();
|
||||
|
||||
if (new_connected_viewer != connected_viewer_) {
|
||||
connected_viewer_ = new_connected_viewer;
|
||||
}
|
||||
|
||||
super::invalidate_cache(adj, from, element, options);
|
||||
} else {
|
||||
// Otherwise, pass signal along normally
|
||||
super::invalidate_cache(range, from, element, options);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::LinkChangeEvent()
|
||||
{
|
||||
block_links_.clear();
|
||||
|
||||
for (Node *n : links()) {
|
||||
ClipBlock *b = dynamic_cast<ClipBlock *>(n);
|
||||
|
||||
if (b) {
|
||||
block_links_.push_back(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::InputConnectedEvent(const std::string &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputConnectedEvent(input, element, output);
|
||||
}
|
||||
|
||||
void ClipBlock::InputDisconnectedEvent(const std::string &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputDisconnectedEvent(input, element, output);
|
||||
}
|
||||
|
||||
void ClipBlock::InputValueChangedEvent(const std::string &input, int element)
|
||||
{
|
||||
super::InputValueChangedEvent(input, element);
|
||||
|
||||
if (input == k_auto_cache_input) {
|
||||
if (is_autocaching()) {
|
||||
request_invalidated_from_connected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange ClipBlock::input_time_adjustment(const std::string &input, int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const
|
||||
{
|
||||
(void) element;
|
||||
|
||||
if (input == k_buffer_in) {
|
||||
return TimeRange(sequence_to_media_time(input_time.in()),
|
||||
sequence_to_media_time(input_time.out()));
|
||||
}
|
||||
|
||||
return super::input_time_adjustment(input, element, input_time, clamp);
|
||||
}
|
||||
|
||||
TimeRange ClipBlock::output_time_adjustment(const std::string &input, int element,
|
||||
const TimeRange &input_time) const
|
||||
{
|
||||
(void) element;
|
||||
|
||||
if (input == k_buffer_in) {
|
||||
return TimeRange(media_to_sequence_time(input_time.in()),
|
||||
media_to_sequence_time(input_time.out()));
|
||||
}
|
||||
|
||||
return super::output_time_adjustment(input, element, input_time);
|
||||
}
|
||||
|
||||
void ClipBlock::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
(void) globals;
|
||||
|
||||
// We discard most values here except for the buffer we received
|
||||
NodeValue data = value.at(k_buffer_in);
|
||||
|
||||
table->clear();
|
||||
if (data.type() != NodeValue::k_none) {
|
||||
table->push(data);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_buffer_in, "Buffer");
|
||||
set_input_name(k_media_in_input, "Media In");
|
||||
set_input_name(k_speed_input, "Speed");
|
||||
set_input_name(k_reverse_input, "Reverse");
|
||||
set_input_name(k_maintain_audio_pitch_input, "Maintain Audio Pitch");
|
||||
set_input_name(k_loop_mode_input, "Loop");
|
||||
set_combo_box_strings(k_loop_mode_input, { "None", "Loop", "Clamp" });
|
||||
}
|
||||
|
||||
void ClipBlock::add_cache_passthrough_from(ClipBlock *other)
|
||||
{
|
||||
if (auto tc = this->video_frame_cache()) {
|
||||
if (auto oc = other->video_frame_cache()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto tc = this->audio_playback_cache()) {
|
||||
if (auto oc = other->audio_playback_cache()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto tc = this->thumbnails()) {
|
||||
if (auto oc = other->thumbnails()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto tc = this->waveform()) {
|
||||
if (auto oc = other->waveform()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::ConnectedToPreviewEvent()
|
||||
{
|
||||
request_invalidated_from_connected();
|
||||
}
|
||||
|
||||
TimeRange ClipBlock::media_range() const
|
||||
{
|
||||
return input_time_adjustment(k_buffer_in, -1, TimeRange(0, length()), false);
|
||||
}
|
||||
|
||||
MultiCamNode *ClipBlock::find_multicam()
|
||||
{
|
||||
auto v = find_input_nodes_connected_to_input<MultiCamNode>(
|
||||
NodeInput(this, k_buffer_in), 1);
|
||||
if (v.empty()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return v.front();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/***
|
||||
|
||||
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_CLIPBLOCK_H
|
||||
#define OAK_CLIPBLOCK_H
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "codec/decoder.h"
|
||||
#include "block/block.h"
|
||||
#include "input/multicam/multicamnode.h"
|
||||
#include "output/track/track.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ViewerOutput;
|
||||
|
||||
/**
|
||||
* @brief Node that represents a block of Media
|
||||
*/
|
||||
class ClipBlock : public Block {
|
||||
public:
|
||||
ClipBlock();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(ClipBlock)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual void set_length_and_media_out(const Rational &length) override;
|
||||
virtual void set_length_and_media_in(const Rational &length) override;
|
||||
|
||||
Track::Type get_track_type() const
|
||||
{
|
||||
if (track()) {
|
||||
return track()->type();
|
||||
} else {
|
||||
return Track::k_none;
|
||||
}
|
||||
}
|
||||
|
||||
virtual Node::ValueHint
|
||||
get_value_hint_for_input(const std::string &input, int element = -1) const override;
|
||||
|
||||
Rational media_in() const;
|
||||
void set_media_in(const Rational &media_in);
|
||||
|
||||
bool is_autocaching() const
|
||||
{
|
||||
return get_standard_value(k_auto_cache_input).to_bool();
|
||||
}
|
||||
void set_autocache(bool e);
|
||||
|
||||
void discard_cache();
|
||||
|
||||
virtual void invalidate_cache(const TimeRange &range, const std::string &from,
|
||||
int element,
|
||||
InvalidateCacheOptions options) override;
|
||||
|
||||
virtual TimeRange input_time_adjustment(const std::string &input, int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const override;
|
||||
|
||||
virtual TimeRange
|
||||
output_time_adjustment(const std::string &input, int element,
|
||||
const TimeRange &input_time) const override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
void
|
||||
request_invalidated_from_connected(bool force_all = false,
|
||||
const TimeRange &intersect = TimeRange());
|
||||
|
||||
double speed() const
|
||||
{
|
||||
return get_standard_value(k_speed_input).to_double();
|
||||
}
|
||||
|
||||
bool reverse() const
|
||||
{
|
||||
return get_standard_value(k_reverse_input).to_bool();
|
||||
}
|
||||
|
||||
void set_reverse(bool e)
|
||||
{
|
||||
set_standard_value(k_reverse_input, e);
|
||||
}
|
||||
|
||||
bool maintain_audio_pitch() const
|
||||
{
|
||||
return get_standard_value(k_maintain_audio_pitch_input).to_bool();
|
||||
}
|
||||
|
||||
void set_maintain_audio_pitch(bool e)
|
||||
{
|
||||
set_standard_value(k_maintain_audio_pitch_input, e);
|
||||
}
|
||||
|
||||
TransitionBlock *in_transition()
|
||||
{
|
||||
return in_transition_;
|
||||
}
|
||||
|
||||
void set_in_transition(TransitionBlock *t)
|
||||
{
|
||||
in_transition_ = t;
|
||||
}
|
||||
|
||||
TransitionBlock *out_transition()
|
||||
{
|
||||
return out_transition_;
|
||||
}
|
||||
|
||||
void set_out_transition(TransitionBlock *t)
|
||||
{
|
||||
out_transition_ = t;
|
||||
}
|
||||
|
||||
const std::vector<Block *> &block_links() const
|
||||
{
|
||||
return block_links_;
|
||||
}
|
||||
|
||||
FrameHashCache *connected_video_cache() const
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->video_frame_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AudioPlaybackCache *connected_audio_cache() const
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->audio_playback_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ThumbnailCache *thumbnails()
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->thumbnail_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AudioWaveformCache *waveform()
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->waveform_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void add_cache_passthrough_from(ClipBlock *other);
|
||||
|
||||
ViewerOutput *connected_viewer() const
|
||||
{
|
||||
return connected_viewer_;
|
||||
}
|
||||
|
||||
virtual TimeRange get_video_cache_range() const override
|
||||
{
|
||||
return TimeRange(0, length());
|
||||
}
|
||||
|
||||
virtual TimeRange get_audio_cache_range() const override
|
||||
{
|
||||
return TimeRange(0, length());
|
||||
}
|
||||
|
||||
virtual void ConnectedToPreviewEvent() override;
|
||||
|
||||
TimeRange media_range() const;
|
||||
|
||||
/**
|
||||
* @brief Get currently set loop mode
|
||||
*/
|
||||
LoopMode loop_mode() const
|
||||
{
|
||||
return static_cast<LoopMode>(get_standard_value(k_loop_mode_input).to_int());
|
||||
}
|
||||
|
||||
void set_loop_mode(LoopMode l)
|
||||
{
|
||||
set_standard_value(k_loop_mode_input, int(l));
|
||||
}
|
||||
|
||||
MultiCamNode *find_multicam();
|
||||
|
||||
static const std::string k_buffer_in;
|
||||
static const std::string k_media_in_input;
|
||||
static const std::string k_speed_input;
|
||||
static const std::string k_reverse_input;
|
||||
static const std::string k_maintain_audio_pitch_input;
|
||||
static const std::string k_loop_mode_input;
|
||||
|
||||
static const std::string k_auto_cache_input;
|
||||
|
||||
protected:
|
||||
virtual void LinkChangeEvent() override;
|
||||
|
||||
virtual void InputConnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputDisconnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputValueChangedEvent(const std::string &input,
|
||||
int element) override;
|
||||
|
||||
private:
|
||||
enum SequenceToMediaTimeFlag {
|
||||
k_stm_none = 0x0,
|
||||
k_stm_ignore_reverse = 0x1,
|
||||
k_stm_ignore_speed = 0x2,
|
||||
k_stm_ignore_loop = 0x4
|
||||
};
|
||||
|
||||
Rational sequence_to_media_time(const Rational &sequence_time,
|
||||
uint64_t flags = k_stm_none) const;
|
||||
|
||||
Rational media_to_sequence_time(const Rational &media_time) const;
|
||||
|
||||
void request_range_from_connected(const TimeRange &range);
|
||||
|
||||
void request_range_for_cache(PlaybackCache *cache, const TimeRange &max_range,
|
||||
const TimeRange &range, bool invalidate,
|
||||
bool request);
|
||||
void request_invalidated_for_cache(PlaybackCache *cache,
|
||||
const TimeRange &max_range);
|
||||
|
||||
bool get_adjusted_thumbnail_range(TimeRange *r) const;
|
||||
|
||||
std::vector<Block *> block_links_;
|
||||
|
||||
TransitionBlock *in_transition_;
|
||||
TransitionBlock *out_transition_;
|
||||
|
||||
// NOTE: in the Qt version this was cleared via the viewer's destroyed()
|
||||
// signal (see invalidate_cache); with signals removed from oaknode, the
|
||||
// facade layer must ensure the viewer outlives this clip or clears this
|
||||
// pointer.
|
||||
ViewerOutput *connected_viewer_;
|
||||
|
||||
private:
|
||||
Rational last_media_in_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEBLOCK_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/gap/gap.h
|
||||
node/block/gap/gap.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
/***
|
||||
|
||||
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 "gap.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
GapBlock::GapBlock()
|
||||
{
|
||||
}
|
||||
|
||||
std::string GapBlock::name() const
|
||||
{
|
||||
return "Gap";
|
||||
}
|
||||
|
||||
std::string GapBlock::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.gap";
|
||||
}
|
||||
|
||||
std::string GapBlock::description() const
|
||||
{
|
||||
return "A time-based node that represents an empty space.";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/***
|
||||
|
||||
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_GAPBLOCK_H
|
||||
#define OAK_GAPBLOCK_H
|
||||
|
||||
#include "block/block.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Node that represents nothing in its respective track for a certain period of time
|
||||
*/
|
||||
class GapBlock : public Block {
|
||||
public:
|
||||
GapBlock();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(GapBlock)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::string description() const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEBLOCK_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/subtitle/subtitle.cpp
|
||||
node/block/subtitle/subtitle.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,73 @@
|
||||
/***
|
||||
|
||||
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 "subtitle.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super ClipBlock
|
||||
|
||||
const std::string SubtitleBlock::k_text_in = "text_in";
|
||||
|
||||
SubtitleBlock::SubtitleBlock()
|
||||
{
|
||||
add_input(k_text_in, NodeValue::k_text,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
set_input_flag(k_buffer_in, k_input_flag_hidden);
|
||||
set_input_flag(k_length_input, k_input_flag_hidden);
|
||||
set_input_flag(k_media_in_input, k_input_flag_hidden);
|
||||
set_input_flag(k_speed_input, k_input_flag_hidden);
|
||||
set_input_flag(k_reverse_input, k_input_flag_hidden);
|
||||
set_input_flag(k_maintain_audio_pitch_input, k_input_flag_hidden);
|
||||
|
||||
// Undo block flag that hides in param view
|
||||
set_flag(k_dont_show_in_param_view, false);
|
||||
}
|
||||
|
||||
std::string SubtitleBlock::name() const
|
||||
{
|
||||
if (get_text().empty()) {
|
||||
return "Subtitle";
|
||||
} else {
|
||||
return get_text();
|
||||
}
|
||||
}
|
||||
|
||||
std::string SubtitleBlock::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.subtitle";
|
||||
}
|
||||
|
||||
std::string SubtitleBlock::description() const
|
||||
{
|
||||
return "A time-based node representing a single subtitle element for a certain period of time.";
|
||||
}
|
||||
|
||||
void SubtitleBlock::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_text_in, "Text");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/***
|
||||
|
||||
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_SUBTITLEBLOCK_H
|
||||
#define OAK_SUBTITLEBLOCK_H
|
||||
|
||||
#include "block/clip/clip.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class SubtitleBlock : public ClipBlock {
|
||||
public:
|
||||
SubtitleBlock();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(SubtitleBlock)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
static const std::string k_text_in;
|
||||
|
||||
std::string get_text() const
|
||||
{
|
||||
return get_standard_value(k_text_in).to_string();
|
||||
}
|
||||
|
||||
void set_text(const std::string &text)
|
||||
{
|
||||
set_standard_value(k_text_in, text);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SUBTITLEBLOCK_H
|
||||
@@ -0,0 +1,25 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(crossdissolve)
|
||||
add_subdirectory(diptocolor)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/transition/transition.h
|
||||
node/block/transition/transition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/transition/crossdissolve/crossdissolvetransition.h
|
||||
node/block/transition/crossdissolve/crossdissolvetransition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "crossdissolvetransition.h"
|
||||
|
||||
#include "filefunctions.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
CrossDissolveTransition::CrossDissolveTransition()
|
||||
{
|
||||
}
|
||||
|
||||
std::string CrossDissolveTransition::name() const
|
||||
{
|
||||
return "Cross Dissolve";
|
||||
}
|
||||
|
||||
std::string CrossDissolveTransition::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.crossdissolve";
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> CrossDissolveTransition::category() const
|
||||
{
|
||||
return { k_category_transition };
|
||||
}
|
||||
|
||||
std::string CrossDissolveTransition::description() const
|
||||
{
|
||||
return "Smoothly transition between two clips.";
|
||||
}
|
||||
|
||||
ShaderCode
|
||||
CrossDissolveTransition::get_shader_code(const ShaderRequest &request) const
|
||||
{
|
||||
(void) request;
|
||||
|
||||
return ShaderCode(
|
||||
FileFunctions::read_file_as_string(":/shaders/crossdissolve.frag"),
|
||||
std::string());
|
||||
}
|
||||
|
||||
void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples,
|
||||
const SampleBuffer &to_samples,
|
||||
SampleBuffer &out_samples,
|
||||
double time_in) const
|
||||
{
|
||||
for (size_t i = 0; i < out_samples.sample_count(); i++) {
|
||||
double this_sample_time =
|
||||
out_samples.audio_params().samples_to_time(i).to_double() + time_in;
|
||||
double progress = get_total_progress(this_sample_time);
|
||||
|
||||
for (int j = 0; j < out_samples.audio_params().channel_count(); j++) {
|
||||
out_samples.data(j)[i] = 0;
|
||||
|
||||
if (from_samples.is_allocated()) {
|
||||
if (i < from_samples.sample_count()) {
|
||||
out_samples.data(j)[i] += from_samples.data(j)[i] *
|
||||
transform_curve(1.0 - progress);
|
||||
}
|
||||
}
|
||||
|
||||
if (to_samples.is_allocated()) {
|
||||
// Offset input samples from the end
|
||||
size_t remain =
|
||||
(out_samples.sample_count() - to_samples.sample_count());
|
||||
if (i >= remain) {
|
||||
int64_t in_index = i - remain;
|
||||
out_samples.data(j)[i] +=
|
||||
to_samples.data(j)[in_index] * transform_curve(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/***
|
||||
|
||||
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_CROSSDISSOLVETRANSITION_H
|
||||
#define OAK_CROSSDISSOLVETRANSITION_H
|
||||
|
||||
#include "block/transition/transition.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CrossDissolveTransition : public TransitionBlock {
|
||||
public:
|
||||
CrossDissolveTransition();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(CrossDissolveTransition)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
//virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode
|
||||
get_shader_code(const ShaderRequest &request) const override;
|
||||
|
||||
protected:
|
||||
virtual void SampleJobEvent(const SampleBuffer &from_samples,
|
||||
const SampleBuffer &to_samples,
|
||||
SampleBuffer &out_samples,
|
||||
double time_in) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CROSSDISSOLVETRANSITION_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/transition/diptocolor/diptocolortransition.h
|
||||
node/block/transition/diptocolor/diptocolortransition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
/***
|
||||
|
||||
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 "diptocolortransition.h"
|
||||
|
||||
#include "filefunctions.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const std::string DipToColorTransition::k_color_input = "color_in";
|
||||
|
||||
#define super TransitionBlock
|
||||
|
||||
DipToColorTransition::DipToColorTransition()
|
||||
{
|
||||
add_input(k_color_input, NodeValue::k_color,
|
||||
Variant::from_value(Color(0, 0, 0)));
|
||||
}
|
||||
|
||||
std::string DipToColorTransition::name() const
|
||||
{
|
||||
return "Dip To Color";
|
||||
}
|
||||
|
||||
std::string DipToColorTransition::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.diptocolor";
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> DipToColorTransition::category() const
|
||||
{
|
||||
return { k_category_transition };
|
||||
}
|
||||
|
||||
std::string DipToColorTransition::description() const
|
||||
{
|
||||
return "Transition between clips by dipping to a color.";
|
||||
}
|
||||
|
||||
ShaderCode
|
||||
DipToColorTransition::get_shader_code(const ShaderRequest &request) const
|
||||
{
|
||||
(void) request;
|
||||
|
||||
return ShaderCode(
|
||||
FileFunctions::read_file_as_string(":/shaders/diptoblack.frag"),
|
||||
std::string());
|
||||
}
|
||||
|
||||
void DipToColorTransition::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_color_input, "Color");
|
||||
}
|
||||
|
||||
void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value,
|
||||
ShaderJob *job) const
|
||||
{
|
||||
job->insert(k_color_input, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/***
|
||||
|
||||
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_DIPTOCOLORTRANSITION_H
|
||||
#define OAK_DIPTOCOLORTRANSITION_H
|
||||
|
||||
#include "block/transition/transition.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class DipToColorTransition : public TransitionBlock {
|
||||
public:
|
||||
DipToColorTransition();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(DipToColorTransition)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual ShaderCode
|
||||
get_shader_code(const ShaderRequest &request) const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
static const std::string k_color_input;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value,
|
||||
ShaderJob *job) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DIPTOCOLORTRANSITION_H
|
||||
@@ -0,0 +1,343 @@
|
||||
/***
|
||||
|
||||
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 "transition.h"
|
||||
|
||||
#include "block/clip/clip.h"
|
||||
#include "output/track/track.h"
|
||||
#include "sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Block
|
||||
|
||||
const std::string TransitionBlock::k_out_block_input = "out_block_in";
|
||||
const std::string TransitionBlock::k_in_block_input = "in_block_in";
|
||||
const std::string TransitionBlock::k_curve_input = "curve_in";
|
||||
const std::string TransitionBlock::k_center_input = "center_in";
|
||||
|
||||
TransitionBlock::TransitionBlock()
|
||||
: connected_out_block_(nullptr)
|
||||
, connected_in_block_(nullptr)
|
||||
{
|
||||
add_input(k_out_block_input, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_in_block_input, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_curve_input, NodeValue::k_combo,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
|
||||
add_input(k_center_input, NodeValue::k_rational,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
set_input_property(k_center_input, "view", slider::k_time);
|
||||
set_input_property(k_center_input, "viewlock", true);
|
||||
|
||||
set_flag(k_dont_show_in_param_view, false);
|
||||
}
|
||||
|
||||
void TransitionBlock::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_out_block_input, "From");
|
||||
set_input_name(k_in_block_input, "To");
|
||||
set_input_name(k_curve_input, "Curve");
|
||||
set_input_name(k_center_input, "Center Offset");
|
||||
|
||||
// These must correspond to the CurveType enum
|
||||
set_combo_box_strings(k_curve_input,
|
||||
{ "Linear", "Exponential", "Logarithmic" });
|
||||
}
|
||||
|
||||
Rational TransitionBlock::in_offset() const
|
||||
{
|
||||
if (is_dual_transition()) {
|
||||
return length() / 2 + offset_center();
|
||||
} else if (connected_in_block()) {
|
||||
return length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Rational TransitionBlock::out_offset() const
|
||||
{
|
||||
if (is_dual_transition()) {
|
||||
return length() / 2 - offset_center();
|
||||
} else if (connected_out_block()) {
|
||||
return length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Rational TransitionBlock::offset_center() const
|
||||
{
|
||||
return get_standard_value(k_center_input).value<Rational>();
|
||||
}
|
||||
|
||||
void TransitionBlock::set_offset_center(const Rational &r)
|
||||
{
|
||||
set_standard_value(k_center_input, Variant::from_value(r));
|
||||
}
|
||||
|
||||
void TransitionBlock::set_offsets_and_length(const Rational &in_offset,
|
||||
const Rational &out_offset)
|
||||
{
|
||||
Rational len = in_offset + out_offset;
|
||||
Rational center = len / 2 - in_offset;
|
||||
|
||||
set_length_and_media_out(len);
|
||||
set_offset_center(center);
|
||||
}
|
||||
|
||||
Block *TransitionBlock::connected_out_block() const
|
||||
{
|
||||
return connected_out_block_;
|
||||
}
|
||||
|
||||
Block *TransitionBlock::connected_in_block() const
|
||||
{
|
||||
return connected_in_block_;
|
||||
}
|
||||
|
||||
double TransitionBlock::get_total_progress(const double &time) const
|
||||
{
|
||||
return get_internal_transition_time(time) / length().to_double();
|
||||
}
|
||||
|
||||
double TransitionBlock::get_out_progress(const double &time) const
|
||||
{
|
||||
if (out_offset() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::clamp(
|
||||
1.0 - (get_internal_transition_time(time) / out_offset().to_double()), 0.0,
|
||||
1.0);
|
||||
}
|
||||
|
||||
double TransitionBlock::get_in_progress(const double &time) const
|
||||
{
|
||||
if (in_offset() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::clamp(
|
||||
(get_internal_transition_time(time) - out_offset().to_double()) /
|
||||
in_offset().to_double(),
|
||||
0.0, 1.0);
|
||||
}
|
||||
|
||||
double TransitionBlock::get_internal_transition_time(const double &time) const
|
||||
{
|
||||
return time;
|
||||
}
|
||||
|
||||
void TransitionBlock::insert_transition_times(AcceleratedJob *job,
|
||||
const double &time) const
|
||||
{
|
||||
// Provides total transition progress from 0.0 (start) - 1.0 (end)
|
||||
job->insert("ove_tprog_all",
|
||||
NodeValue(NodeValue::k_float, get_total_progress(time), this));
|
||||
|
||||
// Provides progress of out section from 1.0 (start) - 0.0 (end)
|
||||
job->insert("ove_tprog_out",
|
||||
NodeValue(NodeValue::k_float, get_out_progress(time), this));
|
||||
|
||||
// Provides progress of in section from 0.0 (start) - 1.0 (end)
|
||||
job->insert("ove_tprog_in",
|
||||
NodeValue(NodeValue::k_float, get_in_progress(time), this));
|
||||
}
|
||||
|
||||
void TransitionBlock::value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
NodeValue out_buffer = value.at(k_out_block_input);
|
||||
NodeValue in_buffer = value.at(k_in_block_input);
|
||||
NodeValue::Type data_type = (out_buffer.type() != NodeValue::k_none) ?
|
||||
out_buffer.type() :
|
||||
in_buffer.type();
|
||||
|
||||
NodeValue::Type job_type = NodeValue::k_none;
|
||||
Variant push_job;
|
||||
|
||||
if (data_type == NodeValue::k_texture) {
|
||||
// This must be a visual transition
|
||||
ShaderJob job;
|
||||
|
||||
if (out_buffer.type() != NodeValue::k_none) {
|
||||
job.insert(k_out_block_input, out_buffer);
|
||||
} else {
|
||||
job.insert(k_out_block_input, NodeValue(NodeValue::k_texture, nullptr));
|
||||
}
|
||||
|
||||
if (in_buffer.type() != NodeValue::k_none) {
|
||||
job.insert(k_in_block_input, in_buffer);
|
||||
} else {
|
||||
job.insert(k_in_block_input, NodeValue(NodeValue::k_texture, nullptr));
|
||||
}
|
||||
|
||||
job.insert(k_curve_input, value);
|
||||
|
||||
double time = globals.time().in().to_double();
|
||||
insert_transition_times(&job, time);
|
||||
|
||||
ShaderJobEvent(value, &job);
|
||||
|
||||
job_type = NodeValue::k_texture;
|
||||
push_job = Variant::from_value(Texture::job(globals.vparams(), job));
|
||||
} else if (data_type == NodeValue::k_samples) {
|
||||
// This must be an audio transition
|
||||
SampleBuffer from_samples = out_buffer.to_samples();
|
||||
SampleBuffer to_samples = in_buffer.to_samples();
|
||||
|
||||
if (from_samples.is_allocated() || to_samples.is_allocated()) {
|
||||
double time_in = globals.time().in().to_double();
|
||||
double time_out = globals.time().out().to_double();
|
||||
|
||||
const AudioParams ¶ms = (from_samples.is_allocated()) ?
|
||||
from_samples.audio_params() :
|
||||
to_samples.audio_params();
|
||||
|
||||
SampleBuffer out_samples;
|
||||
|
||||
if (params.is_valid()) {
|
||||
int nb_samples = params.time_to_samples(time_out - time_in);
|
||||
|
||||
out_samples = SampleBuffer(params, nb_samples);
|
||||
SampleJobEvent(from_samples, to_samples, out_samples, time_in);
|
||||
}
|
||||
|
||||
job_type = NodeValue::k_samples;
|
||||
push_job = Variant::from_value(out_samples);
|
||||
}
|
||||
}
|
||||
|
||||
if (!push_job.is_null()) {
|
||||
table->push(job_type, push_job, this);
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionBlock::invalidate_cache(const TimeRange &range,
|
||||
const std::string &from, int element,
|
||||
InvalidateCacheOptions options)
|
||||
{
|
||||
TimeRange r = range;
|
||||
|
||||
if (from == k_out_block_input || from == k_in_block_input) {
|
||||
Block *n = dynamic_cast<Block *>(get_connected_output(from));
|
||||
if (n) {
|
||||
r = Track::transform_range_from_block(n, r);
|
||||
}
|
||||
}
|
||||
|
||||
super::invalidate_cache(r, from, element, options);
|
||||
}
|
||||
|
||||
double TransitionBlock::transform_curve(double linear) const
|
||||
{
|
||||
switch (static_cast<CurveType>(get_standard_value(k_curve_input).to_int())) {
|
||||
case k_linear:
|
||||
break;
|
||||
case k_exponential:
|
||||
linear *= linear;
|
||||
break;
|
||||
case k_logarithmic:
|
||||
linear = std::sqrt(linear);
|
||||
break;
|
||||
}
|
||||
|
||||
return linear;
|
||||
}
|
||||
|
||||
void TransitionBlock::InputConnectedEvent(const std::string &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
(void) element;
|
||||
|
||||
if (input == k_out_block_input) {
|
||||
// If node is not a block, this will just be null
|
||||
if ((connected_out_block_ = dynamic_cast<ClipBlock *>(output))) {
|
||||
connected_out_block_->set_out_transition(this);
|
||||
}
|
||||
} else if (input == k_in_block_input) {
|
||||
// If node is not a block, this will just be null
|
||||
if ((connected_in_block_ = dynamic_cast<ClipBlock *>(output))) {
|
||||
connected_in_block_->set_in_transition(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionBlock::InputDisconnectedEvent(const std::string &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
(void) element;
|
||||
(void) output;
|
||||
|
||||
if (input == k_out_block_input) {
|
||||
if (connected_out_block_) {
|
||||
connected_out_block_->set_out_transition(nullptr);
|
||||
connected_out_block_ = nullptr;
|
||||
}
|
||||
} else if (input == k_in_block_input) {
|
||||
if (connected_in_block_) {
|
||||
connected_in_block_->set_in_transition(nullptr);
|
||||
connected_in_block_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange TransitionBlock::input_time_adjustment(const std::string &input,
|
||||
int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const
|
||||
{
|
||||
if (input == k_in_block_input || input == k_out_block_input) {
|
||||
Block *block = dynamic_cast<Block *>(get_connected_output(input));
|
||||
if (block) {
|
||||
// Retransform time as if it came from the track
|
||||
return input_time + in() - block->in();
|
||||
}
|
||||
}
|
||||
|
||||
return super::input_time_adjustment(input, element, input_time, clamp);
|
||||
}
|
||||
|
||||
TimeRange
|
||||
TransitionBlock::output_time_adjustment(const std::string &input, int element,
|
||||
const TimeRange &input_time) const
|
||||
{
|
||||
if (input == k_in_block_input || input == k_out_block_input) {
|
||||
Block *block = dynamic_cast<Block *>(get_connected_output(input));
|
||||
if (block) {
|
||||
return input_time + block->in() - in();
|
||||
}
|
||||
}
|
||||
|
||||
return super::output_time_adjustment(input, element, input_time);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/***
|
||||
|
||||
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_TRANSITIONBLOCK_H
|
||||
#define OAK_TRANSITIONBLOCK_H
|
||||
|
||||
#include "block/block.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ClipBlock;
|
||||
|
||||
class TransitionBlock : public Block {
|
||||
public:
|
||||
TransitionBlock();
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
Rational in_offset() const;
|
||||
Rational out_offset() const;
|
||||
|
||||
/**
|
||||
* @brief Return the "middle point" of the transition, relative to the transition
|
||||
*
|
||||
* Used to calculate in/out offsets.
|
||||
*
|
||||
* 0 means the center of the transition is right in the middle and the in and out offsets will
|
||||
* be equal.
|
||||
*/
|
||||
Rational offset_center() const;
|
||||
void set_offset_center(const Rational &r);
|
||||
|
||||
void set_offsets_and_length(const Rational &in_offset,
|
||||
const Rational &out_offset);
|
||||
|
||||
bool is_dual_transition() const
|
||||
{
|
||||
return connected_out_block() && connected_in_block();
|
||||
}
|
||||
|
||||
Block *connected_out_block() const;
|
||||
Block *connected_in_block() const;
|
||||
|
||||
double get_total_progress(const double &time) const;
|
||||
double get_out_progress(const double &time) const;
|
||||
double get_in_progress(const double &time) const;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void invalidate_cache(
|
||||
const TimeRange &range, const std::string &from, int element = -1,
|
||||
InvalidateCacheOptions options = InvalidateCacheOptions()) override;
|
||||
|
||||
static const std::string k_out_block_input;
|
||||
static const std::string k_in_block_input;
|
||||
static const std::string k_curve_input;
|
||||
static const std::string k_center_input;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const
|
||||
{
|
||||
}
|
||||
|
||||
virtual void SampleJobEvent(const SampleBuffer &from_samples,
|
||||
const SampleBuffer &to_samples,
|
||||
SampleBuffer &out_samples, double time_in) const
|
||||
{
|
||||
}
|
||||
|
||||
double transform_curve(double linear) const;
|
||||
|
||||
virtual void InputConnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputDisconnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual TimeRange input_time_adjustment(const std::string &input, int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const override;
|
||||
|
||||
virtual TimeRange
|
||||
output_time_adjustment(const std::string &input, int element,
|
||||
const TimeRange &input_time) const override;
|
||||
|
||||
private:
|
||||
enum CurveType { k_linear, k_exponential, k_logarithmic };
|
||||
|
||||
double get_internal_transition_time(const double &time) const;
|
||||
|
||||
void insert_transition_times(AcceleratedJob *job, const double &time) const;
|
||||
|
||||
ClipBlock *connected_out_block_;
|
||||
|
||||
ClipBlock *connected_in_block_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_TRANSITIONBLOCK_H
|
||||
@@ -0,0 +1,29 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(colormanager)
|
||||
add_subdirectory(displaytransform)
|
||||
add_subdirectory(ociobase)
|
||||
add_subdirectory(ociogradingtransformlinear)
|
||||
add_subdirectory(ociogradingtransformlog)
|
||||
add_subdirectory(ociolut)
|
||||
add_subdirectory(threewaycolor)
|
||||
add_subdirectory(whitebalance)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/colormanager/colormanager.cpp
|
||||
node/color/colormanager/colormanager.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,342 @@
|
||||
/***
|
||||
|
||||
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 "colormanager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <strings.h>
|
||||
|
||||
#include "define.h"
|
||||
#include "filefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "project.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ocio::ConstConfigRcPtr ColorManager::default_config = nullptr;
|
||||
|
||||
ColorManager::ColorManager(Project *project)
|
||||
: project_(project)
|
||||
, config_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void ColorManager::init()
|
||||
{
|
||||
// Set config to our built-in default
|
||||
config_ = get_default_config();
|
||||
set_default_input_color_space(config_->getCanonicalName(ocio::ROLE_DEFAULT));
|
||||
project()->set_color_reference_space(ocio::ROLE_SCENE_LINEAR);
|
||||
}
|
||||
|
||||
ocio::ConstConfigRcPtr ColorManager::get_config() const
|
||||
{
|
||||
return config_;
|
||||
}
|
||||
|
||||
ocio::ConstConfigRcPtr
|
||||
ColorManager::create_config_from_file(const std::string &filename)
|
||||
{
|
||||
return ocio::Config::CreateFromFile(filename.c_str());
|
||||
}
|
||||
|
||||
std::string ColorManager::get_config_filename() const
|
||||
{
|
||||
return project()->get_color_config_filename();
|
||||
}
|
||||
|
||||
ocio::ConstConfigRcPtr ColorManager::get_default_config()
|
||||
{
|
||||
// Set up on first use: Project construction calls ColorManager::Init()
|
||||
// unconditionally, so without this any Project created before
|
||||
// SetUpDefaultConfig() crashed dereferencing a null config.
|
||||
if (!default_config) {
|
||||
set_up_default_config();
|
||||
}
|
||||
|
||||
return default_config;
|
||||
}
|
||||
|
||||
void ColorManager::set_up_default_config()
|
||||
{
|
||||
const char *ocio_env = std::getenv("OCIO");
|
||||
if (ocio_env != nullptr && ocio_env[0] != '\0') {
|
||||
// Attempt to set config from "OCIO" environment variable
|
||||
try {
|
||||
default_config = ocio::Config::CreateFromEnv();
|
||||
|
||||
return;
|
||||
} catch (ocio::Exception &e) {
|
||||
fprintf(stderr,
|
||||
"Failed to load config from OCIO environment variable config: %s\n",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Extract OCIO config - kind of hacky, but it'll work
|
||||
// NOTE: was QStandardPaths::CacheLocation; oakcommon's configuration
|
||||
// location is the closest Qt-free persistent directory available here.
|
||||
std::string dir = FileFunctions::get_configuration_location() + "/ocioconf";
|
||||
|
||||
FileFunctions::copy_directory(":/ocioconf", dir, true);
|
||||
|
||||
fprintf(stderr, "Extracting default OCIO config to %s\n", dir.c_str());
|
||||
|
||||
default_config = create_config_from_file(dir + "/config.ocio");
|
||||
}
|
||||
|
||||
void ColorManager::set_config_filename(const std::string &filename)
|
||||
{
|
||||
project()->set_color_config_filename(filename);
|
||||
}
|
||||
|
||||
StringList ColorManager::list_available_displays()
|
||||
{
|
||||
StringList displays;
|
||||
|
||||
int number_of_displays = config_->getNumDisplays();
|
||||
|
||||
for (int i = 0; i < number_of_displays; i++) {
|
||||
displays.push_back(config_->getDisplay(i));
|
||||
}
|
||||
|
||||
return displays;
|
||||
}
|
||||
|
||||
std::string ColorManager::get_default_display()
|
||||
{
|
||||
return config_->getDefaultDisplay();
|
||||
}
|
||||
|
||||
StringList ColorManager::list_available_views(std::string display)
|
||||
{
|
||||
StringList views;
|
||||
|
||||
int number_of_views = config_->getNumViews(display.c_str());
|
||||
|
||||
for (int i = 0; i < number_of_views; i++) {
|
||||
views.push_back(config_->getView(display.c_str(), i));
|
||||
}
|
||||
|
||||
return views;
|
||||
}
|
||||
|
||||
std::string ColorManager::get_default_view(const std::string &display)
|
||||
{
|
||||
return config_->getDefaultView(display.c_str());
|
||||
}
|
||||
|
||||
StringList ColorManager::list_available_looks()
|
||||
{
|
||||
StringList looks;
|
||||
|
||||
int number_of_looks = config_->getNumLooks();
|
||||
|
||||
for (int i = 0; i < number_of_looks; i++) {
|
||||
looks.push_back(config_->getLookNameByIndex(i));
|
||||
}
|
||||
|
||||
return looks;
|
||||
}
|
||||
|
||||
StringList ColorManager::list_available_colorspaces() const
|
||||
{
|
||||
return list_available_colorspaces(config_);
|
||||
}
|
||||
|
||||
std::string ColorManager::get_default_input_color_space() const
|
||||
{
|
||||
return project()->get_default_input_color_space();
|
||||
}
|
||||
|
||||
void ColorManager::set_default_input_color_space(const std::string &s)
|
||||
{
|
||||
project()->set_default_input_color_space(s);
|
||||
}
|
||||
|
||||
std::string ColorManager::get_colorspace_for_ffmpeg_tags(int primaries,
|
||||
int trc) const
|
||||
{
|
||||
// FFmpeg AVColorPrimaries/AVColorTransferCharacteristic values mapped to
|
||||
// candidate colorspace names, in order of preference
|
||||
struct TagMapping {
|
||||
int primaries;
|
||||
int trc;
|
||||
const char *candidates[3];
|
||||
};
|
||||
|
||||
static const TagMapping k_tag_mappings[] = {
|
||||
{ 1, 1, { "Rec.709 OETF", "Rec.709", "BT.709" } },
|
||||
{ 1, 13, { "sRGB OETF", "sRGB", nullptr } },
|
||||
{ 6, 6, { "Rec.601 OETF (NTSC)", "Rec.601 NTSC", nullptr } },
|
||||
{ 5, 5, { "Rec.601 OETF (PAL)", "Rec.601 PAL", nullptr } },
|
||||
{ 5, 6, { "Rec.601 OETF (PAL)", "Rec.601 PAL", nullptr } },
|
||||
{ 9, 16, { "Rec.2020 PQ", "BT.2020 PQ", "ST 2084 PQ" } },
|
||||
{ 9, 18, { "Rec.2020 HLG", "BT.2020 HLG", "HLG" } },
|
||||
{ 9, 1, { "Rec.2020", "BT.2020", nullptr } },
|
||||
{ 9, 14, { "Rec.2020", "BT.2020", nullptr } },
|
||||
{ 9, 15, { "Rec.2020", "BT.2020", nullptr } },
|
||||
};
|
||||
|
||||
// 0 = unset, 2 = AVCOL_PRI/TRC_UNSPECIFIED
|
||||
if (primaries == 0 || primaries == 2 || trc == 0 || trc == 2) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const StringList available = list_available_colorspaces();
|
||||
|
||||
for (const TagMapping &mapping : k_tag_mappings) {
|
||||
if (mapping.primaries == primaries && mapping.trc == trc) {
|
||||
for (const char *candidate : mapping.candidates) {
|
||||
if (candidate &&
|
||||
std::find(available.begin(), available.end(),
|
||||
std::string(candidate)) != available.end()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
// Known tag pair, but the config has no matching colorspace
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string ColorManager::get_reference_color_space() const
|
||||
{
|
||||
return project()->get_color_reference_space();
|
||||
}
|
||||
|
||||
std::string ColorManager::get_compliant_color_space(const std::string &s)
|
||||
{
|
||||
const StringList available = list_available_colorspaces();
|
||||
if (std::find(available.begin(), available.end(), s) != available.end()) {
|
||||
return s;
|
||||
} else {
|
||||
return get_default_input_color_space();
|
||||
}
|
||||
}
|
||||
|
||||
ColorTransform
|
||||
ColorManager::get_compliant_color_space(const ColorTransform &transform,
|
||||
bool force_display)
|
||||
{
|
||||
if (transform.is_display() || force_display) {
|
||||
// Get display information
|
||||
std::string display = transform.display();
|
||||
std::string view = transform.view();
|
||||
std::string look = transform.look();
|
||||
|
||||
const StringList displays = list_available_displays();
|
||||
|
||||
// Check if display still exists in config
|
||||
if (std::find(displays.begin(), displays.end(), display) ==
|
||||
displays.end()) {
|
||||
display = get_default_display();
|
||||
}
|
||||
|
||||
const StringList views = list_available_views(display);
|
||||
|
||||
// Check if view still exists in display
|
||||
if (std::find(views.begin(), views.end(), view) == views.end()) {
|
||||
view = get_default_view(display);
|
||||
}
|
||||
|
||||
const StringList looks = list_available_looks();
|
||||
|
||||
// Check if looks still exists
|
||||
if (std::find(looks.begin(), looks.end(), look) == looks.end()) {
|
||||
look.clear();
|
||||
}
|
||||
|
||||
return ColorTransform(display, view, look);
|
||||
|
||||
} else {
|
||||
std::string output = transform.output();
|
||||
|
||||
const StringList colorspaces = list_available_colorspaces();
|
||||
|
||||
if (std::find(colorspaces.begin(), colorspaces.end(), output) ==
|
||||
colorspaces.end()) {
|
||||
output = get_default_input_color_space();
|
||||
}
|
||||
|
||||
return ColorTransform(output);
|
||||
}
|
||||
}
|
||||
|
||||
StringList
|
||||
ColorManager::list_available_colorspaces(ocio::ConstConfigRcPtr config)
|
||||
{
|
||||
StringList spaces;
|
||||
|
||||
if (config) {
|
||||
int number_of_colorspaces = config->getNumColorSpaces();
|
||||
|
||||
for (int i = 0; i < number_of_colorspaces; i++) {
|
||||
spaces.push_back(config->getColorSpaceNameByIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
return spaces;
|
||||
}
|
||||
|
||||
void ColorManager::get_default_luma_coefs(double *rgb) const
|
||||
{
|
||||
config_->getDefaultLumaCoefs(rgb);
|
||||
}
|
||||
|
||||
Project *ColorManager::project() const
|
||||
{
|
||||
return project_;
|
||||
}
|
||||
|
||||
void ColorManager::update_config_from_filename()
|
||||
{
|
||||
try {
|
||||
std::string config_filename = get_config_filename();
|
||||
std::string old_default_cs = get_default_input_color_space();
|
||||
|
||||
config_ = ocio::Config::CreateFromFile(config_filename.c_str());
|
||||
|
||||
// Set new default colorspace appropriately
|
||||
std::string new_default = old_default_cs;
|
||||
StringList available_cs = list_available_colorspaces();
|
||||
for (const std::string &c : available_cs) {
|
||||
// NOTE: preserves the original Qt code's truthiness semantics
|
||||
// (QString::compare(...) != 0, i.e. case-insensitively different)
|
||||
if (strcasecmp(c.c_str(), old_default_cs.c_str())) {
|
||||
new_default = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
set_default_input_color_space(new_default);
|
||||
|
||||
// The former config_changed signal is gone with Qt; the facade/event
|
||||
// layer is responsible for notifying subscribers.
|
||||
} catch (ocio::Exception &) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/***
|
||||
|
||||
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_COLORSERVICE_H
|
||||
#define OAK_COLORSERVICE_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "colortransform.h"
|
||||
#include "node.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ColorManager {
|
||||
public:
|
||||
ColorManager(Project *project);
|
||||
|
||||
void init();
|
||||
|
||||
ocio::ConstConfigRcPtr get_config() const;
|
||||
|
||||
static ocio::ConstConfigRcPtr create_config_from_file(const std::string &filename);
|
||||
|
||||
std::string get_config_filename() const;
|
||||
|
||||
static ocio::ConstConfigRcPtr get_default_config();
|
||||
|
||||
static void set_up_default_config();
|
||||
|
||||
void set_config_filename(const std::string &filename);
|
||||
|
||||
StringList list_available_displays();
|
||||
|
||||
std::string get_default_display();
|
||||
|
||||
StringList list_available_views(std::string display);
|
||||
|
||||
std::string get_default_view(const std::string &display);
|
||||
|
||||
StringList list_available_looks();
|
||||
|
||||
StringList list_available_colorspaces() const;
|
||||
|
||||
std::string get_default_input_color_space() const;
|
||||
|
||||
/**
|
||||
* @brief Auto-detects an input colorspace from media color tags
|
||||
*
|
||||
* Maps raw FFmpeg color primaries/transfer values (as exposed on
|
||||
* VideoParams) to a colorspace of the active OCIO config. Returns an
|
||||
* empty string when the tags are unknown or the config has no matching
|
||||
* colorspace, in which case the default input colorspace applies.
|
||||
*/
|
||||
std::string get_colorspace_for_ffmpeg_tags(int primaries, int trc) const;
|
||||
|
||||
void set_default_input_color_space(const std::string &s);
|
||||
|
||||
std::string get_reference_color_space() const;
|
||||
|
||||
std::string get_compliant_color_space(const std::string &s);
|
||||
|
||||
ColorTransform get_compliant_color_space(const ColorTransform &transform,
|
||||
bool force_display = false);
|
||||
|
||||
static StringList list_available_colorspaces(ocio::ConstConfigRcPtr config);
|
||||
|
||||
void get_default_luma_coefs(double *rgb) const;
|
||||
|
||||
Project *project() const;
|
||||
|
||||
void update_config_from_filename();
|
||||
|
||||
private:
|
||||
Project *project_;
|
||||
|
||||
ocio::ConstConfigRcPtr config_;
|
||||
|
||||
static ocio::ConstConfigRcPtr default_config;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_COLORSERVICE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/displaytransform/displaytransform.cpp
|
||||
node/color/displaytransform/displaytransform.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,156 @@
|
||||
/***
|
||||
|
||||
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 "displaytransform.h"
|
||||
|
||||
#include "color/colormanager/colormanager.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const std::string DisplayTransformNode::k_display_input = "display_in";
|
||||
const std::string DisplayTransformNode::k_view_input = "view_in";
|
||||
const std::string DisplayTransformNode::k_direction_input = "dir_in";
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
DisplayTransformNode::DisplayTransformNode()
|
||||
{
|
||||
add_input(k_display_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
|
||||
add_input(k_view_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
|
||||
add_input(k_direction_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
}
|
||||
|
||||
std::string DisplayTransformNode::name() const
|
||||
{
|
||||
return "Display Transform";
|
||||
}
|
||||
|
||||
std::string DisplayTransformNode::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.displaytransform";
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> DisplayTransformNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
std::string DisplayTransformNode::description() const
|
||||
{
|
||||
return "Converts an image to or from a display color space.";
|
||||
}
|
||||
|
||||
void DisplayTransformNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, "Input");
|
||||
set_input_name(k_display_input, "Display");
|
||||
set_input_name(k_view_input, "View");
|
||||
set_input_name(k_direction_input, "Direction");
|
||||
set_combo_box_strings(k_direction_input, { "Forward", "Inverse" });
|
||||
}
|
||||
|
||||
void DisplayTransformNode::InputValueChangedEvent(const std::string &input,
|
||||
int element)
|
||||
{
|
||||
(void) element;
|
||||
if (input == k_display_input || input == k_direction_input ||
|
||||
input == k_view_input) {
|
||||
if (input == k_display_input) {
|
||||
update_views();
|
||||
}
|
||||
generate_processor();
|
||||
}
|
||||
}
|
||||
|
||||
std::string DisplayTransformNode::get_display() const
|
||||
{
|
||||
if (manager()) {
|
||||
int index = get_standard_value(k_display_input).to_int();
|
||||
if (index < int(manager()->list_available_displays().size())) {
|
||||
return manager()->list_available_displays().at(index);
|
||||
}
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string DisplayTransformNode::get_view() const
|
||||
{
|
||||
if (manager()) {
|
||||
std::string display = get_display();
|
||||
if (!display.empty()) {
|
||||
int index = get_standard_value(k_view_input).to_int();
|
||||
StringList views = manager()->list_available_views(display);
|
||||
if (index < int(views.size())) {
|
||||
return views.at(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
ColorProcessor::Direction DisplayTransformNode::get_direction() const
|
||||
{
|
||||
return static_cast<ColorProcessor::Direction>(
|
||||
get_standard_value(k_direction_input).to_int());
|
||||
;
|
||||
}
|
||||
|
||||
void DisplayTransformNode::update_displays()
|
||||
{
|
||||
if (manager()) {
|
||||
set_combo_box_strings(k_display_input, manager()->list_available_displays());
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayTransformNode::update_views()
|
||||
{
|
||||
if (manager()) {
|
||||
set_combo_box_strings(k_view_input,
|
||||
manager()->list_available_views(get_display()));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayTransformNode::config_changed()
|
||||
{
|
||||
update_displays();
|
||||
update_views();
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void DisplayTransformNode::generate_processor()
|
||||
{
|
||||
if (manager()) {
|
||||
ColorTransform transform(get_display(), get_view(), std::string());
|
||||
set_processor(ColorProcessor::create(
|
||||
manager(), manager()->get_reference_color_space(), transform,
|
||||
get_direction()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/***
|
||||
|
||||
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_DISPLAYTRANSFORMNODE_H
|
||||
#define OAK_DISPLAYTRANSFORMNODE_H
|
||||
|
||||
#include "color/ociobase/ociobase.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class DisplayTransformNode : public OCIOBaseNode {
|
||||
public:
|
||||
DisplayTransformNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(DisplayTransformNode)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
virtual void InputValueChangedEvent(const std::string &input,
|
||||
int element) override;
|
||||
|
||||
std::string get_display() const;
|
||||
std::string get_view() const;
|
||||
ColorProcessor::Direction get_direction() const;
|
||||
|
||||
static const std::string k_display_input;
|
||||
static const std::string k_view_input;
|
||||
static const std::string k_direction_input;
|
||||
|
||||
protected:
|
||||
virtual void config_changed() override;
|
||||
|
||||
private:
|
||||
void generate_processor();
|
||||
|
||||
void update_displays();
|
||||
|
||||
void update_views();
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif // OAK_DISPLAYTRANSFORMNODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociobase/ociobase.cpp
|
||||
node/color/ociobase/ociobase.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
/***
|
||||
|
||||
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 "ociobase.h"
|
||||
|
||||
#include "color/colormanager/colormanager.h"
|
||||
#include "project.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const std::string OCIOBaseNode::k_texture_input = "tex_in";
|
||||
|
||||
OCIOBaseNode::OCIOBaseNode()
|
||||
: manager_(nullptr)
|
||||
, processor_(nullptr)
|
||||
{
|
||||
add_input(k_texture_input, NodeValue::k_texture,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
set_effect_input(k_texture_input);
|
||||
|
||||
set_flag(k_video_effect);
|
||||
}
|
||||
|
||||
void OCIOBaseNode::AddedToGraphEvent(Project *p)
|
||||
{
|
||||
manager_ = p->color_manager();
|
||||
config_changed();
|
||||
}
|
||||
|
||||
void OCIOBaseNode::RemovedFromGraphEvent(Project *p)
|
||||
{
|
||||
if (manager_) {
|
||||
manager_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOBaseNode::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
auto tex_met = value.at(k_texture_input);
|
||||
TexturePtr t = tex_met.to_texture();
|
||||
if (t) {
|
||||
if (processor_) {
|
||||
ColorTransformJob job;
|
||||
|
||||
job.set_color_processor(processor_);
|
||||
job.set_input_texture(tex_met);
|
||||
|
||||
table->push(NodeValue::k_texture, t->to_job(job), this);
|
||||
} else {
|
||||
// Processor isn't ready yet (e.g. still being generated
|
||||
// asynchronously), pass the input through unchanged.
|
||||
table->push(NodeValue::k_texture, Variant::from_value(t), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/***
|
||||
|
||||
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_OCIOBASENODE_H
|
||||
#define OAK_OCIOBASENODE_H
|
||||
|
||||
#include "node.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OCIOBaseNode : public Node {
|
||||
public:
|
||||
OCIOBaseNode();
|
||||
|
||||
virtual void AddedToGraphEvent(Project *p) override;
|
||||
virtual void RemovedFromGraphEvent(Project *p) override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const std::string k_texture_input;
|
||||
|
||||
protected:
|
||||
// Called when the OCIO config changes. The former ColorManager::config_changed
|
||||
// signal connection is gone with Qt; the facade/event layer invokes this.
|
||||
virtual void config_changed() = 0;
|
||||
|
||||
protected:
|
||||
ColorManager *manager() const
|
||||
{
|
||||
return manager_;
|
||||
}
|
||||
|
||||
ColorProcessorPtr processor() const
|
||||
{
|
||||
return processor_;
|
||||
}
|
||||
void set_processor(ColorProcessorPtr p)
|
||||
{
|
||||
processor_ = p;
|
||||
}
|
||||
|
||||
private:
|
||||
ColorManager *manager_;
|
||||
|
||||
ColorProcessorPtr processor_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_OCIOBASENODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp
|
||||
node/color/ociogradingtransformlinear/ociogradingtransformlinear.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,300 @@
|
||||
/***
|
||||
|
||||
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 "ociogradingtransformlinear.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include "ocioutils.h"
|
||||
#include "project.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const std::string OCIOGradingTransformLinearNode::k_contrast_input =
|
||||
"ocio_grading_primary_contrast";
|
||||
const std::string OCIOGradingTransformLinearNode::k_offset_input =
|
||||
"ocio_grading_primary_offset";
|
||||
const std::string OCIOGradingTransformLinearNode::k_exposure_input =
|
||||
"ocio_grading_primary_exposure";
|
||||
const std::string OCIOGradingTransformLinearNode::k_saturation_input =
|
||||
"ocio_grading_primary_saturation";
|
||||
const std::string OCIOGradingTransformLinearNode::k_pivot_input =
|
||||
"ocio_grading_primary_pivot";
|
||||
const std::string OCIOGradingTransformLinearNode::k_clamp_black_enable_input =
|
||||
"clamp_black_enable_in";
|
||||
const std::string OCIOGradingTransformLinearNode::k_clamp_black_input =
|
||||
"ocio_grading_primary_clampBlack";
|
||||
const std::string OCIOGradingTransformLinearNode::k_clamp_white_enable_input =
|
||||
"clamp_white_enable_in";
|
||||
const std::string OCIOGradingTransformLinearNode::k_clamp_white_input =
|
||||
"ocio_grading_primary_clampWhite";
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
OCIOGradingTransformLinearNode::OCIOGradingTransformLinearNode()
|
||||
{
|
||||
add_input(k_contrast_input, NodeValue::k_vec4, Vector4D{ 1.0, 1.0, 1.0, 1.0 });
|
||||
// Minimum based on ocio::GradingPrimary::validate
|
||||
set_input_property(k_contrast_input, "min",
|
||||
Vector4D{ 0.01f, 0.01f, 0.01f, 0.01f });
|
||||
set_input_property(k_contrast_input, "base", 0.01);
|
||||
set_vec4_input_colors(k_contrast_input);
|
||||
|
||||
add_input(k_offset_input, NodeValue::k_vec4, Vector4D{ 0.0, 0.0, 0.0, 0.0 });
|
||||
set_input_property(k_offset_input, "base", 0.01);
|
||||
set_vec4_input_colors(k_offset_input);
|
||||
|
||||
add_input(k_exposure_input, NodeValue::k_vec4, Vector4D{ 0.0, 0.0, 0.0, 0.0 });
|
||||
set_input_property(k_exposure_input, "base", 0.01);
|
||||
set_vec4_input_colors(k_exposure_input);
|
||||
|
||||
add_input(k_saturation_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_saturation_input, "view", slider::k_percentage);
|
||||
set_input_property(k_saturation_input, "min", 0.0);
|
||||
|
||||
add_input(k_pivot_input, NodeValue::k_float,
|
||||
0.18); // Default listed in ocio::GradingPrimary
|
||||
set_input_property(k_pivot_input, "base", 0.01);
|
||||
|
||||
add_input(k_clamp_black_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_black_input, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_clamp_black_input, "enabled",
|
||||
get_standard_value(k_clamp_black_enable_input).to_bool());
|
||||
set_input_property(k_clamp_black_input, "base", 0.01);
|
||||
|
||||
add_input(k_clamp_white_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_white_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_clamp_white_input, "enabled",
|
||||
get_standard_value(k_clamp_white_enable_input).to_bool());
|
||||
set_input_property(k_clamp_white_input, "base", 0.01);
|
||||
|
||||
// Constrain the white clamp minimum to just above the (static) black clamp
|
||||
// as per ocio::GradingPrimary::validate. When the black clamp is keyframed
|
||||
// or connected, Value() enforces the invariant per frame instead.
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
std::string OCIOGradingTransformLinearNode::name() const
|
||||
{
|
||||
return "OCIO Color Grading (Linear)";
|
||||
}
|
||||
|
||||
std::string OCIOGradingTransformLinearNode::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.ociogradingtransformlinear";
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> OCIOGradingTransformLinearNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
std::string OCIOGradingTransformLinearNode::description() const
|
||||
{
|
||||
return "Simple linear color grading using OpenColorIO.";
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, "Input");
|
||||
set_input_name(k_contrast_input, "Contrast");
|
||||
set_input_name(k_offset_input, "Offset");
|
||||
set_input_name(k_exposure_input, "Exposure");
|
||||
set_input_property(k_exposure_input, "tooltip",
|
||||
"Exposure increments in stops.");
|
||||
set_input_name(k_saturation_input, "Saturation");
|
||||
set_input_name(k_pivot_input, "Pivot");
|
||||
set_input_name(k_clamp_black_enable_input, "Enable Black Clamp");
|
||||
set_input_name(k_clamp_black_input, "Black Clamp");
|
||||
set_input_name(k_clamp_white_enable_input, "Enable White Clamp");
|
||||
set_input_name(k_clamp_white_input, "White Clamp");
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::InputValueChangedEvent(
|
||||
const std::string &input, int element)
|
||||
{
|
||||
(void) element;
|
||||
|
||||
if (input == k_clamp_white_enable_input) {
|
||||
set_input_property(k_clamp_white_input, "enabled",
|
||||
get_standard_value(k_clamp_white_enable_input).to_bool());
|
||||
} else if (input == k_clamp_black_enable_input) {
|
||||
set_input_property(k_clamp_black_input, "enabled",
|
||||
get_standard_value(k_clamp_black_enable_input).to_bool());
|
||||
} else if (input == k_clamp_black_input) {
|
||||
// Ensure the white clamp is always greater than the black clamp as per
|
||||
// ocio::GradingPrimary::validate
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::InputConnectedEvent(const std::string &input,
|
||||
int element, Node *output)
|
||||
{
|
||||
super::InputConnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::InputDisconnectedEvent(const std::string &input,
|
||||
int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputDisconnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::update_clamp_white_minimum()
|
||||
{
|
||||
// A static UI minimum cannot follow an animated black clamp; for keyframed
|
||||
// or connected values the white>black invariant is enforced per frame in
|
||||
// Value() instead
|
||||
if (is_input_keyframing(k_clamp_black_input) ||
|
||||
is_input_connected(k_clamp_black_input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_input_property(k_clamp_white_input, "min",
|
||||
get_standard_value(k_clamp_black_input).to_double() + 0.000001);
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::generate_processor()
|
||||
{
|
||||
if (manager()) {
|
||||
ocio::GradingPrimaryTransformRcPtr gp =
|
||||
ocio::GradingPrimaryTransform::Create(ocio::GRADING_LIN);
|
||||
gp->makeDynamic();
|
||||
gp->setDirection(ocio::TransformDirection::TRANSFORM_DIR_FORWARD);
|
||||
|
||||
try {
|
||||
set_processor(ColorProcessor::create(
|
||||
manager()->get_config()->getProcessor(gp)));
|
||||
} catch (const ocio::Exception &e) {
|
||||
std::cerr << std::endl << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
|
||||
if (processor()) {
|
||||
ColorTransformJob job(value);
|
||||
|
||||
job.set_color_processor(processor());
|
||||
job.set_input_texture(value.at(k_texture_input));
|
||||
|
||||
// Vector4D components stand in for the former QVector4D indices:
|
||||
// x = master channel, y = red, z = green, w = blue.
|
||||
|
||||
// Oddly, OCIO uses RGBMs when setting the GradingPrimary on the CPU, but uses vec3s on the GPU.
|
||||
// Even more oddly, the conversion from RGBM to vec3 does not appear to have a public API.
|
||||
// Therefore, this code has been duplicated from OCIO here:
|
||||
// https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/3abbe5b20521169580fcfe3692aca81859859953/src/OpenColorIO/ops/gradingprimary/GradingPrimary.cpp#L157
|
||||
Vector4D offset = value.at(k_offset_input).to_vec4();
|
||||
offset.set_y(offset.y() + offset.x());
|
||||
offset.set_z(offset.z() + offset.x());
|
||||
offset.set_w(offset.w() + offset.x());
|
||||
job.insert(k_offset_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
Vector3D(offset.y(), offset.z(), offset.w())));
|
||||
|
||||
Vector4D exposure = value.at(k_exposure_input).to_vec4();
|
||||
exposure.set_y(std::pow(2.0f, exposure.x() + exposure.y()));
|
||||
exposure.set_z(std::pow(2.0f, exposure.x() + exposure.z()));
|
||||
exposure.set_w(std::pow(2.0f, exposure.x() + exposure.w()));
|
||||
job.insert(k_exposure_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
Vector3D(exposure.y(), exposure.z(),
|
||||
exposure.w())));
|
||||
|
||||
Vector4D contrast = value.at(k_contrast_input).to_vec4();
|
||||
contrast.set_y(contrast.y() * contrast.x());
|
||||
contrast.set_z(contrast.z() * contrast.x());
|
||||
contrast.set_w(contrast.w() * contrast.x());
|
||||
job.insert(k_contrast_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
Vector3D(contrast.y(), contrast.z(),
|
||||
contrast.w())));
|
||||
|
||||
if (!value.at(k_clamp_black_enable_input).to_bool()) {
|
||||
job.insert(k_clamp_black_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampBlack()));
|
||||
}
|
||||
|
||||
if (!value.at(k_clamp_white_enable_input).to_bool()) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampWhite()));
|
||||
}
|
||||
|
||||
if (value.at(k_clamp_black_enable_input).to_bool() &&
|
||||
value.at(k_clamp_white_enable_input).to_bool()) {
|
||||
// ocio::GradingPrimary::validate requires the white clamp to be
|
||||
// greater than the black clamp. Keyframed or connected values
|
||||
// can violate this at arbitrary times, so enforce the invariant
|
||||
// per frame here.
|
||||
const double clamp_black = value.at(k_clamp_black_input).to_double();
|
||||
const double clamp_white = value.at(k_clamp_white_input).to_double();
|
||||
if (clamp_white <= clamp_black) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
clamp_black + 0.000001));
|
||||
}
|
||||
}
|
||||
|
||||
table->push(NodeValue::k_texture, tex->to_job(job), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::config_changed()
|
||||
{
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::set_vec4_input_colors(const std::string &input)
|
||||
{
|
||||
set_input_property(input, "color0", "#c0c0c0");
|
||||
set_input_property(input, "color1", "#ff0000");
|
||||
set_input_property(input, "color2", "#00ff00");
|
||||
set_input_property(input, "color3", "#0000ff");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/***
|
||||
|
||||
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_OCIOGRADINGTRANSFORMLINEARNODE_H
|
||||
#define OAK_OCIOGRADINGTRANSFORMLINEARNODE_H
|
||||
|
||||
#include "color/ociobase/ociobase.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OCIOGradingTransformLinearNode : public OCIOBaseNode {
|
||||
public:
|
||||
OCIOGradingTransformLinearNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(OCIOGradingTransformLinearNode)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
virtual void InputValueChangedEvent(const std::string &input,
|
||||
int element) override;
|
||||
virtual void InputConnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
virtual void InputDisconnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
void generate_processor();
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const std::string k_contrast_input;
|
||||
static const std::string k_offset_input;
|
||||
static const std::string k_exposure_input;
|
||||
static const std::string k_saturation_input;
|
||||
static const std::string k_pivot_input;
|
||||
static const std::string k_clamp_black_enable_input;
|
||||
static const std::string k_clamp_black_input;
|
||||
static const std::string k_clamp_white_enable_input;
|
||||
static const std::string k_clamp_white_input;
|
||||
|
||||
protected:
|
||||
virtual void config_changed() override;
|
||||
|
||||
private:
|
||||
void set_vec4_input_colors(const std::string &input);
|
||||
|
||||
/**
|
||||
* @brief Constrains the white clamp UI minimum to just above the black
|
||||
* clamp, as required by ocio::GradingPrimary::validate
|
||||
*
|
||||
* Only applies while the black clamp is a static value; when it is
|
||||
* keyframed or connected the invariant is enforced per frame in Value()
|
||||
* instead.
|
||||
*/
|
||||
void update_clamp_white_minimum();
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2026 Oak Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociogradingtransformlog/ociogradingtransformlog.cpp
|
||||
node/color/ociogradingtransformlog/ociogradingtransformlog.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,295 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
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 "ociogradingtransformlog.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include "ocioutils.h"
|
||||
#include "project.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
// These ids double as the OCIO GPU uniform names for the dynamic
|
||||
// GradingPrimaryTransform; do not rename them. OCIO's log style maps the
|
||||
// classic wheels as: brightness = lift, contrast = gain, gamma = gamma.
|
||||
const std::string OCIOGradingTransformLogNode::k_lift_input =
|
||||
"ocio_grading_primary_brightness";
|
||||
const std::string OCIOGradingTransformLogNode::k_gain_input =
|
||||
"ocio_grading_primary_contrast";
|
||||
const std::string OCIOGradingTransformLogNode::k_gamma_input =
|
||||
"ocio_grading_primary_gamma";
|
||||
const std::string OCIOGradingTransformLogNode::k_saturation_input =
|
||||
"ocio_grading_primary_saturation";
|
||||
const std::string OCIOGradingTransformLogNode::k_pivot_input =
|
||||
"ocio_grading_primary_pivot";
|
||||
const std::string OCIOGradingTransformLogNode::k_clamp_black_enable_input =
|
||||
"clamp_black_enable_in";
|
||||
const std::string OCIOGradingTransformLogNode::k_clamp_black_input =
|
||||
"ocio_grading_primary_clampBlack";
|
||||
const std::string OCIOGradingTransformLogNode::k_clamp_white_enable_input =
|
||||
"clamp_white_enable_in";
|
||||
const std::string OCIOGradingTransformLogNode::k_clamp_white_input =
|
||||
"ocio_grading_primary_clampWhite";
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
OCIOGradingTransformLogNode::OCIOGradingTransformLogNode()
|
||||
{
|
||||
add_input(k_lift_input, NodeValue::k_vec4, Vector4D{ 0.0, 0.0, 0.0, 0.0 });
|
||||
set_input_property(k_lift_input, "base", 0.01);
|
||||
set_vec4_input_colors(k_lift_input);
|
||||
|
||||
add_input(k_gain_input, NodeValue::k_vec4, Vector4D{ 1.0, 1.0, 1.0, 1.0 });
|
||||
set_input_property(k_gain_input, "base", 0.01);
|
||||
set_vec4_input_colors(k_gain_input);
|
||||
|
||||
add_input(k_gamma_input, NodeValue::k_vec4, Vector4D{ 1.0, 1.0, 1.0, 1.0 });
|
||||
set_input_property(k_gamma_input, "base", 0.01);
|
||||
set_vec4_input_colors(k_gamma_input);
|
||||
|
||||
add_input(k_saturation_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_saturation_input, "view", slider::k_percentage);
|
||||
set_input_property(k_saturation_input, "min", 0.0);
|
||||
|
||||
add_input(k_pivot_input, NodeValue::k_float,
|
||||
-0.2); // Default for GRADING_LOG listed in ocio::GradingPrimary
|
||||
set_input_property(k_pivot_input, "base", 0.01);
|
||||
|
||||
add_input(k_clamp_black_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_black_input, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_clamp_black_input, "enabled",
|
||||
get_standard_value(k_clamp_black_enable_input).to_bool());
|
||||
set_input_property(k_clamp_black_input, "base", 0.01);
|
||||
|
||||
add_input(k_clamp_white_enable_input, NodeValue::k_boolean, false);
|
||||
|
||||
add_input(k_clamp_white_input, NodeValue::k_float, 1.0);
|
||||
set_input_property(k_clamp_white_input, "enabled",
|
||||
get_standard_value(k_clamp_white_enable_input).to_bool());
|
||||
set_input_property(k_clamp_white_input, "base", 0.01);
|
||||
|
||||
// Constrain the white clamp minimum to just above the (static) black clamp
|
||||
// as per ocio::GradingPrimary::validate. When the black clamp is keyframed
|
||||
// or connected, Value() enforces the invariant per frame instead.
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
std::string OCIOGradingTransformLogNode::name() const
|
||||
{
|
||||
return "OCIO Color Grading (Log)";
|
||||
}
|
||||
|
||||
std::string OCIOGradingTransformLogNode::id() const
|
||||
{
|
||||
return "org.olivevideoeditor.Olive.ociogradingtransformlog";
|
||||
}
|
||||
|
||||
std::vector<Node::CategoryID> OCIOGradingTransformLogNode::category() const
|
||||
{
|
||||
return { k_category_color };
|
||||
}
|
||||
|
||||
std::string OCIOGradingTransformLogNode::description() const
|
||||
{
|
||||
return "Lift/gamma/gain color grading using OpenColorIO.";
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_texture_input, "Input");
|
||||
set_input_name(k_lift_input, "Lift");
|
||||
set_input_name(k_gain_input, "Gain");
|
||||
set_input_name(k_gamma_input, "Gamma");
|
||||
set_input_name(k_saturation_input, "Saturation");
|
||||
set_input_name(k_pivot_input, "Pivot");
|
||||
set_input_name(k_clamp_black_enable_input, "Enable Black Clamp");
|
||||
set_input_name(k_clamp_black_input, "Black Clamp");
|
||||
set_input_name(k_clamp_white_enable_input, "Enable White Clamp");
|
||||
set_input_name(k_clamp_white_input, "White Clamp");
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::InputValueChangedEvent(const std::string &input,
|
||||
int element)
|
||||
{
|
||||
(void) element;
|
||||
|
||||
if (input == k_clamp_white_enable_input) {
|
||||
set_input_property(k_clamp_white_input, "enabled",
|
||||
get_standard_value(k_clamp_white_enable_input).to_bool());
|
||||
} else if (input == k_clamp_black_enable_input) {
|
||||
set_input_property(k_clamp_black_input, "enabled",
|
||||
get_standard_value(k_clamp_black_enable_input).to_bool());
|
||||
} else if (input == k_clamp_black_input) {
|
||||
// Ensure the white clamp is always greater than the black clamp as per
|
||||
// ocio::GradingPrimary::validate
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::InputConnectedEvent(const std::string &input,
|
||||
int element, Node *output)
|
||||
{
|
||||
super::InputConnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::InputDisconnectedEvent(const std::string &input,
|
||||
int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputDisconnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_clamp_black_input) {
|
||||
update_clamp_white_minimum();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::update_clamp_white_minimum()
|
||||
{
|
||||
// A static UI minimum cannot follow an animated black clamp; for keyframed
|
||||
// or connected values the white>black invariant is enforced per frame in
|
||||
// Value() instead
|
||||
if (is_input_keyframing(k_clamp_black_input) ||
|
||||
is_input_connected(k_clamp_black_input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_input_property(k_clamp_white_input, "min",
|
||||
get_standard_value(k_clamp_black_input).to_double() + 0.000001);
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::generate_processor()
|
||||
{
|
||||
if (manager()) {
|
||||
ocio::GradingPrimaryTransformRcPtr gp =
|
||||
ocio::GradingPrimaryTransform::Create(ocio::GRADING_LOG);
|
||||
gp->makeDynamic();
|
||||
gp->setDirection(ocio::TransformDirection::TRANSFORM_DIR_FORWARD);
|
||||
|
||||
try {
|
||||
set_processor(ColorProcessor::create(
|
||||
manager()->get_config()->getProcessor(gp)));
|
||||
} catch (const ocio::Exception &e) {
|
||||
std::cerr << std::endl << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
|
||||
if (processor()) {
|
||||
ColorTransformJob job(value);
|
||||
|
||||
job.set_color_processor(processor());
|
||||
job.set_input_texture(value.at(k_texture_input));
|
||||
|
||||
// Vector4D components stand in for the former QVector4D indices:
|
||||
// x = master channel, y = red, z = green, w = blue.
|
||||
|
||||
// OCIO expects vec3s on the GPU but RGBMs (master + RGB) on the
|
||||
// CPU; the per-style master combination below mirrors
|
||||
// ocio::GradingPrimary. Lift is additive, gain/gamma multiply.
|
||||
Vector4D lift = value.at(k_lift_input).to_vec4();
|
||||
lift.set_y(lift.y() + lift.x());
|
||||
lift.set_z(lift.z() + lift.x());
|
||||
lift.set_w(lift.w() + lift.x());
|
||||
job.insert(k_lift_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
Vector3D(lift.y(), lift.z(), lift.w())));
|
||||
|
||||
Vector4D gain = value.at(k_gain_input).to_vec4();
|
||||
gain.set_y(gain.y() * gain.x());
|
||||
gain.set_z(gain.z() * gain.x());
|
||||
gain.set_w(gain.w() * gain.x());
|
||||
job.insert(k_gain_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
Vector3D(gain.y(), gain.z(), gain.w())));
|
||||
|
||||
Vector4D gamma = value.at(k_gamma_input).to_vec4();
|
||||
gamma.set_y(gamma.y() * gamma.x());
|
||||
gamma.set_z(gamma.z() * gamma.x());
|
||||
gamma.set_w(gamma.w() * gamma.x());
|
||||
job.insert(k_gamma_input,
|
||||
NodeValue(NodeValue::k_vec3,
|
||||
Vector3D(gamma.y(), gamma.z(), gamma.w())));
|
||||
|
||||
if (!value.at(k_clamp_black_enable_input).to_bool()) {
|
||||
job.insert(k_clamp_black_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampBlack()));
|
||||
}
|
||||
|
||||
if (!value.at(k_clamp_white_enable_input).to_bool()) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
ocio::GradingPrimary::NoClampWhite()));
|
||||
}
|
||||
|
||||
if (value.at(k_clamp_black_enable_input).to_bool() &&
|
||||
value.at(k_clamp_white_enable_input).to_bool()) {
|
||||
// ocio::GradingPrimary::validate requires the white clamp to be
|
||||
// greater than the black clamp. Keyframed or connected values
|
||||
// can violate this at arbitrary times, so enforce the invariant
|
||||
// per frame here.
|
||||
const double clamp_black = value.at(k_clamp_black_input).to_double();
|
||||
const double clamp_white = value.at(k_clamp_white_input).to_double();
|
||||
if (clamp_white <= clamp_black) {
|
||||
job.insert(k_clamp_white_input,
|
||||
NodeValue(NodeValue::k_float,
|
||||
clamp_black + 0.000001));
|
||||
}
|
||||
}
|
||||
|
||||
table->push(NodeValue::k_texture, tex->to_job(job), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::config_changed()
|
||||
{
|
||||
generate_processor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLogNode::set_vec4_input_colors(const std::string &input)
|
||||
{
|
||||
set_input_property(input, "color0", "#c0c0c0");
|
||||
set_input_property(input, "color1", "#ff0000");
|
||||
set_input_property(input, "color2", "#00ff00");
|
||||
set_input_property(input, "color3", "#0000ff");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
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_OCIOGRADINGTRANSFORMLOGNODE_H
|
||||
#define OAK_OCIOGRADINGTRANSFORMLOGNODE_H
|
||||
|
||||
#include "color/ociobase/ociobase.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Lift/gamma/gain grading node built on ocio::GRADING_LOG
|
||||
*
|
||||
* Mirrors OCIOGradingTransformLinearNode for the log grading style. OCIO's
|
||||
* log-style GPU uniforms map to the classic wheels as: brightness = lift,
|
||||
* contrast = gain, gamma = gamma.
|
||||
*/
|
||||
class OCIOGradingTransformLogNode : public OCIOBaseNode {
|
||||
public:
|
||||
OCIOGradingTransformLogNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(OCIOGradingTransformLogNode)
|
||||
|
||||
virtual std::string name() const override;
|
||||
virtual std::string id() const override;
|
||||
virtual std::vector<CategoryID> category() const override;
|
||||
virtual std::string description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
virtual void InputValueChangedEvent(const std::string &input,
|
||||
int element) override;
|
||||
virtual void InputConnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
virtual void InputDisconnectedEvent(const std::string &input, int element,
|
||||
Node *output) override;
|
||||
void generate_processor();
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
static const std::string k_lift_input;
|
||||
static const std::string k_gain_input;
|
||||
static const std::string k_gamma_input;
|
||||
static const std::string k_saturation_input;
|
||||
static const std::string k_pivot_input;
|
||||
static const std::string k_clamp_black_enable_input;
|
||||
static const std::string k_clamp_black_input;
|
||||
static const std::string k_clamp_white_enable_input;
|
||||
static const std::string k_clamp_white_input;
|
||||
|
||||
protected:
|
||||
virtual void config_changed() override;
|
||||
|
||||
private:
|
||||
void set_vec4_input_colors(const std::string &input);
|
||||
|
||||
/**
|
||||
* @brief Constrains the white clamp UI minimum to just above the black
|
||||
* clamp, as required by ocio::GradingPrimary::validate
|
||||
*
|
||||
* Only applies while the black clamp is a static value; when it is
|
||||
* keyframed or connected the invariant is enforced per frame in Value()
|
||||
* instead.
|
||||
*/
|
||||
void update_clamp_white_minimum();
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user