refactor(engine): single-lib phase 3 — plugin/timeline/task/codec bridges converted

- oakplugin bridges -> direct (dlsym 28->0; OFX suites untouched)
- oaktimeline bridges -> direct (teststubs slimmed to pure-Rust mocks)
- oaktask bridges -> direct; facade EncodingParamsPOD -> oakcodec alias
- dlsym total 102->24 (all in documented dead/host directions)
- All crates build+test green; dylib exports unchanged
This commit is contained in:
2026-08-10 19:11:25 +08:00
parent 96e57b3705
commit f8540e3892
49 changed files with 5164 additions and 1827 deletions
+57 -12
View File
@@ -415,19 +415,64 @@ encoding-params POD`oakcodec_encoding_params` 等)在 4 个 crate 各有
`oakcodec_decoder_probe` 直接调用,测试的 `is_err()` 断言仍成立
(真实 codec 对不存在文件返回错误句柄)。
### 11.4 未完成(后续步骤
### 11.4 第三轮完成(2026-08-10
- **模块 bridge 其余方向**(步骤 b 剩余):oakplugin `bridge/{node,
render,undo}`28 处 dlsym + 状态化 test-stubs 迁移)、oaktimeline
`bridge/{node,undo,common}`teststubs.rs 52921 行 + 16 个测试文件)、
oaktask `bridge/*`7 文件 + tests/common 的 C ABI 桩)、oakcodec
`bridge/common``OakVideoParams` 类型从桩句柄对齐到真实 CHandle,
波及 frame.rs 等内部类型)。每个方向模式已由 node/render/audio 证明;
工作量集中在测试侧(桩删除 + 迁移 + 类型对齐)。
- **dlsym 删除**(步骤 c):node 20 处、render 4 处(均在死/宿主方向
bridge)、plugin 28 处,在各自 bridge 转换完成后清除。
- **facade 侧后续**`linkage.rs` 注释更新;EncodingParamsPOD 等 POD
统一到 oakcore-rs
本轮完成的方向(每步 `cargo build` + 该 crate `cargo test` 绿):
- **oakplugin `bridge/{node,render,undo}` → 直接调用**28 处 dlsym → 0):
oaknode/oakrender/oakundo 加入依赖;28 个桥函数直接调 `oaknode::ffi`/
`oakrender::ffi`/`oakundo::ffi``Value` 桥接用布局一致的指针 cast
(避免 49 处 `r#type``kind` 改名);`VideoParams` 别名到
`oakrender::ffi::OakRenderVideoParams``test-stubs` 特性保留,桩改为
纯 Rust(无 `#[no_mangle]`,与真实 crate 导出不冲突);`dlsym` 模块
删除。测试迁移:两个"符号缺失"单测改为空/NULL 句柄经真实 crate 的
容错路径(`texture_is_dummy` 空句柄 0、`instance_render` 空 dst 被
插件层 E_INVALID 拒绝、`render_job` 同);OFX suites 层未触碰
- **oaktimeline `bridge/{node,undo,common}` → 直接调用**oaknode/
oakundo/oakcommon 加入依赖;81 个桥函数直接调用。`teststubs.rs`
1658 行)改为纯 Rust mock(去 `#[no_mangle]`),桥函数加
`#[cfg(any(test, feature="test-stubs"))]` mock 变体 / 真实变体,
测试二进制(cfg(test) 对依赖关)经 `--features test-stubs` 解析
`bridge::teststubs`——与真实 crate 导出共存无冲突。修复桥签名漂移:
`oakcommon_xml_reader_read_next_start_element` 2 参(found 标志,
调用点 ffi.rs/tests 同步);`oakundo_stack_push`→真实
`oakundo_undostack_push`。260 测试绿。
- **oaktask `bridge/*` → 直接调用**240 个桥函数):oaknode/oakundo/
oakcommon/oakcodec/oakrender/oaktimeline 加入依赖(oakrender 由可选
转必选,`real-oakrender` 特性改为标记)。镜像类型对齐真实 crate:
`OakCancelAtom`/`OakRenderTicket`/`OakRenderCache`/`OakColorProcessor`
等 → CHandle 别名,`OakCodecEncodingParams`/`OakCodecProxyParams`/
`OakCodecTaskRequest`/`OakRenderVideoTicketParams`/`OakUndoCommandVtable`
→ 真实 crate 类型;`from_chandle`/`to_chandle` 助手删除(恒等)。
测试侧:encoding-params 结构从 `[c_char;N]``[u8;N]``write_cstr`
改 u8`fake_atom` 等 fixtures 仍可用(真实 crate 的 cancel 路径由
task 内部创建真实 atom)。90 测试绿。
- **oakcodec `bridge/{common,render}` 句柄类型对齐**`OakVideoParams`/
`OakAudioParams`/`OakSubtitleParams`/`OakNodeBlock`/`OakRenderTexture`/
`OakCancelAtom`/`OakRenderRenderer`/`OakCodecFrame` → CHandle 别名
(删除其上的 `unsafe impl Send/Sync` 与固有 impl);共享
`oakcore_rs::handle::CHandle` 增补 `PartialEq, Eq`codec 的
`CodecStream` 派生比较)。
- **facade EncodingParamsPOD 统一**(步骤 c 的 POD 部分):facade 的
`EncodingParamsPOD`(41 字段 c_char 镜像)→ 别名
`oakcodec::ffi::encoder::oakcodec_encoding_params`u8);`zeroed()`
`zeroed_encoding_params()` 自由函数;`write_field`/`field_str`
u8`oakcodec_encoder_init`/`oaktask_create_export`/
`oakaudio_manager_start_recording` 三个 encoding-params link-time
extern 全部改直接调用(audio 的 `EncodingParams` 同别名后打通)。
facade bridge 仅剩宿主 `oakcore_audioparams_*` externC++ lib,非
Rust crate)。
### 11.5 未完成(后续步骤)
- **dlsym 残留**node 20、render 4):全在死/宿主方向——node
`bridge/core.rs``oakcore_audioparams_*` 宿主符号)、
`bridge/render.rs`+`bridge/timeline.rs`(死方向,node 测试
`ffi_timeline_test` 使用)、render `bridge/node.rs`copier 死方向,
`copier_test` 使用)。这些是文档化的"保留 dlsym"(死/宿主),转换
需同步迁移对应测试;`linkage.rs` 锚定注释可更新。
- `oakcodec::bridge::render` 的 cancel-atom C 边界(设计 §4.2)保持。
- OFX suites 层保持 C(冻结,未触碰)。
## 10. 风险与回退
+10 -56
View File
@@ -35,74 +35,28 @@
use std::ffi::{c_char, c_int, c_void};
use crate::handle::CHandle;
/// `OakVideoParams` — refcounted video-parameter handle.
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakVideoParams {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version (see [`OAKCODEC_ABI_VERSION`]).
pub abi_version: u32,
}
pub type OakVideoParams = CHandle;
/// `OakAudioParams` — refcounted audio-parameter handle.
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakAudioParams {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version.
pub abi_version: u32,
}
pub type OakAudioParams = CHandle;
/// `OakSubtitleParams` — refcounted subtitle-parameter handle.
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakSubtitleParams {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version.
pub abi_version: u32,
}
pub type OakSubtitleParams = CHandle;
/// `OakNodeBlock` — opaque node-block handle (owned elsewhere; codec
/// only stores and forwards it).
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakNodeBlock {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version.
pub abi_version: u32,
}
pub type OakNodeBlock = CHandle;
// The handle structs are opaque refcounted handles pointing into a C
// library; the boxed objects are independently synchronized there, so
// moving a handle between threads is sound.
unsafe impl Send for OakVideoParams {}
unsafe impl Sync for OakVideoParams {}
unsafe impl Send for OakAudioParams {}
unsafe impl Sync for OakAudioParams {}
unsafe impl Send for OakSubtitleParams {}
unsafe impl Sync for OakSubtitleParams {}
unsafe impl Send for OakNodeBlock {}
unsafe impl Sync for OakNodeBlock {}
extern "C" {
/// `oakcommon_videoparams_init`.
+10 -56
View File
@@ -23,71 +23,25 @@
use std::ffi::{c_char, c_int, c_void};
use crate::handle::CHandle;
/// `OakRenderTexture` — refcounted GPU texture handle.
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakRenderTexture {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version.
pub abi_version: u32,
}
pub type OakRenderTexture = CHandle;
/// `OakCancelAtom` — refcounted cancellation atom handle.
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakCancelAtom {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version.
pub abi_version: u32,
}
pub type OakCancelAtom = CHandle;
/// `OakRenderRenderer` — refcounted display-renderer handle.
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakRenderRenderer {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version.
pub abi_version: u32,
}
pub type OakRenderRenderer = CHandle;
/// `OakCodecFrame` — refcounted CPU-frame handle shared with oakrender.
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct OakCodecFrame {
/// Opaque object pointer.
pub ctx: *mut c_void,
/// Atomically increments the reference count.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Decrements the count, destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version.
pub abi_version: u32,
}
pub type OakCodecFrame = CHandle;
// Refcounted opaque handles; thread-safe in the C library.
unsafe impl Send for OakRenderTexture {}
unsafe impl Sync for OakRenderTexture {}
unsafe impl Send for OakCancelAtom {}
unsafe impl Sync for OakCancelAtom {}
unsafe impl Send for OakRenderRenderer {}
unsafe impl Sync for OakRenderRenderer {}
unsafe impl Send for OakCodecFrame {}
unsafe impl Sync for OakCodecFrame {}
/// `oakrender_video_params` — flattened POD of `olive::VideoParams`
/// passed into oakrender; see `include/render/renderer.h`.
+12
View File
@@ -830,6 +830,9 @@ version = "0.1.0"
dependencies = [
"cc",
"oakcore-rs",
"oaknode",
"oakrender",
"oakundo",
]
[[package]]
@@ -846,15 +849,24 @@ dependencies = [
name = "oaktask"
version = "0.1.0"
dependencies = [
"oakcodec",
"oakcommon",
"oakcore-rs",
"oaknode",
"oakotio",
"oakrender",
"oaktimeline",
"oakundo",
]
[[package]]
name = "oaktimeline"
version = "0.1.0"
dependencies = [
"oakcommon",
"oakcore-rs",
"oaknode",
"oakundo",
]
[[package]]
+4 -19
View File
@@ -66,14 +66,9 @@ pub type SourceClip = oakaudio::ffi::sync::SourceClip;
/// Opaque mirror of the oakaudio recording-params POD
/// (`include/audio/manager.h`). The facade passes the engine's
/// `OakEngineEncodingParams*` through untouched — both are the same C ABI
/// mirror of `olive::EncodingParams`.
#[repr(C)]
pub struct EncodingParams {
_opaque: [u8; 0],
}
/// `oakaudio` recording-params POD — single-lib unification: aliases the
/// oakaudio crate's type (itself the oakcodec `oakcodec_encoding_params`).
pub type EncodingParams = oakaudio::bridge::codec::EncodingParams;
extern "C" {
pub fn oakcore_audioparams_create(sample_rate: c_int, channel_layout: u64, format: c_int) -> *mut c_void;
@@ -188,17 +183,7 @@ pub fn oakaudio_manager_start_recording(
error_buf: *mut c_char,
error_buf_size: c_int,
) -> c_int {
unsafe { oakaudio_start_recording_extern(_self, params, error_buf, error_buf_size) }
}
extern "C" {
#[link_name = "oakaudio_manager_start_recording"]
pub fn oakaudio_start_recording_extern(
_self: CHandle,
params: *const EncodingParams,
error_buf: *mut c_char,
error_buf_size: c_int,
) -> c_int;
unsafe { oakaudio::ffi::manager::oakaudio_manager_start_recording(_self, params, error_buf, error_buf_size) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
+14 -95
View File
@@ -52,97 +52,21 @@ use std::ffi::{c_char, c_int};
use crate::handle::CHandle;
/// `include/codec/encoder.h` — the encoding-params POD, a complete mirror
/// of `olive::EncodingParams`. The facade's `OakEngineEncodingParams`
/// handle is a heap box over exactly this struct, so every engine getter/
/// setter reads/writes a field and `encoder_init`/recording can consume
/// the pointer directly.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct EncodingParamsPOD {
/// Output filename (NUL-terminated).
pub filename: [c_char; 1024],
/// Output container format id.
pub format: c_int,
/// Whether video is enabled (1/0).
pub video_enabled: c_int,
/// Video codec id.
pub video_codec: c_int,
/// Video width in pixels.
pub video_width: c_int,
/// Video height in pixels.
pub video_height: c_int,
/// Frame duration numerator.
pub video_time_base_num: c_int,
/// Frame duration denominator.
pub video_time_base_den: c_int,
/// Delivery pixel format (`OakPixelFormat`).
pub video_pixel_format: c_int,
/// Interlacing mode (`OAKCODEC_INTERLACE_*`).
pub video_interlacing: c_int,
/// Pixel aspect ratio numerator.
pub video_pixel_aspect_num: c_int,
/// Pixel aspect ratio denominator.
pub video_pixel_aspect_den: c_int,
/// Video bit rate in bits per second (0 = codec default).
pub video_bit_rate: i64,
/// Minimum video bit rate.
pub video_min_bit_rate: i64,
/// Maximum video bit rate.
pub video_max_bit_rate: i64,
/// Video buffer size in bytes.
pub video_buffer_size: i64,
/// Encoder threads (0 = auto).
pub video_threads: c_int,
/// Encoded pixel format name ("yuv420p", NUL-terminated).
pub video_pix_fmt: [c_char; 64],
/// Whether video is an image sequence (1/0).
pub video_is_image_sequence: c_int,
/// Scaling method (`OAKCODEC_ENCODING_SCALING_*`).
pub video_scaling_method: c_int,
/// Whether audio is enabled (1/0).
pub audio_enabled: c_int,
/// Audio codec id.
pub audio_codec: c_int,
/// Audio sample rate in Hz.
pub audio_sample_rate: c_int,
/// Audio channel layout mask.
pub audio_channel_layout: u64,
/// Audio sample format (`SampleFormat::Format`).
pub audio_sample_format: c_int,
/// Audio bit rate in bits per second.
pub audio_bit_rate: i64,
/// Whether subtitles are enabled (1/0).
pub subtitles_enabled: c_int,
/// Subtitle codec id.
pub subtitles_codec: c_int,
/// Whether subtitles are a sidecar file (1/0).
pub subtitles_are_sidecar: c_int,
/// Sidecar subtitle format (`ExportFormat::Format`).
pub subtitles_sidecar_format: c_int,
/// Output OCIO colorspace name (empty = reference space).
pub color_transform_output: [c_char; 256],
/// Export length in seconds (rational), numerator.
pub export_length_num: c_int,
/// Export length in seconds (rational), denominator.
pub export_length_den: c_int,
/// Whether a custom export range is set (1/0).
pub has_custom_range: c_int,
/// Custom range in point numerator (seconds).
pub custom_range_in_num: i64,
/// Custom range in point denominator (seconds).
pub custom_range_in_den: i64,
/// Custom range out point numerator (seconds).
pub custom_range_out_num: i64,
/// Custom range out point denominator (seconds).
pub custom_range_out_den: i64,
/// `olive::EncodingParams` — single-lib unification: aliases the oakcodec
/// crate's POD (`oakcodec_encoding_params`, identical `#[repr(C)]` layout;
/// the engine's `OakEngineEncodingParams` handle is a heap box over exactly
/// this struct, so every engine getter/setter reads/writes a field and
/// `encoder_init`/recording can consume the pointer directly).
pub type EncodingParamsPOD = oakcodec::ffi::encoder::oakcodec_encoding_params;
/// Zeroed encoding-params POD (all fields 0 / NUL). The codec crate's
/// struct has no zeroed constructor; this facade helper provides it.
pub fn zeroed_encoding_params() -> EncodingParamsPOD {
// All-C fields: all-zero is a valid value.
unsafe { std::mem::zeroed() }
}
impl EncodingParamsPOD {
/// Zeroed POD: all tracks disabled, format unset.
pub fn zeroed() -> Self {
unsafe { std::mem::zeroed() }
}
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
@@ -298,12 +222,7 @@ pub fn oakcodec_encoding_generate_matrix(
/// Kept as a link-time `extern "C"` declaration against the frozen module
/// C ABI.
pub fn oakcodec_encoder_init(params: *const EncodingParamsPOD) -> CHandle {
unsafe { oakcodec_encoder_init_extern(params) }
}
extern "C" {
#[link_name = "oakcodec_encoder_init"]
pub fn oakcodec_encoder_init_extern(params: *const EncodingParamsPOD) -> CHandle;
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_init(params) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
+4 -13
View File
@@ -256,24 +256,15 @@ pub fn oaktask_create_precache(footage: CHandle, index: c_int, sequence: CHandle
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
/// `oaktask_create_export` — crosses the encoding-params C ABI POD
/// (the facade keeps its own POD mirror). Kept as a link-time
/// `extern "C"` declaration against the frozen module C ABI.
/// `oaktask_create_export` — direct call into the `oaktask` crate
/// (single-lib unification; the encoding-params POD is the shared
/// oakcodec type).
pub fn oaktask_create_export(
viewer: CHandle,
color_manager: CHandle,
params: *const crate::bridge::codec::EncodingParamsPOD,
) -> CHandle {
unsafe { oaktask_create_export_extern(viewer, color_manager, params) }
}
extern "C" {
#[link_name = "oaktask_create_export"]
pub fn oaktask_create_export_extern(
viewer: CHandle,
color_manager: CHandle,
params: *const crate::bridge::codec::EncodingParamsPOD,
) -> CHandle;
unsafe { oaktask::ffi::project::oaktask_create_export(viewer, color_manager, params) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
+7 -15
View File
@@ -38,7 +38,7 @@ use std::collections::HashMap;
use std::ffi::{c_char, c_int, c_void};
use crate::bridge::codec as k;
use crate::bridge::codec::EncodingParamsPOD;
use crate::bridge::codec::{EncodingParamsPOD, zeroed_encoding_params};
use crate::common::OakVideoParamsPod;
use crate::error::{Error, Result};
use crate::handle::{guard, guard_int, string_result};
@@ -59,7 +59,7 @@ struct ParamsBox {
impl ParamsBox {
fn new() -> Self {
let mut pod = EncodingParamsPOD::zeroed();
let mut pod = zeroed_encoding_params();
pod.format = -1; // unset (POD 0 is a valid format, DNxHD)
ParamsBox {
pod,
@@ -89,23 +89,15 @@ unsafe fn params_mut(ptr: *mut OakEngineEncodingParams) -> Result<&'static mut P
}
/// Read a NUL-terminated fixed array field as a `String`.
fn field_str(field: &[c_char]) -> String {
let bytes: Vec<u8> = field
.iter()
.take_while(|c| **c != 0)
.map(|c| *c as u8)
.collect();
fn field_str(field: &[u8]) -> String {
let bytes: Vec<u8> = field.iter().take_while(|c| **c != 0).copied().collect();
String::from_utf8_lossy(&bytes).into_owned()
}
/// Write a string into a fixed array field (truncated, NUL-terminated).
fn write_field(field: &mut [c_char], value: &str) {
fn write_field(field: &mut [u8], value: &str) {
for (i, slot) in field.iter_mut().enumerate() {
*slot = if i < value.len() {
value.as_bytes()[i] as c_char
} else {
0
};
*slot = if i < value.len() { value.as_bytes()[i] } else { 0 };
}
}
@@ -1075,7 +1067,7 @@ pub unsafe extern "C" fn oakengine_encoding_start_audio_recording(
}
let rc = crate::bridge::audio::oakaudio_manager_start_recording(
m,
&p.pod as *const EncodingParamsPOD as *const c_void as *const crate::bridge::audio::EncodingParams,
&p.pod as *const oakaudio::bridge::codec::EncodingParams,
errbuf,
errbuf_size,
);
+3 -3
View File
@@ -43,7 +43,7 @@ use std::ffi::{c_char, c_int, c_void};
use std::sync::{Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::bridge::codec::EncodingParamsPOD;
use crate::bridge::codec::{EncodingParamsPOD, zeroed_encoding_params};
use crate::bridge::node as n;
use crate::bridge::task as t;
use crate::codec::OakEngineEncodingParams;
@@ -622,7 +622,7 @@ pub unsafe extern "C" fn oakengine_task_create_export(
/// them is behaviorally identical to the original for the export task; all
/// other POD fields stay zeroed.
fn export_params_pod(params: *const OakEngineEncodingParams) -> Result<EncodingParamsPOD> {
let mut pod = EncodingParamsPOD::zeroed();
let mut pod = zeroed_encoding_params();
// filename (two-stage; writes NUL-terminated into `buf`)
let mut buf = [0 as c_char; 1024];
@@ -633,7 +633,7 @@ fn export_params_pod(params: *const OakEngineEncodingParams) -> Result<EncodingP
return Err(Error::Invalid);
}
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
unsafe { std::ptr::copy_nonoverlapping(buf.as_ptr(), pod.filename.as_mut_ptr(), len) };
unsafe { std::ptr::copy_nonoverlapping(buf.as_ptr() as *const u8, pod.filename.as_mut_ptr(), len) };
pod.format = unsafe { crate::codec::oakengine_encoding_params_format(params) };
pod.video_enabled = unsafe { crate::codec::oakengine_encoding_params_video_enabled(params) };
+1 -1
View File
@@ -37,7 +37,7 @@ use std::ffi::c_void;
/// empty handles). Structurally identical to every `Oak<Mod><Type>` value
/// handle, so a handle can cross any module boundary by value.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CHandle {
/// Opaque box pointer.
pub ctx: *mut c_void,
+1621 -1
View File
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -18,13 +18,18 @@ panic = "unwind"
[dependencies]
# 共享 ABI 类型(CHandle 等,单库化统一,见 docs/zh/plans/riir/single-lib.md)。
# 模块桥改直接 Rust 调用(单库化,见 docs/zh/plans/riir/single-lib.md);
# 只允许零依赖起步。确需引入的 crate 必须在 README 登记理由。
oakcore-rs = { path = "../../oakcore-rs" }
oakundo = { path = "../../undo/rust" }
oaknode = { path = "../../node/rust" }
oakrender = { path = "../../render/rust" }
[features]
# oakrender 测试桩(库内 no_mangle 符号 + 状态访问器):cargo test
# --features test-stubs 时桥直连桩,像素路径可在无 liboakrender 的
# 环境下跑通;真实链路仍走 dlsym 解析。
# 测试桩(库内状态访问器):cargo test --features test-stubs 时桥直连
# 桩,像素路径可在无真实 GL/GPU 的环境下跑通。桩的 C ABI 符号不再
# no_mangle(真实 oaknode/oakrender/oakundo 已作为 path 依赖链接,
# 避免重复符号);真实链路走直接 Rust 调用。
test-stubs = []
[build-dependencies]
+10 -53
View File
@@ -14,64 +14,21 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! bridgeoak 其余模块的 C ABI 导入。
//! bridgeoak 其余模块的 C ABI 调用——直接 Rust 调用(单库化,见
//! `docs/zh/plans/riir/single-lib.md`)。
//!
//! 每个子模块(node/render/undo只声明对应公共头的子集并包一层
//! 极薄的 safe 封装。链接在模块 dylib 装配时完成(liboaknode/
//! liboakrender/liboakundo
//! 每个子模块(node/render/undo调用对应 crate 的 `ffi` 导出(同名
//! `#[no_mangle]` 符号仍从 dylib 导出供外部 C ABI 使用;内部调用绕过
//! 它们)。句柄全部是共享的 [`crate::handle::CHandle`]
//!
//! ## 双态实现(所有子模块统一)
//!
//! - 默认:`dlsym(RTLD_DEFAULT)` 运行时解析(本模块被 force_load
//! 进宿主进程,符号在全局作用域;cargo test 无这些库时符号缺失
//! → 可解释错误,测试可走通至桥边界);
//! - `--features test-stubs`:库内 no_mangle 桩 + 状态访问器
//! (各子模块的 [`node::stub`]/[`render::stub`]/[`undo::stub`])——
//! 桥路径在 cargo test 里全链路可跑。两种形态的调用面完全一致。
//!
//! [`dlsym`] 是三个子模块共享的解析实现(macOS/Linux 的 RTLD_DEFAULT
//! 取值不同;函数指针按调用方签名转换)。
//! - 默认:直接调用 oaknode/oakrender/oakundo 的 ffi
//! - `--features test-stubs`:桥走库内状态桩(各子模块的
//! [`node::stub`]/[`render::stub`]/[`undo::stub`],纯 Rust、无
//! `#[no_mangle]`,与真实 crate 的导出不冲突)——像素路径可在无
//! 真实 GL/GPU 的环境下跑通。两种形态的调用面完全一致。
pub mod node;
pub mod render;
pub mod undo;
/// 共享的 dlsym 运行时解析(`#[cfg(not(feature = "test-stubs"))]`)。
#[cfg(not(feature = "test-stubs"))]
pub(crate) mod dlsym {
use std::ffi::{c_char, c_void};
/// RTLD_DEFAULTmacOS: -2Linux: 0)。
#[cfg(target_os = "macos")]
pub(crate) const RTLD_DEFAULT: *mut c_void = -2isize as *mut c_void;
#[cfg(target_os = "linux")]
pub(crate) const RTLD_DEFAULT: *mut c_void = 0isize as *mut c_void;
extern "C" {
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
}
/// 解析全局作用域符号;缺失返回 None。
pub(crate) fn resolve(name: &str) -> Option<*mut c_void> {
let c = std::ffi::CString::new(name).ok()?;
let p = unsafe { dlsym(RTLD_DEFAULT, c.as_ptr()) };
if p.is_null() {
None
} else {
Some(p)
}
}
/// 解析并按签名调用;符号缺失返回 None。
///
/// # Safety
/// 调用方保证 `T` 与符号的真实函数类型一致。
pub(crate) fn call<T, R>(name: &str, f: impl FnOnce(T) -> R) -> Option<R>
where
T: Copy,
{
let p = resolve(name)?;
let f_ptr: T = unsafe { std::mem::transmute_copy(&p) };
Some(f(f_ptr))
}
}
+26 -21
View File
@@ -28,10 +28,11 @@
//!
//! ## 双态实现(同 [`crate::bridge::render`]
//!
//! - 默认:`dlsym(RTLD_DEFAULT)` 运行时解析(cargo test 无 liboaknode
//! 时符号缺失 → 错误码/空句柄,桥路径可解释地失败);
//! - `--features test-stubs`:库内桩([`stub`])——节点值、undo 命令
//! 全链路可在 cargo test 跑通。两种形态的调用面完全一致。
//! - 默认:直接 Rust 调用 oaknode 的 `ffi`(单库化,见
//! `docs/zh/plans/riir/single-lib.md`);
//! - `--features test-stubs`:库内状态桩([`stub`],纯 Rust、无
//! `#[no_mangle]`,与真实 oaknode 的导出不冲突)——节点值、undo
//! 命令全链路可在 cargo test 跑通。两种形态的调用面完全一致。
use std::ffi::c_char;
@@ -177,9 +178,7 @@ pub(crate) unsafe fn node_from_identity(id: usize) -> NodeHandle {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(usize) -> NodeHandle;
crate::bridge::dlsym::call::<F, NodeHandle>("oaknode_node_from_identity", |f| unsafe { f(id) })
.unwrap_or_else(CHandle::null)
unsafe { oaknode::ffi::node::oaknode_node_from_identity(id) }
}
}
@@ -201,11 +200,14 @@ pub(crate) unsafe fn set_input_undoable(
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(NodeHandle, *const c_char, *const Value, *mut CommandHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oaknode_node_set_input_undoable", |f| {
unsafe { f(node, input, value, out) }
})
.unwrap_or(-30001)
unsafe {
oaknode::ffi::node::oaknode_node_set_input_undoable(
node,
input,
value as *const oaknode::value::OakNodeValue,
out,
)
}
}
}
@@ -226,11 +228,14 @@ pub(crate) unsafe fn set_input_string_undoable(
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(NodeHandle, *const c_char, *const c_char, *mut CommandHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oaknode_node_set_input_string_undoable", |f| {
unsafe { f(node, input, value, out) }
})
.unwrap_or(-30001)
unsafe {
oaknode::ffi::node::oaknode_node_set_input_string_undoable(
node,
input,
value,
out,
)
}
}
}
@@ -389,16 +394,16 @@ pub mod stub {
}
}
// ---- 默认模式单测(无桩;dlsym 缺失的可解释失败路径)----------------------
// ---- 默认模式单测(无桩;空/NULL 句柄经真实 crate 的可解释失败路径)----------------------
#[cfg(all(test, not(feature = "test-stubs")))]
mod tests {
use super::*;
/// cargo test 无 liboaknode:符号缺失 → 空句柄/负错误码;空句柄
/// 与空指针参数被拒。
/// 空/NULL 句柄经真实 oaknode 容错:未登记身份 → 空句柄;空句柄
/// 与空指针参数被拒OAKNODE_E_INVALID
#[test]
fn dlsym_missing_error_paths() {
fn empty_handle_error_paths() {
let mut cmd = CommandHandle::null();
unsafe {
assert!(node_from_identity(0xDEAD).is_null(), "未登记身份 → 空句柄");
+48 -139
View File
@@ -26,45 +26,19 @@
//!
//! ## 双态实现
//!
//! - 默认:`dlsym(RTLD_DEFAULT)` 运行时解析(本模块被 force_load
//! 进宿主进程,符号在全局作用域;cargo test 无 liboakrender 时
//! 符号缺失 → 可解释错误,测试可走通至渲染边界);
//! - `--features test-stubs`:库内 no_mangle 桩 + 状态访问器
//! [`stub`])——像素路径在 cargo test 里全链路可跑。
//! 两种形态的调用面`texture_get_frame` 等)完全一致。
//! - 默认:直接 Rust 调用 oakrender 的 `ffi`(单库化,见
//! `docs/zh/plans/riir/single-lib.md`);
//! - `--features test-stubs`:库内状态桩 + 状态访问器([`stub`],纯
//! Rust、无 `#[no_mangle]`,与真实 oakrender 的导出不冲突)——像素
//! 路径在 cargo test 里全链路可跑。两种形态的调用面
//! `texture_get_frame` 等)完全一致。
use std::ffi::c_void;
/// oakrender 视频参数(include/render/renderer.h:78,字段逐字一致)。
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VideoParams {
/// 宽。
pub width: i32,
/// 高。
pub height: i32,
/// 帧时长分子(如 1001/30000 秒)。
pub time_base_num: i32,
/// 帧时长分母。
pub time_base_den: i32,
/// `olive::PixelFormat::Format`invalid=-1, u8=0, u10=1, u16=2,
/// f16=3, f32=4)。
pub format: i32,
/// 像素比分子。
pub pixel_aspect_num: i32,
/// 像素比分母。
pub pixel_aspect_den: i32,
/// `olive::VideoParams::Interlacing`。
pub interlacing: i32,
/// `olive::VideoParams::ColorRange`。
pub color_range: i32,
/// 预览分辨率除数(1 = 全分辨率)。
pub divider: i32,
/// `olive::VideoParams::Type`0 = video)。
pub video_type: i32,
/// 预乘 alpha0/1)。
pub premultiplied_alpha: i32,
}
/// `oakrender_video_params` POD — single-lib unification: aliases the
/// oakrender crate's struct (identical layout; include/render/renderer.h:78).
pub type VideoParams = oakrender::ffi::OakRenderVideoParams;
/// olive::PixelFormat::Format 的 f32 值。
pub const PIXEL_FORMAT_F32: i32 = 4;
@@ -90,11 +64,7 @@ pub(crate) unsafe fn texture_get_frame(texture: TextureHandle, out: *mut FrameHa
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(TextureHandle, *mut FrameHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_texture_get_frame", |f| {
unsafe { f(texture, out) }
})
.unwrap_or(-1)
unsafe { oakrender::ffi::renderer::oakrender_display_texture_get_frame(texture, out) }
}
}
@@ -106,9 +76,7 @@ pub(crate) unsafe fn texture_is_dummy(texture: TextureHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(TextureHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_texture_is_dummy", |f| unsafe { f(texture) })
.unwrap_or(1)
unsafe { oakrender::ffi::renderer::oakrender_display_texture_is_dummy(texture) }
}
}
@@ -120,9 +88,7 @@ pub(crate) unsafe fn frame_width(frame: FrameHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(FrameHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_codec_frame_width", |f| unsafe { f(frame) })
.unwrap_or(0)
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_width(frame) }
}
}
@@ -134,9 +100,7 @@ pub(crate) unsafe fn frame_height(frame: FrameHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(FrameHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_codec_frame_height", |f| unsafe { f(frame) })
.unwrap_or(0)
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_height(frame) }
}
}
@@ -148,9 +112,7 @@ pub(crate) unsafe fn frame_data(frame: FrameHandle) -> *mut c_void {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(FrameHandle) -> *mut c_void;
crate::bridge::dlsym::call::<F, *mut c_void>("oakrender_codec_frame_data", |f| unsafe { f(frame) })
.unwrap_or(std::ptr::null_mut())
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_data(frame) }
}
}
@@ -162,9 +124,7 @@ pub(crate) unsafe fn frame_get_params(frame: FrameHandle, out: *mut VideoParams)
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(FrameHandle, *mut VideoParams) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_codec_frame_get_params", |f| unsafe { f(frame, out) })
.unwrap_or(-1)
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_get_params(frame, out) }
}
}
@@ -176,9 +136,7 @@ pub(crate) unsafe fn frame_allocate(frame: FrameHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(FrameHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_codec_frame_allocate", |f| unsafe { f(frame) })
.unwrap_or(-1)
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_allocate(frame) }
}
}
@@ -190,13 +148,7 @@ pub(crate) unsafe fn frame_free(frame: *mut FrameHandle) {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(*mut FrameHandle);
if let Some(f) = crate::bridge::dlsym::call::<F, ()>("oakrender_codec_frame_free", |f| {
unsafe { f(frame) }
}) {
// 调用已完成;返回值忽略。
let _ = f;
}
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_free(frame) }
}
}
@@ -211,11 +163,7 @@ pub(crate) unsafe fn frame_linesize_bytes(frame: FrameHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(FrameHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_codec_frame_linesize_bytes", |f| {
unsafe { f(frame) }
})
.unwrap_or(0)
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_linesize_bytes(frame) }
}
}
@@ -231,11 +179,7 @@ pub(crate) unsafe fn renderer_create_dynamic(backend: *const std::ffi::c_char) -
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(*const std::ffi::c_char) -> RendererHandle;
crate::bridge::dlsym::call::<F, RendererHandle>("oakrender_display_renderer_create_dynamic", |f| {
unsafe { f(backend) }
})
.unwrap_or_else(RendererHandle::null)
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_create_dynamic(backend) }
}
}
@@ -252,11 +196,7 @@ pub(crate) unsafe fn renderer_init(
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(RendererHandle, *mut std::ffi::c_void) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_renderer_init", |f| {
unsafe { f(renderer, gl_context) }
})
.unwrap_or(-1)
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_init(renderer, gl_context) }
}
}
@@ -269,11 +209,7 @@ pub(crate) unsafe fn renderer_is_open_gl(renderer: RendererHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(RendererHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_renderer_is_open_gl", |f| {
unsafe { f(renderer) }
})
.unwrap_or(0)
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_is_open_gl(renderer) }
}
}
@@ -287,12 +223,7 @@ pub(crate) unsafe fn renderer_destroy(renderer: *mut RendererHandle) {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(*mut RendererHandle);
if let Some(f) = crate::bridge::dlsym::call::<F, ()>("oakrender_display_renderer_destroy", |f| {
unsafe { f(renderer) }
}) {
let _ = f;
}
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_destroy(renderer) }
}
}
@@ -315,11 +246,11 @@ pub(crate) unsafe fn texture_create(
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(RendererHandle, *const VideoParams, *const std::ffi::c_void, i32) -> TextureHandle;
crate::bridge::dlsym::call::<F, TextureHandle>("oakrender_display_texture_create", |f| {
unsafe { f(renderer, params, pixels, linesize) }
})
.unwrap_or_else(TextureHandle::null)
unsafe {
oakrender::ffi::renderer::oakrender_display_texture_create(
renderer, params, pixels, linesize,
)
}
}
}
@@ -332,9 +263,7 @@ pub(crate) unsafe fn texture_id(texture: TextureHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(TextureHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_texture_id", |f| unsafe { f(texture) })
.unwrap_or(0)
unsafe { oakrender::ffi::renderer::oakrender_display_texture_id(texture) }
}
}
@@ -346,11 +275,7 @@ pub(crate) unsafe fn texture_get_params(texture: TextureHandle, out: *mut VideoP
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(TextureHandle, *mut VideoParams) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_texture_get_params", |f| {
unsafe { f(texture, out) }
})
.unwrap_or(-1)
unsafe { oakrender::ffi::renderer::oakrender_display_texture_get_params(texture, out) }
}
}
@@ -368,11 +293,7 @@ pub(crate) unsafe fn texture_download(
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(TextureHandle, *mut std::ffi::c_void, i32) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_texture_download", |f| {
unsafe { f(texture, pixels, linesize) }
})
.unwrap_or(-1)
unsafe { oakrender::ffi::renderer::oakrender_display_texture_download(texture, pixels, linesize) }
}
}
@@ -385,11 +306,7 @@ pub(crate) unsafe fn texture_retain(texture: TextureHandle) -> TextureHandle {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(TextureHandle) -> TextureHandle;
crate::bridge::dlsym::call::<F, TextureHandle>("oakrender_display_texture_retain", |f| {
unsafe { f(texture) }
})
.unwrap_or_else(TextureHandle::null)
unsafe { oakrender::ffi::renderer::oakrender_display_texture_retain(texture) }
}
}
@@ -402,12 +319,7 @@ pub(crate) unsafe fn texture_free(texture: *mut TextureHandle) {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(*mut TextureHandle);
if let Some(f) = crate::bridge::dlsym::call::<F, ()>("oakrender_display_texture_free", |f| {
unsafe { f(texture) }
}) {
let _ = f;
}
unsafe { oakrender::ffi::renderer::oakrender_display_texture_free(texture) }
}
}
@@ -425,11 +337,7 @@ pub(crate) unsafe fn texture_upload(
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(TextureHandle, *const std::ffi::c_void, i32) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_texture_upload", |f| {
unsafe { f(texture, pixels, linesize) }
})
.unwrap_or(-1)
unsafe { oakrender::ffi::renderer::oakrender_display_texture_upload(texture, pixels, linesize) }
}
}
@@ -449,11 +357,11 @@ pub(crate) unsafe fn renderer_download_from_texture(
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(RendererHandle, i32, *const VideoParams, *mut std::ffi::c_void, i32) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakrender_display_renderer_download_from_texture", |f| {
unsafe { f(renderer, texture_id, params, dst, linesize) }
})
.unwrap_or(-1)
unsafe {
oakrender::ffi::renderer::oakrender_display_renderer_download_from_texture(
renderer, texture_id, params, dst, linesize,
)
}
}
}
@@ -949,26 +857,27 @@ pub mod stub {
}
}
// ---- 默认模式单测(无桩;dlsym 缺失的可解释失败路径)----------------------
// ---- 默认模式单测(无桩;空/NULL 句柄经真实 crate 的可解释失败路径)----------------------
#[cfg(all(test, not(feature = "test-stubs")))]
mod tests {
use super::*;
/// cargo test 无 liboakrender:符号缺失 → 明确错误码/空指针/占位
/// 判定(1),全部生命周期函数对空句柄容错不崩
/// 空/NULL 句柄经真实 oakrender 全部容错不崩:返回
/// `OAKRENDER_E_INVALID`-70001)或空值
#[test]
fn dlsym_missing_error_paths() {
fn empty_handle_error_paths() {
let mut frame = FrameHandle::null();
let tex = TextureHandle::null();
unsafe {
assert_eq!(texture_get_frame(tex, &mut frame), -1);
assert_eq!(texture_is_dummy(tex), 1, "符号缺失 → 视为占位");
assert_eq!(texture_get_frame(tex, &mut frame), -70001);
// 空句柄不是占位纹理:真实 oakrender 返回 0。
assert_eq!(texture_is_dummy(tex), 0);
assert_eq!(frame_width(frame), 0);
assert_eq!(frame_height(frame), 0);
assert!(frame_data(frame).is_null());
assert_eq!(frame_get_params(frame, &mut VideoParams::default()), -1);
assert_eq!(frame_allocate(frame), -1);
assert_eq!(frame_get_params(frame, &mut VideoParams::default()), -70001);
assert_eq!(frame_allocate(frame), -70001);
frame_free(&mut frame); // 空句柄 no-op
frame_free(std::ptr::null_mut()); // NULL no-op
}
+15 -26
View File
@@ -27,8 +27,10 @@
//!
//! ## 双态实现(同 [`crate::bridge::render`]
//!
//! - 默认:`dlsym(RTLD_DEFAULT)` 运行时解析;
//! - `--features test-stubs`:库内桩([`stub`])——命令表 + undo/redo
//! - 默认:直接 Rust 调用 oakundo 的 `ffi`(单库化,见
//! `docs/zh/plans/riir/single-lib.md`);
//! - `--features test-stubs`:库内状态桩([`stub`],纯 Rust、无
//! `#[no_mangle]`,与真实 oakundo 的导出不冲突)——命令表 + undo/redo
//! 语义在 cargo test 里全链路可跑。两种形态的调用面完全一致。
use crate::handle::CHandle;
@@ -46,9 +48,7 @@ pub(crate) unsafe fn command_init_multi() -> CommandHandle {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn() -> CommandHandle;
crate::bridge::dlsym::call::<F, CommandHandle>("oakundo_command_init_multi", |f| unsafe { f() })
.unwrap_or_else(CHandle::null)
unsafe { oakundo::ffi::command::oakundo_command_init_multi() }
}
}
@@ -60,9 +60,7 @@ pub(crate) unsafe fn command_redo_now(command: CommandHandle) -> i32 {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(CommandHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakundo_command_redo_now", |f| unsafe { f(command) })
.unwrap_or(-40001)
unsafe { oakundo::ffi::command::oakundo_command_redo_now(command) }
}
}
@@ -74,11 +72,7 @@ pub(crate) unsafe fn command_multi_add_child(multi: CommandHandle, child: Comman
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(CommandHandle, CommandHandle) -> i32;
crate::bridge::dlsym::call::<F, i32>("oakundo_command_multi_add_child", |f| unsafe {
f(multi, child)
})
.unwrap_or(-40001)
unsafe { oakundo::ffi::command::oakundo_command_multi_add_child(multi, child) }
}
}
@@ -90,12 +84,7 @@ pub(crate) unsafe fn command_free(command: *mut CommandHandle) {
}
#[cfg(not(feature = "test-stubs"))]
{
type F = unsafe fn(*mut CommandHandle);
if let Some(f) = crate::bridge::dlsym::call::<F, ()>("oakundo_command_free", |f| {
unsafe { f(command) }
}) {
let _ = f;
}
unsafe { oakundo::ffi::command::oakundo_command_free(command) }
}
}
@@ -364,23 +353,23 @@ pub mod stub {
}
}
// ---- 默认模式单测(无桩;dlsym 缺失的可解释失败路径)----------------------
// ---- 默认模式单测(无桩;空/NULL 句柄经真实 crate 的可解释失败路径)----------------------
#[cfg(all(test, not(feature = "test-stubs")))]
mod tests {
use super::*;
/// cargo test 无 liboakundo:符号缺失 → 空句柄/负错误码,命令
/// 生命周期函数对空句柄/NULL 容错不崩
/// 空/NULL 句柄经真实 oakundo 全部容错不崩:`init_multi` 产生
/// 真实命令,空句柄操作返回 `OAKUNDO_E_INVALID`-20001
#[test]
fn dlsym_missing_error_paths() {
fn empty_handle_error_paths() {
unsafe {
let mut m = command_init_multi();
assert!(m.is_null(), "无 liboakundo 时 init_multi 应返回空句柄");
assert_eq!(command_redo_now(CommandHandle::null()), -40001);
assert!(!m.is_null(), "init_multi 应产生真实命令句柄");
assert_eq!(command_redo_now(CommandHandle::null()), -20001);
assert_eq!(
command_multi_add_child(CommandHandle::null(), CommandHandle::null()),
-40001
-20001
);
command_free(&mut m); // 空句柄 no-op
command_free(std::ptr::null_mut()); // NULL no-op
+5 -3
View File
@@ -208,7 +208,9 @@ fn instance_render_one_frame() {
});
}
/// render 无桩(默认构建):桥符号缺失 → 明确失败码而非崩溃。
/// render 默认构建(真实 oakrender 链接):NULL 输出纹理在触达
/// oakrender 之前被插件层校验拒绝(E_INVALID),不崩溃。真实纹理
/// 路径需要 GL/GPU,属集成测试范畴。
#[cfg(not(feature = "test-stubs"))]
#[test]
fn instance_render_one_frame() {
@@ -217,8 +219,8 @@ fn instance_render_one_frame() {
if h.is_null() {
return;
}
let dst = fake_texture(0xA1);
assert_eq!(unsafe { oakplugin_instance_render(h, dst, CHandle::null(), 0.0) }, -90003);
let dst = CHandle::null();
assert_eq!(unsafe { oakplugin_instance_render(h, dst, CHandle::null(), 0.0) }, -90001);
unsafe { oakplugin_instance_free(&mut h) };
});
}
+3 -2
View File
@@ -207,11 +207,12 @@ fn render_job_graceful_without_bridge() {
if h.is_null() {
return;
}
let dst = fake_texture(0xA1);
// NULL 输出纹理在触达 oakrender 前被插件层校验拒绝(E_INVALID)。
let dst = CHandle::null();
let r = unsafe {
oakplugin_instance_render_job(h, dst, 0.0, 0, 0, std::ptr::null(), CHandle::null(), std::ptr::null(), 0, std::ptr::null(), 0, CHandle::null())
};
assert_eq!(r, -90003, "无 liboakrender 时输出纹理无 CPU 帧 → E_FAILED");
assert_eq!(r, -90001, "NULL 输出纹理 → E_INVALID(不崩溃)");
unsafe { oakplugin_instance_free(&mut h) };
});
}
+215 -3
View File
@@ -8,6 +8,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.6"
@@ -38,6 +47,24 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags 2.13.1",
"cexpr",
"clang-sys",
"itertools",
"proc-macro2",
"quote",
"regex",
"rustc-hash 2.1.3",
"shlex 1.3.0",
"syn 2.0.119",
]
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -113,7 +140,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
dependencies = [
"find-msvc-tools",
"shlex",
"shlex 2.0.1",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
@@ -128,6 +164,17 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
[[package]]
name = "clang-sys"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "cmake"
version = "0.1.58"
@@ -199,6 +246,12 @@ dependencies = [
"litrs",
]
[[package]]
name = "either"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -211,6 +264,31 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
[[package]]
name = "ffmpeg-next"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6380599799e175191eb7ffe82c97f36a2a90a36cbc54c738a903e5287d7f516a"
dependencies = [
"bitflags 2.13.1",
"ffmpeg-sys-next",
"libc",
]
[[package]]
name = "ffmpeg-sys-next"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b939bf79dd5949412a4b81cfe21a07f48ea21b47fcbb5f57816c8c2de5ae30b"
dependencies = [
"bindgen",
"cc",
"libc",
"num_cpus",
"pkg-config",
"vcpkg",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.10"
@@ -295,6 +373,12 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "glob"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
[[package]]
name = "glow"
version = "0.16.0"
@@ -400,6 +484,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "hexf-parse"
version = "0.2.1"
@@ -429,6 +519,15 @@ dependencies = [
"hashbrown 0.17.1",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -564,6 +663,12 @@ dependencies = [
"paste",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -602,7 +707,7 @@ dependencies = [
"log",
"num-traits",
"once_cell",
"rustc-hash",
"rustc-hash 1.1.0",
"spirv",
"strum",
"thiserror 2.0.20",
@@ -618,6 +723,16 @@ dependencies = [
"jni-sys 0.3.1",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -628,6 +743,24 @@ dependencies = [
"libm",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "oakcodec"
version = "0.1.0"
dependencies = [
"ffmpeg-next",
"oakcore-rs",
]
[[package]]
name = "oakcommon"
version = "0.1.0"
@@ -643,6 +776,16 @@ dependencies = [
name = "oakcore-rs"
version = "0.1.0"
[[package]]
name = "oaknode"
version = "0.1.0"
dependencies = [
"oakcodec",
"oakcommon",
"oakcore-rs",
"oakundo",
]
[[package]]
name = "oakotio"
version = "0.1.0"
@@ -667,9 +810,31 @@ dependencies = [
name = "oaktask"
version = "0.1.0"
dependencies = [
"oakcodec",
"oakcommon",
"oakcore-rs",
"oaknode",
"oakotio",
"oakrender",
"oaktimeline",
"oakundo",
]
[[package]]
name = "oaktimeline"
version = "0.1.0"
dependencies = [
"oakcommon",
"oakcore-rs",
"oaknode",
"oakundo",
]
[[package]]
name = "oakundo"
version = "0.1.0"
dependencies = [
"oakcore-rs",
]
[[package]]
@@ -835,6 +1000,35 @@ dependencies = [
"bitflags 2.13.1",
]
[[package]]
name = "regex"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "renderdoc-sys"
version = "1.1.0"
@@ -847,6 +1041,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustversion"
version = "1.0.23"
@@ -903,6 +1103,12 @@ dependencies = [
"zmij",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "shlex"
version = "2.0.1"
@@ -1070,6 +1276,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -1196,7 +1408,7 @@ dependencies = [
"portable-atomic",
"profiling",
"raw-window-handle",
"rustc-hash",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 2.0.20",
"wgpu-core-deps-apple",
+11 -6
View File
@@ -21,7 +21,7 @@ crate-type = ["staticlib", "rlib"]
# `--test`) also compiles the stub-based tests, which cannot coexist with
# the real oakrender exports in one binary (duplicate `#[no_mangle]`
# symbols), so always pass the `--test` filter when the feature is on.
real-oakrender = ["dep:oakrender"]
real-oakrender = []
[profile.release]
# FFI discipline: panics must be catchable at every exported entry.
@@ -29,6 +29,11 @@ panic = "unwind"
[dependencies]
oakcore-rs = { path = "../../oakcore-rs" }
oakundo = { path = "../../undo/rust" }
oakcommon = { path = "../../common/rust" }
oakcodec = { path = "../../codec/rust" }
oaknode = { path = "../../node/rust" }
oaktimeline = { path = "../../timeline/rust" }
# Pure-Rust OpenTimelineIO JSON binding (serde model of the OTIO JSON format
# covering exactly the object graph the C++ loadotio/saveotio tasks use).
@@ -36,8 +41,8 @@ oakcore-rs = { path = "../../oakcore-rs" }
# LoadOTIOTask/SaveOTIOTask parse/serialize through this crate.
oakotio = { path = "../../bindings/oakotio" }
# Optional: only built when the `real-oakrender` feature is on, which links
# the real oakrender crate into the integration-test binary. The library
# itself never references it (all render access goes through the C ABI in
# `bridge/render.rs`); plain `cargo test`/`cargo build` do not compile it.
oakrender = { path = "../../render/rust", optional = true }
# oakrender is a regular path dependency (single-lib: bridge/render.rs calls
# its ffi directly). The `real-oakrender` marker feature keeps the
# render-real integration test's cargo invocation working (it links the
# real oakrender — now always — and skips the ticket stubs).
oakrender = { path = "../../render/rust" }
+150 -183
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oakcodec C ABI imports. The task module reaches the codec side through
//! `include/codec/task.h` (the task submitter), `include/codec/decoder.h`
//! (conform/proxy work) and `include/codec/encoder.h` (export). Signatures
@@ -36,27 +52,8 @@ pub const OAKCODEC_E_INVALID: c_int = -50001;
pub const OAKCODEC_E_FAILED: c_int = -50003;
/// Mirror of `OakCodecTaskRequest` (`include/codec/task.h`).
#[repr(C)]
pub struct OakCodecTaskRequest {
/// `OakCodecTaskKind` as int.
pub kind: c_int,
/// Source media filename.
pub input_filename: *const c_char,
/// Final destination path.
pub output_filename: *const c_char,
/// Stream inside the source media.
pub stream_index: c_int,
/// conform: target sample rate.
pub sample_rate: c_int,
/// conform: target channel layout mask.
pub channel_layout: u64,
/// conform: target sample format (enum as int).
pub sample_format: c_int,
/// proxy: target width, 0 = unspecified/divider.
pub proxy_width: c_int,
/// proxy: target height, 0 = unspecified/divider.
pub proxy_height: c_int,
}
pub type OakCodecTaskRequest = oakcodec::task::OakCodecTaskRequest;
/// `oakcodec_task_submit_fn` callback typedef.
pub type OakCodecTaskSubmitFn =
@@ -72,147 +69,74 @@ pub type OakEncoder = CHandle;
pub type OakDecoder = CHandle;
/// Mirror of `OakCodecProxyParams` (`include/codec/proxy.h`).
#[repr(C)]
pub struct OakCodecProxyParams {
/// Target width.
pub width: c_int,
/// Target height.
pub height: c_int,
/// Resolution divider.
pub divider: c_int,
/// Proxy format version.
pub version: c_int,
/// CRF for the proxy encode.
pub crf: c_int,
/// Whether audio is included.
pub include_audio: c_int,
/// Output container extension.
pub extension: [c_char; 32],
/// ffmpeg preset name.
pub preset: [c_char; 32],
}
pub type OakCodecProxyParams = oakcodec::ffi::proxy::oakcodec_proxy_params;
/// Mirror of `oakcodec_encoding_params` (`include/codec/encoder.h`) — the
/// export task fills this to open the encoder. Layout mirrors the header
/// verbatim (a zeroed struct = all tracks disabled).
#[repr(C)]
pub struct OakCodecEncodingParams {
/// Output filename.
pub filename: [c_char; 1024],
/// Export format id (`olive::ExportFormat::Format`).
pub format: c_int,
/// Whether video is exported.
pub video_enabled: c_int,
/// Video codec id.
pub video_codec: c_int,
/// Output width.
pub video_width: c_int,
/// Output height.
pub video_height: c_int,
/// Frame duration rational numerator.
pub video_time_base_num: c_int,
/// Frame duration rational denominator.
pub video_time_base_den: c_int,
/// Delivery pixel format (`OakPixelFormat` as int).
pub video_pixel_format: c_int,
/// Interlacing (`OAKCODEC_INTERLACE_*`).
pub video_interlacing: c_int,
/// Pixel aspect ratio numerator.
pub video_pixel_aspect_num: c_int,
/// Pixel aspect ratio denominator.
pub video_pixel_aspect_den: c_int,
/// Video bit rate (bit/s, 0 = default).
pub video_bit_rate: i64,
/// Minimum video bit rate.
pub video_min_bit_rate: i64,
/// Maximum video bit rate.
pub video_max_bit_rate: i64,
/// Video buffer size (bytes).
pub video_buffer_size: i64,
/// Video threads (0 = auto).
pub video_threads: c_int,
/// Encoded pixel format name ("yuv420p").
pub video_pix_fmt: [c_char; 64],
/// Whether the output is an image sequence.
pub video_is_image_sequence: c_int,
/// Scaling method (`OAKCODEC_ENCODING_SCALING_*`).
pub video_scaling_method: c_int,
/// Whether audio is exported.
pub audio_enabled: c_int,
/// Audio codec id.
pub audio_codec: c_int,
/// Audio sample rate.
pub audio_sample_rate: c_int,
/// Audio channel layout mask.
pub audio_channel_layout: u64,
/// Audio sample format (`olive::core::SampleFormat::Format` as int).
pub audio_sample_format: c_int,
/// Audio bit rate.
pub audio_bit_rate: i64,
/// Whether subtitles are exported.
pub subtitles_enabled: c_int,
/// Subtitle codec id.
pub subtitles_codec: c_int,
/// Whether subtitles go to a sidecar file.
pub subtitles_are_sidecar: c_int,
/// Sidecar subtitle format.
pub subtitles_sidecar_format: c_int,
/// Output OCIO colorspace name (empty = reference space).
pub color_transform_output: [c_char; 256],
/// Export length in seconds (rational).
pub export_length_num: c_int,
/// Export length denominator.
pub export_length_den: c_int,
/// Whether a custom export range is in effect.
pub has_custom_range: c_int,
/// Custom range in (seconds, rational).
pub custom_range_in_num: i64,
/// Custom range in denominator.
pub custom_range_in_den: i64,
/// Custom range out numerator.
pub custom_range_out_num: i64,
/// Custom range out denominator.
pub custom_range_out_den: i64,
}
pub type OakCodecEncodingParams = oakcodec::ffi::encoder::oakcodec_encoding_params;
extern "C" {
/// `oakcodec_set_task_submit_cb` — install the task submitter (`None`
/// unregisters, matching the header's `cb == NULL`).
pub fn oakcodec_set_task_submit_cb(
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_set_task_submit_cb(
cb: Option<OakCodecTaskSubmitFn>,
userdata: *mut c_void,
);
/// `oakcodec_task_submit_is_registered`.
pub fn oakcodec_task_submit_is_registered() -> c_int;
) {
unsafe { oakcodec::ffi::task::oakcodec_set_task_submit_cb(cb, userdata) }
}
/// `oakcodec_decoder_init`.
pub fn oakcodec_decoder_init() -> OakDecoder;
/// `oakcodec_decoder_free`.
pub fn oakcodec_decoder_free(decoder: *mut OakDecoder);
/// `oakcodec_decoder_open`.
pub fn oakcodec_decoder_open(
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_task_submit_is_registered() -> c_int {
unsafe { oakcodec::ffi::task::oakcodec_task_submit_is_registered() }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_init() -> OakDecoder {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_init() }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_free(decoder: *mut OakDecoder) {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_free(decoder) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_open(
decoder: OakDecoder,
filename: *const c_char,
stream_index: c_int,
) -> c_int;
/// `oakcodec_decoder_close`.
pub fn oakcodec_decoder_close(decoder: OakDecoder) -> c_int;
/// `oakcodec_decoder_is_open`.
pub fn oakcodec_decoder_is_open(decoder: OakDecoder) -> c_int;
/// `oakcodec_decoder_decode_audio`.
pub fn oakcodec_decoder_decode_audio(
) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_open(decoder, filename, stream_index) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_close(decoder: OakDecoder) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_close(decoder) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_is_open(decoder: OakDecoder) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_is_open(decoder) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_decode_audio(
decoder: OakDecoder,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
sample_rate: c_int,
channel_layout: c_uint,
channel_layout: u64,
buf: *mut f32,
buf_frames: c_int,
) -> c_int;
/// `oakcodec_decoder_conform_audio` — the whole-stream conform pass.
pub fn oakcodec_decoder_conform_audio(
) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_decode_audio(decoder, in_num, in_den, out_num, out_den, sample_rate, channel_layout, buf, buf_frames) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_conform_audio(
decoder: OakDecoder,
output_filenames: *const *const c_char,
filename_count: c_int,
@@ -220,65 +144,108 @@ extern "C" {
channel_layout: u64,
sample_format: c_int,
cancelled: OakCancelAtom,
) -> c_int;
/// `oakcodec_decoder_last_error`.
pub fn oakcodec_decoder_last_error(decoder: OakDecoder, buf: *mut c_char, buf_size: c_int) -> c_int;
) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_conform_audio(decoder, output_filenames, filename_count, sample_rate, channel_layout, sample_format, cancelled) }
}
// --- decoder.h: image-sequence heuristics ---
/// `oakcodec_decoder_get_image_sequence_digit_count`.
pub fn oakcodec_decoder_get_image_sequence_digit_count(filename: *const c_char) -> c_int;
/// `oakcodec_decoder_get_image_sequence_index`.
pub fn oakcodec_decoder_get_image_sequence_index(filename: *const c_char) -> i64;
/// `oakcodec_decoder_transform_image_sequence_file_name` (two-stage).
pub fn oakcodec_decoder_transform_image_sequence_file_name(
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_last_error(decoder: OakDecoder, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_last_error(decoder, buf, buf_size) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_get_image_sequence_digit_count(filename: *const c_char) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_get_image_sequence_digit_count(filename) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_get_image_sequence_index(filename: *const c_char) -> i64 {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_get_image_sequence_index(filename) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_decoder_transform_image_sequence_file_name(
filename: *const c_char,
number: i64,
buf: *mut c_char,
buf_size: c_int,
) -> c_int;
) -> c_int {
unsafe { oakcodec::ffi::decoder::oakcodec_decoder_transform_image_sequence_file_name(filename, number, buf, buf_size) }
}
// --- encoder.h extras ---
/// `oakcodec_encoder_get_desired_pixel_format`.
pub fn oakcodec_encoder_get_desired_pixel_format(encoder: OakEncoder) -> c_int;
/// `oakcodec_encoding_generate_matrix` (row-major 4x4).
pub fn oakcodec_encoding_generate_matrix(
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_get_desired_pixel_format(encoder: OakEncoder) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_get_desired_pixel_format(encoder) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoding_generate_matrix(
method: c_int,
src_width: c_int,
src_height: c_int,
dst_width: c_int,
dst_height: c_int,
out_matrix: *mut f64,
) -> c_int;
) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoding_generate_matrix(method, src_width, src_height, dst_width, dst_height, out_matrix) }
}
// --- proxy.h ---
/// `oakcodec_proxy_find_ffmpeg` (buf/size getter).
pub fn oakcodec_proxy_find_ffmpeg(configured_path: *const c_char, buf: *mut c_char, buf_size: c_int) -> c_int;
/// `oakcodec_proxy_params_default`.
pub fn oakcodec_proxy_params_default(out: *mut OakCodecProxyParams) -> c_int;
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_proxy_find_ffmpeg(configured_path: *const c_char, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakcodec::ffi::proxy::oakcodec_proxy_find_ffmpeg(configured_path, buf, buf_size) }
}
/// `oakcodec_encoder_init`.
pub fn oakcodec_encoder_init(params: *const OakCodecEncodingParams) -> OakEncoder;
/// `oakcodec_encoder_free`.
pub fn oakcodec_encoder_free(encoder: *mut OakEncoder);
/// `oakcodec_encoder_open`.
pub fn oakcodec_encoder_open(encoder: OakEncoder) -> c_int;
/// `oakcodec_encoder_write_video`.
pub fn oakcodec_encoder_write_video(encoder: OakEncoder, frame: OakFrame) -> c_int;
/// `oakcodec_encoder_write_audio`.
pub fn oakcodec_encoder_write_audio(
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_proxy_params_default(out: *mut OakCodecProxyParams) -> c_int {
unsafe { oakcodec::ffi::proxy::oakcodec_proxy_params_default(out) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_init(params: *const OakCodecEncodingParams) -> OakEncoder {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_init(params) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_free(encoder: *mut OakEncoder) {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_free(encoder) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_open(encoder: OakEncoder) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_open(encoder) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_write_video(encoder: OakEncoder, frame: OakFrame) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_write_video(encoder, frame) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_write_audio(
encoder: OakEncoder,
samples: *const f32,
frame_count: c_int,
) -> c_int;
/// `oakcodec_encoder_write_subtitle`.
pub fn oakcodec_encoder_write_subtitle(
) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_write_audio(encoder, samples, frame_count) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_write_subtitle(
encoder: OakEncoder,
text: *const c_char,
in_seconds: f64,
out_seconds: f64,
) -> c_int;
/// `oakcodec_encoder_flush`.
pub fn oakcodec_encoder_flush(encoder: OakEncoder) -> c_int;
/// `oakcodec_encoder_last_error`.
pub fn oakcodec_encoder_last_error(encoder: OakEncoder, buf: *mut c_char, buf_size: c_int) -> c_int;
) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_write_subtitle(encoder, text, in_seconds, out_seconds) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_flush(encoder: OakEncoder) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_flush(encoder) }
}
/// Direct call into the `oakcodec` crate (single-lib unification).
pub fn oakcodec_encoder_last_error(encoder: OakEncoder, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_last_error(encoder, buf, buf_size) }
}
+141 -66
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oakcommon / oakcore C ABI imports. The task module reaches the shared
//! value types through `include/common/videoparams.h`,
//! `include/common/colortransform.h` and the oakcore audio-params C ABI.
@@ -45,12 +61,13 @@ pub const OAKCOMMON_VIDEO_TYPE_STILL: c_int = 1;
/// `OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE` (`include/common/videoparams.h`).
pub const OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE: c_int = 2;
extern "C" {
// --- videoparams.h ---
/// `oakcommon_videoparams_init`.
pub fn oakcommon_videoparams_init() -> OakVideoParams;
/// `oakcommon_videoparams_init_basic`.
pub fn oakcommon_videoparams_init_basic(
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_init() -> OakVideoParams {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_init() }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_init_basic(
width: c_int,
height: c_int,
pixel_format: c_int,
@@ -59,88 +76,146 @@ extern "C" {
pixel_aspect_den: c_int,
interlacing: c_int,
divider: c_int,
) -> OakVideoParams;
/// `oakcommon_videoparams_free`.
pub fn oakcommon_videoparams_free(params: *mut OakVideoParams);
/// `oakcommon_videoparams_get_width`.
pub fn oakcommon_videoparams_get_width(params: OakVideoParams, width: *mut c_int) -> c_int;
/// `oakcommon_videoparams_get_height`.
pub fn oakcommon_videoparams_get_height(params: OakVideoParams, height: *mut c_int) -> c_int;
/// `oakcommon_videoparams_get_format`.
pub fn oakcommon_videoparams_get_format(params: OakVideoParams, format: *mut c_int) -> c_int;
/// `oakcommon_videoparams_set_format`.
pub fn oakcommon_videoparams_set_format(params: OakVideoParams, format: c_int) -> c_int;
/// `oakcommon_videoparams_get_time_base`.
pub fn oakcommon_videoparams_get_time_base(
) -> OakVideoParams {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_init_basic(width, height, pixel_format, nb_channels, pixel_aspect_num, pixel_aspect_den, interlacing, divider) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_free(params: *mut OakVideoParams) {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_free(params) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_width(params: OakVideoParams, width: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_width(params, width) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_height(params: OakVideoParams, height: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_height(params, height) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_format(params: OakVideoParams, format: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_format(params, format) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_set_format(params: OakVideoParams, format: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_format(params, format) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_time_base(
params: OakVideoParams,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int;
/// `oakcommon_videoparams_set_time_base`.
pub fn oakcommon_videoparams_set_time_base(
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_time_base(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_set_time_base(
params: OakVideoParams,
numerator: c_int,
denominator: c_int,
) -> c_int;
/// `oakcommon_videoparams_frame_rate_as_time_base` — frame rate flipped
/// to a frame duration rational.
pub fn oakcommon_videoparams_frame_rate_as_time_base(
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_time_base(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_frame_rate_as_time_base(
params: OakVideoParams,
out_num: *mut c_int,
out_den: *mut c_int,
) -> c_int;
/// `oakcommon_videoparams_set_frame_rate`.
pub fn oakcommon_videoparams_set_frame_rate(
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_frame_rate_as_time_base(params, out_num, out_den) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_set_frame_rate(
params: OakVideoParams,
numerator: c_int,
denominator: c_int,
) -> c_int;
/// `oakcommon_videoparams_get_video_type`.
pub fn oakcommon_videoparams_get_video_type(params: OakVideoParams, out_type: *mut c_int) -> c_int;
/// `oakcommon_videoparams_set_video_type`.
pub fn oakcommon_videoparams_set_video_type(params: OakVideoParams, video_type: c_int) -> c_int;
/// `oakcommon_videoparams_set_start_time`.
pub fn oakcommon_videoparams_set_start_time(params: OakVideoParams, start: i64) -> c_int;
/// `oakcommon_videoparams_set_duration`.
pub fn oakcommon_videoparams_set_duration(params: OakVideoParams, duration: i64) -> c_int;
/// `oakcommon_videoparams_get_is_valid`.
pub fn oakcommon_videoparams_get_is_valid(params: OakVideoParams, out_valid: *mut c_int) -> c_int;
/// `oakcommon_videoparams_get_frame_rate`.
pub fn oakcommon_videoparams_get_frame_rate(
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_frame_rate(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_video_type(params: OakVideoParams, out_type: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_video_type(params, out_type) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_set_video_type(params: OakVideoParams, video_type: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_video_type(params, video_type) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_set_start_time(params: OakVideoParams, start: i64) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_start_time(params, start) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_set_duration(params: OakVideoParams, duration: i64) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_duration(params, duration) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_is_valid(params: OakVideoParams, out_valid: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_is_valid(params, out_valid) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_frame_rate(
params: OakVideoParams,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int;
/// `oakcommon_videoparams_get_duration`.
pub fn oakcommon_videoparams_get_duration(params: OakVideoParams, duration: *mut i64) -> c_int;
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_frame_rate(params, numerator, denominator) }
}
// --- colortransform.h ---
/// `oakcommon_colortransform_init_display`.
pub fn oakcommon_colortransform_init_display(
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_videoparams_get_duration(params: OakVideoParams, duration: *mut i64) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_duration(params, duration) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_colortransform_init_display(
display: *const c_char,
view: *const c_char,
look: *const c_char,
) -> OakColorTransform;
/// `oakcommon_colortransform_init_output`.
pub fn oakcommon_colortransform_init_output(
output: *const c_char,
display: *const c_char,
view: *const c_char,
look: *const c_char,
) -> OakColorTransform;
/// `oakcommon_colortransform_free`.
pub fn oakcommon_colortransform_free(transform: *mut OakColorTransform);
) -> OakColorTransform {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_init_display(display, view, look) }
}
// --- config.h ---
/// `oakcommon_config_get` (buf/size getter; returns needed size).
pub fn oakcommon_config_get(
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_colortransform_init_output(output: *const c_char) -> OakColorTransform {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_init_output(output) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_colortransform_free(transform: *mut OakColorTransform) {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_free(transform) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_config_get(
group: *const c_char,
key: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int;
/// `oakcommon_config_get_int`.
pub fn oakcommon_config_get_int(group: *const c_char, key: *const c_char, default: c_int) -> c_int;
/// `oakcommon_config_get_bool`.
pub fn oakcommon_config_get_bool(group: *const c_char, key: *const c_char, default: c_int) -> c_int;
) -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_get(group, key, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_config_get_int(group: *const c_char, key: *const c_char, default: c_int) -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_get_int(group, key, default) }
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_config_get_bool(group: *const c_char, key: *const c_char, default: c_int) -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_get_bool(group, key, default) }
}
File diff suppressed because it is too large Load Diff
+166 -258
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oakrender C ABI imports. The task module reaches the render side through
//! `include/render/cancelatom.h` (task cancellation), `include/render/ticket.h`
//! (frame/audio render tickets), `include/render/copier.h` (export project
@@ -37,166 +53,27 @@ use crate::bridge::node::{OakNodeColorManager, OakNodeNode, OakNodeProject};
use crate::handle::CHandle;
/// Mirror of `OakCancelAtom` (`include/render/cancelatom.h`).
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakCancelAtom {
/// Opaque pointer to the reference-counted object.
pub ctx: *mut c_void,
/// Atomic increment.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Atomic decrement; destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version (`OAKRENDER_ABI_VERSION`).
pub abi_version: u32,
}
pub type OakCancelAtom = oakrender::ffi::OakCancelAtom;
impl OakCancelAtom {
/// The empty (null) atom.
pub fn null() -> Self {
OakCancelAtom {
ctx: std::ptr::null_mut(),
addref: None,
release: None,
abi_version: 0,
}
}
/// Whether the atom is empty (`ctx == NULL`).
pub fn is_null(&self) -> bool {
self.ctx.is_null()
}
/// Adopt the fields of a generic `CHandle` (layout-identical) — the task
/// module stores the atom as a `CHandle` for its public API.
pub fn from_chandle(h: CHandle) -> Self {
OakCancelAtom {
ctx: h.ctx,
addref: h.addref,
release: h.release,
abi_version: h.abi_version,
}
}
/// Convert back to a generic `CHandle` (layout-identical).
pub fn to_chandle(&self) -> CHandle {
CHandle {
ctx: self.ctx,
addref: self.addref,
release: self.release,
abi_version: self.abi_version,
}
}
}
/// Mirror of `OakRenderTicket` (`include/render/ticket.h`).
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakRenderTicket {
/// Opaque pointer to the reference-counted object.
pub ctx: *mut c_void,
/// Atomic increment.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Atomic decrement; destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version (`OAKRENDER_ABI_VERSION`).
pub abi_version: u32,
}
pub type OakRenderTicket = oakrender::ffi::OakRenderTicket;
/// Mirror of `OakRenderCache` (`include/render/cache.h`).
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakRenderCache {
/// Opaque pointer to the reference-counted object.
pub ctx: *mut c_void,
/// Atomic increment.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Atomic decrement; destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version (`OAKRENDER_ABI_VERSION`).
pub abi_version: u32,
}
pub type OakRenderCache = oakrender::ffi::OakRenderCache;
impl OakRenderCache {
/// The empty (null) cache handle.
pub fn null() -> Self {
OakRenderCache {
ctx: std::ptr::null_mut(),
addref: None,
release: None,
abi_version: 0,
}
}
/// Convert to the layout-identical generic `CHandle`.
pub fn to_chandle(&self) -> CHandle {
CHandle {
ctx: self.ctx,
addref: self.addref,
release: self.release,
abi_version: self.abi_version,
}
}
/// Adopt the fields of a generic `CHandle` (layout-identical).
pub fn from_chandle(h: CHandle) -> Self {
OakRenderCache {
ctx: h.ctx,
addref: h.addref,
release: h.release,
abi_version: h.abi_version,
}
}
}
/// Mirror of `OakRenderProjectCopier` (`include/render/copier.h`).
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakRenderProjectCopier {
/// Opaque pointer to the reference-counted object.
pub ctx: *mut c_void,
/// Atomic increment.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Atomic decrement; destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version (`OAKRENDER_ABI_VERSION`).
pub abi_version: u32,
}
pub type OakRenderProjectCopier = oakrender::ffi::OakRenderProjectCopier;
/// Mirror of `OakColorProcessor` (`include/render/color.h`).
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakColorProcessor {
/// Opaque pointer to the reference-counted object.
pub ctx: *mut c_void,
/// Atomic increment.
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
/// Atomic decrement; destroys at zero.
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
/// ABI version (`OAKRENDER_ABI_VERSION`).
pub abi_version: u32,
}
pub type OakColorProcessor = oakrender::ffi::OakColorProcessor;
impl OakColorProcessor {
/// Convert to the layout-identical generic `CHandle`.
pub fn to_chandle(&self) -> CHandle {
CHandle {
ctx: self.ctx,
addref: self.addref,
release: self.release,
abi_version: self.abi_version,
}
}
/// Adopt the fields of a generic `CHandle` (layout-identical).
pub fn from_chandle(h: CHandle) -> Self {
OakColorProcessor {
ctx: h.ctx,
addref: h.addref,
release: h.release,
abi_version: h.abi_version,
}
}
}
/// Mirror of `oakrender_ticket_finished_fn` (`include/render/ticket.h`).
///
@@ -208,64 +85,45 @@ impl OakColorProcessor {
pub type OakRenderTicketFinishedFn = unsafe extern "C" fn(ticket: OakRenderTicket, userdata: *mut c_void);
/// Mirror of `oakrender_video_ticket_params` (`include/render/ticket.h`).
#[repr(C)]
pub struct OakRenderVideoTicketParams {
/// Connected texture output node (borrowed).
pub output_node: OakNodeNode,
/// Video params by value (oakcommon handle).
pub video_params: OakVideoParams,
/// Borrowed oakcore audio params, may be null ctx.
pub audio_params: *const OakAudioParams,
/// Frame timestamp numerator (seconds rational).
pub time_num: i64,
/// Frame timestamp denominator.
pub time_den: i64,
/// Borrowed color manager; empty ctx = NULL.
pub color_manager: OakNodeColorManager,
/// `olive::RenderMode::Mode` as int.
pub mode: c_int,
/// Forced output width (0 = off).
pub force_width: c_int,
/// Forced output height (0 = off).
pub force_height: c_int,
/// Forced color matrix (used when `has_force_matrix` != 0).
pub force_matrix: [f64; 16],
/// Whether `force_matrix` is in effect.
pub has_force_matrix: c_int,
/// Forced pixel format (`PixelFormat` as int, -1 = off).
pub force_format: c_int,
/// Forced channel count (0 = off).
pub force_channel_count: c_int,
/// Forced color output (borrowed; empty ctx = none).
pub force_color_output: OakColorProcessor,
/// Forced color transform by value (empty ctx = default).
pub force_color_transform: OakColorTransform,
/// Borrowed frame cache; empty ctx = none.
pub cache: OakRenderCache,
pub type OakRenderVideoTicketParams = oakrender::ffi::OakVideoTicketParams;
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_cancelatom_init() -> OakCancelAtom {
unsafe { oakrender::ffi::cancelatom::oakrender_cancelatom_init() }
}
extern "C" {
// --- cancelatom.h ---
/// `oakrender_cancelatom_init`.
pub fn oakrender_cancelatom_init() -> OakCancelAtom;
/// `oakrender_cancelatom_free`.
pub fn oakrender_cancelatom_free(atom: *mut OakCancelAtom);
/// `oakrender_cancelatom_cancel`.
pub fn oakrender_cancelatom_cancel(atom: OakCancelAtom) -> c_int;
/// `oakrender_cancelatom_is_cancelled`.
pub fn oakrender_cancelatom_is_cancelled(atom: OakCancelAtom, cancelled: *mut c_int) -> c_int;
/// `oakrender_cancelatom_heard_cancel`.
pub fn oakrender_cancelatom_heard_cancel(atom: OakCancelAtom, heard: *mut c_int) -> c_int;
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_cancelatom_free(atom: *mut OakCancelAtom) {
unsafe { oakrender::ffi::cancelatom::oakrender_cancelatom_free(atom) }
}
// --- ticket.h ---
/// `oakrender_ticket_render_frame`.
pub fn oakrender_ticket_render_frame(
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_cancelatom_cancel(atom: OakCancelAtom) -> c_int {
unsafe { oakrender::ffi::cancelatom::oakrender_cancelatom_cancel(atom) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_cancelatom_is_cancelled(atom: OakCancelAtom, cancelled: *mut c_int) -> c_int {
unsafe { oakrender::ffi::cancelatom::oakrender_cancelatom_is_cancelled(atom, cancelled) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_cancelatom_heard_cancel(atom: OakCancelAtom, heard: *mut c_int) -> c_int {
unsafe { oakrender::ffi::cancelatom::oakrender_cancelatom_heard_cancel(atom, heard) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_render_frame(
params: *const OakRenderVideoTicketParams,
cb: Option<OakRenderTicketFinishedFn>,
userdata: *mut c_void,
) -> OakRenderTicket;
/// `oakrender_ticket_render_audio`.
pub fn oakrender_ticket_render_audio(
) -> OakRenderTicket {
unsafe { oakrender::ffi::ticket::oakrender_ticket_render_frame(params, cb, userdata) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_render_audio(
output_node: OakNodeNode,
in_num: i64,
in_den: i64,
@@ -275,80 +133,125 @@ extern "C" {
mode: c_int,
cb: Option<OakRenderTicketFinishedFn>,
userdata: *mut c_void,
) -> OakRenderTicket;
/// `oakrender_ticket_is_finished`.
pub fn oakrender_ticket_is_finished(ticket: OakRenderTicket) -> c_int;
/// `oakrender_ticket_wait`.
pub fn oakrender_ticket_wait(ticket: OakRenderTicket) -> c_int;
/// `oakrender_ticket_cancel`.
pub fn oakrender_ticket_cancel(ticket: OakRenderTicket) -> c_int;
/// `oakrender_ticket_get_type`.
pub fn oakrender_ticket_get_type(ticket: OakRenderTicket) -> c_int;
/// `oakrender_ticket_get_frame`.
pub fn oakrender_ticket_get_frame(ticket: OakRenderTicket, out: *mut OakFrame) -> c_int;
/// `oakrender_ticket_get_time`.
pub fn oakrender_ticket_get_time(ticket: OakRenderTicket, out_num: *mut i64, out_den: *mut i64) -> c_int;
/// `oakrender_ticket_get_range`.
pub fn oakrender_ticket_get_range(
) -> OakRenderTicket {
unsafe { oakrender::ffi::ticket::oakrender_ticket_render_audio(output_node, in_num, in_den, out_num, out_den, params, mode, cb, userdata) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_is_finished(ticket: OakRenderTicket) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_is_finished(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_wait(ticket: OakRenderTicket) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_wait(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_cancel(ticket: OakRenderTicket) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_cancel(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_get_type(ticket: OakRenderTicket) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_get_type(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_get_frame(ticket: OakRenderTicket, out: *mut OakFrame) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_get_frame(ticket, out) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_get_time(ticket: OakRenderTicket, out_num: *mut i64, out_den: *mut i64) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_get_time(ticket, out_num, out_den) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_get_range(
ticket: OakRenderTicket,
in_num: *mut i64,
in_den: *mut i64,
out_num: *mut i64,
out_den: *mut i64,
) -> c_int;
/// `oakrender_ticket_get_samples`.
pub fn oakrender_ticket_get_samples(ticket: OakRenderTicket, out: *mut *mut std::ffi::c_void) -> c_int;
/// `oakrender_ticket_free`.
pub fn oakrender_ticket_free(ticket: *mut OakRenderTicket);
) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_get_range(ticket, in_num, in_den, out_num, out_den) }
}
// --- copier.h ---
/// `oakrender_project_copier_create`.
pub fn oakrender_project_copier_create() -> OakRenderProjectCopier;
/// `oakrender_project_copier_free`.
pub fn oakrender_project_copier_free(copier: *mut OakRenderProjectCopier);
/// `oakrender_project_copier_set_project`.
pub fn oakrender_project_copier_set_project(
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_get_samples(ticket: OakRenderTicket, out: *mut *mut std::ffi::c_void) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_get_samples(ticket, out) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_ticket_free(ticket: *mut OakRenderTicket) {
unsafe { oakrender::ffi::ticket::oakrender_ticket_free(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_project_copier_create() -> OakRenderProjectCopier {
unsafe { oakrender::ffi::copier::oakrender_project_copier_create() }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_project_copier_free(copier: *mut OakRenderProjectCopier) {
unsafe { oakrender::ffi::copier::oakrender_project_copier_free(copier) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_project_copier_set_project(
copier: OakRenderProjectCopier,
project: OakNodeProject,
) -> c_int;
/// `oakrender_project_copier_get_copy`.
pub fn oakrender_project_copier_get_copy(
) -> c_int {
unsafe { oakrender::ffi::copier::oakrender_project_copier_set_project(copier, project) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_project_copier_get_copy(
copier: OakRenderProjectCopier,
original: OakNodeNode,
) -> OakNodeNode;
/// `oakrender_project_copier_get_copied_project`.
pub fn oakrender_project_copier_get_copied_project(
copier: OakRenderProjectCopier,
) -> OakNodeProject;
) -> OakNodeNode {
unsafe { oakrender::ffi::copier::oakrender_project_copier_get_copy(copier, original) }
}
// --- color.h ---
/// `oakrender_color_processor_create` — builds a processor between two
/// color spaces; `direction` is `OAKRENDER_COLOR_DIRECTION_*`
/// (include/render/color.h).
pub fn oakrender_color_processor_create(
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_project_copier_get_copied_project(
copier: OakRenderProjectCopier,
) -> OakNodeProject {
unsafe { oakrender::ffi::copier::oakrender_project_copier_get_copied_project(copier) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_color_processor_create(
src_space: *const c_char,
dst_transform: *const c_char,
direction: c_int,
) -> OakColorProcessor;
/// `oakrender_color_processor_free`.
pub fn oakrender_color_processor_free(processor: *mut OakColorProcessor);
/// `oakrender_color_processor_is_valid`.
pub fn oakrender_color_processor_is_valid(processor: OakColorProcessor) -> c_int;
) -> OakColorProcessor {
unsafe { oakrender::ffi::color::oakrender_color_processor_create(src_space, dst_transform, direction) }
}
// --- renderer.h ---
/// `oakrender_codec_frame_free` — release a frame handed out by a
/// render ticket.
pub fn oakrender_codec_frame_free(frame: *mut crate::bridge::codec::OakFrame);
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_color_processor_free(processor: *mut OakColorProcessor) {
unsafe { oakrender::ffi::color::oakrender_color_processor_free(processor) }
}
// --- manager.h ---
/// `oakrender_manager_set_aggressive_gc`.
pub fn oakrender_manager_set_aggressive_gc(enabled: c_int) -> c_int;
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_color_processor_is_valid(processor: OakColorProcessor) -> c_int {
unsafe { oakrender::ffi::color::oakrender_color_processor_is_valid(processor) }
}
// --- cache.h ---
/// `oakrender_cache_get_invalidated_ranges` — two-stage range query on
/// a frame cache; returns the number of ranges (or the required flat
/// array size).
pub fn oakrender_cache_get_invalidated_ranges(
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_codec_frame_free(frame: *mut crate::bridge::codec::OakFrame) {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_free(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_manager_set_aggressive_gc(enabled: c_int) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_manager_set_aggressive_gc(enabled) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_cache_get_invalidated_ranges(
cache: OakRenderCache,
in_num: i64,
in_den: i64,
@@ -356,7 +259,12 @@ extern "C" {
out_den: i64,
flat: *mut i64,
flat_size: c_int,
) -> c_int;
/// `oakrender_cache_free`.
pub fn oakrender_cache_free(cache: *mut OakRenderCache);
) -> c_int {
unsafe { oakrender::ffi::cache::oakrender_cache_get_invalidated_ranges(cache, in_num, in_den, out_num, out_den, flat, flat_size) }
}
/// Direct call into the `oakrender` crate (single-lib unification).
pub fn oakrender_cache_free(cache: *mut OakRenderCache) {
unsafe { oakrender::ffi::cache::oakrender_cache_free(cache) }
}
+20 -4
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oaktimeline C ABI imports. The OTIO load task builds tracks through the
//! timeline edit factories (`include/timeline/edit.h`) instead of the raw
//! track-list primitives, matching `src/task/src/project/loadotio/loadotio.cpp`.
@@ -22,8 +38,8 @@
use crate::bridge::node::OakNodeTrackList;
use crate::bridge::undo::OakUndoCommand;
extern "C" {
/// `oaktimeline_add_track_command` (`include/timeline/edit.h`) —
/// olive::TimelineAddTrackCommand.
pub fn oaktimeline_add_track_command(list: OakNodeTrackList) -> OakUndoCommand;
/// Direct call into the `oaktimeline` crate (single-lib unification).
pub fn oaktimeline_add_track_command(list: OakNodeTrackList) -> OakUndoCommand {
unsafe { oaktimeline::ffi::edit::oaktimeline_add_track_command(list) }
}
+56 -25
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oakundo C ABI imports. Import/load tasks build undo commands through the
//! C ABI vtable (`oakundo_command_init` with Rust closures as userdata) —
//! no C++ UndoCommand subclassing exists on this side. Mirrors
@@ -27,31 +43,46 @@ use crate::handle::CHandle;
pub type OakUndoCommand = CHandle;
/// Mirror of `OakUndoCommandVtable` (`include/undo/undocommand.h`).
#[repr(C)]
pub struct OakUndoCommandVtable {
/// Redo callback; may be NULL.
pub redo: Option<unsafe extern "C" fn(userdata: *mut c_void)>,
/// Undo callback; may be NULL.
pub undo: Option<unsafe extern "C" fn(userdata: *mut c_void)>,
/// Free callback; may be NULL.
pub free_fn: Option<unsafe extern "C" fn(userdata: *mut c_void)>,
pub type OakUndoCommandVtable = oakundo::undocommand::OakUndoCommandVtable;
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_init(vtable: *const OakUndoCommandVtable, userdata: *mut c_void) -> OakUndoCommand {
unsafe { oakundo::ffi::command::oakundo_command_init(vtable, userdata) }
}
extern "C" {
/// `oakundo_command_init` (vtable command).
pub fn oakundo_command_init(vtable: *const OakUndoCommandVtable, userdata: *mut c_void) -> OakUndoCommand;
/// `oakundo_command_init_multi`.
pub fn oakundo_command_init_multi() -> OakUndoCommand;
/// `oakundo_command_multi_add_child`.
pub fn oakundo_command_multi_add_child(multi: OakUndoCommand, child: OakUndoCommand) -> c_int;
/// `oakundo_command_multi_child_count`.
pub fn oakundo_command_multi_child_count(multi: OakUndoCommand, out_count: *mut c_int) -> c_int;
/// `oakundo_command_multi_child`.
pub fn oakundo_command_multi_child(multi: OakUndoCommand, index: c_int, out_child: *mut OakUndoCommand) -> c_int;
/// `oakundo_command_redo_now`.
pub fn oakundo_command_redo_now(command: OakUndoCommand) -> c_int;
/// `oakundo_command_undo_now`.
pub fn oakundo_command_undo_now(command: OakUndoCommand) -> c_int;
/// `oakundo_command_free`.
pub fn oakundo_command_free(command: *mut OakUndoCommand);
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_init_multi() -> OakUndoCommand {
unsafe { oakundo::ffi::command::oakundo_command_init_multi() }
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_multi_add_child(multi: OakUndoCommand, child: OakUndoCommand) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_multi_add_child(multi, child) }
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_multi_child_count(multi: OakUndoCommand, out_count: *mut c_int) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_multi_child_count(multi, out_count) }
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_multi_child(multi: OakUndoCommand, index: c_int, out_child: *mut OakUndoCommand) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_multi_child(multi, index, out_child) }
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_redo_now(command: OakUndoCommand) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_redo_now(command) }
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_undo_now(command: OakUndoCommand) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_undo_now(command) }
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_free(command: *mut OakUndoCommand) {
unsafe { oakundo::ffi::command::oakundo_command_free(command) }
}
+1 -1
View File
@@ -47,7 +47,7 @@ fn proxy_params_from_codec(params: &bridge::codec::OakCodecProxyParams) -> Proxy
}
/// Read a NUL-terminated char array into a String (lossy).
unsafe fn cstr_buf_to_string(buf: &[c_char]) -> String {
unsafe fn cstr_buf_to_string(buf: &[u8]) -> String {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
let bytes = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, len) };
String::from_utf8_lossy(bytes).into_owned()
+1 -1
View File
@@ -142,7 +142,7 @@ impl TaskBehavior for ConformTask {
self.sample_rate,
self.channel_layout,
self.sample_format,
bridge::render::OakCancelAtom::from_chandle(task.get_cancel_atom()),
task.get_cancel_atom(),
)
};
unsafe {
+2 -2
View File
@@ -49,7 +49,7 @@ impl CustomCacheState {
pub fn finish(&self) {
*self.cancelled_through_finish.lock().unwrap() = true;
if !self.atom.is_null() {
let atom = crate::bridge::render::OakCancelAtom::from_chandle(self.atom);
let atom = self.atom;
unsafe {
crate::bridge::render::oakrender_cancelatom_cancel(atom);
}
@@ -72,7 +72,7 @@ impl CustomCacheState {
/// hook. Used by the owning cache to abort a fill.
pub fn cancel(&self) {
if !self.atom.is_null() {
let atom = crate::bridge::render::OakCancelAtom::from_chandle(self.atom);
let atom = self.atom;
unsafe {
crate::bridge::render::oakrender_cancelatom_cancel(atom);
}
+1 -1
View File
@@ -165,7 +165,7 @@ impl ExportTask {
};
let bytes = self.encoding_params.filename.as_bytes();
let n = bytes.len().min(1023);
params.filename[..n].copy_from_slice(unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const i8, n) });
params.filename[..n].copy_from_slice(&bytes[..n]);
params.format = self.encoding_params.format;
params.video_enabled = self.encoding_params.video_enabled as i32;
params.video_codec = self.encoding_params.video_codec;
+2 -3
View File
@@ -386,8 +386,7 @@ unsafe fn convert_encoding_params(p: &OakCodecEncodingParams) -> EncodingParams
}
/// Read a NUL-terminated char array into a String (lossy).
unsafe fn c_char_array_to_string(buf: &[c_char]) -> String {
unsafe fn c_char_array_to_string(buf: &[u8]) -> String {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
let bytes = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, len) };
String::from_utf8_lossy(bytes).into_owned()
String::from_utf8_lossy(&buf[..len]).into_owned()
}
+1 -1
View File
@@ -97,7 +97,7 @@ impl TaskBehavior for PreCacheTask {
bridge::node::oaknode_node_get_video_frame_cache(self.sequence, &mut cache);
}
self.render.set_render_inputs(color_manager, cache.to_chandle(), 0 /* k_online */, false, range);
self.render.set_render_inputs(color_manager, cache, 0 /* k_online */, false, range);
// Drive the render with `self` as the subclass behavior (the C++
// virtual dispatch receiver); the render is temporarily moved out to
+1 -1
View File
@@ -197,7 +197,7 @@ impl ProjectImportTask {
}
unsafe {
bridge::node::oaknode_footage_set_cancel_atom(footage, bridge::render::OakCancelAtom::from_chandle(task.get_cancel_atom()));
bridge::node::oaknode_footage_set_cancel_atom(footage, task.get_cancel_atom());
}
let ok = unsafe { bridge::node::oaknode_footage_set_filename(footage, cstr(file_path)) } == 0;
unsafe {
+2 -2
View File
@@ -259,14 +259,14 @@ impl RenderTask {
has_force_matrix: if force.has_force_matrix { 1 } else { 0 },
force_format: force.force_format,
force_channel_count: force.force_channel_count,
force_color_output: bridge::render::OakColorProcessor::from_chandle(force.force_color_output),
force_color_output: force.force_color_output,
force_color_transform: OakColorTransform {
ctx: std::ptr::null_mut(),
addref: None,
release: None,
abi_version: 0,
},
cache: bridge::render::OakRenderCache::from_chandle(self.cache),
cache: self.cache,
};
let ticket = unsafe {
+5 -5
View File
@@ -122,7 +122,7 @@ impl Drop for Task {
// Free the cancellation atom only when we created it ourselves;
// borrowed atoms are released by their owner.
if self.owns_atom && !self.cancel_atom.is_null() {
let mut atom = bridge::render::OakCancelAtom::from_chandle(self.cancel_atom);
let mut atom = self.cancel_atom;
unsafe {
bridge::render::oakrender_cancelatom_free(&mut atom);
}
@@ -137,7 +137,7 @@ impl Task {
pub fn new(title: &str, cancel_atom: CHandle) -> Task {
let (cancel_atom, owns_atom) = if cancel_atom.is_null() {
let atom = unsafe { bridge::render::oakrender_cancelatom_init() };
(atom.to_chandle(), true)
(atom, true)
} else {
(cancel_atom, false)
};
@@ -210,7 +210,7 @@ impl Task {
/// invoke the cancel event callback if one is registered.
pub fn cancel(&mut self) {
if !self.cancel_atom.is_null() {
let atom = bridge::render::OakCancelAtom::from_chandle(self.cancel_atom);
let atom = self.cancel_atom;
unsafe {
bridge::render::oakrender_cancelatom_cancel(atom);
}
@@ -225,7 +225,7 @@ impl Task {
if self.cancel_atom.is_null() {
return false;
}
let atom = bridge::render::OakCancelAtom::from_chandle(self.cancel_atom);
let atom = self.cancel_atom;
let mut cancelled = 0;
unsafe {
bridge::render::oakrender_cancelatom_is_cancelled(atom, &mut cancelled);
@@ -243,7 +243,7 @@ impl Task {
/// between a task and its behavior's inner base task.
pub fn set_cancel_atom(&mut self, atom: CHandle) {
if self.owns_atom && !self.cancel_atom.is_null() {
let mut old = bridge::render::OakCancelAtom::from_chandle(self.cancel_atom);
let mut old = self.cancel_atom;
unsafe {
bridge::render::oakrender_cancelatom_free(&mut old);
}
+26 -26
View File
@@ -62,12 +62,12 @@ pub fn fake_atom() -> oaktask::bridge::render::OakCancelAtom {
}
}
pub fn write_cstr(s: &str, buf: *mut c_char, size: i32) -> i32 {
pub fn write_cstr(s: &str, buf: *mut u8, size: i32) -> i32 {
let bytes = s.as_bytes();
let needed = bytes.len() as i32 + 1;
if !buf.is_null() && size >= needed {
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr() as *const c_char, buf, bytes.len());
std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf, bytes.len());
*buf.add(bytes.len()) = 0;
}
}
@@ -346,7 +346,7 @@ pub unsafe extern "C" fn oakcodec_decoder_last_error(
buf: *mut c_char,
size: c_int,
) -> c_int {
write_cstr(&DECODER_LAST_ERROR.lock().unwrap().clone(), buf, size)
write_cstr(&DECODER_LAST_ERROR.lock().unwrap().clone(), buf as *mut u8, size)
}
#[no_mangle]
@@ -390,7 +390,7 @@ pub unsafe extern "C" fn oakcodec_decoder_transform_image_sequence_file_name(
let count = digits.len().max(1);
let prefix = &stem[..stem.len() - digits.len()];
let out = format!("{prefix}{:0count$}{ext}", number);
write_cstr(&out, buf, size)
write_cstr(&out, buf as *mut u8, size)
}
#[no_mangle]
@@ -460,7 +460,7 @@ pub unsafe extern "C" fn oakcodec_encoder_last_error(
size: c_int,
) -> c_int {
if ENCODER_FLUSH_ERROR.load(Ordering::SeqCst) != 0 {
write_cstr(&ENCODER_LAST_ERROR.lock().unwrap().clone(), buf, size)
write_cstr(&ENCODER_LAST_ERROR.lock().unwrap().clone(), buf as *mut u8, size)
} else {
0
}
@@ -485,7 +485,7 @@ pub unsafe extern "C" fn oakcodec_encoding_generate_matrix(
#[no_mangle]
pub unsafe extern "C" fn oakcodec_export_format_get_extension(_format: c_int, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("srt", buf, size)
write_cstr("srt", buf as *mut u8, size)
}
#[no_mangle]
@@ -498,7 +498,7 @@ pub unsafe extern "C" fn oakcodec_proxy_find_ffmpeg(
if path.is_empty() {
return 0;
}
write_cstr(&path, buf, size)
write_cstr(&path, buf as *mut u8, size)
}
#[no_mangle]
@@ -729,7 +729,7 @@ pub unsafe extern "C" fn oakcommon_config_get(
}
let key = std::ffi::CStr::from_ptr(key).to_string_lossy().into_owned();
if key == "DefaultSequenceFrameRate" && CONFIG_DEFAULT_SEQ_FRAME_RATE.load(Ordering::SeqCst) != 0 {
write_cstr("25/1", buf, size)
write_cstr("25/1", buf as *mut u8, size)
} else {
0
}
@@ -774,17 +774,17 @@ pub unsafe extern "C" fn oaknode_project_root(_project: oaktask::bridge::node::O
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_name(_project: oaktask::bridge::node::OakNodeProject, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("proj", buf, size)
write_cstr("proj", buf as *mut u8, size)
}
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_filename(_project: oaktask::bridge::node::OakNodeProject, buf: *mut c_char, size: c_int) -> c_int {
write_cstr(&PROJECT_FILENAME.lock().unwrap().clone(), buf, size)
write_cstr(&PROJECT_FILENAME.lock().unwrap().clone(), buf as *mut u8, size)
}
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_pretty_filename(_project: oaktask::bridge::node::OakNodeProject, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("pretty", buf, size)
write_cstr("pretty", buf as *mut u8, size)
}
#[no_mangle]
@@ -809,7 +809,7 @@ pub unsafe extern "C" fn oaknode_project_is_new(_project: oaktask::bridge::node:
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_cache_path(_project: oaktask::bridge::node::OakNodeProject, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("/tmp/oak-cache", buf, size)
write_cstr("/tmp/oak-cache", buf as *mut u8, size)
}
#[no_mangle]
@@ -829,7 +829,7 @@ pub unsafe extern "C" fn oaknode_project_set_cache_location_setting(_project: oa
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_get_custom_cache_path(_project: oaktask::bridge::node::OakNodeProject, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("", buf, size)
write_cstr("", buf as *mut u8, size)
}
#[no_mangle]
@@ -839,7 +839,7 @@ pub unsafe extern "C" fn oaknode_project_set_custom_cache_path(_project: oaktask
#[no_mangle]
pub unsafe extern "C" fn oaknode_project_get_uuid(_project: oaktask::bridge::node::OakNodeProject, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("uuid", buf, size)
write_cstr("uuid", buf as *mut u8, size)
}
#[no_mangle]
@@ -941,7 +941,7 @@ pub unsafe extern "C" fn oaknode_footage_as_node(footage: oaktask::bridge::node:
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_filename(_footage: oaktask::bridge::node::OakNodeFootage, buf: *mut c_char, size: c_int) -> c_int {
write_cstr(&FOOTAGE_FILENAME.lock().unwrap().clone(), buf, size)
write_cstr(&FOOTAGE_FILENAME.lock().unwrap().clone(), buf as *mut u8, size)
}
#[no_mangle]
@@ -973,7 +973,7 @@ pub unsafe extern "C" fn oaknode_footage_set_timestamp(_footage: oaktask::bridge
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_decoder(_footage: oaktask::bridge::node::OakNodeFootage, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("ffmpeg", buf, size)
write_cstr("ffmpeg", buf as *mut u8, size)
}
#[no_mangle]
@@ -1023,7 +1023,7 @@ pub unsafe extern "C" fn oaknode_footage_set_proxy_enabled(_footage: oaktask::br
#[no_mangle]
pub unsafe extern "C" fn oaknode_footage_proxy_path(_footage: oaktask::bridge::node::OakNodeFootage, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("", buf, size)
write_cstr("", buf as *mut u8, size)
}
#[no_mangle]
@@ -1335,7 +1335,7 @@ pub unsafe extern "C" fn oaknode_tracklist_get_sequence(_list: oaktask::bridge::
#[no_mangle]
pub unsafe extern "C" fn oaknode_tracklist_get_track_input_id(_list: oaktask::bridge::node::OakNodeTrackList, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("sub_in", buf, size)
write_cstr("sub_in", buf as *mut u8, size)
}
#[no_mangle]
@@ -1648,7 +1648,7 @@ pub unsafe extern "C" fn oaknode_serializer_savedata_set_property(
#[no_mangle]
pub unsafe extern "C" fn oaknode_serializer_save_to_xml(_save_data: oaktask::bridge::node::OakNodeSerializerSaveData, buf: *mut c_char, size: c_int) -> c_int {
write_cstr("<oakproj/>", buf, size)
write_cstr("<oakproj/>", buf as *mut u8, size)
}
#[no_mangle]
@@ -1667,7 +1667,7 @@ pub unsafe extern "C" fn oaknode_serializer_load_from_xml(
if !_out_load_data.is_null() {
*_out_load_data = fake_handle();
}
write_cstr(&SERIALIZER_DETAILS.lock().unwrap().clone(), _details, _details_size);
write_cstr(&SERIALIZER_DETAILS.lock().unwrap().clone(), _details as *mut u8, _details_size);
SERIALIZER_LOAD_RET.load(Ordering::SeqCst)
}
@@ -1692,7 +1692,7 @@ pub unsafe extern "C" fn oaknode_serializer_loaddata_get_property(
buf: *mut c_char,
size: c_int,
) -> c_int {
write_cstr("", buf, size)
write_cstr("", buf as *mut u8, size)
}
#[no_mangle]
@@ -1726,7 +1726,7 @@ pub unsafe extern "C" fn oaknode_serializer_save_to_file(
if !out_code.is_null() {
*out_code = code;
}
write_cstr(&SERIALIZER_DETAILS.lock().unwrap().clone(), _details, _details_size);
write_cstr(&SERIALIZER_DETAILS.lock().unwrap().clone(), _details as *mut u8, _details_size);
if code == 0 && !filename.is_null() {
// Success path writes a real file so tests can assert existence.
let name = std::ffi::CStr::from_ptr(filename).to_string_lossy().into_owned();
@@ -1755,13 +1755,13 @@ pub unsafe extern "C" fn oaknode_serializer_load_from_file(
if !out_code.is_null() {
*out_code = code;
}
write_cstr(&SERIALIZER_DETAILS.lock().unwrap().clone(), _details, _details_size);
write_cstr(&SERIALIZER_DETAILS.lock().unwrap().clone(), _details as *mut u8, _details_size);
SERIALIZER_LOAD_RET.load(Ordering::SeqCst)
}
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_label(_node: oaktask::bridge::node::OakNodeNode, buf: *mut c_char, size: c_int) -> c_int {
write_cstr(&NODE_LABEL.lock().unwrap().clone(), buf, size)
write_cstr(&NODE_LABEL.lock().unwrap().clone(), buf as *mut u8, size)
}
#[no_mangle]
@@ -1771,7 +1771,7 @@ pub unsafe extern "C" fn oaknode_node_set_label(_node: oaktask::bridge::node::Oa
#[no_mangle]
pub unsafe extern "C" fn oaknode_node_get_id(_node: oaktask::bridge::node::OakNodeNode, buf: *mut c_char, size: c_int) -> c_int {
write_cstr(&NODE_ID.lock().unwrap().clone(), buf, size)
write_cstr(&NODE_ID.lock().unwrap().clone(), buf as *mut u8, size)
}
#[no_mangle]
@@ -1831,7 +1831,7 @@ pub unsafe extern "C" fn oaknode_node_get_input_string(
buf: *mut c_char,
size: c_int,
) -> c_int {
write_cstr("subtitle text", buf, size)
write_cstr("subtitle text", buf as *mut u8, size)
}
#[no_mangle]
+2 -2
View File
@@ -54,7 +54,7 @@ fn render_task(frames: i64, audio: bool) -> RenderTask {
VIDEO_TIME_BASE_NUM.store(1, Ordering::SeqCst);
VIDEO_TIME_BASE_DEN.store(1, Ordering::SeqCst);
let range = TimeRange::new(Rational::new(0, 1), Rational::new(frames, 1));
let base = Task::new("render-loop-test", fake_atom().to_chandle());
let base = Task::new("render-loop-test", fake_atom());
let mut rt = RenderTask::new(
base,
CHandle::null(),
@@ -190,7 +190,7 @@ fn render_cancel_drains_inflight_and_fires_their_completions() {
// the loop; the loop then sees the cancellation and drains.
unsafe {
oaktask::bridge::render::oakrender_cancelatom_cancel(
oaktask::bridge::render::OakCancelAtom::from_chandle(atom),
atom,
);
}
+2 -2
View File
@@ -476,7 +476,7 @@ fn task_base_api_and_events() {
let _g = lock();
reset_stubs();
let mut task = Task::new("Base API", common::fake_atom().to_chandle());
let mut task = Task::new("Base API", common::fake_atom());
task.set_title("Renamed");
assert_eq!(task.title(), "Renamed");
assert!(task.error().is_none());
@@ -514,7 +514,7 @@ fn task_base_api_and_events() {
task.cancel();
assert!(task.is_cancelled());
unsafe {
oaktask::bridge::render::oakrender_cancelatom_cancel(oaktask::bridge::render::OakCancelAtom::from_chandle(atom));
oaktask::bridge::render::oakrender_cancelatom_cancel(atom);
}
assert!(task.is_cancelled());
+559
View File
@@ -2,13 +2,572 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex 1.3.0",
"syn",
]
[[package]]
name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "bytemuck"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cc"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
dependencies = [
"find-msvc-tools",
"shlex 2.0.1",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "either"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]]
name = "fax"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
[[package]]
name = "ffmpeg-next"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6380599799e175191eb7ffe82c97f36a2a90a36cbc54c738a903e5287d7f516a"
dependencies = [
"bitflags",
"ffmpeg-sys-next",
"libc",
]
[[package]]
name = "ffmpeg-sys-next"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b939bf79dd5949412a4b81cfe21a07f48ea21b47fcbb5f57816c8c2de5ae30b"
dependencies = [
"bindgen",
"cc",
"libc",
"num_cpus",
"pkg-config",
"vcpkg",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "glob"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"tiff",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "oakcodec"
version = "0.1.0"
dependencies = [
"ffmpeg-next",
"oakcore-rs",
]
[[package]]
name = "oakcommon"
version = "0.1.0"
dependencies = [
"image",
"log",
"oakcore-rs",
"ocio-rs",
"quick-xml",
]
[[package]]
name = "oakcore-rs"
version = "0.1.0"
[[package]]
name = "oaknode"
version = "0.1.0"
dependencies = [
"oakcodec",
"oakcommon",
"oakcore-rs",
"oakundo",
]
[[package]]
name = "oaktimeline"
version = "0.1.0"
dependencies = [
"oakcommon",
"oakcore-rs",
"oaknode",
"oakundo",
]
[[package]]
name = "oakundo"
version = "0.1.0"
dependencies = [
"oakcore-rs",
]
[[package]]
name = "ocio-rs"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3492534019b59e29dba06014f907dd12824537ed4d293d4108c4bfc669de7fd"
dependencies = [
"ocio-sys",
"thiserror",
]
[[package]]
name = "ocio-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63251d72d848de5eda39d59cd6490260cf031738ebd518ea37d76b5aae614ec"
dependencies = [
"cc",
"cmake",
]
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pxfm"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "rustc-hash"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "simd-adler32"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiff"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
dependencies = [
"fax",
"flate2",
"half",
"quick-error",
"weezl",
"zune-jpeg",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "zerocopy"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zune-core"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]
+8 -3
View File
@@ -13,10 +13,15 @@ crate-type = ["staticlib", "rlib"]
panic = "unwind"
[features]
# Compile the in-crate C ABI mocks (src/bridge/teststubs.rs) so the unit and
# integration tests can link without the oaknode/oakundo/oakcommon DLLs.
# Every test run must pass this flag (see README.md "testing").
# Compile the in-crate mock implementations (src/bridge/teststubs.rs) so the
# integration tests can resolve `bridge::teststubs`; the bridge fns route to
# them under this feature (see single-lib.md). Plain Rust mocks, no
# `#[no_mangle]` — they coexist with the real oaknode/oakundo/oakcommon
# path dependencies. Every test run must pass this flag (README "testing").
test-stubs = []
[dependencies]
oakcore-rs = { path = "../../oakcore-rs" }
oakundo = { path = "../../undo/rust" }
oaknode = { path = "../../node/rust" }
oakcommon = { path = "../../common/rust" }
+245 -45
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oakcommon C ABI imports (XML reader/writer + config), mirroring
//! `include/common/xmlutils.h` and `include/common/config.h`.
@@ -21,49 +37,233 @@ use std::ffi::{c_char, c_int};
use crate::handle::CHandle;
extern "C" {
/// `oakcommon_xml_reader_init` — create a reader over a NUL-terminated
/// document; the document must outlive the reader.
pub fn oakcommon_xml_reader_init(data: *const c_char) -> CHandle;
/// `oakcommon_xml_reader_free` — release the reader; NULL is a no-op.
pub fn oakcommon_xml_reader_free(reader: *mut CHandle);
/// `oakcommon_xml_reader_skip_current_element` — skip to the end of the
/// current element subtree.
pub fn oakcommon_xml_reader_skip_current_element(reader: CHandle) -> c_int;
/// `oakcommon_xml_reader_read_next_start_element` — advance to the next
/// start element, writing its (possibly empty) name into `name`.
pub fn oakcommon_xml_reader_read_next_start_element(reader: CHandle, name: *mut c_char, buf_size: c_int) -> c_int;
/// `oakcommon_xml_reader_name` — the current element's name.
pub fn oakcommon_xml_reader_name(reader: CHandle, name: *mut c_char, buf_size: c_int) -> c_int;
/// `oakcommon_xml_reader_read_element_text` — read the current element's
/// text content.
pub fn oakcommon_xml_reader_read_element_text(reader: CHandle, text: *mut c_char, buf_size: c_int) -> c_int;
/// `oakcommon_xml_reader_attribute_count` — number of attributes on the
/// current element.
pub fn oakcommon_xml_reader_attribute_count(reader: CHandle, count: *mut c_int) -> c_int;
/// `oakcommon_xml_reader_attribute_name` — name of attribute `i`.
pub fn oakcommon_xml_reader_attribute_name(reader: CHandle, index: c_int, name: *mut c_char, buf_size: c_int) -> c_int;
/// `oakcommon_xml_reader_attribute_value` — value of attribute `i`.
pub fn oakcommon_xml_reader_attribute_value(reader: CHandle, index: c_int, value: *mut c_char, buf_size: c_int) -> c_int;
/// `oakcommon_xml_reader_has_error` — whether the reader hit an error.
pub fn oakcommon_xml_reader_has_error(reader: CHandle, has_error: *mut c_int) -> c_int;
/// `oakcommon_xml_writer_init` — create a writer.
pub fn oakcommon_xml_writer_init() -> CHandle;
/// `oakcommon_xml_writer_free` — release the writer; NULL is a no-op.
pub fn oakcommon_xml_writer_free(writer: *mut CHandle);
/// `oakcommon_xml_writer_write_start_element` — open `<name>`.
pub fn oakcommon_xml_writer_write_start_element(writer: CHandle, name: *const c_char) -> c_int;
/// `oakcommon_xml_writer_write_end_element` — close the current element.
pub fn oakcommon_xml_writer_write_end_element(writer: CHandle) -> c_int;
/// `oakcommon_xml_writer_write_end_document` — finish the document.
pub fn oakcommon_xml_writer_write_end_document(writer: CHandle) -> c_int;
/// `oakcommon_xml_writer_write_attribute` — write `key="value"`.
pub fn oakcommon_xml_writer_write_attribute(writer: CHandle, key: *const c_char, value: *const c_char) -> c_int;
/// `oakcommon_xml_writer_write_characters` — write raw text content.
pub fn oakcommon_xml_writer_write_characters(writer: CHandle, text: *const c_char) -> c_int;
/// `oakcommon_xml_writer_write_text_element` — write `<name>text</name>`.
pub fn oakcommon_xml_writer_write_text_element(writer: CHandle, name: *const c_char, text: *const c_char) -> c_int;
/// `oakcommon_config_get_int` — read an integer config value (marker default
/// colour).
pub fn oakcommon_config_get_int(group: *const c_char, key: *const c_char, default: c_int) -> c_int;
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_init(data: *const c_char) -> CHandle {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_init(data)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_init(data) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_free(reader: *mut CHandle) {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_free(reader)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_free(reader) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_skip_current_element(reader: CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_skip_current_element(reader)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_skip_current_element(reader) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification). The
/// real ABI advances the reader and writes a 1/0 `found` flag (the name is
/// read with [`oakcommon_xml_reader_name`]).
pub fn oakcommon_xml_reader_read_next_start_element(reader: CHandle, found: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_read_next_start_element(reader, found)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_next_start_element(reader, found) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_name(reader: CHandle, name: *mut c_char, buf_size: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_name(reader, name, buf_size)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_name(reader, name, buf_size) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_read_element_text(reader: CHandle, text: *mut c_char, buf_size: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_read_element_text(reader, text, buf_size)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_element_text(reader, text, buf_size) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_attribute_count(reader: CHandle, count: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_attribute_count(reader, count)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_count(reader, count) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_attribute_name(reader: CHandle, index: c_int, name: *mut c_char, buf_size: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_attribute_name(reader, index, name, buf_size)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_name(reader, index, name, buf_size) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_attribute_value(reader: CHandle, index: c_int, value: *mut c_char, buf_size: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_attribute_value(reader, index, value, buf_size)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_value(reader, index, value, buf_size) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_reader_has_error(reader: CHandle, has_error: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_reader_has_error(reader, has_error)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_has_error(reader, has_error) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_init() -> CHandle {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_init()
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_init() }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_free(writer: *mut CHandle) {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_free(writer)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_free(writer) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_write_start_element(writer: CHandle, name: *const c_char) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_write_start_element(writer, name)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_start_element(writer, name) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_write_end_element(writer: CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_write_end_element(writer)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_element(writer) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_write_end_document(writer: CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_write_end_document(writer)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_document(writer) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_write_attribute(writer: CHandle, key: *const c_char, value: *const c_char) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_write_attribute(writer, key, value)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_attribute(writer, key, value) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_write_characters(writer: CHandle, text: *const c_char) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_write_characters(writer, text)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_characters(writer, text) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_xml_writer_write_text_element(writer: CHandle, name: *const c_char, text: *const c_char) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_xml_writer_write_text_element(writer, name, text)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_text_element(writer, name, text) }
}
}
/// Direct call into the `oakcommon` crate (single-lib unification).
pub fn oakcommon_config_get_int(group: *const c_char, key: *const c_char, default: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakcommon_config_get_int(group, key, default)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakcommon::ffi::config::oakcommon_config_get_int(group, key, default) }
}
}
+6 -5
View File
@@ -25,9 +25,10 @@ pub mod common;
pub mod node;
pub mod undo;
/// In-crate C ABI mocks, compiled only for tests (feature `test-stubs`).
/// Each `#[no_mangle]` function here provides a definition for the `extern
/// "C"` symbol declared in the submodules above, so `cargo test
/// --features test-stubs` links without the real oak DLLs.
#[cfg(feature = "test-stubs")]
/// In-crate mock implementations of the bridge surface, compiled for tests
/// (`cfg(test)` or the `test-stubs` feature); the bridge fns route to them
/// via their `#[cfg(any(test, feature = "test-stubs"))]` variants. Plain
/// Rust (no `#[no_mangle]`), so they coexist with the real
/// oaknode/oakundo/oakcommon rlibs linked in the same test binary.
#[cfg(any(test, feature = "test-stubs"))]
pub mod teststubs;
+675 -125
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oaknode C ABI imports (`include/node/{block,node,sequence,track,project}.h`).
//! Timeline reads and mutates the block/track/sequence graph exclusively
//! through these frozen functions — it never reimplements node internals.
@@ -39,129 +55,663 @@ pub type OakNodeProject = CHandle;
/// A track list (`OakNodeTrackList`). Opaque handle.
pub type OakNodeTrackList = CHandle;
extern "C" {
/// `oaknode_block_clip_create` — a new clip block (count 1).
pub fn oaknode_block_clip_create() -> OakNodeBlock;
/// `oaknode_block_gap_create` — a new gap block (count 1).
pub fn oaknode_block_gap_create() -> OakNodeBlock;
/// `oaknode_block_as_node` — the block viewed as a generic node.
pub fn oaknode_block_as_node(block: OakNodeBlock) -> OakNodeNode;
/// `oaknode_block_get_in` — the block's in point as an int pair.
pub fn oaknode_block_get_in(block: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int;
/// `oaknode_block_get_out` — the block's out point as an int pair.
pub fn oaknode_block_get_out(block: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int;
/// `oaknode_block_get_length` — the block's length as an int pair.
pub fn oaknode_block_get_length(block: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int;
/// `oaknode_block_set_length_and_media_out` — resize keeping media-in fixed.
pub fn oaknode_block_set_length_and_media_out(block: OakNodeBlock, numerator: c_int, denominator: c_int) -> c_int;
/// `oaknode_block_set_length_and_media_in` — resize keeping the out point fixed.
pub fn oaknode_block_set_length_and_media_in(block: OakNodeBlock, numerator: c_int, denominator: c_int) -> c_int;
/// `oaknode_block_get_enabled` — the block's enabled flag.
pub fn oaknode_block_get_enabled(block: OakNodeBlock, enabled: *mut c_int) -> c_int;
/// `oaknode_block_set_enabled` — set the enabled flag.
pub fn oaknode_block_set_enabled(block: OakNodeBlock, enabled: c_int) -> c_int;
/// `oaknode_block_get_previous` — borrowed previous block (empty when none).
pub fn oaknode_block_get_previous(block: OakNodeBlock, out: *mut OakNodeBlock) -> c_int;
/// `oaknode_block_get_next` — borrowed next block (empty when none).
pub fn oaknode_block_get_next(block: OakNodeBlock, out: *mut OakNodeBlock) -> c_int;
/// `oaknode_block_get_track` — the owning track (empty when detached).
pub fn oaknode_block_get_track(block: OakNodeBlock, out: *mut OakNodeTrack) -> c_int;
/// `oaknode_block_link` — link two blocks so they move together.
pub fn oaknode_block_link(a: OakNodeBlock, b: OakNodeBlock) -> c_int;
/// `oaknode_block_unlink` — unlink two blocks.
pub fn oaknode_block_unlink(a: OakNodeBlock, b: OakNodeBlock) -> c_int;
/// `oaknode_track_get_length` — the track's length as an int pair.
pub fn oaknode_track_get_length(track: OakNodeTrack, numerator: *mut c_int, denominator: *mut c_int) -> c_int;
/// `oaknode_track_get_sequence` — the owning sequence (empty when detached).
pub fn oaknode_track_get_sequence(track: OakNodeTrack, out: *mut OakNodeSequence) -> c_int;
/// `oaknode_track_prepend_block` — prepend a block.
pub fn oaknode_track_prepend_block(track: OakNodeTrack, block: OakNodeBlock) -> c_int;
/// `oaknode_track_insert_block_after` — insert after `before`.
pub fn oaknode_track_insert_block_after(track: OakNodeTrack, block: OakNodeBlock, before: OakNodeBlock) -> c_int;
/// `oaknode_track_ripple_remove_block` — remove a block, shifting later ones
/// earlier; ownership returns to the caller.
pub fn oaknode_track_ripple_remove_block(track: OakNodeTrack, block: OakNodeBlock) -> c_int;
/// `oaknode_track_replace_block` — replace `old_block` with `new_block`
/// (equal lengths required).
pub fn oaknode_track_replace_block(track: OakNodeTrack, old_block: OakNodeBlock, new_block: OakNodeBlock) -> c_int;
/// `oaknode_sequence_as_node` — the sequence viewed as a generic node.
pub fn oaknode_sequence_as_node(sequence: OakNodeSequence) -> OakNodeNode;
/// `oaknode_sequence_from_node` — a generic node viewed as a sequence.
pub fn oaknode_sequence_from_node(node: OakNodeNode) -> OakNodeSequence;
/// `oaknode_node_get_project` — the owning project (empty when detached).
pub fn oaknode_node_get_project(node: OakNodeNode, out: *mut OakNodeProject) -> c_int;
/// `oaknode_node_output_connection_count` — number of output connections.
pub fn oaknode_node_output_connection_count(node: OakNodeNode, out_count: *mut c_int) -> c_int;
/// `oaknode_node_get_markers` — borrow the node's marker list.
pub fn oaknode_node_get_markers(node: OakNodeNode, out: *mut crate::handle::CHandle) -> c_int;
/// `oaknode_node_get_work_area` — borrow the node's work area.
pub fn oaknode_node_get_work_area(node: OakNodeNode, out: *mut crate::handle::CHandle) -> c_int;
/// `oaknode_project_add_node` — adopt a node into a project.
pub fn oaknode_project_add_node(project: OakNodeProject, node: OakNodeNode) -> c_int;
/// `oaknode_project_remove_node` — remove a node from a project.
pub fn oaknode_project_remove_node(project: OakNodeProject, node: OakNodeNode) -> c_int;
/// `oaknode_command_create_remove_node` — a command that removes a node.
pub fn oaknode_command_create_remove_node(node: OakNodeNode) -> crate::handle::CHandle;
// --- block.h: querying / building blocks -------------------------------
/// `oaknode_block_get_kind` — the block's kind (`OAKNODE_BLOCK_*`).
pub fn oaknode_block_get_kind(block: OakNodeBlock, out_kind: *mut c_int) -> c_int;
/// `oaknode_block_from_node` — a generic node viewed as a block (empty when not a block).
pub fn oaknode_block_from_node(node: OakNodeNode) -> OakNodeBlock;
/// `oaknode_block_are_linked` — whether two blocks are linked (`linked` receives 1/0).
pub fn oaknode_block_are_linked(a: OakNodeBlock, b: OakNodeBlock, linked: *mut c_int) -> c_int;
/// `oaknode_clip_add_cache_passthrough_from` — copy render-cache passthroughs from `other`.
pub fn oaknode_clip_add_cache_passthrough_from(clip: OakNodeBlock, other: OakNodeBlock) -> c_int;
/// `oaknode_clip_get_media_in` — the clip's media-in as an int pair (clip blocks only).
pub fn oaknode_clip_get_media_in(clip: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int;
/// `oaknode_clip_set_media_in` — set the clip's media-in as an int pair (clip blocks only).
pub fn oaknode_clip_set_media_in(clip: OakNodeBlock, numerator: c_int, denominator: c_int) -> c_int;
// --- track.h: tracks and track lists -----------------------------------
/// `oaknode_track_create` — a new detached track of the given type.
pub fn oaknode_track_create(kind: c_int) -> OakNodeTrack;
/// `oaknode_track_get_locked` — whether the track is locked (`locked` receives 1/0).
pub fn oaknode_track_get_locked(track: OakNodeTrack, locked: *mut c_int) -> c_int;
/// `oaknode_track_set_locked` — set the locked flag.
pub fn oaknode_track_set_locked(track: OakNodeTrack, locked: c_int) -> c_int;
/// `oaknode_track_get_block_count` — number of blocks on the track.
pub fn oaknode_track_get_block_count(track: OakNodeTrack, count: *mut c_int) -> c_int;
/// `oaknode_track_get_block_at` — borrowed block at `index`.
pub fn oaknode_track_get_block_at(track: OakNodeTrack, index: c_int, out: *mut OakNodeBlock) -> c_int;
/// `oaknode_track_get_block_containing_time` — block strictly containing `time` (in < t < out).
pub fn oaknode_track_get_block_containing_time(track: OakNodeTrack, numerator: c_int, denominator: c_int, out: *mut OakNodeBlock) -> c_int;
/// `oaknode_track_get_nearest_block_before_or_at` — block at or before `time`.
pub fn oaknode_track_get_nearest_block_before_or_at(track: OakNodeTrack, numerator: c_int, denominator: c_int, out: *mut OakNodeBlock) -> c_int;
/// `oaknode_track_get_nearest_block_after_or_at` — block at or after `time`.
pub fn oaknode_track_get_nearest_block_after_or_at(track: OakNodeTrack, numerator: c_int, denominator: c_int, out: *mut OakNodeBlock) -> c_int;
/// `oaknode_tracklist_get_type` — the track list's track type.
pub fn oaknode_tracklist_get_type(list: OakNodeTrackList, kind: *mut c_int) -> c_int;
/// `oaknode_tracklist_get_track_count` — number of tracks in the list.
pub fn oaknode_tracklist_get_track_count(list: OakNodeTrackList, count: *mut c_int) -> c_int;
/// `oaknode_tracklist_get_track_at` — borrowed track at `index`.
pub fn oaknode_tracklist_get_track_at(list: OakNodeTrackList, index: c_int, out: *mut OakNodeTrack) -> c_int;
/// `oaknode_tracklist_array_append` — append a track-array element on the parent sequence.
pub fn oaknode_tracklist_array_append(list: OakNodeTrackList) -> c_int;
/// `oaknode_tracklist_array_remove_last` — remove the last track-array element.
pub fn oaknode_tracklist_array_remove_last(list: OakNodeTrackList) -> c_int;
// --- sequence.h ---------------------------------------------------------
/// `oaknode_sequence_get_track_list` — borrowed per-type track list.
pub fn oaknode_sequence_get_track_list(sequence: OakNodeSequence, kind: c_int, out: *mut OakNodeTrackList) -> c_int;
/// `oaknode_sequence_get_all_track_count` — total connected tracks across all types.
pub fn oaknode_sequence_get_all_track_count(sequence: OakNodeSequence, count: *mut c_int) -> c_int;
/// `oaknode_sequence_get_all_track_at` — borrowed track at `index` (flat cache).
pub fn oaknode_sequence_get_all_track_at(sequence: OakNodeSequence, index: c_int, out: *mut OakNodeTrack) -> c_int;
// --- node.h: edges and cloning ------------------------------------------
/// `oaknode_node_connect` — connect `output_node` to `input_node`'s `input_id` (live).
pub fn oaknode_node_connect(output_node: OakNodeNode, input_node: OakNodeNode, input_id: *const c_char) -> c_int;
/// `oaknode_node_disconnect` — remove the edge feeding `input_node`'s `input_id` (live).
pub fn oaknode_node_disconnect(input_node: OakNodeNode, input_id: *const c_char) -> c_int;
/// `oaknode_node_copy_in_graph` — clone `node` in its graph; `*out_command` receives an owned undo handle.
pub fn oaknode_node_copy_in_graph(node: OakNodeNode, out_command: *mut crate::handle::CHandle) -> OakNodeNode;
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_clip_create() -> OakNodeBlock {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_clip_create()
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_clip_create() }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_gap_create() -> OakNodeBlock {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_gap_create()
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_gap_create() }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_as_node(block: OakNodeBlock) -> OakNodeNode {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_as_node(block)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_as_node(block) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_in(block: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_in(block, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_in(block, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_out(block: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_out(block, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_out(block, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_length(block: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_length(block, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_length(block, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_set_length_and_media_out(block: OakNodeBlock, numerator: c_int, denominator: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_set_length_and_media_out(block, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_set_length_and_media_out(block, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_set_length_and_media_in(block: OakNodeBlock, numerator: c_int, denominator: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_set_length_and_media_in(block, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_set_length_and_media_in(block, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_enabled(block: OakNodeBlock, enabled: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_enabled(block, enabled)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_enabled(block, enabled) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_set_enabled(block: OakNodeBlock, enabled: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_set_enabled(block, enabled)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_set_enabled(block, enabled) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_previous(block: OakNodeBlock, out: *mut OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_previous(block, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_previous(block, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_next(block: OakNodeBlock, out: *mut OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_next(block, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_next(block, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_track(block: OakNodeBlock, out: *mut OakNodeTrack) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_track(block, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_track(block, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_link(a: OakNodeBlock, b: OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_link(a, b)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_link(a, b) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_unlink(a: OakNodeBlock, b: OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_unlink(a, b)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_unlink(a, b) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_length(track: OakNodeTrack, numerator: *mut c_int, denominator: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_length(track, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_length(track, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_sequence(track: OakNodeTrack, out: *mut OakNodeSequence) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_sequence(track, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_sequence(track, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_prepend_block(track: OakNodeTrack, block: OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_prepend_block(track, block)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_prepend_block(track, block) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_insert_block_after(track: OakNodeTrack, block: OakNodeBlock, before: OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_insert_block_after(track, block, before)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_insert_block_after(track, block, before) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_ripple_remove_block(track: OakNodeTrack, block: OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_ripple_remove_block(track, block)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_ripple_remove_block(track, block) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_replace_block(track: OakNodeTrack, old_block: OakNodeBlock, new_block: OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_replace_block(track, old_block, new_block)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_replace_block(track, old_block, new_block) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_sequence_as_node(sequence: OakNodeSequence) -> OakNodeNode {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_sequence_as_node(sequence)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::sequence::oaknode_sequence_as_node(sequence) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_sequence_from_node(node: OakNodeNode) -> OakNodeSequence {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_sequence_from_node(node)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::sequence::oaknode_sequence_from_node(node) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_node_get_project(node: OakNodeNode, out: *mut OakNodeProject) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_node_get_project(node, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_node_get_project(node, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_node_output_connection_count(node: OakNodeNode, out_count: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_node_output_connection_count(node, out_count)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_node_output_connection_count(node, out_count) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_node_get_markers(node: OakNodeNode, out: *mut crate::handle::CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_node_get_markers(node, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_node_get_markers(node, out as *mut std::ffi::c_void) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_node_get_work_area(node: OakNodeNode, out: *mut crate::handle::CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_node_get_work_area(node, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_node_get_work_area(node, out as *mut std::ffi::c_void) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_project_add_node(project: OakNodeProject, node: OakNodeNode) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_project_add_node(project, node)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::project::oaknode_project_add_node(project, node) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_project_remove_node(project: OakNodeProject, node: OakNodeNode) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_project_remove_node(project, node)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::project::oaknode_project_remove_node(project, node) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_command_create_remove_node(node: OakNodeNode) -> crate::handle::CHandle {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_command_create_remove_node(node)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_command_create_remove_node(node) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_get_kind(block: OakNodeBlock, out_kind: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_get_kind(block, out_kind)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_get_kind(block, out_kind) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_from_node(node: OakNodeNode) -> OakNodeBlock {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_from_node(node)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_from_node(node) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_block_are_linked(a: OakNodeBlock, b: OakNodeBlock, linked: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_block_are_linked(a, b, linked)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_block_are_linked(a, b, linked) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_clip_add_cache_passthrough_from(clip: OakNodeBlock, other: OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_clip_add_cache_passthrough_from(clip, other)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_clip_add_cache_passthrough_from(clip, other) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_clip_get_media_in(clip: OakNodeBlock, numerator: *mut c_int, denominator: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_clip_get_media_in(clip, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_clip_get_media_in(clip, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_clip_set_media_in(clip: OakNodeBlock, numerator: c_int, denominator: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_clip_set_media_in(clip, numerator, denominator)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::block::oaknode_clip_set_media_in(clip, numerator, denominator) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_create(kind: c_int) -> OakNodeTrack {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_create(kind)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_create(kind) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_locked(track: OakNodeTrack, locked: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_locked(track, locked)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_locked(track, locked) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_set_locked(track: OakNodeTrack, locked: c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_set_locked(track, locked)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_set_locked(track, locked) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_block_count(track: OakNodeTrack, count: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_block_count(track, count)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_block_count(track, count) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_block_at(track: OakNodeTrack, index: c_int, out: *mut OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_block_at(track, index, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_block_at(track, index, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_block_containing_time(track: OakNodeTrack, numerator: c_int, denominator: c_int, out: *mut OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_block_containing_time(track, numerator, denominator, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_block_containing_time(track, numerator, denominator, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_nearest_block_before_or_at(track: OakNodeTrack, numerator: c_int, denominator: c_int, out: *mut OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_nearest_block_before_or_at(track, numerator, denominator, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_nearest_block_before_or_at(track, numerator, denominator, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_track_get_nearest_block_after_or_at(track: OakNodeTrack, numerator: c_int, denominator: c_int, out: *mut OakNodeBlock) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_track_get_nearest_block_after_or_at(track, numerator, denominator, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_track_get_nearest_block_after_or_at(track, numerator, denominator, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_tracklist_get_type(list: OakNodeTrackList, kind: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_tracklist_get_type(list, kind)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_tracklist_get_type(list, kind) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_tracklist_get_track_count(list: OakNodeTrackList, count: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_tracklist_get_track_count(list, count)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_tracklist_get_track_count(list, count) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_tracklist_get_track_at(list: OakNodeTrackList, index: c_int, out: *mut OakNodeTrack) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_tracklist_get_track_at(list, index, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_tracklist_get_track_at(list, index, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_tracklist_array_append(list: OakNodeTrackList) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_tracklist_array_append(list)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_tracklist_array_append(list) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_tracklist_array_remove_last(list: OakNodeTrackList) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_tracklist_array_remove_last(list)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::track::oaknode_tracklist_array_remove_last(list) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_sequence_get_track_list(sequence: OakNodeSequence, kind: c_int, out: *mut OakNodeTrackList) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_sequence_get_track_list(sequence, kind, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::sequence::oaknode_sequence_get_track_list(sequence, kind, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_sequence_get_all_track_count(sequence: OakNodeSequence, count: *mut c_int) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_sequence_get_all_track_count(sequence, count)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::sequence::oaknode_sequence_get_all_track_count(sequence, count) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_sequence_get_all_track_at(sequence: OakNodeSequence, index: c_int, out: *mut OakNodeTrack) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_sequence_get_all_track_at(sequence, index, out)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::sequence::oaknode_sequence_get_all_track_at(sequence, index, out) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_node_connect(output_node: OakNodeNode, input_node: OakNodeNode, input_id: *const c_char) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_node_connect(output_node, input_node, input_id)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_node_connect(output_node, input_node, input_id) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_node_disconnect(input_node: OakNodeNode, input_id: *const c_char) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_node_disconnect(input_node, input_id)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_node_disconnect(input_node, input_id) }
}
}
/// Direct call into the `oaknode` crate (single-lib unification).
pub fn oaknode_node_copy_in_graph(node: OakNodeNode, out_command: *mut crate::handle::CHandle) -> OakNodeNode {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oaknode_node_copy_in_graph(node, out_command)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oaknode::ffi::node::oaknode_node_copy_in_graph(node, out_command) }
}
}
+92 -172
View File
@@ -16,10 +16,12 @@
//! In-crate C ABI mocks, compiled only with `--features test-stubs`.
//!
//! Each `#[no_mangle]` function here supplies a definition for the `extern
//! "C"` symbol declared in [`super::undo`] / [`super::common`] /
//! [`super::node`], so unit and integration tests link without the real
//! oaknode / oakundo / oakcommon DLLs.
//! In-crate mock implementations of the oaknode/oakundo/oakcommon bridge
//! surface, called directly by the bridge fns' `#[cfg(test)]` variants.
//! The mocks are plain Rust (no `#[no_mangle]`/`extern "C"`), so they
//! coexist with the real oaknode/oakundo/oakcommon rlibs linked in the
//! same test binary. Timeline's tests build fixtures with these mocks; the
//! production (non-test) bridge fns call the real crates.
//!
//! The mocks are intentionally simple and single-threaded. Handles box a
//! [`MockNode`] (for every node-graph kind), a [`MockMarkerList`], a
@@ -59,8 +61,7 @@ unsafe impl Send for MockUndoCommand {}
/// `oakundo_command_init`: box the vtable + userdata and hand back an owning
/// handle.
#[no_mangle]
pub extern "C" fn oakundo_command_init(
pub fn oakundo_command_init(
vtable: *const super::undo::OakUndoCommandVtable,
userdata: *mut c_void,
) -> CHandle {
@@ -78,8 +79,7 @@ pub extern "C" fn oakundo_command_init(
}
/// `oakundo_command_init_multi`: an empty multi command (no-op here).
#[no_mangle]
pub extern "C" fn oakundo_command_init_multi() -> CHandle {
pub fn oakundo_command_init_multi() -> CHandle {
make_owned(MockUndoCommand {
vtable: super::undo::OakUndoCommandVtable {
redo: None,
@@ -91,14 +91,12 @@ pub extern "C" fn oakundo_command_init_multi() -> CHandle {
}
/// `oakundo_command_multi_add_child`: no-op in the mock (returns 0).
#[no_mangle]
pub extern "C" fn oakundo_command_multi_add_child(_multi: CHandle, _child: CHandle) -> c_int {
pub fn oakundo_command_multi_add_child(_multi: CHandle, _child: CHandle) -> c_int {
0
}
/// `oakundo_command_redo_now`: invoke the redo callback.
#[no_mangle]
pub extern "C" fn oakundo_command_redo_now(command: CHandle) -> c_int {
pub fn oakundo_command_redo_now(command: CHandle) -> c_int {
// SAFETY: the handle boxes a MockUndoCommand (created by init above).
if let Some(m) = unsafe { get_mut::<MockUndoCommand>(&command) } {
if let Some(f) = m.vtable.redo {
@@ -111,8 +109,7 @@ pub extern "C" fn oakundo_command_redo_now(command: CHandle) -> c_int {
}
/// `oakundo_command_undo_now`: invoke the undo callback.
#[no_mangle]
pub extern "C" fn oakundo_command_undo_now(command: CHandle) -> c_int {
pub fn oakundo_command_undo_now(command: CHandle) -> c_int {
// SAFETY: the handle boxes a MockUndoCommand.
if let Some(m) = unsafe { get_mut::<MockUndoCommand>(&command) } {
if let Some(f) = m.vtable.undo {
@@ -125,8 +122,7 @@ pub extern "C" fn oakundo_command_undo_now(command: CHandle) -> c_int {
/// `oakundo_command_free`: run `free_fn`, drop the box and clear the handle.
/// NULL / empty-handle is a no-op.
#[no_mangle]
pub extern "C" fn oakundo_command_free(command: *mut CHandle) {
pub fn oakundo_command_free(command: *mut CHandle) {
if command.is_null() {
return;
}
@@ -153,8 +149,7 @@ pub extern "C" fn oakundo_command_free(command: *mut CHandle) {
}
/// `oakundo_stack_push`: record the push into the stack's mock (returns 0).
#[no_mangle]
pub extern "C" fn oakundo_stack_push(
pub fn oakundo_stack_push(
stack: CHandle,
_command: CHandle,
_text: *const c_char,
@@ -216,47 +211,41 @@ pub fn xml_reader_handle(nodes: Vec<MockXmlNode>) -> CHandle {
/// `oakcommon_xml_reader_init`: build an empty reader over a NUL-terminated
/// document. The mock only supports the `reader_handle` builder, so a
/// document string yields an empty reader.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_init(_data: *const c_char) -> CHandle {
pub fn oakcommon_xml_reader_init(_data: *const c_char) -> CHandle {
make_owned(MockXmlReader::new(Vec::new()))
}
/// `oakcommon_xml_reader_free`: release and clear; NULL / empty no-op.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_free(reader: *mut CHandle) {
pub fn oakcommon_xml_reader_free(reader: *mut CHandle) {
free_box::<MockXmlReader>(reader);
}
/// `oakcommon_xml_reader_skip_current_element`: no-op (flat model).
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_skip_current_element(_reader: CHandle) -> c_int {
pub fn oakcommon_xml_reader_skip_current_element(_reader: CHandle) -> c_int {
0
}
/// `oakcommon_xml_reader_read_next_start_element`: advance and write the
/// element name. Returns 1 while elements remain, else 0.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_read_next_start_element(
reader: CHandle,
name: *mut c_char,
buf_size: c_int,
) -> c_int {
pub fn oakcommon_xml_reader_read_next_start_element(reader: CHandle, found: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockXmlReader.
let Some(r) = (unsafe { get_mut::<MockXmlReader>(&reader) }) else {
return 0;
};
if found.is_null() {
return 0;
}
if r.cur >= r.nodes.len() {
// End of stream: no element yielded (0), found stays 0.
return 0;
}
let n = r.nodes[r.cur].name.clone();
r.cur += 1;
write_cstr(name, buf_size, &n);
unsafe { *found = 1 };
1
}
/// `oakcommon_xml_reader_name`: the current element's name.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_name(reader: CHandle, name: *mut c_char, buf_size: c_int) -> c_int {
pub fn oakcommon_xml_reader_name(reader: CHandle, name: *mut c_char, buf_size: c_int) -> c_int {
// SAFETY: handle boxes a MockXmlReader.
let Some(r) = (unsafe { get::<MockXmlReader>(&reader) }) else {
return 0;
@@ -269,8 +258,7 @@ pub extern "C" fn oakcommon_xml_reader_name(reader: CHandle, name: *mut c_char,
}
/// `oakcommon_xml_reader_read_element_text`: the current element's text.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_read_element_text(
pub fn oakcommon_xml_reader_read_element_text(
reader: CHandle,
text: *mut c_char,
buf_size: c_int,
@@ -288,8 +276,7 @@ pub extern "C" fn oakcommon_xml_reader_read_element_text(
/// `oakcommon_xml_reader_attribute_count`: attribute count on the current
/// element.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_attribute_count(reader: CHandle, count: *mut c_int) -> c_int {
pub fn oakcommon_xml_reader_attribute_count(reader: CHandle, count: *mut c_int) -> c_int {
// SAFETY: caller passes a valid count pointer.
let c = match unsafe { get::<MockXmlReader>(&reader) } {
Some(r) => r
@@ -305,8 +292,7 @@ pub extern "C" fn oakcommon_xml_reader_attribute_count(reader: CHandle, count: *
}
/// `oakcommon_xml_reader_attribute_name`: name of attribute `i`.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_attribute_name(
pub fn oakcommon_xml_reader_attribute_name(
reader: CHandle,
index: c_int,
name: *mut c_char,
@@ -327,8 +313,7 @@ pub extern "C" fn oakcommon_xml_reader_attribute_name(
}
/// `oakcommon_xml_reader_attribute_value`: value of attribute `i`.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_attribute_value(
pub fn oakcommon_xml_reader_attribute_value(
reader: CHandle,
index: c_int,
value: *mut c_char,
@@ -349,8 +334,7 @@ pub extern "C" fn oakcommon_xml_reader_attribute_value(
}
/// `oakcommon_xml_reader_has_error`: whether the reader errored.
#[no_mangle]
pub extern "C" fn oakcommon_xml_reader_has_error(reader: CHandle, has_error: *mut c_int) -> c_int {
pub fn oakcommon_xml_reader_has_error(reader: CHandle, has_error: *mut c_int) -> c_int {
let e = match unsafe { get::<MockXmlReader>(&reader) } {
Some(r) => r.error as c_int,
None => 0,
@@ -367,20 +351,17 @@ pub struct MockXmlWriter {
}
/// `oakcommon_xml_writer_init`: a new writer.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_init() -> CHandle {
pub fn oakcommon_xml_writer_init() -> CHandle {
make_owned(MockXmlWriter { buf: String::new() })
}
/// `oakcommon_xml_writer_free`: release and clear; NULL / empty no-op.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_free(writer: *mut CHandle) {
pub fn oakcommon_xml_writer_free(writer: *mut CHandle) {
free_box::<MockXmlWriter>(writer);
}
/// `oakcommon_xml_writer_write_start_element`: append `<name>`.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_write_start_element(writer: CHandle, name: *const c_char) -> c_int {
pub fn oakcommon_xml_writer_write_start_element(writer: CHandle, name: *const c_char) -> c_int {
// SAFETY: caller passes a NUL-terminated string.
let n = unsafe { cstr(name) };
// SAFETY: handle boxes a MockXmlWriter.
@@ -391,8 +372,7 @@ pub extern "C" fn oakcommon_xml_writer_write_start_element(writer: CHandle, name
}
/// `oakcommon_xml_writer_write_end_element`: append `</>`.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_write_end_element(writer: CHandle) -> c_int {
pub fn oakcommon_xml_writer_write_end_element(writer: CHandle) -> c_int {
// SAFETY: handle boxes a MockXmlWriter.
if let Some(w) = unsafe { get_mut::<MockXmlWriter>(&writer) } {
w.buf.push_str("</>");
@@ -401,14 +381,12 @@ pub extern "C" fn oakcommon_xml_writer_write_end_element(writer: CHandle) -> c_i
}
/// `oakcommon_xml_writer_write_end_document`: no-op.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_write_end_document(_writer: CHandle) -> c_int {
pub fn oakcommon_xml_writer_write_end_document(_writer: CHandle) -> c_int {
0
}
/// `oakcommon_xml_writer_write_attribute`: append ` key="value"`.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_write_attribute(
pub fn oakcommon_xml_writer_write_attribute(
writer: CHandle,
key: *const c_char,
value: *const c_char,
@@ -423,8 +401,7 @@ pub extern "C" fn oakcommon_xml_writer_write_attribute(
}
/// `oakcommon_xml_writer_write_characters`: append raw text.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_write_characters(writer: CHandle, text: *const c_char) -> c_int {
pub fn oakcommon_xml_writer_write_characters(writer: CHandle, text: *const c_char) -> c_int {
// SAFETY: caller passes a NUL-terminated string.
let t = unsafe { cstr(text) };
// SAFETY: handle boxes a MockXmlWriter.
@@ -435,8 +412,7 @@ pub extern "C" fn oakcommon_xml_writer_write_characters(writer: CHandle, text: *
}
/// `oakcommon_xml_writer_write_text_element`: append `<name>text</name>`.
#[no_mangle]
pub extern "C" fn oakcommon_xml_writer_write_text_element(
pub fn oakcommon_xml_writer_write_text_element(
writer: CHandle,
name: *const c_char,
text: *const c_char,
@@ -452,8 +428,7 @@ pub extern "C" fn oakcommon_xml_writer_write_text_element(
/// `oakcommon_config_get_int`: return the supplied default (the mock keeps no
/// config store; the marker default colour path uses the default).
#[no_mangle]
pub extern "C" fn oakcommon_config_get_int(
pub fn oakcommon_config_get_int(
_group: *const c_char,
_key: *const c_char,
default: c_int,
@@ -554,8 +529,7 @@ impl Default for MockNode {
}
/// `oaknode_block_clip_create`: a new clip block.
#[no_mangle]
pub extern "C" fn oaknode_block_clip_create() -> CHandle {
pub fn oaknode_block_clip_create() -> CHandle {
make_owned(MockNode {
kind: MockKind::Clip,
..Default::default()
@@ -563,8 +537,7 @@ pub extern "C" fn oaknode_block_clip_create() -> CHandle {
}
/// `oaknode_block_gap_create`: a new gap block.
#[no_mangle]
pub extern "C" fn oaknode_block_gap_create() -> CHandle {
pub fn oaknode_block_gap_create() -> CHandle {
make_owned(MockNode {
kind: MockKind::Gap,
..Default::default()
@@ -572,14 +545,12 @@ pub extern "C" fn oaknode_block_gap_create() -> CHandle {
}
/// `oaknode_block_as_node`: a borrowed generic-node view of a block.
#[no_mangle]
pub extern "C" fn oaknode_block_as_node(block: CHandle) -> CHandle {
pub fn oaknode_block_as_node(block: CHandle) -> CHandle {
ref_clone(&block)
}
/// `oaknode_block_get_in`: read the in point as an int pair.
#[no_mangle]
pub extern "C" fn oaknode_block_get_in(
pub fn oaknode_block_get_in(
block: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
@@ -593,8 +564,7 @@ pub extern "C" fn oaknode_block_get_in(
}
/// `oaknode_block_get_out`: read the out point as an int pair.
#[no_mangle]
pub extern "C" fn oaknode_block_get_out(
pub fn oaknode_block_get_out(
block: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
@@ -608,8 +578,7 @@ pub extern "C" fn oaknode_block_get_out(
}
/// `oaknode_block_get_length`: read the length as an int pair.
#[no_mangle]
pub extern "C" fn oaknode_block_get_length(
pub fn oaknode_block_get_length(
block: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
@@ -624,8 +593,7 @@ pub extern "C" fn oaknode_block_get_length(
/// `oaknode_block_set_length_and_media_out`: set length, keeping the media-in
/// fixed (the out point follows).
#[no_mangle]
pub extern "C" fn oaknode_block_set_length_and_media_out(
pub fn oaknode_block_set_length_and_media_out(
block: CHandle,
numerator: c_int,
denominator: c_int,
@@ -641,8 +609,7 @@ pub extern "C" fn oaknode_block_set_length_and_media_out(
/// `oaknode_block_set_length_and_media_in`: set length, keeping the out point
/// fixed (the media-in follows).
#[no_mangle]
pub extern "C" fn oaknode_block_set_length_and_media_in(
pub fn oaknode_block_set_length_and_media_in(
block: CHandle,
numerator: c_int,
denominator: c_int,
@@ -657,8 +624,7 @@ pub extern "C" fn oaknode_block_set_length_and_media_in(
}
/// `oaknode_block_get_enabled`: read the enabled flag.
#[no_mangle]
pub extern "C" fn oaknode_block_get_enabled(block: CHandle, enabled: *mut c_int) -> c_int {
pub fn oaknode_block_get_enabled(block: CHandle, enabled: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(b) = (unsafe { get::<MockNode>(&block) }) else {
return -1;
@@ -669,8 +635,7 @@ pub extern "C" fn oaknode_block_get_enabled(block: CHandle, enabled: *mut c_int)
}
/// `oaknode_block_set_enabled`: set the enabled flag.
#[no_mangle]
pub extern "C" fn oaknode_block_set_enabled(block: CHandle, enabled: c_int) -> c_int {
pub fn oaknode_block_set_enabled(block: CHandle, enabled: c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(b) = (unsafe { get_mut::<MockNode>(&block) }) else {
return -1;
@@ -680,8 +645,7 @@ pub extern "C" fn oaknode_block_set_enabled(block: CHandle, enabled: c_int) -> c
}
/// `oaknode_block_get_previous`: write a borrowed handle to the previous block.
#[no_mangle]
pub extern "C" fn oaknode_block_get_previous(block: CHandle, out: *mut CHandle) -> c_int {
pub fn oaknode_block_get_previous(block: CHandle, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(b) = (unsafe { get::<MockNode>(&block) }) else {
return -1;
@@ -691,8 +655,7 @@ pub extern "C" fn oaknode_block_get_previous(block: CHandle, out: *mut CHandle)
}
/// `oaknode_block_get_next`: write a borrowed handle to the next block.
#[no_mangle]
pub extern "C" fn oaknode_block_get_next(block: CHandle, out: *mut CHandle) -> c_int {
pub fn oaknode_block_get_next(block: CHandle, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(b) = (unsafe { get::<MockNode>(&block) }) else {
return -1;
@@ -702,8 +665,7 @@ pub extern "C" fn oaknode_block_get_next(block: CHandle, out: *mut CHandle) -> c
}
/// `oaknode_block_get_track`: write a borrowed handle to the owning track.
#[no_mangle]
pub extern "C" fn oaknode_block_get_track(block: CHandle, out: *mut CHandle) -> c_int {
pub fn oaknode_block_get_track(block: CHandle, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(b) = (unsafe { get::<MockNode>(&block) }) else {
return -1;
@@ -713,8 +675,7 @@ pub extern "C" fn oaknode_block_get_track(block: CHandle, out: *mut CHandle) ->
}
/// `oaknode_block_link`: link two blocks so they move together.
#[no_mangle]
pub extern "C" fn oaknode_block_link(a: CHandle, b: CHandle) -> c_int {
pub fn oaknode_block_link(a: CHandle, b: CHandle) -> c_int {
// SAFETY: both handles box MockNodes.
let (pa, pb) = unsafe {
match (get_mut::<MockNode>(&a), get_mut::<MockNode>(&b)) {
@@ -734,8 +695,7 @@ pub extern "C" fn oaknode_block_link(a: CHandle, b: CHandle) -> c_int {
}
/// `oaknode_block_unlink`: unlink two blocks.
#[no_mangle]
pub extern "C" fn oaknode_block_unlink(a: CHandle, b: CHandle) -> c_int {
pub fn oaknode_block_unlink(a: CHandle, b: CHandle) -> c_int {
// SAFETY: both handles box MockNodes.
let (pa, pb) = unsafe {
match (get_mut::<MockNode>(&a), get_mut::<MockNode>(&b)) {
@@ -753,8 +713,7 @@ pub extern "C" fn oaknode_block_unlink(a: CHandle, b: CHandle) -> c_int {
}
/// `oaknode_track_get_length`: read the track length as an int pair.
#[no_mangle]
pub extern "C" fn oaknode_track_get_length(track: CHandle, numerator: *mut c_int, denominator: *mut c_int) -> c_int {
pub fn oaknode_track_get_length(track: CHandle, numerator: *mut c_int, denominator: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(t) = (unsafe { get::<MockNode>(&track) }) else {
return -1;
@@ -770,8 +729,7 @@ pub extern "C" fn oaknode_track_get_length(track: CHandle, numerator: *mut c_int
}
/// `oaknode_track_get_sequence`: write a borrowed handle to the owning sequence.
#[no_mangle]
pub extern "C" fn oaknode_track_get_sequence(track: CHandle, out: *mut CHandle) -> c_int {
pub fn oaknode_track_get_sequence(track: CHandle, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(t) = (unsafe { get::<MockNode>(&track) }) else {
return -1;
@@ -781,8 +739,7 @@ pub extern "C" fn oaknode_track_get_sequence(track: CHandle, out: *mut CHandle)
}
/// `oaknode_track_prepend_block`: insert a block at the front of the track.
#[no_mangle]
pub extern "C" fn oaknode_track_prepend_block(track: CHandle, block: CHandle) -> c_int {
pub fn oaknode_track_prepend_block(track: CHandle, block: CHandle) -> c_int {
// SAFETY: both handles box MockNodes.
let (pt, pb) = unsafe {
match (get_mut::<MockNode>(&track), get_mut::<MockNode>(&block)) {
@@ -808,8 +765,7 @@ pub extern "C" fn oaknode_track_prepend_block(track: CHandle, block: CHandle) ->
}
/// `oaknode_track_insert_block_after`: insert `block` after `before` on `track`.
#[no_mangle]
pub extern "C" fn oaknode_track_insert_block_after(
pub fn oaknode_track_insert_block_after(
track: CHandle,
block: CHandle,
before: CHandle,
@@ -866,8 +822,7 @@ pub extern "C" fn oaknode_track_insert_block_after(
/// `oaknode_track_ripple_remove_block`: remove a block, shifting later ones
/// earlier; ownership returns to the caller (the caller keeps its handle).
#[no_mangle]
pub extern "C" fn oaknode_track_ripple_remove_block(track: CHandle, block: CHandle) -> c_int {
pub fn oaknode_track_ripple_remove_block(track: CHandle, block: CHandle) -> c_int {
// SAFETY: handles box MockNodes.
let (pt, pb) = unsafe {
match (get_mut::<MockNode>(&track), get_mut::<MockNode>(&block)) {
@@ -900,8 +855,7 @@ pub extern "C" fn oaknode_track_ripple_remove_block(track: CHandle, block: CHand
}
/// `oaknode_track_replace_block`: replace `old_block` with `new_block`.
#[no_mangle]
pub extern "C" fn oaknode_track_replace_block(
pub fn oaknode_track_replace_block(
track: CHandle,
old_block: CHandle,
new_block: CHandle,
@@ -948,20 +902,17 @@ pub extern "C" fn oaknode_track_replace_block(
}
/// `oaknode_sequence_as_node`: a borrowed generic-node view of a sequence.
#[no_mangle]
pub extern "C" fn oaknode_sequence_as_node(sequence: CHandle) -> CHandle {
pub fn oaknode_sequence_as_node(sequence: CHandle) -> CHandle {
ref_clone(&sequence)
}
/// `oaknode_sequence_from_node`: a borrowed sequence view of a generic node.
#[no_mangle]
pub extern "C" fn oaknode_sequence_from_node(node: CHandle) -> CHandle {
pub fn oaknode_sequence_from_node(node: CHandle) -> CHandle {
ref_clone(&node)
}
/// `oaknode_node_get_project`: write a borrowed handle to the owning project.
#[no_mangle]
pub extern "C" fn oaknode_node_get_project(node: CHandle, out: *mut CHandle) -> c_int {
pub fn oaknode_node_get_project(node: CHandle, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(n) = (unsafe { get::<MockNode>(&node) }) else {
return -1;
@@ -971,8 +922,7 @@ pub extern "C" fn oaknode_node_get_project(node: CHandle, out: *mut CHandle) ->
}
/// `oaknode_node_output_connection_count`: read the output connection count.
#[no_mangle]
pub extern "C" fn oaknode_node_output_connection_count(node: CHandle, out_count: *mut c_int) -> c_int {
pub fn oaknode_node_output_connection_count(node: CHandle, out_count: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(n) = (unsafe { get::<MockNode>(&node) }) else {
return -1;
@@ -983,8 +933,7 @@ pub extern "C" fn oaknode_node_output_connection_count(node: CHandle, out_count:
}
/// `oaknode_node_get_markers`: write a borrowed handle to the node's markers.
#[no_mangle]
pub extern "C" fn oaknode_node_get_markers(node: CHandle, out: *mut CHandle) -> c_int {
pub fn oaknode_node_get_markers(node: CHandle, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(n) = (unsafe { get::<MockNode>(&node) }) else {
return -1;
@@ -995,8 +944,7 @@ pub extern "C" fn oaknode_node_get_markers(node: CHandle, out: *mut CHandle) ->
}
/// `oaknode_node_get_work_area`: write a borrowed handle to the node's work area.
#[no_mangle]
pub extern "C" fn oaknode_node_get_work_area(node: CHandle, out: *mut CHandle) -> c_int {
pub fn oaknode_node_get_work_area(node: CHandle, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(n) = (unsafe { get::<MockNode>(&node) }) else {
return -1;
@@ -1007,8 +955,7 @@ pub extern "C" fn oaknode_node_get_work_area(node: CHandle, out: *mut CHandle) -
}
/// `oaknode_project_add_node`: adopt a node into a project.
#[no_mangle]
pub extern "C" fn oaknode_project_add_node(project: CHandle, node: CHandle) -> c_int {
pub fn oaknode_project_add_node(project: CHandle, node: CHandle) -> c_int {
// SAFETY: handles box MockNodes.
let (pp, pn) = unsafe {
match (get_mut::<MockNode>(&project), get_mut::<MockNode>(&node)) {
@@ -1022,8 +969,7 @@ pub extern "C" fn oaknode_project_add_node(project: CHandle, node: CHandle) -> c
}
/// `oaknode_project_remove_node`: remove a node from a project.
#[no_mangle]
pub extern "C" fn oaknode_project_remove_node(project: CHandle, node: CHandle) -> c_int {
pub fn oaknode_project_remove_node(project: CHandle, node: CHandle) -> c_int {
// SAFETY: handles box MockNodes.
let (pp, pn) = unsafe {
match (get_mut::<MockNode>(&project), get_mut::<MockNode>(&node)) {
@@ -1043,8 +989,7 @@ pub extern "C" fn oaknode_project_remove_node(project: CHandle, node: CHandle) -
/// `oaknode_command_create_remove_node`: a command that removes a node. The
/// mock returns a command handle whose redo/undo no-op, but whose free drops
/// the wrapped node handle it borrows.
#[no_mangle]
pub extern "C" fn oaknode_command_create_remove_node(node: CHandle) -> CHandle {
pub fn oaknode_command_create_remove_node(node: CHandle) -> CHandle {
let node_owned = ref_clone(&node);
make_owned(MockUndoCommand {
vtable: super::undo::OakUndoCommandVtable {
@@ -1064,8 +1009,7 @@ extern "C" fn free_owned_handle(userdata: *mut c_void) {
}
/// `oaknode_block_get_kind`: map a block's kind to an `OAKNODE_BLOCK_*` int.
#[no_mangle]
pub extern "C" fn oaknode_block_get_kind(block: CHandle, out_kind: *mut c_int) -> c_int {
pub fn oaknode_block_get_kind(block: CHandle, out_kind: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(b) = (unsafe { get::<MockNode>(&block) }) else {
return -1;
@@ -1082,8 +1026,7 @@ pub extern "C" fn oaknode_block_get_kind(block: CHandle, out_kind: *mut c_int) -
/// `oaknode_block_from_node`: a borrowed block view of a generic node (empty
/// when the node is not a block).
#[no_mangle]
pub extern "C" fn oaknode_block_from_node(node: CHandle) -> CHandle {
pub fn oaknode_block_from_node(node: CHandle) -> CHandle {
// SAFETY: handle boxes a MockNode.
let Some(n) = (unsafe { get::<MockNode>(&node) }) else {
return CHandle::null();
@@ -1096,8 +1039,7 @@ pub extern "C" fn oaknode_block_from_node(node: CHandle) -> CHandle {
/// `oaknode_block_are_linked`: whether `a` and `b` are linked (`linked` gets
/// 1/0).
#[no_mangle]
pub extern "C" fn oaknode_block_are_linked(a: CHandle, b: CHandle, linked: *mut c_int) -> c_int {
pub fn oaknode_block_are_linked(a: CHandle, b: CHandle, linked: *mut c_int) -> c_int {
// SAFETY: both handles box MockNodes.
let (pa, pb) = unsafe {
match (get::<MockNode>(&a), get::<MockNode>(&b)) {
@@ -1119,14 +1061,12 @@ pub extern "C" fn oaknode_block_are_linked(a: CHandle, b: CHandle, linked: *mut
/// `oaknode_clip_add_cache_passthrough_from`: copy render-cache passthroughs
/// from `other`. The mock has no cache model, so this is a no-op.
#[no_mangle]
pub extern "C" fn oaknode_clip_add_cache_passthrough_from(_clip: CHandle, _other: CHandle) -> c_int {
pub fn oaknode_clip_add_cache_passthrough_from(_clip: CHandle, _other: CHandle) -> c_int {
0
}
/// `oaknode_clip_get_media_in`: read a clip's media-in as an int pair.
#[no_mangle]
pub extern "C" fn oaknode_clip_get_media_in(
pub fn oaknode_clip_get_media_in(
clip: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
@@ -1143,8 +1083,7 @@ pub extern "C" fn oaknode_clip_get_media_in(
}
/// `oaknode_clip_set_media_in`: set a clip's media-in as an int pair.
#[no_mangle]
pub extern "C" fn oaknode_clip_set_media_in(
pub fn oaknode_clip_set_media_in(
clip: CHandle,
numerator: c_int,
denominator: c_int,
@@ -1161,8 +1100,7 @@ pub extern "C" fn oaknode_clip_set_media_in(
}
/// `oaknode_track_create`: a new detached track of the given type.
#[no_mangle]
pub extern "C" fn oaknode_track_create(kind: c_int) -> CHandle {
pub fn oaknode_track_create(kind: c_int) -> CHandle {
make_owned(MockNode {
kind: MockKind::Track,
track_type: kind,
@@ -1171,8 +1109,7 @@ pub extern "C" fn oaknode_track_create(kind: c_int) -> CHandle {
}
/// `oaknode_track_get_locked`: read a track's locked flag (`locked` gets 1/0).
#[no_mangle]
pub extern "C" fn oaknode_track_get_locked(track: CHandle, locked: *mut c_int) -> c_int {
pub fn oaknode_track_get_locked(track: CHandle, locked: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(t) = (unsafe { get::<MockNode>(&track) }) else {
return -1;
@@ -1186,8 +1123,7 @@ pub extern "C" fn oaknode_track_get_locked(track: CHandle, locked: *mut c_int) -
}
/// `oaknode_track_set_locked`: set a track's locked flag.
#[no_mangle]
pub extern "C" fn oaknode_track_set_locked(track: CHandle, locked: c_int) -> c_int {
pub fn oaknode_track_set_locked(track: CHandle, locked: c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(t) = (unsafe { get_mut::<MockNode>(&track) }) else {
return -1;
@@ -1200,8 +1136,7 @@ pub extern "C" fn oaknode_track_set_locked(track: CHandle, locked: c_int) -> c_i
}
/// `oaknode_track_get_block_count`: number of blocks on a track.
#[no_mangle]
pub extern "C" fn oaknode_track_get_block_count(track: CHandle, count: *mut c_int) -> c_int {
pub fn oaknode_track_get_block_count(track: CHandle, count: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(t) = (unsafe { get::<MockNode>(&track) }) else {
return -1;
@@ -1215,8 +1150,7 @@ pub extern "C" fn oaknode_track_get_block_count(track: CHandle, count: *mut c_in
}
/// `oaknode_track_get_block_at`: borrowed block at `index` on a track.
#[no_mangle]
pub extern "C" fn oaknode_track_get_block_at(track: CHandle, index: c_int, out: *mut CHandle) -> c_int {
pub fn oaknode_track_get_block_at(track: CHandle, index: c_int, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(t) = (unsafe { get::<MockNode>(&track) }) else {
return -1;
@@ -1233,8 +1167,7 @@ pub extern "C" fn oaknode_track_get_block_at(track: CHandle, index: c_int, out:
/// `oaknode_track_get_block_containing_time`: the block strictly containing
/// `time` (in < t < out); writes null and returns -1 when none.
#[no_mangle]
pub extern "C" fn oaknode_track_get_block_containing_time(
pub fn oaknode_track_get_block_containing_time(
track: CHandle,
numerator: c_int,
denominator: c_int,
@@ -1264,8 +1197,7 @@ pub extern "C" fn oaknode_track_get_block_containing_time(
/// `oaknode_track_get_nearest_block_before_or_at`: the block whose in point is
/// at or before `time` (the latest such); null when none.
#[no_mangle]
pub extern "C" fn oaknode_track_get_nearest_block_before_or_at(
pub fn oaknode_track_get_nearest_block_before_or_at(
track: CHandle,
numerator: c_int,
denominator: c_int,
@@ -1295,8 +1227,7 @@ pub extern "C" fn oaknode_track_get_nearest_block_before_or_at(
/// `oaknode_track_get_nearest_block_after_or_at`: the block whose in point is
/// at or after `time` (the earliest such); null when none.
#[no_mangle]
pub extern "C" fn oaknode_track_get_nearest_block_after_or_at(
pub fn oaknode_track_get_nearest_block_after_or_at(
track: CHandle,
numerator: c_int,
denominator: c_int,
@@ -1325,8 +1256,7 @@ pub extern "C" fn oaknode_track_get_nearest_block_after_or_at(
}
/// `oaknode_tracklist_get_type`: the track list's track type.
#[no_mangle]
pub extern "C" fn oaknode_tracklist_get_type(list: CHandle, kind: *mut c_int) -> c_int {
pub fn oaknode_tracklist_get_type(list: CHandle, kind: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(l) = (unsafe { get::<MockNode>(&list) }) else {
return -1;
@@ -1340,8 +1270,7 @@ pub extern "C" fn oaknode_tracklist_get_type(list: CHandle, kind: *mut c_int) ->
}
/// `oaknode_tracklist_get_track_count`: number of tracks in a track list.
#[no_mangle]
pub extern "C" fn oaknode_tracklist_get_track_count(list: CHandle, count: *mut c_int) -> c_int {
pub fn oaknode_tracklist_get_track_count(list: CHandle, count: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(l) = (unsafe { get::<MockNode>(&list) }) else {
return -1;
@@ -1355,8 +1284,7 @@ pub extern "C" fn oaknode_tracklist_get_track_count(list: CHandle, count: *mut c
}
/// `oaknode_tracklist_get_track_at`: borrowed track at `index` in a track list.
#[no_mangle]
pub extern "C" fn oaknode_tracklist_get_track_at(list: CHandle, index: c_int, out: *mut CHandle) -> c_int {
pub fn oaknode_tracklist_get_track_at(list: CHandle, index: c_int, out: *mut CHandle) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(l) = (unsafe { get::<MockNode>(&list) }) else {
return -1;
@@ -1374,22 +1302,19 @@ pub extern "C" fn oaknode_tracklist_get_track_at(list: CHandle, index: c_int, ou
/// `oaknode_tracklist_array_append`: append a track-array element on the
/// parent sequence. The mock keeps track lists as plain vectors, so this is a
/// no-op.
#[no_mangle]
pub extern "C" fn oaknode_tracklist_array_append(_list: CHandle) -> c_int {
pub fn oaknode_tracklist_array_append(_list: CHandle) -> c_int {
0
}
/// `oaknode_tracklist_array_remove_last`: remove the last track-array element.
/// No-op in the mock (see `oaknode_tracklist_array_append`).
#[no_mangle]
pub extern "C" fn oaknode_tracklist_array_remove_last(_list: CHandle) -> c_int {
pub fn oaknode_tracklist_array_remove_last(_list: CHandle) -> c_int {
0
}
/// `oaknode_sequence_get_track_list`: the borrowed per-type track list, or
/// null when the sequence has no list of that type.
#[no_mangle]
pub extern "C" fn oaknode_sequence_get_track_list(
pub fn oaknode_sequence_get_track_list(
sequence: CHandle,
kind: c_int,
out: *mut CHandle,
@@ -1416,8 +1341,7 @@ pub extern "C" fn oaknode_sequence_get_track_list(
/// `oaknode_sequence_get_all_track_count`: total connected tracks across all
/// types.
#[no_mangle]
pub extern "C" fn oaknode_sequence_get_all_track_count(sequence: CHandle, count: *mut c_int) -> c_int {
pub fn oaknode_sequence_get_all_track_count(sequence: CHandle, count: *mut c_int) -> c_int {
// SAFETY: handle boxes a MockNode.
let Some(s) = (unsafe { get::<MockNode>(&sequence) }) else {
return -1;
@@ -1440,8 +1364,7 @@ pub extern "C" fn oaknode_sequence_get_all_track_count(sequence: CHandle, count:
/// `oaknode_sequence_get_all_track_at`: borrowed track at `index` across the
/// flat, all-types track list.
#[no_mangle]
pub extern "C" fn oaknode_sequence_get_all_track_at(
pub fn oaknode_sequence_get_all_track_at(
sequence: CHandle,
index: c_int,
out: *mut CHandle,
@@ -1471,8 +1394,7 @@ pub extern "C" fn oaknode_sequence_get_all_track_at(
/// `oaknode_node_connect`: connect `output_node` to `input_node`'s `input_id`.
/// The mock tracks only the output connection count (input edge table is not
/// modelled), so this bumps `output_conns`.
#[no_mangle]
pub extern "C" fn oaknode_node_connect(
pub fn oaknode_node_connect(
output_node: CHandle,
_input_node: CHandle,
_input_id: *const c_char,
@@ -1487,16 +1409,14 @@ pub extern "C" fn oaknode_node_connect(
/// `oaknode_node_disconnect`: remove the edge feeding `input_node`'s
/// `input_id`. The mock has no input-edge table, so this is a no-op.
#[no_mangle]
pub extern "C" fn oaknode_node_disconnect(_input_node: CHandle, _input_id: *const c_char) -> c_int {
pub fn oaknode_node_disconnect(_input_node: CHandle, _input_id: *const c_char) -> c_int {
0
}
/// `oaknode_node_copy_in_graph`: clone `node` in its graph; `*out_command`
/// receives an owned undo handle. The mock clones the node box and hands back
/// a no-op command.
#[no_mangle]
pub extern "C" fn oaknode_node_copy_in_graph(node: CHandle, out_command: *mut CHandle) -> CHandle {
pub fn oaknode_node_copy_in_graph(node: CHandle, out_command: *mut CHandle) -> CHandle {
// SAFETY: handle boxes a MockNode.
let Some(n) = (unsafe { get::<MockNode>(&node) }) else {
return CHandle::null();
+102 -24
View File
@@ -14,6 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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/>.
//! oakundo C ABI imports (`include/undo/undocommand.h`). Undo commands are
//! created through the C ABI vtable (`oakundo_command_init` with Rust
//! closures as `userdata`) — timeline does not subclass the C++
@@ -27,30 +43,92 @@ use crate::handle::CHandle;
/// `OakUndoCommandVtable` — callback table backing a caller-defined undo
/// command (undocommand.h). Any callback may be NULL (a NULL redo/undo makes
/// that direction a no-op); `free_fn` runs when the command is destroyed.
#[repr(C)]
pub struct OakUndoCommandVtable {
/// `redo` callback (NULL = no-op).
pub redo: Option<unsafe extern "C" fn(*mut c_void)>,
/// `undo` callback (NULL = no-op).
pub undo: Option<unsafe extern "C" fn(*mut c_void)>,
/// `free_fn` — releases `userdata` on destruction.
pub free_fn: Option<unsafe extern "C" fn(*mut c_void)>,
/// Single-lib unification: aliases the oakundo crate's vtable POD.
pub type OakUndoCommandVtable = oakundo::undocommand::OakUndoCommandVtable;
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_init(vtable: *const OakUndoCommandVtable, userdata: *mut c_void) -> CHandle {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakundo_command_init(vtable, userdata)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakundo::ffi::command::oakundo_command_init(vtable, userdata) }
}
}
extern "C" {
/// `oakundo_command_init` — create a command backed by C callbacks; takes
/// ownership of `userdata`, copies `vtable`.
pub fn oakundo_command_init(vtable: *const OakUndoCommandVtable, userdata: *mut c_void) -> CHandle;
/// `oakundo_command_init_multi` — an empty multi command.
pub fn oakundo_command_init_multi() -> CHandle;
/// `oakundo_command_multi_add_child` — add a child to a multi command.
pub fn oakundo_command_multi_add_child(multi: CHandle, child: CHandle) -> c_int;
/// `oakundo_command_redo_now` — run the command's redo outside a stack.
pub fn oakundo_command_redo_now(command: CHandle) -> c_int;
/// `oakundo_command_undo_now` — run the command's undo outside a stack.
pub fn oakundo_command_undo_now(command: CHandle) -> c_int;
/// `oakundo_command_free` — release one reference; NULL is a no-op.
pub fn oakundo_command_free(command: *mut CHandle);
/// `oakundo_stack_push` — push a command onto a facade-owned stack.
pub fn oakundo_stack_push(stack: CHandle, command: CHandle, text: *const c_char) -> c_int;
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_init_multi() -> CHandle {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakundo_command_init_multi()
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakundo::ffi::command::oakundo_command_init_multi() }
}
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_multi_add_child(multi: CHandle, child: CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakundo_command_multi_add_child(multi, child)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakundo::ffi::command::oakundo_command_multi_add_child(multi, child) }
}
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_redo_now(command: CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakundo_command_redo_now(command)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakundo::ffi::command::oakundo_command_redo_now(command) }
}
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_undo_now(command: CHandle) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakundo_command_undo_now(command)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakundo::ffi::command::oakundo_command_undo_now(command) }
}
}
/// Direct call into the `oakundo` crate (single-lib unification).
pub fn oakundo_command_free(command: *mut CHandle) {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakundo_command_free(command)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakundo::ffi::command::oakundo_command_free(command) }
}
}
/// Direct call into the `oakundo` crate (single-lib unification; the C ABI
/// name is `oakundo_undostack_push` — kept as `oakundo_stack_push` here for
/// source compatibility).
pub fn oakundo_stack_push(stack: CHandle, command: CHandle, text: *const c_char) -> c_int {
#[cfg(any(test, feature = "test-stubs"))]
{
super::teststubs::oakundo_stack_push(stack, command, text)
}
#[cfg(not(any(test, feature = "test-stubs")))]
{
unsafe { oakundo::ffi::undostack::oakundo_undostack_push(stack, command, text) }
}
}
+10 -4
View File
@@ -976,12 +976,18 @@ fn write_cstr(dst: *mut c_char, size: c_int, s: &str) {
/// Read the next start element's name from `reader`; empty at EOF/error.
fn xml_read_next(reader: &H) -> String {
// Advance to the next start element (the real ABI writes a 1/0 flag).
let mut found = 0;
// SAFETY: `found` is a valid out pointer; `reader` is a valid handle.
let r = unsafe { xml::oakcommon_xml_reader_read_next_start_element(reader.clone(), &mut found) };
if r <= 0 || found == 0 {
return String::new();
}
// Read the current element's name (two-stage).
let mut buf = [0 as c_char; 4096];
// SAFETY: `buf` is a valid writable buffer; `reader` is a valid handle.
let r = unsafe {
xml::oakcommon_xml_reader_read_next_start_element(reader.clone(), buf.as_mut_ptr(), buf.len() as c_int)
};
if r <= 0 {
let n = unsafe { xml::oakcommon_xml_reader_name(reader.clone(), buf.as_mut_ptr(), buf.len() as c_int) };
if n <= 0 {
return String::new();
}
// SAFETY: the reader wrote a NUL-terminated string into `buf`.
+1 -1
View File
@@ -105,7 +105,7 @@ pub fn free_detached_handle(h: *mut CHandle) {
}
/// Read a block time field as a `Rational`.
fn block_pair(b: CHandle, f: unsafe extern "C" fn(CHandle, *mut i32, *mut i32) -> i32) -> Rational {
fn block_pair(b: CHandle, f: fn(CHandle, *mut i32, *mut i32) -> i32) -> Rational {
let mut n = 0;
let mut d = 0;
// SAFETY: `n`/`d` are valid out pointers.
+11 -3
View File
@@ -208,8 +208,9 @@ fn xml_bridge_validate_handles() {
// A null reader is an invalid argument: read fails cleanly (0).
let mut buf = [0i8; 64];
let mut found = 0;
assert_eq!(
unsafe { oakcommon_xml_reader_read_next_start_element(CHandle::null(), buf.as_mut_ptr(), 64) },
unsafe { oakcommon_xml_reader_read_next_start_element(CHandle::null(), &mut found) },
0
);
@@ -217,8 +218,9 @@ fn xml_bridge_validate_handles() {
// elements it reports end-of-stream for every accessor.
let mut reader = unsafe { oakcommon_xml_reader_init(b"<project/>\0".as_ptr() as *const std::ffi::c_char) };
assert!(!reader.is_null());
let mut found = 0;
assert_eq!(
unsafe { oakcommon_xml_reader_read_next_start_element(reader.clone(), buf.as_mut_ptr(), 64) },
unsafe { oakcommon_xml_reader_read_next_start_element(reader.clone(), &mut found) },
0
);
assert_eq!(unsafe { oakcommon_xml_reader_name(reader.clone(), buf.as_mut_ptr(), 64) }, 0);
@@ -242,8 +244,14 @@ fn xml_bridge_validate_handles() {
text: "hello".to_string(),
attrs: vec![("color".to_string(), "red".to_string())],
}]);
let mut found = 0;
assert_eq!(
unsafe { oakcommon_xml_reader_read_next_start_element(r.clone(), buf.as_mut_ptr(), 64) },
unsafe { oakcommon_xml_reader_read_next_start_element(r.clone(), &mut found) },
1
);
assert_eq!(found, 1);
assert_eq!(
unsafe { oakcommon_xml_reader_name(r.clone(), buf.as_mut_ptr(), 64) },
1
);
assert_eq!(
+4 -2
View File
@@ -107,8 +107,9 @@ fn xml_reader_accessors_missing() {
let mut buf = [0 as c_char; 16];
// No current element: every accessor returns 0.
let mut r = xml_reader_handle(Vec::new());
let mut found = 0;
unsafe { oaktimeline::bridge::common::oakcommon_xml_reader_read_next_start_element(
r.clone(), buf.as_mut_ptr(), 16,
r.clone(), &mut found,
) };
assert_eq!(unsafe { oakcommon_xml_reader_name(r.clone(), buf.as_mut_ptr(), 16) }, 0);
assert_eq!(unsafe { oakcommon_xml_reader_read_element_text(r.clone(), buf.as_mut_ptr(), 16) }, 0);
@@ -126,8 +127,9 @@ fn xml_reader_accessors_missing() {
text: String::new(),
attrs: Vec::new(),
}]);
let mut found = 0;
unsafe { oaktimeline::bridge::common::oakcommon_xml_reader_read_next_start_element(
r2.clone(), buf.as_mut_ptr(), 16,
r2.clone(), &mut found,
) };
unsafe { oakcommon_xml_reader_attribute_count(r2.clone(), &mut count) };
assert_eq!(count, 0);