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:
2026-08-05 23:55:54 +08:00
parent c50017127b
commit d77348ad9f
375 changed files with 59682 additions and 1 deletions
+6
View File
@@ -0,0 +1,6 @@
add_subdirectory(src)
add_subdirectory(c_api)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
+313
View File
@@ -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=vcentergizmo 波次与 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/...`OpenFXinclude **原样保留**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. VariantQVariant 替代)速查
```cpp
#include "node/variant.h" // olive::Variant, olive::StringList, olive::ByteArray
Variant v; // nullv.is_null() == true
Variant a = 42; // int(有符号统一存 int64_t
Variant b = 3.14; // doublefloat 也存 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()` overridemodified 语义由 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. undooakundo)适配
- `#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/oakstubrender/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()` 取被拖 gizmogizmo 波次定型为
`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 完成。
+15
View File
@@ -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
)
+39
View File
@@ -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
+477
View File
@@ -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;
}
+369
View File
@@ -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;
}
+144
View File
@@ -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;
}
}
+232
View File
@@ -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;
}
}
+322
View File
@@ -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;
}
}
+294
View File
@@ -0,0 +1,294 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "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;
}
}
+588
View File
@@ -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
+373
View File
@@ -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;
}
}
+319
View File
@@ -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;
}
+357
View File
@@ -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;
}
}
+574
View File
@@ -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;
}
+236
View File
@@ -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;
}
}
+273
View File
@@ -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
+36
View File
@@ -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}
)
+23
View File
@@ -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
)
+22
View File
@@ -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
)
+132
View File
@@ -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");
}
}
+60
View File
@@ -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
+22
View File
@@ -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
)
+114
View File
@@ -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");
}
}
+56
View File
@@ -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
+27
View File
@@ -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
)
+144
View File
@@ -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);
}
}
}
+133
View File
@@ -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
+22
View File
@@ -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
)
+591
View File
@@ -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();
}
}
}
+279
View File
@@ -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
+22
View File
@@ -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
)
+46
View File
@@ -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.";
}
}
+46
View File
@@ -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
)
+73
View File
@@ -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");
}
}
+57
View File
@@ -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 &params = (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);
}
}
+120
View File
@@ -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
+29
View File
@@ -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
)
+78
View File
@@ -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);
}
}
}
}
+71
View File
@@ -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
+14
View File
@@ -0,0 +1,14 @@
# Olive - 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.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/color/ociolut/ociolut.cpp
node/color/ociolut/ociolut.h
PARENT_SCOPE
)
+315
View File
@@ -0,0 +1,315 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "ociolut.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <filesystem>
#include <mutex>
#include "color/colormanager/colormanager.h"
#include "render/lutlibrary.h"
#include "render/previewautocacher.h"
#include "render/rendermanager.h"
namespace olive
{
const std::string OCIOLutNode::k_file_input = "lut_file_in";
const std::string OCIOLutNode::k_direction_input = "lut_dir_in";
#define super OCIOBaseNode
namespace
{
bool is_main_process()
{
// Qt-free replacement for qobject_cast<QApplication*>(QCoreApplication::instance()):
// only the main GUI process creates a RenderManager (the render worker
// never does), so its presence identifies the main process.
return RenderManager::instance() != nullptr;
}
int read_direction_input(const Node *node)
{
Variant v = node->get_standard_value(OCIOLutNode::k_direction_input);
bool ok = false;
int direction = v.to_int(&ok);
if (ok) {
return direction;
}
// Some old serializers stored the combo value as a string.
std::string s = v.to_string();
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::tolower(c); });
if (s == "forward" || s == "0") {
return 0;
}
if (s == "inverse" || s == "1") {
return 1;
}
fprintf(stderr, "OCIOLutNode: unexpected direction value %s\n",
v.to_string().c_str());
return 0;
}
} // namespace
OCIOLutNode::OCIOLutNode()
{
add_input(k_file_input, NodeValue::k_file, std::string(),
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
const StringList &extensions = LUTLibrary::supported_extensions();
std::string all_luts = "*.";
for (size_t i = 0; i < extensions.size(); i++) {
if (i > 0) {
all_luts += " *.";
}
all_luts += extensions[i];
}
set_input_property(k_file_input, "filter",
"LUT Files (" + all_luts + ");;All Files (*)");
set_input_property(k_file_input, "placeholder", "Select a LUT file");
// Allow the UI to offer the global LUT library for this input
set_input_property(k_file_input, "lut_library", true);
add_input(k_direction_input, NodeValue::k_combo, 0,
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
}
std::string OCIOLutNode::name() const
{
return "OCIO LUT";
}
std::string OCIOLutNode::id() const
{
return "org.olivevideoeditor.Olive.ociolut";
}
std::vector<Node::CategoryID> OCIOLutNode::category() const
{
return { k_category_color };
}
std::string OCIOLutNode::description() const
{
return "Applies a LUT file through OpenColorIO.";
}
void OCIOLutNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_file_input, "LUT File");
set_input_name(k_direction_input, "Direction");
set_combo_box_strings(k_direction_input, { "Forward", "Inverse" });
}
void OCIOLutNode::InputValueChangedEvent(const std::string &input, int element)
{
(void) element;
if (input == k_file_input || input == k_direction_input) {
// In the worker process, creating the OCIO processor can be slow and we
// are often called from LoadGraph while the main process is blocked
// waiting for a response. Defer generation to Value() time so the worker
// can ack the graph load immediately.
if (is_main_process()) {
generate_processor();
} else {
std::lock_guard<std::mutex> locker(gen_mutex_);
processor_dirty_ = true;
}
}
}
void OCIOLutNode::config_changed()
{
if (is_main_process()) {
generate_processor();
} else {
std::lock_guard<std::mutex> locker(gen_mutex_);
processor_dirty_ = true;
}
}
void OCIOLutNode::value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const
{
// Ensure the processor is up-to-date before the base class emits the color
// transform job. This is especially important in the render worker, where
// processor creation is deferred until the first render.
ensure_processor();
super::value(value, globals, table);
}
void OCIOLutNode::generate_processor()
{
ensure_processor();
// The processor has changed. In the main GUI process, refresh the viewer by
// invalidating the cache and cancelling background cache jobs.
// Invalidating first ensures any in-flight renders that complete afterwards
// won't write stale frames back. The worker process has no
// RenderManager/PreviewAutoCacher, so skip this step to avoid crashing.
if (is_main_process()) {
invalidate_all(k_texture_input);
if (RenderManager *rm = RenderManager::instance()) {
if (PreviewAutoCacher *cacher = rm->get_cacher()) {
cacher->cancel_video_tasks(false);
}
}
}
}
void OCIOLutNode::ensure_processor() const
{
std::lock_guard<std::mutex> locker(gen_mutex_);
if (!processor_dirty_ && last_processor_ &&
get_standard_value(k_file_input).to_string() == last_path_ &&
read_direction_input(this) == last_direction_) {
return;
}
create_processor_from_inputs();
}
void OCIOLutNode::set_last_error(const std::string &error) const
{
if (last_error_ == error) {
return;
}
last_error_ = error;
// The Qt version surfaced the error on the main window status bar through
// EngineCore; that layer is out of oaknode, so the error is now only
// recorded here and surfaced via last_error().
}
bool OCIOLutNode::create_processor_from_inputs() const
{
if (!manager()) {
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
return false;
}
const std::string path = get_standard_value(k_file_input).to_string();
const int direction = read_direction_input(this);
if (path.empty()) {
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
set_last_error(std::string());
return false;
}
// Re-use the existing processor if the file and direction haven't changed.
if (path == last_path_ && direction == last_direction_ && last_processor_) {
processor_dirty_ = false;
return false;
}
std::error_code fs_ec;
const bool is_file =
std::filesystem::is_regular_file(path, fs_ec) && !fs_ec;
if (!is_file) {
fprintf(stderr, "OCIO LUT file does not exist: %s\n", path.c_str());
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
set_last_error("OCIO LUT: file does not exist: " + path);
return false;
}
std::string suffix = std::filesystem::path(path).extension().string();
if (!suffix.empty() && suffix.front() == '.') {
suffix.erase(suffix.begin());
}
if (!LUTLibrary::is_supported_extension(suffix)) {
fprintf(stderr, "Unsupported OCIO LUT file extension: %s\n",
path.c_str());
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
set_last_error("OCIO LUT: unsupported LUT file extension: " + path);
return false;
}
ColorProcessorPtr processor;
try {
const bool forward = static_cast<ColorProcessor::Direction>(
direction) == ColorProcessor::k_normal;
fprintf(stderr,
"OCIOLutNode: creating processor for %s direction=%d "
"ocio_dir=%s process=%s\n",
path.c_str(), direction, forward ? "FORWARD" : "INVERSE",
is_main_process() ? "main" : "worker");
ocio::FileTransformRcPtr transform = ocio::FileTransform::Create();
transform->setSrc(path.c_str());
transform->setInterpolation(ocio::INTERP_LINEAR);
transform->setDirection(forward ? ocio::TRANSFORM_DIR_FORWARD :
ocio::TRANSFORM_DIR_INVERSE);
processor = ColorProcessor::create(
manager()->get_config()->getProcessor(transform));
} catch (const std::exception &e) {
fprintf(stderr, "OCIO LUT processor error: %s\n", e.what());
processor = nullptr;
}
if (!processor) {
set_last_error("OCIO LUT: failed to load LUT file: " + path);
} else {
set_last_error(std::string());
}
last_path_ = path;
last_direction_ = direction;
last_processor_ = processor;
const_cast<OCIOLutNode *>(this)->set_processor(processor);
processor_dirty_ = false;
return true;
}
} // namespace olive
+85
View File
@@ -0,0 +1,85 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_OCIOLUTNODE_H
#define OAK_OCIOLUTNODE_H
#include <mutex>
#include <string>
#include "color/ociobase/ociobase.h"
#include "render/colorprocessor.h"
namespace olive
{
class OCIOLutNode : public OCIOBaseNode {
public:
OCIOLutNode();
NODE_DEFAULT_FUNCTIONS(OCIOLutNode)
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 value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static const std::string k_file_input;
static const std::string k_direction_input;
/**
* @brief Human-readable description of why no LUT processor is active
*
* Empty when a valid LUT processor is in use or no LUT file has been
* selected yet. This allows the UI (and tests) to surface silent
* passthrough states (missing file, unsupported extension, OCIO errors).
*/
const std::string &last_error() const
{
return last_error_;
}
protected:
virtual void config_changed() override;
private:
void generate_processor();
void ensure_processor() const;
bool create_processor_from_inputs() const;
void set_last_error(const std::string &error) const;
mutable std::mutex gen_mutex_;
mutable bool processor_dirty_ = true;
mutable std::string last_path_;
mutable int last_direction_ = -1;
mutable ColorProcessorPtr last_processor_;
mutable std::string last_error_;
};
} // namespace olive
#endif // OAK_OCIOLUTNODE_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/threewaycolor/threewaycolor.h
node/color/threewaycolor/threewaycolor.cpp
PARENT_SCOPE
)
@@ -0,0 +1,121 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2026 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 "threewaycolor.h"
#include "filefunctions.h"
#include "project.h"
#include "sliderdisplaytype.h"
namespace olive
{
#define super Node
const std::string ThreeWayColorNode::k_texture_input = "tex_in";
const std::string ThreeWayColorNode::k_shadows_color_input = "shadows_color_in";
const std::string ThreeWayColorNode::k_midtones_color_input =
"midtones_color_in";
const std::string ThreeWayColorNode::k_highlights_color_input =
"highlights_color_in";
const std::string ThreeWayColorNode::k_shadows_amount_input =
"shadows_amount_in";
const std::string ThreeWayColorNode::k_midtones_amount_input =
"midtones_amount_in";
const std::string ThreeWayColorNode::k_highlights_amount_input =
"highlights_amount_in";
const std::string ThreeWayColorNode::k_luma_coefficients_input =
"luma_coefficients_in";
ThreeWayColorNode::ThreeWayColorNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
const Variant neutral = Variant::from_value(Color(0.5, 0.5, 0.5, 1.0));
add_input(k_shadows_color_input, NodeValue::k_color, neutral);
add_input(k_midtones_color_input, NodeValue::k_color, neutral);
add_input(k_highlights_color_input, NodeValue::k_color, neutral);
add_input(k_shadows_amount_input, NodeValue::k_float, 1.0);
add_input(k_midtones_amount_input, NodeValue::k_float, 1.0);
add_input(k_highlights_amount_input, NodeValue::k_float, 1.0);
const std::string min = "min";
const std::string view = "view";
set_input_property(k_shadows_amount_input, min, 0.0);
set_input_property(k_midtones_amount_input, min, 0.0);
set_input_property(k_highlights_amount_input, min, 0.0);
set_input_property(k_shadows_amount_input, view, slider::k_percentage);
set_input_property(k_midtones_amount_input, view, slider::k_percentage);
set_input_property(k_highlights_amount_input, view, slider::k_percentage);
set_effect_input(k_texture_input);
set_flag(k_video_effect);
}
void ThreeWayColorNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_shadows_color_input, "Shadows");
set_input_name(k_midtones_color_input, "Midtones");
set_input_name(k_highlights_color_input, "Highlights");
set_input_name(k_shadows_amount_input, "Shadows Amount");
set_input_name(k_midtones_amount_input, "Midtones Amount");
set_input_name(k_highlights_amount_input, "Highlights Amount");
}
ShaderCode ThreeWayColorNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(
FileFunctions::read_file_as_string(":/shaders/threewaycolor.frag"));
}
void ThreeWayColorNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
(void) globals;
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
ShaderJob job(value);
double luma_coeffs[3] = { 0.0, 0.0, 0.0 };
if (project() && project()->color_manager()) {
project()->color_manager()->get_default_luma_coefs(luma_coeffs);
} else {
luma_coeffs[0] = 0.2126;
luma_coeffs[1] = 0.7152;
luma_coeffs[2] = 0.0722;
}
job.insert(k_luma_coefficients_input,
NodeValue(NodeValue::k_vec3,
Vector3D(luma_coeffs[0], luma_coeffs[1],
luma_coeffs[2])));
table->push(NodeValue::k_texture, tex->to_job(job), this);
}
}
}
@@ -0,0 +1,76 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2026 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_THREEWAYCOLORNODE_H
#define OAK_THREEWAYCOLORNODE_H
#include "node.h"
namespace olive
{
class ThreeWayColorNode : public Node {
public:
ThreeWayColorNode();
NODE_DEFAULT_FUNCTIONS(ThreeWayColorNode)
virtual std::string name() const override
{
return "Three-Way Color";
}
virtual std::string id() const override
{
return "org.olivevideoeditor.Olive.threewaycolor";
}
virtual std::vector<CategoryID> category() const override
{
return { k_category_color };
}
virtual std::string description() const override
{
return "Adjusts shadows, midtones, and highlights separately.";
}
virtual void retranslate() override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static const std::string k_texture_input;
static const std::string k_shadows_color_input;
static const std::string k_midtones_color_input;
static const std::string k_highlights_color_input;
static const std::string k_shadows_amount_input;
static const std::string k_midtones_amount_input;
static const std::string k_highlights_amount_input;
static const std::string k_luma_coefficients_input;
};
}
#endif // OAK_THREEWAYCOLORNODE_H
@@ -0,0 +1,22 @@
# Oak - Non-Linear Video Editor
# Copyright (C) 2026 Oak Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/color/whitebalance/whitebalance.cpp
node/color/whitebalance/whitebalance.h
PARENT_SCOPE
)
@@ -0,0 +1,155 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "whitebalance.h"
#include <algorithm>
#include <cmath>
#include "filefunctions.h"
#include "render/job/shaderjob.h"
#include "render/texture.h"
#include "sliderdisplaytype.h"
namespace olive
{
#define super Node
const std::string WhiteBalanceNode::k_texture_input = "tex_in";
const std::string WhiteBalanceNode::k_temperature_input = "temperature_in";
const std::string WhiteBalanceNode::k_tint_input = "tint_in";
const std::string WhiteBalanceNode::k_gain_input = "wb_gain_in";
WhiteBalanceNode::WhiteBalanceNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_temperature_input, NodeValue::k_float, 6500.0);
set_input_property(k_temperature_input, "min", 1000.0);
set_input_property(k_temperature_input, "max", 40000.0);
set_input_property(k_temperature_input, "view", slider::k_normal);
add_input(k_tint_input, NodeValue::k_float, 0.0);
set_input_property(k_tint_input, "min", -1.0);
set_input_property(k_tint_input, "max", 1.0);
set_input_property(k_tint_input, "base", 0.01);
set_effect_input(k_texture_input);
set_flag(k_video_effect);
}
std::string WhiteBalanceNode::name() const
{
return "White Balance";
}
std::string WhiteBalanceNode::id() const
{
return "org.olivevideoeditor.Olive.whitebalance";
}
std::vector<Node::CategoryID> WhiteBalanceNode::category() const
{
return { k_category_color };
}
std::string WhiteBalanceNode::description() const
{
return "Adjust white balance by color temperature and tint.";
}
void WhiteBalanceNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_temperature_input, "Temperature (K)");
set_input_name(k_tint_input, "Tint");
}
ShaderCode WhiteBalanceNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(
FileFunctions::read_file_as_string(":/shaders/whitebalance.frag"));
}
void WhiteBalanceNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
(void) globals;
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
ShaderJob job(value);
const Vector3D gain = get_gain_for_temperature(
value.at(k_temperature_input).to_double(),
value.at(k_tint_input).to_double());
job.insert(k_gain_input, NodeValue(NodeValue::k_vec3, gain));
table->push(NodeValue::k_texture, tex->to_job(job), this);
}
}
Vector3D WhiteBalanceNode::get_gain_for_temperature(double kelvin, double tint)
{
// Tanner Helland blackbody approximation (1000K-40000K), returning
// 0-255 per channel
kelvin = std::max(1000.0, std::min(kelvin, 40000.0));
const double t = kelvin / 100.0;
double red;
if (t <= 66.0) {
red = 255.0;
} else {
red = 329.698727446 * std::pow(t - 60.0, -0.1332047592);
}
double green;
if (t <= 66.0) {
green = 99.4708025861 * std::log(t) - 161.1195681661;
} else {
green = 288.1221695283 * std::pow(t - 60.0, -0.0755148492);
}
double blue;
if (t >= 66.0) {
blue = 255.0;
} else if (t <= 19.0) {
blue = 0.0;
} else {
blue = 138.5177312231 * std::log(t - 10.0) - 305.0447927307;
}
// Normalize to the green channel so temperature shifts do not change
// exposure, then let tint move along the green-magenta axis
red /= green;
blue /= green;
green = 1.0;
const double tint_gain = std::max(0.0, std::min(1.0 + tint, 2.0));
return Vector3D(float(red), float(green * tint_gain), float(blue));
}
}
@@ -0,0 +1,72 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_WHITEBALANCENODE_H
#define OAK_WHITEBALANCENODE_H
#include "mathtypes.h"
#include "node.h"
namespace olive
{
/**
* @brief White balance correction by color temperature and tint
*
* Converts a scene illuminant temperature (in Kelvin) into per-channel RGB
* gains using the Tanner Helland blackbody approximation, normalized so the
* green channel is preserved (no exposure shift). Tint shifts the image
* along the green-magenta axis.
*/
class WhiteBalanceNode : public Node {
public:
WhiteBalanceNode();
NODE_DEFAULT_FUNCTIONS(WhiteBalanceNode)
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;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
/**
* @brief RGB gains for a given illuminant temperature and tint
*
* Extracted for testability. Kelvin is clamped to [1000, 40000]; the
* result is normalized so the green channel gain is 1.0 at tint 0.
*/
static Vector3D get_gain_for_temperature(double kelvin, double tint);
static const std::string k_texture_input;
static const std::string k_temperature_input;
static const std::string k_tint_input;
static const std::string k_gain_input;
};
} // olive
#endif
+30
View File
@@ -0,0 +1,30 @@
# 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(cornerpin)
add_subdirectory(crop)
add_subdirectory(flip)
add_subdirectory(mask)
add_subdirectory(ripple)
add_subdirectory(swirl)
add_subdirectory(tile)
add_subdirectory(transform)
add_subdirectory(wave)
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/distort/cornerpin/cornerpindistortnode.cpp
node/distort/cornerpin/cornerpindistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,223 @@
/***
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 "cornerpindistortnode.h"
#include <cassert>
#include "common/lerp.h"
namespace olive
{
const std::string CornerPinDistortNode::k_texture_input = "tex_in";
const std::string CornerPinDistortNode::k_top_left_input = "top_left_in";
const std::string CornerPinDistortNode::k_top_right_input = "top_right_in";
const std::string CornerPinDistortNode::k_bottom_right_input =
"bottom_right_in";
const std::string CornerPinDistortNode::k_bottom_left_input = "bottom_left_in";
const std::string CornerPinDistortNode::k_perspective_input = "perspective_in";
#define super Node
CornerPinDistortNode::CornerPinDistortNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_perspective_input, NodeValue::k_boolean, true);
add_input(k_top_left_input, NodeValue::k_vec2, Vector2D(0.0, 0.0));
add_input(k_top_right_input, NodeValue::k_vec2, Vector2D(0.0, 0.0));
add_input(k_bottom_right_input, NodeValue::k_vec2, Vector2D(0.0, 0.0));
add_input(k_bottom_left_input, NodeValue::k_vec2, Vector2D(0.0, 0.0));
// Initiate gizmos
gizmo_whole_rect_ = add_draggable_gizmo<PolygonGizmo>();
gizmo_resize_handle_[0] = add_draggable_gizmo<PointGizmo>(
{ NodeKeyframeTrackReference(NodeInput(this, k_top_left_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_top_left_input), 1) });
gizmo_resize_handle_[1] = add_draggable_gizmo<PointGizmo>(
{ NodeKeyframeTrackReference(NodeInput(this, k_top_right_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_top_right_input), 1) });
gizmo_resize_handle_[2] = add_draggable_gizmo<PointGizmo>(
{ NodeKeyframeTrackReference(NodeInput(this, k_bottom_right_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_bottom_right_input), 1) });
gizmo_resize_handle_[3] = add_draggable_gizmo<PointGizmo>(
{ NodeKeyframeTrackReference(NodeInput(this, k_bottom_left_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_bottom_left_input), 1) });
set_flag(k_video_effect);
set_effect_input(k_texture_input);
}
void CornerPinDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Texture");
set_input_name(k_perspective_input, "Perspective");
set_input_name(k_top_left_input, "Top Left");
set_input_name(k_top_right_input, "Top Right");
set_input_name(k_bottom_right_input, "Bottom Right");
set_input_name(k_bottom_left_input, "Bottom Left");
}
void CornerPinDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// If no texture do nothing
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
// In the special case that all sliders are in their default position just
// push the texture.
if (!(value.at(k_top_left_input).to_vec2().is_null() &&
value.at(k_top_right_input).to_vec2().is_null() &&
value.at(k_bottom_right_input).to_vec2().is_null() &&
value.at(k_bottom_left_input).to_vec2().is_null())) {
ShaderJob job(value);
job.insert("resolution_in",
NodeValue(NodeValue::k_vec2, tex->virtual_resolution(),
this));
// Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the
// vertex coordinates.
const Vector2D &resolution = tex->virtual_resolution();
Vector2D half_resolution = resolution * 0.5;
PointF top_left_pt = value_to_pixel(0, value, resolution);
Vector2D top_left = Vector2D(top_left_pt.x(), top_left_pt.y()) /
half_resolution -
Vector2D(1.0, 1.0);
PointF top_right_pt = value_to_pixel(1, value, resolution);
Vector2D top_right =
Vector2D(top_right_pt.x(), top_right_pt.y()) /
half_resolution -
Vector2D(1.0, 1.0);
PointF bottom_right_pt = value_to_pixel(2, value, resolution);
Vector2D bottom_right =
Vector2D(bottom_right_pt.x(), bottom_right_pt.y()) /
half_resolution -
Vector2D(1.0, 1.0);
PointF bottom_left_pt = value_to_pixel(3, value, resolution);
Vector2D bottom_left =
Vector2D(bottom_left_pt.x(), bottom_left_pt.y()) /
half_resolution -
Vector2D(1.0, 1.0);
// Override default vertex coordinates.
std::vector<float> adjusted_vertices = {
top_left.x(), top_left.y(), 0.0f,
top_right.x(), top_right.y(), 0.0f,
bottom_right.x(), bottom_right.y(), 0.0f,
top_left.x(), top_left.y(), 0.0f,
bottom_left.x(), bottom_left.y(), 0.0f,
bottom_right.x(), bottom_right.y(), 0.0f
};
job.set_vertex_coordinates(adjusted_vertices);
table->push(NodeValue::k_texture, tex->to_job(job), this);
} else {
table->push(value.at(k_texture_input));
}
}
}
ShaderCode
CornerPinDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(FileFunctions::read_file_as_string(
":/shaders/cornerpin.frag"),
FileFunctions::read_file_as_string(
":/shaders/cornerpin.vert"));
}
PointF CornerPinDistortNode::value_to_pixel(int value, const NodeValueRow &row,
const Vector2D &resolution) const
{
assert(value >= 0 && value <= 3);
Vector2D v;
switch (value) {
case 0: // Top left
v = row.at(k_top_left_input).to_vec2();
return PointF(v.x(), v.y());
case 1: // Top right
v = row.at(k_top_right_input).to_vec2();
return PointF(resolution.x() + v.x(), v.y());
case 2: // Bottom right
v = row.at(k_bottom_right_input).to_vec2();
return PointF(resolution.x() + v.x(), resolution.y() + v.y());
case 3: //Bottom left
v = row.at(k_bottom_left_input).to_vec2();
return PointF(v.x(), v.y() + resolution.y());
default: // We should never get here
return PointF();
}
}
void CornerPinDistortNode::gizmo_drag_move(double x, double y, int modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo *>(current_gizmo());
if (gizmo != gizmo_whole_rect_) {
gizmo->get_draggers()[0].drag(
gizmo->get_draggers()[0].get_start_value().to_double() + x);
gizmo->get_draggers()[1].drag(
gizmo->get_draggers()[1].get_start_value().to_double() + y);
}
}
void CornerPinDistortNode::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
if (TexturePtr tex = row.at(k_texture_input).to_texture()) {
const Vector2D &resolution = tex->virtual_resolution();
PointF top_left = value_to_pixel(0, row, resolution);
PointF top_right = value_to_pixel(1, row, resolution);
PointF bottom_right = value_to_pixel(2, row, resolution);
PointF bottom_left = value_to_pixel(3, row, resolution);
// Add the correct offset to each slider
set_input_property(k_top_left_input, "offset",
Vector2D(0.0, 0.0));
set_input_property(k_top_right_input, "offset",
Vector2D(resolution.x(), 0.0));
set_input_property(k_bottom_right_input, "offset",
resolution);
set_input_property(k_bottom_left_input, "offset",
Vector2D(0.0, resolution.y()));
// Draw bounding box
gizmo_whole_rect_->set_polygon(
{ top_left, top_right, bottom_right, bottom_left, top_left });
// Create handles
gizmo_resize_handle_[0]->set_point(top_left);
gizmo_resize_handle_[1]->set_point(top_right);
gizmo_resize_handle_[2]->set_point(bottom_right);
gizmo_resize_handle_[3]->set_point(bottom_left);
}
}
}
@@ -0,0 +1,95 @@
/***
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_CORNERPINDISTORTNODE_H
#define OAK_CORNERPINDISTORTNODE_H
#include "gizmo/point.h"
#include "gizmo/polygon.h"
#include "inputdragger.h"
#include "node.h"
namespace olive
{
class CornerPinDistortNode : public Node {
public:
CornerPinDistortNode();
NODE_DEFAULT_FUNCTIONS(CornerPinDistortNode)
virtual std::string name() const override
{
return "Corner Pin";
}
virtual std::string id() const override
{
return "org.olivevideoeditor.Olive.cornerpin";
}
virtual std::vector<CategoryID> category() const override
{
return { k_category_distort };
}
virtual std::string description() const override
{
return "Distort the image by dragging the corners.";
}
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
/**
* @brief Convenience function - converts the 2D slider values from being
* an offset to the actual pixel value.
*/
PointF value_to_pixel(int value, const NodeValueRow &row,
const Vector2D &resolution) const;
static const std::string k_texture_input;
static const std::string k_perspective_input;
static const std::string k_top_left_input;
static const std::string k_top_right_input;
static const std::string k_bottom_right_input;
static const std::string k_bottom_left_input;
protected:
virtual void gizmo_drag_move(double x, double y, int modifiers) override;
private:
// Gizmo variables
static const int k_gizmo_corner_count = 4;
PointGizmo *gizmo_resize_handle_[k_gizmo_corner_count];
PolygonGizmo *gizmo_whole_rect_;
};
}
#endif // OAK_CORNERPINDISTORTNODE_H
+22
View File
@@ -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/distort/crop/cropdistortnode.cpp
node/distort/crop/cropdistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,191 @@
/***
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 "cropdistortnode.h"
#include "common/util.h"
#include "sliderdisplaytype.h"
namespace olive
{
const std::string CropDistortNode::k_texture_input = "tex_in";
const std::string CropDistortNode::k_left_input = "left_in";
const std::string CropDistortNode::k_top_input = "top_in";
const std::string CropDistortNode::k_right_input = "right_in";
const std::string CropDistortNode::k_bottom_input = "bottom_in";
const std::string CropDistortNode::k_feather_input = "feather_in";
#define super Node
CropDistortNode::CropDistortNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
create_crop_side_input(k_left_input);
create_crop_side_input(k_top_input);
create_crop_side_input(k_right_input);
create_crop_side_input(k_bottom_input);
add_input(k_feather_input, NodeValue::k_float, 0.0);
set_input_property(k_feather_input, "min", 0.0);
// Initiate gizmos
poly_gizmo_ = add_draggable_gizmo<PolygonGizmo>(
{ k_left_input, k_top_input, k_right_input, k_bottom_input });
point_gizmo_[k_gizmo_scale_top_left] =
add_draggable_gizmo<PointGizmo>({ k_left_input, k_top_input });
point_gizmo_[k_gizmo_scale_top_center] =
add_draggable_gizmo<PointGizmo>({ k_top_input });
point_gizmo_[k_gizmo_scale_top_right] =
add_draggable_gizmo<PointGizmo>({ k_right_input, k_top_input });
point_gizmo_[k_gizmo_scale_bottom_left] =
add_draggable_gizmo<PointGizmo>({ k_left_input, k_bottom_input });
point_gizmo_[k_gizmo_scale_bottom_center] =
add_draggable_gizmo<PointGizmo>({ k_bottom_input });
point_gizmo_[k_gizmo_scale_bottom_right] =
add_draggable_gizmo<PointGizmo>({ k_right_input, k_bottom_input });
point_gizmo_[k_gizmo_scale_center_left] =
add_draggable_gizmo<PointGizmo>({ k_left_input });
point_gizmo_[k_gizmo_scale_center_right] =
add_draggable_gizmo<PointGizmo>({ k_right_input });
set_flag(k_video_effect);
set_effect_input(k_texture_input);
}
void CropDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Texture");
set_input_name(k_left_input, "Left");
set_input_name(k_top_input, "Top");
set_input_name(k_right_input, "Right");
set_input_name(k_bottom_input, "Bottom");
set_input_name(k_feather_input, "Feather");
}
void CropDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
ShaderJob job;
job.insert(value);
if (TexturePtr texture = job.get(k_texture_input).to_texture()) {
job.insert("resolution_in",
NodeValue(NodeValue::k_vec2,
Vector2D(texture->params().width(),
texture->params().height()),
this));
if (job.get(k_left_input).to_double() != 0.0 ||
job.get(k_right_input).to_double() != 0.0 ||
job.get(k_top_input).to_double() != 0.0 ||
job.get(k_bottom_input).to_double() != 0.0) {
table->push(NodeValue::k_texture, texture->to_job(job), this);
} else {
table->push(job.get(k_texture_input));
}
}
}
ShaderCode CropDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(
FileFunctions::read_file_as_string(":/shaders/crop.frag"));
}
void CropDistortNode::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
if (TexturePtr tex = row.at(k_texture_input).to_texture()) {
const Vector2D &resolution = tex->virtual_resolution();
temp_resolution_ = resolution;
double left_pt = resolution.x() * row.at(k_left_input).to_double();
double top_pt = resolution.y() * row.at(k_top_input).to_double();
double right_pt = resolution.x() * (1.0 - row.at(k_right_input).to_double());
double bottom_pt =
resolution.y() * (1.0 - row.at(k_bottom_input).to_double());
double center_x_pt = mid(left_pt, right_pt);
double center_y_pt = mid(top_pt, bottom_pt);
point_gizmo_[k_gizmo_scale_top_left]->set_point(PointF(left_pt, top_pt));
point_gizmo_[k_gizmo_scale_top_center]->set_point(
PointF(center_x_pt, top_pt));
point_gizmo_[k_gizmo_scale_top_right]->set_point(PointF(right_pt, top_pt));
point_gizmo_[k_gizmo_scale_bottom_left]->set_point(
PointF(left_pt, bottom_pt));
point_gizmo_[k_gizmo_scale_bottom_center]->set_point(
PointF(center_x_pt, bottom_pt));
point_gizmo_[k_gizmo_scale_bottom_right]->set_point(
PointF(right_pt, bottom_pt));
point_gizmo_[k_gizmo_scale_center_left]->set_point(
PointF(left_pt, center_y_pt));
point_gizmo_[k_gizmo_scale_center_right]->set_point(
PointF(right_pt, center_y_pt));
poly_gizmo_->set_polygon({ PointF(left_pt, top_pt),
PointF(right_pt, top_pt),
PointF(right_pt, bottom_pt),
PointF(left_pt, bottom_pt),
PointF(left_pt, top_pt) });
}
}
void CropDistortNode::gizmo_drag_move(double x_diff, double y_diff,
int modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo *>(current_gizmo());
Vector2D res = temp_resolution_;
x_diff /= res.x();
y_diff /= res.y();
for (int j = 0; j < int(gizmo->get_draggers().size()); j++) {
NodeInputDragger &i = gizmo->get_draggers()[j];
double s = i.get_start_value().to_double();
if (i.get_input().input().input() == k_left_input) {
i.drag(s + x_diff);
} else if (i.get_input().input().input() == k_top_input) {
i.drag(s + y_diff);
} else if (i.get_input().input().input() == k_right_input) {
i.drag(s - x_diff);
} else if (i.get_input().input().input() == k_bottom_input) {
i.drag(s - y_diff);
}
}
}
void CropDistortNode::create_crop_side_input(const std::string &id)
{
add_input(id, NodeValue::k_float, 0.0);
set_input_property(id, "min", 0.0);
set_input_property(id, "max", 1.0);
set_input_property(id, "view", slider::k_percentage);
}
}
@@ -0,0 +1,92 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_CROPDISTORTNODE_H
#define OAK_CROPDISTORTNODE_H
#include "gizmo/point.h"
#include "gizmo/polygon.h"
#include "inputdragger.h"
#include "node.h"
namespace olive
{
class CropDistortNode : public Node {
public:
CropDistortNode();
NODE_DEFAULT_FUNCTIONS(CropDistortNode)
virtual std::string name() const override
{
return "Crop";
}
virtual std::string id() const override
{
return "org.olivevideoeditor.Olive.crop";
}
virtual std::vector<CategoryID> category() const override
{
return { k_category_distort };
}
virtual std::string description() const override
{
return "Crop the edges of an image.";
}
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
static const std::string k_texture_input;
static const std::string k_left_input;
static const std::string k_top_input;
static const std::string k_right_input;
static const std::string k_bottom_input;
static const std::string k_feather_input;
protected:
virtual void gizmo_drag_move(double delta_x, double delta_y,
int modifiers) override;
private:
void create_crop_side_input(const std::string &id);
// Gizmo variables
PointGizmo *point_gizmo_[k_gizmo_scale_count];
PolygonGizmo *poly_gizmo_;
Vector2D temp_resolution_;
};
}
#endif // OAK_CROPDISTORTNODE_H
+22
View File
@@ -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/distort/flip/flipdistortnode.cpp
node/distort/flip/flipdistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,99 @@
/***
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 "flipdistortnode.h"
namespace olive
{
const std::string FlipDistortNode::k_texture_input = "tex_in";
const std::string FlipDistortNode::k_horizontal_input = "horiz_in";
const std::string FlipDistortNode::k_vertical_input = "vert_in";
#define super Node
FlipDistortNode::FlipDistortNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_horizontal_input, NodeValue::k_boolean, false);
add_input(k_vertical_input, NodeValue::k_boolean, false);
set_flag(k_video_effect);
set_effect_input(k_texture_input);
}
std::string FlipDistortNode::name() const
{
return "Flip";
}
std::string FlipDistortNode::id() const
{
return "org.olivevideoeditor.Olive.flip";
}
std::vector<Node::CategoryID> FlipDistortNode::category() const
{
return { k_category_distort };
}
std::string FlipDistortNode::description() const
{
return "Flips an image horizontally or vertically";
}
void FlipDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_horizontal_input, "Horizontal");
set_input_name(k_vertical_input, "Vertical");
}
ShaderCode FlipDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(FileFunctions::read_file_as_string(":/shaders/flip.frag"));
}
void FlipDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// If there's no texture, no need to run an operation
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
// Only run shader if at least one of flip or flop are selected
if (value.at(k_horizontal_input).to_bool() ||
value.at(k_vertical_input).to_bool()) {
table->push(NodeValue::k_texture, tex->to_job(ShaderJob(value)),
this);
} else {
// If we're not flipping or flopping just push the texture
table->push(value.at(k_texture_input));
}
}
}
}
@@ -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_FLIPDISTORTNODE_H
#define OAK_FLIPDISTORTNODE_H
#include "node.h"
namespace olive
{
class FlipDistortNode : public Node {
public:
FlipDistortNode();
NODE_DEFAULT_FUNCTIONS(FlipDistortNode)
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;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static const std::string k_texture_input;
static const std::string k_horizontal_input;
static const std::string k_vertical_input;
};
}
#endif // OAK_FLIPDISTORTNODE_H
+22
View File
@@ -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/distort/mask/mask.cpp
node/distort/mask/mask.h
PARENT_SCOPE
)
+133
View File
@@ -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/>.
***/
#include "mask.h"
#include "filter/blur/blur.h"
namespace olive
{
#define super PolygonGenerator
const std::string MaskDistortNode::k_feather_input = "feather_in";
const std::string MaskDistortNode::k_invert_input = "invert_in";
MaskDistortNode::MaskDistortNode()
{
// Mask should always be (1.0, 1.0, 1.0) for multiply to work correctly
set_input_flag(k_color_input, k_input_flag_hidden);
add_input(k_invert_input, NodeValue::k_boolean, false);
add_input(k_feather_input, NodeValue::k_float, 0.0);
set_input_property(k_feather_input, "min", 0.0);
}
ShaderCode MaskDistortNode::get_shader_code(const ShaderRequest &request) const
{
if (request.id == "mrg") {
return ShaderCode(FileFunctions::read_file_as_string(
":/shaders/multiply.frag"));
} else if (request.id == "feather") {
return ShaderCode(FileFunctions::read_file_as_string(
":/shaders/blur.frag"));
} else if (request.id == "invert") {
return ShaderCode(FileFunctions::read_file_as_string(
":/shaders/invertrgba.frag"));
} else {
return super::get_shader_code(request);
}
}
void MaskDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_base_input, "Texture");
set_input_name(k_invert_input, "Invert");
set_input_name(k_feather_input, "Feather");
}
void MaskDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
TexturePtr texture = value.at(k_base_input).to_texture();
VideoParams job_params = texture ? texture->params() : globals.vparams();
NodeValue job(NodeValue::k_texture,
Texture::job(job_params, get_generate_job(value, job_params)),
this);
if (value.at(k_invert_input).to_bool()) {
ShaderJob invert;
invert.set_shader_id("invert");
invert.insert("tex_in", job);
job.set_value(Texture::job(job_params, invert));
}
if (texture) {
// Push as merge node
ShaderJob merge;
merge.set_shader_id("mrg");
merge.insert("tex_a", value.at(k_base_input));
if (value.at(k_feather_input).to_double() > 0.0) {
// Nest a blur shader in there too
ShaderJob feather;
feather.set_shader_id("feather");
feather.insert(BlurFilterNode::k_texture_input, job);
feather.insert(BlurFilterNode::k_method_input,
NodeValue(NodeValue::k_int,
int(BlurFilterNode::k_gaussian), this));
feather.insert(BlurFilterNode::k_horiz_input,
NodeValue(NodeValue::k_boolean, true, this));
feather.insert(BlurFilterNode::k_vert_input,
NodeValue(NodeValue::k_boolean, true, this));
feather.insert(BlurFilterNode::k_repeat_edge_pixels_input,
NodeValue(NodeValue::k_boolean, true, this));
feather.insert(BlurFilterNode::k_radius_input,
NodeValue(NodeValue::k_float,
value.at(k_feather_input).to_double(), this));
feather.set_iterations(2, BlurFilterNode::k_texture_input);
feather.insert("resolution_in",
NodeValue(NodeValue::k_vec2,
texture ? texture->virtual_resolution() :
globals.square_resolution(),
this));
merge.insert("tex_b",
NodeValue(NodeValue::k_texture,
Texture::job(job_params, feather), this));
} else {
merge.insert("tex_b", job);
}
table->push(NodeValue::k_texture, Texture::job(job_params, merge), this);
} else {
table->push(job);
}
}
}
+70
View File
@@ -0,0 +1,70 @@
/***
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_MASKDISTORTNODE_H
#define OAK_MASKDISTORTNODE_H
#include "generator/polygon/polygon.h"
namespace olive
{
class MaskDistortNode : public PolygonGenerator {
public:
MaskDistortNode();
NODE_DEFAULT_FUNCTIONS(MaskDistortNode)
virtual std::string name() const override
{
return "Mask";
}
virtual std::string id() const override
{
return "org.olivevideoeditor.Olive.mask";
}
virtual std::vector<CategoryID> category() const override
{
return { k_category_distort };
}
virtual std::string description() const override
{
return "Apply a polygonal mask.";
}
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static const std::string k_invert_input;
static const std::string k_feather_input;
};
}
#endif // OAK_MASKDISTORTNODE_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/distort/ripple/rippledistortnode.cpp
node/distort/ripple/rippledistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,137 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "rippledistortnode.h"
namespace olive
{
const std::string RippleDistortNode::k_texture_input = "tex_in";
const std::string RippleDistortNode::k_evolution_input = "evolution_in";
const std::string RippleDistortNode::k_intensity_input = "intensity_in";
const std::string RippleDistortNode::k_frequency_input = "frequency_in";
const std::string RippleDistortNode::k_position_input = "position_in";
const std::string RippleDistortNode::k_stretch_input = "stretch_in";
#define super Node
RippleDistortNode::RippleDistortNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_evolution_input, NodeValue::k_float, 0);
add_input(k_intensity_input, NodeValue::k_float, 100);
add_input(k_frequency_input, NodeValue::k_float, 1);
set_input_property(k_frequency_input, "base", 0.01);
add_input(k_position_input, NodeValue::k_vec2, Vector2D(0, 0));
add_input(k_stretch_input, NodeValue::k_boolean, false);
set_flag(k_video_effect);
set_effect_input(k_texture_input);
gizmo_ = add_draggable_gizmo<PointGizmo>({
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 1),
});
gizmo_->set_shape(PointGizmo::k_anchor_point);
}
std::string RippleDistortNode::name() const
{
return "Ripple";
}
std::string RippleDistortNode::id() const
{
return "org.olivevideoeditor.Olive.ripple";
}
std::vector<Node::CategoryID> RippleDistortNode::category() const
{
return { k_category_distort };
}
std::string RippleDistortNode::description() const
{
return "Distorts an image with a ripple effect.";
}
void RippleDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_frequency_input, "Frequency");
set_input_name(k_intensity_input, "Intensity");
set_input_name(k_evolution_input, "Evolution");
set_input_name(k_position_input, "Position");
set_input_name(k_stretch_input, "Stretch");
}
ShaderCode RippleDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(FileFunctions::read_file_as_string(":/shaders/ripple.frag"));
}
void RippleDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// If there's no texture, no need to run an operation
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
// Only run shader if at least one of flip or flop are selected
if (value.at(k_intensity_input).to_double() != 0.0) {
ShaderJob job(value);
job.insert("resolution_in",
NodeValue(NodeValue::k_vec2, tex->virtual_resolution(),
this));
table->push(NodeValue::k_texture, tex->to_job(job), this);
} else {
// If we're not flipping or flopping just push the texture
table->push(value.at(k_texture_input));
}
}
}
void RippleDistortNode::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
if (TexturePtr tex = row.at(k_texture_input).to_texture()) {
PointF half_res(tex->virtual_resolution().x() / 2,
tex->virtual_resolution().y() / 2);
gizmo_->set_point(half_res + row.at(k_position_input).to_vec2().to_point_f());
}
}
void RippleDistortNode::gizmo_drag_move(double x, double y, int modifiers)
{
NodeInputDragger &x_drag = gizmo_->get_draggers()[0];
NodeInputDragger &y_drag = gizmo_->get_draggers()[1];
x_drag.drag(x_drag.get_start_value().to_double() + x);
y_drag.drag(y_drag.get_start_value().to_double() + y);
}
}
@@ -0,0 +1,68 @@
/***
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_RIPPLEDISTORTNODE_H
#define OAK_RIPPLEDISTORTNODE_H
#include "gizmo/point.h"
#include "node.h"
namespace olive
{
class RippleDistortNode : public Node {
public:
RippleDistortNode();
NODE_DEFAULT_FUNCTIONS(RippleDistortNode)
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;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
static const std::string k_texture_input;
static const std::string k_evolution_input;
static const std::string k_intensity_input;
static const std::string k_frequency_input;
static const std::string k_position_input;
static const std::string k_stretch_input;
protected:
virtual void gizmo_drag_move(double x, double y, int modifiers) override;
private:
PointGizmo *gizmo_;
};
}
#endif // OAK_RIPPLEDISTORTNODE_H
+22
View File
@@ -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/distort/swirl/swirldistortnode.cpp
node/distort/swirl/swirldistortnode.h
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 "swirldistortnode.h"
namespace olive
{
const std::string SwirlDistortNode::k_texture_input = "tex_in";
const std::string SwirlDistortNode::k_radius_input = "radius_in";
const std::string SwirlDistortNode::k_angle_input = "angle_in";
const std::string SwirlDistortNode::k_position_input = "pos_in";
#define super Node
SwirlDistortNode::SwirlDistortNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_radius_input, NodeValue::k_float, 200);
set_input_property(k_radius_input, "min", 0);
add_input(k_angle_input, NodeValue::k_float, 10);
set_input_property(k_angle_input, "base", 0.1);
add_input(k_position_input, NodeValue::k_vec2, Vector2D(0, 0));
set_flag(k_video_effect);
set_effect_input(k_texture_input);
gizmo_ = add_draggable_gizmo<PointGizmo>({
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 1),
});
gizmo_->set_shape(PointGizmo::k_anchor_point);
}
std::string SwirlDistortNode::name() const
{
return "Swirl";
}
std::string SwirlDistortNode::id() const
{
return "org.olivevideoeditor.Olive.swirl";
}
std::vector<Node::CategoryID> SwirlDistortNode::category() const
{
return { k_category_distort };
}
std::string SwirlDistortNode::description() const
{
return "Distorts an image by swirling it around a center point.";
}
void SwirlDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_radius_input, "Radius");
set_input_name(k_angle_input, "Angle");
set_input_name(k_position_input, "Position");
}
ShaderCode SwirlDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(FileFunctions::read_file_as_string(":/shaders/swirl.frag"));
}
void SwirlDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// If there's no texture, no need to run an operation
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
// Only run shader if at least one of flip or flop are selected
if (value.at(k_angle_input).to_double() != 0.0 &&
value.at(k_radius_input).to_double() != 0.0) {
ShaderJob job(value);
job.insert("resolution_in",
NodeValue(NodeValue::k_vec2, tex->virtual_resolution(),
this));
table->push(NodeValue::k_texture, tex->to_job(job), this);
} else {
// If we're not flipping or flopping just push the texture
table->push(value.at(k_texture_input));
}
}
}
void SwirlDistortNode::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
PointF half_res(globals.square_resolution().x() / 2,
globals.square_resolution().y() / 2);
gizmo_->set_point(half_res + row.at(k_position_input).to_vec2().to_point_f());
}
void SwirlDistortNode::gizmo_drag_move(double x, double y, int modifiers)
{
NodeInputDragger &x_drag = gizmo_->get_draggers()[0];
NodeInputDragger &y_drag = gizmo_->get_draggers()[1];
x_drag.drag(x_drag.get_start_value().to_double() + x);
y_drag.drag(y_drag.get_start_value().to_double() + y);
}
}
@@ -0,0 +1,66 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_SWIRLDISTORTNODE_H
#define OAK_SWIRLDISTORTNODE_H
#include "gizmo/point.h"
#include "node.h"
namespace olive
{
class SwirlDistortNode : public Node {
public:
SwirlDistortNode();
NODE_DEFAULT_FUNCTIONS(SwirlDistortNode)
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;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
static const std::string k_texture_input;
static const std::string k_radius_input;
static const std::string k_angle_input;
static const std::string k_position_input;
protected:
virtual void gizmo_drag_move(double x, double y, int modifiers) override;
private:
PointGizmo *gizmo_;
};
}
#endif // OAK_SWIRLDISTORTNODE_H
+22
View File
@@ -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/distort/tile/tiledistortnode.cpp
node/distort/tile/tiledistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,180 @@
/***
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 "tiledistortnode.h"
#include <cmath>
#include "sliderdisplaytype.h"
namespace olive
{
const std::string TileDistortNode::k_texture_input = "tex_in";
const std::string TileDistortNode::k_scale_input = "scale_in";
const std::string TileDistortNode::k_position_input = "position_in";
const std::string TileDistortNode::k_anchor_input = "anchor_in";
const std::string TileDistortNode::k_mirror_x_input = "mirrorx_in";
const std::string TileDistortNode::k_mirror_y_input = "mirrory_in";
#define super Node
TileDistortNode::TileDistortNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_scale_input, NodeValue::k_float, 0.5);
set_input_property(k_scale_input, "min", 0);
set_input_property(k_scale_input, "view",
slider::k_percentage);
add_input(k_position_input, NodeValue::k_vec2, Vector2D(0, 0));
add_input(k_anchor_input, NodeValue::k_combo, k_middle_center);
add_input(k_mirror_x_input, NodeValue::k_boolean, false);
add_input(k_mirror_y_input, NodeValue::k_boolean, false);
set_flag(k_video_effect);
set_effect_input(k_texture_input);
gizmo_ = add_draggable_gizmo<PointGizmo>({
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 1),
});
gizmo_->set_shape(PointGizmo::k_anchor_point);
}
std::string TileDistortNode::name() const
{
return "Tile";
}
std::string TileDistortNode::id() const
{
return "org.olivevideoeditor.Olive.tile";
}
std::vector<Node::CategoryID> TileDistortNode::category() const
{
return { k_category_distort };
}
std::string TileDistortNode::description() const
{
return "Infinitely tile an image horizontally and vertically.";
}
void TileDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_scale_input, "Scale");
set_input_name(k_position_input, "Position");
set_input_name(k_mirror_x_input, "Mirror Horizontally");
set_input_name(k_mirror_y_input, "Mirror Vertically");
set_input_name(k_anchor_input, "Anchor");
set_combo_box_strings(k_anchor_input, {
"Top-Left",
"Top-Center",
"Top-Right",
"Middle-Left",
"Middle-Center",
"Middle-Right",
"Bottom-Left",
"Bottom-Center",
"Bottom-Right",
});
}
ShaderCode TileDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(FileFunctions::read_file_as_string(":/shaders/tile.frag"));
}
void TileDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// If there's no texture, no need to run an operation
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
// Only run shader if at least one of flip or flop are selected
double scale_value = value.at(k_scale_input).to_double();
if (!(std::abs(scale_value - 1.0) * 1000000000000.0 <=
std::min(std::abs(scale_value), std::abs(1.0)))) {
ShaderJob job(value);
job.insert("resolution_in",
NodeValue(NodeValue::k_vec2, tex->virtual_resolution(),
this));
table->push(NodeValue::k_texture, tex->to_job(job), this);
} else {
// If we're not flipping or flopping just push the texture
table->push(value.at(k_texture_input));
}
}
}
void TileDistortNode::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
if (TexturePtr tex = row.at(k_texture_input).to_texture()) {
PointF res = tex->virtual_resolution().to_point_f();
PointF pos = row.at(k_position_input).to_vec2().to_point_f();
double x = pos.x();
double y = pos.y();
Anchor a = static_cast<Anchor>(row.at(k_anchor_input).to_int());
if (a == k_top_left || a == k_top_center || a == k_top_right) {
// Do nothing
} else if (a == k_middle_left || a == k_middle_center ||
a == k_middle_right) {
y += res.y() / 2;
} else if (a == k_bottom_left || a == k_bottom_center ||
a == k_bottom_right) {
y += res.y();
}
if (a == k_top_left || a == k_middle_left || a == k_bottom_left) {
// Do nothing
} else if (a == k_top_center || a == k_middle_center ||
a == k_bottom_center) {
x += res.x() / 2;
} else if (a == k_top_right || a == k_middle_right || a == k_bottom_right) {
x += res.x();
}
gizmo_->set_point(PointF(x, y));
}
}
void TileDistortNode::gizmo_drag_move(double x, double y, int modifiers)
{
NodeInputDragger &x_drag = gizmo_->get_draggers()[0];
NodeInputDragger &y_drag = gizmo_->get_draggers()[1];
x_drag.drag(x_drag.get_start_value().to_double() + x);
y_drag.drag(y_drag.get_start_value().to_double() + y);
}
}
@@ -0,0 +1,80 @@
/***
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_TILEDISTORTNODE_H
#define OAK_TILEDISTORTNODE_H
#include "gizmo/point.h"
#include "node.h"
namespace olive
{
class TileDistortNode : public Node {
public:
TileDistortNode();
NODE_DEFAULT_FUNCTIONS(TileDistortNode)
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;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
static const std::string k_texture_input;
static const std::string k_scale_input;
static const std::string k_position_input;
static const std::string k_anchor_input;
static const std::string k_mirror_x_input;
static const std::string k_mirror_y_input;
protected:
virtual void gizmo_drag_move(double x, double y, int modifiers) override;
private:
enum Anchor {
k_top_left,
k_top_center,
k_top_right,
k_middle_left,
k_middle_center,
k_middle_right,
k_bottom_left,
k_bottom_center,
k_bottom_right
};
PointGizmo *gizmo_;
};
}
#endif // OAK_TILEDISTORTNODE_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/distort/transform/transformdistortnode.cpp
node/distort/transform/transformdistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,513 @@
/***
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 "transformdistortnode.h"
#include <cmath>
namespace olive
{
const std::string TransformDistortNode::k_parent_input = "parent_in";
const std::string TransformDistortNode::k_texture_input = "tex_in";
const std::string TransformDistortNode::k_autoscale_input = "autoscale_in";
const std::string TransformDistortNode::k_interpolation_input =
"interpolation_in";
#define super MatrixGenerator
TransformDistortNode::TransformDistortNode()
{
add_input(k_parent_input, NodeValue::k_matrix);
add_input(k_autoscale_input, NodeValue::k_combo, 0);
add_input(k_interpolation_input, NodeValue::k_combo, 2);
prepend_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
// Initiate gizmos
rotation_gizmo_ = add_draggable_gizmo<ScreenGizmo>();
rotation_gizmo_->add_input(NodeInput(this, k_rotation_input));
rotation_gizmo_->set_drag_value_behavior(ScreenGizmo::k_absolute);
poly_gizmo_ = add_draggable_gizmo<PolygonGizmo>();
poly_gizmo_->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 0));
poly_gizmo_->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 1));
anchor_gizmo_ = add_draggable_gizmo<PointGizmo>();
anchor_gizmo_->set_shape(PointGizmo::k_anchor_point);
anchor_gizmo_->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_anchor_input), 0));
anchor_gizmo_->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_anchor_input), 1));
anchor_gizmo_->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 0));
anchor_gizmo_->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 1));
for (int i = 0; i < k_gizmo_scale_count; i++) {
point_gizmo_[i] = add_draggable_gizmo<PointGizmo>();
point_gizmo_[i]->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_scale_input), 0));
point_gizmo_[i]->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_scale_input), 1));
point_gizmo_[i]->set_drag_value_behavior(PointGizmo::k_absolute);
}
set_flag(k_video_effect);
set_effect_input(k_texture_input);
}
void TransformDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_parent_input, "Parent");
set_input_name(k_autoscale_input, "Auto-Scale");
set_input_name(k_texture_input, "Texture");
set_input_name(k_interpolation_input, "Interpolation");
set_combo_box_strings(k_autoscale_input,
{ "None", "Fit", "Fill", "Stretch" });
set_combo_box_strings(k_interpolation_input,
{ "Nearest Neighbor", "Bilinear",
"Mipmapped Bilinear" });
}
void TransformDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// Generate matrix
Matrix4x4 generated_matrix = generate_matrix(
value, false, false, false, value.at(k_parent_input).to_matrix());
// Pop texture
NodeValue texture_meta = value.at(k_texture_input);
TexturePtr job_to_push = nullptr;
// If we have a texture, generate a matrix and make it happen
if (TexturePtr texture = texture_meta.to_texture()) {
// Adjust our matrix by the resolutions involved
Matrix4x4 real_matrix = generate_auto_scaled_matrix(
generated_matrix, value, globals, texture->params());
if (!real_matrix.is_identity()) {
// The matrix will transform things
ShaderJob job;
job.insert("ove_maintex", texture_meta);
job.insert("ove_mvpmat",
NodeValue(NodeValue::k_matrix, real_matrix, this));
job.set_interpolation("ove_maintex",
static_cast<Texture::Interpolation>(
value.at(k_interpolation_input).to_int()));
// Use global resolution rather than texture resolution because this may result in a size change
job_to_push = Texture::job(globals.vparams(), job);
}
}
table->push(NodeValue::k_matrix, Variant::from_value(generated_matrix),
this);
if (!job_to_push) {
// Re-push whatever value we received
table->push(texture_meta);
} else {
table->push(NodeValue::k_texture, job_to_push, this);
}
}
ShaderCode
TransformDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
// Returns default frag and vert shader
return ShaderCode();
}
void TransformDistortNode::gizmo_drag_start(const NodeValueRow &row, double x,
double y, const Rational &time)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo *>(current_gizmo());
if (gizmo == anchor_gizmo_) {
gizmo_inverted_transform_ =
generate_matrix(row, true, true, false, row.at(k_parent_input).to_matrix())
.inverted();
} else if (is_a_scale_gizmo(gizmo)) {
// Dragging scale handle
TexturePtr tex = row.at(k_texture_input).to_texture();
if (!tex) {
return;
}
gizmo_scale_uniform_ = row.at(k_uniform_scale_input).to_bool();
gizmo_anchor_pt_ = (row.at(k_anchor_input).to_vec2() +
gizmo->get_globals().nonsquare_resolution() / 2)
.to_point_f();
if (gizmo == point_gizmo_[k_gizmo_scale_top_left] ||
gizmo == point_gizmo_[k_gizmo_scale_top_right] ||
gizmo == point_gizmo_[k_gizmo_scale_bottom_left] ||
gizmo == point_gizmo_[k_gizmo_scale_bottom_right]) {
gizmo_scale_axes_ = k_gizmo_scale_both;
} else if (gizmo == point_gizmo_[k_gizmo_scale_center_left] ||
gizmo == point_gizmo_[k_gizmo_scale_center_right]) {
gizmo_scale_axes_ = k_gizmo_scale_x_only;
} else {
gizmo_scale_axes_ = k_gizmo_scale_y_only;
}
// Store texture size
VideoParams texture_params = tex->params();
Vector2D texture_sz(texture_params.square_pixel_width(),
texture_params.height());
gizmo_scale_anchor_ = row.at(k_anchor_input).to_vec2() + texture_sz / 2;
if (gizmo == point_gizmo_[k_gizmo_scale_top_right] ||
gizmo == point_gizmo_[k_gizmo_scale_bottom_right] ||
gizmo == point_gizmo_[k_gizmo_scale_center_right]) {
// Right handles, flip X axis
gizmo_scale_anchor_.set_x(texture_sz.x() - gizmo_scale_anchor_.x());
}
if (gizmo == point_gizmo_[k_gizmo_scale_bottom_left] ||
gizmo == point_gizmo_[k_gizmo_scale_bottom_right] ||
gizmo == point_gizmo_[k_gizmo_scale_bottom_center]) {
// Bottom handles, flip Y axis
gizmo_scale_anchor_.set_y(texture_sz.y() - gizmo_scale_anchor_.y());
}
// Store current matrix
gizmo_inverted_transform_ =
generate_matrix(row, true, true, true, row.at(k_parent_input).to_matrix())
.inverted();
} else if (gizmo == rotation_gizmo_) {
gizmo_anchor_pt_ = (row.at(k_anchor_input).to_vec2() +
gizmo->get_globals().nonsquare_resolution() / 2)
.to_point_f();
gizmo_start_angle_ =
std::atan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x());
gizmo_last_angle_ = gizmo_start_angle_;
gizmo_last_alt_angle_ =
std::atan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y());
gizmo_rotate_wrap_ = 0;
gizmo_rotate_last_dir_ = k_direction_none;
}
}
void TransformDistortNode::gizmo_drag_move(double x, double y, int modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo *>(current_gizmo());
if (gizmo == poly_gizmo_) {
NodeInputDragger &x_drag = gizmo->get_draggers()[0];
NodeInputDragger &y_drag = gizmo->get_draggers()[1];
x_drag.drag(x_drag.get_start_value().to_double() + x);
y_drag.drag(y_drag.get_start_value().to_double() + y);
} else if (gizmo == anchor_gizmo_) {
NodeInputDragger &x_anchor_drag = gizmo->get_draggers()[0];
NodeInputDragger &y_anchor_drag = gizmo->get_draggers()[1];
NodeInputDragger &x_pos_drag = gizmo->get_draggers()[2];
NodeInputDragger &y_pos_drag = gizmo->get_draggers()[3];
PointF inverted_movement(gizmo_inverted_transform_.map(PointF(x, y)));
x_anchor_drag.drag(x_anchor_drag.get_start_value().to_double() +
inverted_movement.x());
y_anchor_drag.drag(y_anchor_drag.get_start_value().to_double() +
inverted_movement.y());
x_pos_drag.drag(x_pos_drag.get_start_value().to_double() + x);
y_pos_drag.drag(y_pos_drag.get_start_value().to_double() + y);
} else if (gizmo == rotation_gizmo_) {
double raw_angle =
std::atan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x());
double alt_angle =
std::atan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y());
double current_angle = raw_angle;
// Detect rotation wrap around
RotationDirection this_dir =
get_direction_from_angles(gizmo_last_angle_, raw_angle);
RotationDirection alt_dir =
get_direction_from_angles(gizmo_last_alt_angle_, alt_angle);
if (gizmo_rotate_last_dir_ != k_direction_none &&
this_dir != gizmo_rotate_last_dir_) {
if (alt_dir == gizmo_rotate_last_alt_dir_) {
if ((raw_angle - gizmo_last_angle_) < 0) {
gizmo_rotate_wrap_++;
} else {
gizmo_rotate_wrap_--;
}
this_dir = gizmo_rotate_last_dir_;
alt_dir = gizmo_rotate_last_alt_dir_;
}
}
gizmo_rotate_last_dir_ = this_dir;
gizmo_rotate_last_alt_dir_ = alt_dir;
gizmo_last_angle_ = raw_angle;
gizmo_last_alt_angle_ = alt_angle;
current_angle += M_PI * 2 * gizmo_rotate_wrap_;
// Convert radians to degrees
double rotation_difference =
(current_angle - gizmo_start_angle_) * 57.2958;
NodeInputDragger &d = gizmo->get_draggers()[0];
d.drag(d.get_start_value().to_double() + rotation_difference);
} else if (is_a_scale_gizmo(gizmo)) {
PointF mouse_relative =
gizmo_inverted_transform_.map(PointF(x, y) - gizmo_anchor_pt_);
double x_scaled_movement =
std::abs(mouse_relative.x() / gizmo_scale_anchor_.x());
double y_scaled_movement =
std::abs(mouse_relative.y() / gizmo_scale_anchor_.y());
NodeInputDragger &x_drag = gizmo->get_draggers()[0];
NodeInputDragger &y_drag = gizmo->get_draggers()[1];
switch (gizmo_scale_axes_) {
case k_gizmo_scale_x_only:
x_drag.drag(x_scaled_movement);
break;
case k_gizmo_scale_y_only:
if (gizmo_scale_uniform_) {
x_drag.drag(y_scaled_movement);
} else {
y_drag.drag(y_scaled_movement);
}
break;
case k_gizmo_scale_both:
if (gizmo_scale_uniform_) {
double distance =
std::hypot(mouse_relative.x(), mouse_relative.y());
double texture_diag = std::hypot(gizmo_scale_anchor_.x(),
gizmo_scale_anchor_.y());
x_drag.drag(std::abs(distance / texture_diag));
} else {
x_drag.drag(x_scaled_movement);
y_drag.drag(y_scaled_movement);
}
break;
}
}
}
Matrix4x4 TransformDistortNode::adjust_matrix_by_resolutions(
const Matrix4x4 &mat, const Vector2D &sequence_res,
const Vector2D &texture_res, const Vector2D &offset,
AutoScaleType autoscale_type)
{
// First, create an identity matrix
Matrix4x4 adjusted_matrix;
// Scale it to a square based on the sequence's resolution
adjusted_matrix.scale(2.0 / sequence_res.x(), 2.0 / sequence_res.y(), 1.0);
// Apply offset if applicable
adjusted_matrix.translate(offset.x(), offset.y());
// Adjust by the matrix we generated earlier
adjusted_matrix *= mat;
// Scale back out to texture size (adjusted by pixel aspect)
adjusted_matrix.scale(texture_res.x() * 0.5, texture_res.y() * 0.5, 1.0);
// If auto-scale is enabled, fit the texture to the sequence (without cropping)
if (autoscale_type != k_auto_scale_none) {
if (autoscale_type == k_auto_scale_stretch) {
adjusted_matrix.scale(sequence_res.x() / texture_res.x(),
sequence_res.y() / texture_res.y(), 1.0);
} else {
double footage_real_ar = texture_res.x() / texture_res.y();
double sequence_real_ar = sequence_res.x() / sequence_res.y();
double scale_by_x = sequence_res.x() / texture_res.x();
double scale_by_y = sequence_res.y() / texture_res.y();
double autoscale_val;
if ((autoscale_type == k_auto_scale_fit) ==
(sequence_real_ar > footage_real_ar)) {
// Scale by height. Either the sequence is wider than the footage or we're using fill and
// cutting off the sides
autoscale_val = scale_by_y;
} else {
// Scale by width. Either the footage is wider than the sequence or we're using fill and
// cutting off the top and bottom
autoscale_val = scale_by_x;
}
adjusted_matrix.scale(autoscale_val, autoscale_val, 1.0);
}
}
return adjusted_matrix;
}
void TransformDistortNode::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
TexturePtr tex = row.at(k_texture_input).to_texture();
if (!tex) {
return;
}
// Get the sequence resolution
const Vector2D &sequence_res = globals.nonsquare_resolution();
Vector2D sequence_half_res = sequence_res * 0.5;
PointF sequence_half_res_pt = sequence_half_res.to_point_f();
// GizmoTraverser just returns the sizes of the textures and no other data
VideoParams tex_params = tex->params();
Vector2D tex_sz(tex_params.square_pixel_width(), tex_params.height());
Vector2D tex_offset(tex_params.x(), tex_params.y());
// Retrieve autoscale value
AutoScaleType autoscale =
static_cast<AutoScaleType>(row.at(k_autoscale_input).to_int());
// Fold values into a matrix for the rectangle
Matrix4x4 rectangle_matrix;
rectangle_matrix.scale(sequence_half_res.x(), sequence_half_res.y());
rectangle_matrix *= adjust_matrix_by_resolutions(
generate_matrix(row, false, false, false, row.at(k_parent_input).to_matrix()),
sequence_res, tex_sz, tex_offset, autoscale);
// Create rect and transform it
const std::vector<PointF> points = { PointF(-1, -1), PointF(1, -1),
PointF(1, 1), PointF(-1, 1),
PointF(-1, -1) };
std::vector<PointF> r;
r.reserve(points.size());
for (const PointF &p : points) {
r.push_back(rectangle_matrix.map(p));
}
for (PointF &p : r) {
p += sequence_half_res_pt;
}
poly_gizmo_->set_polygon(r);
// Draw anchor point
Matrix4x4 anchor_matrix;
anchor_matrix.scale(sequence_half_res.x(), sequence_half_res.y());
anchor_matrix *= adjust_matrix_by_resolutions(
generate_matrix(row, true, false, false, row.at(k_parent_input).to_matrix()),
sequence_res, tex_sz, tex_offset, autoscale);
anchor_gizmo_->set_point(anchor_matrix.map(PointF(0, 0)) +
sequence_half_res_pt);
// Draw scale handles
point_gizmo_[k_gizmo_scale_top_left]->set_point(
create_scale_point(-1, -1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[k_gizmo_scale_top_center]->set_point(
create_scale_point(0, -1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[k_gizmo_scale_top_right]->set_point(
create_scale_point(1, -1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[k_gizmo_scale_bottom_left]->set_point(
create_scale_point(-1, 1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[k_gizmo_scale_bottom_center]->set_point(
create_scale_point(0, 1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[k_gizmo_scale_bottom_right]->set_point(
create_scale_point(1, 1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[k_gizmo_scale_center_left]->set_point(
create_scale_point(-1, 0, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[k_gizmo_scale_center_right]->set_point(
create_scale_point(1, 0, sequence_half_res_pt, rectangle_matrix));
// Use offsets to make the appearance of values that start in the top left, even though we
// really anchor around the center
set_input_property(k_position_input, "offset",
sequence_half_res + tex_offset);
set_input_property(k_anchor_input, "offset", tex_sz * 0.5);
}
Matrix4x4
TransformDistortNode::gizmo_transformation(const NodeValueRow &row,
const NodeGlobals &globals) const
{
if (TexturePtr texture = row.at(k_texture_input).to_texture()) {
//auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix());
auto m = generate_matrix(row, false, false, false, Matrix4x4());
return generate_auto_scaled_matrix(m, row, globals, texture->params());
}
return super::gizmo_transformation(row, globals);
}
PointF TransformDistortNode::create_scale_point(double x, double y,
const PointF &half_res,
const Matrix4x4 &mat)
{
return mat.map(PointF(x, y)) + half_res;
}
Matrix4x4 TransformDistortNode::generate_auto_scaled_matrix(
const Matrix4x4 &generated_matrix, const NodeValueRow &value,
const NodeGlobals &globals, const VideoParams &texture_params) const
{
const Vector2D &sequence_res = globals.nonsquare_resolution();
Vector2D texture_res(texture_params.square_pixel_width(),
texture_params.height());
AutoScaleType autoscale =
static_cast<AutoScaleType>(value.at(k_autoscale_input).to_int());
return adjust_matrix_by_resolutions(generated_matrix, sequence_res,
texture_res, Vector2D(texture_params.x(), texture_params.y()),
autoscale);
}
bool TransformDistortNode::is_a_scale_gizmo(NodeGizmo *g) const
{
for (int i = 0; i < k_gizmo_scale_count; i++) {
if (point_gizmo_[i] == g) {
return true;
}
}
return false;
}
TransformDistortNode::RotationDirection
TransformDistortNode::get_direction_from_angles(double last, double current)
{
return (current > last) ? k_direction_positive : k_direction_negative;
}
}
@@ -0,0 +1,147 @@
/***
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_TRANSFORMDISTORTNODE_H
#define OAK_TRANSFORMDISTORTNODE_H
#include "generator/matrix/matrix.h"
#include "gizmo/point.h"
#include "gizmo/polygon.h"
#include "gizmo/screen.h"
namespace olive
{
class TransformDistortNode : public MatrixGenerator {
public:
TransformDistortNode();
NODE_DEFAULT_FUNCTIONS(TransformDistortNode)
virtual std::string name() const override
{
return "Transform";
}
virtual std::string short_name() const override
{
// Override MatrixGenerator's short name "Ortho"
return name();
}
virtual std::string id() const override
{
return "org.olivevideoeditor.Olive.transform";
}
virtual std::vector<CategoryID> category() const override
{
return { k_category_distort };
}
virtual std::string description() const override
{
return "Transform an image in 2D space. Equivalent to multiplying by an orthographic matrix.";
}
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
enum AutoScaleType {
k_auto_scale_none,
k_auto_scale_fit,
k_auto_scale_fill,
k_auto_scale_stretch
};
static Matrix4x4 adjust_matrix_by_resolutions(
const Matrix4x4 &mat, const Vector2D &sequence_res,
const Vector2D &texture_res, const Vector2D &offset,
AutoScaleType autoscale_type = k_auto_scale_none);
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
virtual Matrix4x4
gizmo_transformation(const NodeValueRow &row,
const NodeGlobals &globals) const override;
static const std::string k_parent_input;
static const std::string k_texture_input;
static const std::string k_autoscale_input;
static const std::string k_interpolation_input;
protected:
virtual void gizmo_drag_start(const olive::NodeValueRow &row, double x,
double y, const olive::Rational &time) override;
virtual void gizmo_drag_move(double x, double y, int modifiers) override;
private:
static PointF create_scale_point(double x, double y, const PointF &half_res,
const Matrix4x4 &mat);
Matrix4x4
generate_auto_scaled_matrix(const Matrix4x4 &generated_matrix,
const NodeValueRow &db, const NodeGlobals &globals,
const VideoParams &texture_params) const;
bool is_a_scale_gizmo(NodeGizmo *g) const;
// Gizmo variables
double gizmo_start_angle_;
Matrix4x4 gizmo_inverted_transform_;
PointF gizmo_anchor_pt_;
bool gizmo_scale_uniform_;
double gizmo_last_angle_;
double gizmo_last_alt_angle_;
int gizmo_rotate_wrap_;
enum RotationDirection {
k_direction_none,
k_direction_positive, // Clockwise
k_direction_negative // Counter-clockwise
};
static RotationDirection get_direction_from_angles(double last,
double current);
RotationDirection gizmo_rotate_last_dir_;
RotationDirection gizmo_rotate_last_alt_dir_;
enum GizmoScaleType { k_gizmo_scale_x_only, k_gizmo_scale_y_only, k_gizmo_scale_both };
GizmoScaleType gizmo_scale_axes_;
Vector2D gizmo_scale_anchor_;
// Gizmo on screen object storage
PointGizmo *point_gizmo_[k_gizmo_scale_count];
PointGizmo *anchor_gizmo_;
PolygonGizmo *poly_gizmo_;
ScreenGizmo *rotation_gizmo_;
};
}
#endif // OAK_TRANSFORMDISTORTNODE_H
+22
View File
@@ -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/distort/wave/wavedistortnode.cpp
node/distort/wave/wavedistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,106 @@
/***
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 "wavedistortnode.h"
namespace olive
{
const std::string WaveDistortNode::k_texture_input = "tex_in";
const std::string WaveDistortNode::k_frequency_input = "frequency_in";
const std::string WaveDistortNode::k_intensity_input = "intensity_in";
const std::string WaveDistortNode::k_evolution_input = "evolution_in";
const std::string WaveDistortNode::k_vertical_input = "vertical_in";
#define super Node
WaveDistortNode::WaveDistortNode()
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_frequency_input, NodeValue::k_float, 10);
add_input(k_intensity_input, NodeValue::k_float, 10);
add_input(k_evolution_input, NodeValue::k_float, 0);
add_input(k_vertical_input, NodeValue::k_combo, false);
set_flag(k_video_effect);
set_effect_input(k_texture_input);
}
std::string WaveDistortNode::name() const
{
return "Wave";
}
std::string WaveDistortNode::id() const
{
return "org.olivevideoeditor.Olive.wave";
}
std::vector<Node::CategoryID> WaveDistortNode::category() const
{
return { k_category_distort };
}
std::string WaveDistortNode::description() const
{
return "Distorts an image along a sine wave.";
}
void WaveDistortNode::retranslate()
{
super::retranslate();
set_input_name(k_texture_input, "Input");
set_input_name(k_frequency_input, "Frequency");
set_input_name(k_intensity_input, "Intensity");
set_input_name(k_evolution_input, "Evolution");
set_input_name(k_vertical_input, "Direction");
set_combo_box_strings(k_vertical_input, { "Horizontal", "Vertical" });
}
ShaderCode WaveDistortNode::get_shader_code(const ShaderRequest &request) const
{
(void) request;
return ShaderCode(FileFunctions::read_file_as_string(":/shaders/wave.frag"));
}
void WaveDistortNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// If there's no texture, no need to run an operation
if (TexturePtr texture = value.at(k_texture_input).to_texture()) {
// Only run shader if at least one of flip or flop are selected
if (value.at(k_intensity_input).to_double() != 0.0) {
table->push(NodeValue::k_texture,
Texture::job(texture->params(), ShaderJob(value)),
this);
} else {
// If we're not flipping or flopping just push the texture
table->push(value.at(k_texture_input));
}
}
}
}
@@ -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_WAVEDISTORTNODE_H
#define OAK_WAVEDISTORTNODE_H
#include "node.h"
namespace olive
{
class WaveDistortNode : public Node {
public:
WaveDistortNode();
NODE_DEFAULT_FUNCTIONS(WaveDistortNode)
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;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static const std::string k_texture_input;
static const std::string k_frequency_input;
static const std::string k_intensity_input;
static const std::string k_evolution_input;
static const std::string k_vertical_input;
};
}
#endif // OAK_WAVEDISTORTNODE_H

Some files were not shown because too many files have changed in this diff Show More