refactor: workspace layout — crates/, app at root, legacy C++ removed

Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
This commit is contained in:
2026-08-10 20:24:25 +08:00
parent f8540e3892
commit 013a175707
4212 changed files with 8331 additions and 2274987 deletions
+576
View File
@@ -0,0 +1,576 @@
// 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/>.
//! `engine/include/oakengine/audio.h` over the oakaudio module.
//!
//! The engine API is static (the singleton is implicit); the oakaudio C
//! ABI passes the manager handle explicitly, so every family call goes
//! through [`manager()`] (a borrowed handle; empty when no instance
//! exists — engine semantics then report `paNoDevice`/error as
//! documented). The borrowed `OakAudioParams*` handles are read through
//! the liboakcore `oakcore_audioparams_*` accessors.
use std::ffi::{c_char, c_double, c_int, c_void};
use crate::bridge::audio as a;
use crate::error::Error;
use crate::handle::{
box_handle, free_box, guard, guard_i64, guard_void, unbox, CHandle, OakEngineAudioProcessor,
};
/// paNoDevice — no audio device selected.
const PA_NO_DEVICE: i64 = -1;
/// Borrowed handle of the AudioManager singleton (empty when none).
fn manager() -> CHandle {
unsafe { a::oakaudio_manager_instance() }
}
/// Borrowed handle of the AudioManager singleton for other facade
/// families (empty ctx == NULL when none).
pub(crate) fn audio_manager_handle_raw() -> CHandle {
manager()
}
/// `oakengine_audio_create_instance` — create the singleton (no-op when
/// it already exists).
#[no_mangle]
pub extern "C" fn oakengine_audio_create_instance() -> c_int {
guard(|| Error::from_module(unsafe { a::oakaudio_manager_create_instance() }))
}
/// `oakengine_audio_destroy_instance` — destroy the singleton (no-op when
/// none exists).
#[no_mangle]
pub extern "C" fn oakengine_audio_destroy_instance() -> c_int {
guard_void(|| unsafe {
a::oakaudio_manager_destroy_instance();
});
crate::error::OAKENGINE_OK
}
/// `oakengine_audio_manager_handle` — borrowed token of the singleton
/// (NULL when none); only for event-subscription use, never freed.
#[no_mangle]
pub extern "C" fn oakengine_audio_manager_handle() -> *mut c_void {
let m = manager();
if m.is_null() {
std::ptr::null_mut()
} else {
m.ctx
}
}
/// `oakengine_audio_get_output_device` — paNoDevice when none/no instance.
#[no_mangle]
pub extern "C" fn oakengine_audio_get_output_device() -> i64 {
guard_i64(|| {
let m = manager();
if m.is_null() {
return Ok(PA_NO_DEVICE);
}
Ok(i64::from(unsafe { a::oakaudio_manager_get_output_device(m) }))
})
}
/// `oakengine_audio_set_output_device`.
#[no_mangle]
pub extern "C" fn oakengine_audio_set_output_device(device: i64) -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_set_output_device(m, device as c_int) })
})
}
/// `oakengine_audio_get_input_device` — paNoDevice when none/no instance.
#[no_mangle]
pub extern "C" fn oakengine_audio_get_input_device() -> i64 {
guard_i64(|| {
let m = manager();
if m.is_null() {
return Ok(PA_NO_DEVICE);
}
Ok(i64::from(unsafe { a::oakaudio_manager_get_input_device(m) }))
})
}
/// `oakengine_audio_set_input_device`.
#[no_mangle]
pub extern "C" fn oakengine_audio_set_input_device(device: i64) -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_set_input_device(m, device as c_int) })
})
}
/// `oakengine_audio_hard_reset` — re-initialize PortAudio and refresh the
/// device lists.
#[no_mangle]
pub extern "C" fn oakengine_audio_hard_reset() -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_hard_reset(m) })
})
}
/// `oakengine_audio_clear_buffered_output`.
#[no_mangle]
pub extern "C" fn oakengine_audio_clear_buffered_output() -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_clear_buffered_output(m) })
})
}
/// `oakengine_audio_push_to_output` — queue interleaved samples described
/// by the borrowed `OakAudioParams*` handle.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_push_to_output(
params: *const c_void,
samples: *const c_char,
samples_size: i64,
error_buf: *mut c_char,
error_buf_size: c_int,
) -> c_int {
guard(|| unsafe {
let m = manager();
if m.is_null() || params.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
let rate = a::oakcore_audioparams_sample_rate(params);
let layout = a::oakcore_audioparams_channel_layout(params);
let format = a::oakcore_audioparams_format(params);
Error::from_module(a::oakaudio_manager_push_to_output(
m,
rate,
layout,
format,
samples,
samples_size,
error_buf,
error_buf_size,
))
})
}
/// `oakengine_audio_stop_recording`.
#[no_mangle]
pub extern "C" fn oakengine_audio_stop_recording() -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_stop_recording(m) })
})
}
/// `oakengine_audio_stop_output`.
#[no_mangle]
pub extern "C" fn oakengine_audio_stop_output() -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_stop_output(m) })
})
}
/// `oakengine_audio_reset_output_clock`.
#[no_mangle]
pub extern "C" fn oakengine_audio_reset_output_clock() -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_reset_output_clock(m) })
})
}
/// `oakengine_audio_set_output_notify_interval`.
#[no_mangle]
pub extern "C" fn oakengine_audio_set_output_notify_interval(bytes: i64) -> c_int {
guard(|| {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
Error::from_module(unsafe { a::oakaudio_manager_set_output_notify_interval(m, bytes) })
})
}
/// `oakengine_audio_start_recording` — takes ownership of `params`
/// (the handle is destroyed when the recording ends).
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_start_recording(
params: *mut c_void,
error_buf: *mut c_char,
error_buf_size: c_int,
) -> c_int {
guard(|| unsafe {
let m = manager();
if m.is_null() {
return Err(Error::Failed("no AudioManager instance".into()));
}
if params.is_null() {
return Err(Error::Invalid);
}
let rc = a::oakaudio_manager_start_recording(
m,
params.cast::<a::EncodingParams>(),
error_buf,
error_buf_size,
);
Error::from_module(rc)
})
}
// ---------------------------------------------------------------------------
// Audio synchronization
// ---------------------------------------------------------------------------
/// `oakengine_audio_estimate_envelope_offset` — estimate the sample offset
/// between two RMS envelopes.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_estimate_envelope_offset(
reference: *const c_double,
reference_len: c_int,
candidate: *const c_double,
candidate_len: c_int,
reference_valid: *const u8,
_reference_valid_len: c_int,
candidate_valid: *const u8,
_candidate_valid_len: c_int,
window_samples: u64,
max_offset_windows: i64,
out: *mut OakAudioWaveformOffset,
) -> c_int {
guard(|| unsafe {
if reference.is_null() || candidate.is_null() || out.is_null() {
return Err(Error::Invalid);
}
let mut result = a::OffsetResult {
offset_samples: 0,
confidence: 0.0,
valid: 0,
};
Error::from_module(a::oakaudio_sync_estimate_envelope_offset(
reference,
reference_len,
candidate,
candidate_len,
reference_valid,
candidate_valid,
window_samples,
max_offset_windows,
&mut result,
))?;
(*out).offset_samples = result.offset_samples;
(*out).confidence = result.confidence;
(*out).valid = result.valid;
Ok(())
})
}
/// `oakengine_audio_estimate_stretch_and_offset` — rate + offset
/// correlation.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_estimate_stretch_and_offset(
reference: *const c_double,
reference_len: c_int,
candidate: *const c_double,
candidate_len: c_int,
reference_valid: *const u8,
_reference_valid_len: c_int,
candidate_valid: *const u8,
_candidate_valid_len: c_int,
window_samples: u64,
max_offset_windows: i64,
min_rate: c_double,
max_rate: c_double,
rate_step: c_double,
out: *mut OakAudioWaveformStretchOffset,
) -> c_int {
guard(|| unsafe {
if reference.is_null() || candidate.is_null() || out.is_null() {
return Err(Error::Invalid);
}
let mut result = a::StretchOffsetResult {
rate: 0.0,
offset_samples: 0,
confidence: 0.0,
valid: 0,
};
Error::from_module(a::oakaudio_sync_estimate_stretch_and_offset(
reference,
reference_len,
candidate,
candidate_len,
reference_valid,
candidate_valid,
window_samples,
max_offset_windows,
min_rate,
max_rate,
rate_step,
&mut result,
))?;
(*out).rate = result.rate;
(*out).offset_samples = result.offset_samples;
(*out).confidence = result.confidence;
(*out).valid = result.valid;
Ok(())
})
}
/// `oakengine_audio_sync_place_by_source_time` — timeline placement from
/// source timecodes.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_sync_place_by_source_time(
reference: *const OakAudioSyncSourceClip,
candidate: *const OakAudioSyncSourceClip,
reference_timeline_in_num: i64,
reference_timeline_in_den: i64,
out: *mut OakAudioSyncPlacement,
) -> c_int {
guard(|| unsafe {
if reference.is_null() || candidate.is_null() || out.is_null() {
return Err(Error::Invalid);
}
let mut num: i64 = 0;
let mut den: i64 = 0;
let mut valid: c_int = 0;
Error::from_module(a::oakaudio_sync_place_by_source_time(
reference.cast::<a::SourceClip>(),
candidate.cast::<a::SourceClip>(),
reference_timeline_in_num,
reference_timeline_in_den,
&mut num,
&mut den,
&mut valid,
))?;
(*out).timeline_in_num = num;
(*out).timeline_in_den = den;
(*out).valid = valid;
Ok(())
})
}
/// `oakengine_audio_sync_place_by_waveform_offset` — timeline placement
/// from a waveform offset.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_sync_place_by_waveform_offset(
reference_timeline_in_num: i64,
reference_timeline_in_den: i64,
candidate_offset_samples: i64,
sample_rate: c_int,
out: *mut OakAudioSyncPlacement,
) -> c_int {
guard(|| unsafe {
if out.is_null() {
return Err(Error::Invalid);
}
let mut num: i64 = 0;
let mut den: i64 = 0;
let mut valid: c_int = 0;
Error::from_module(a::oakaudio_sync_place_by_waveform_offset(
reference_timeline_in_num,
reference_timeline_in_den,
candidate_offset_samples,
sample_rate,
&mut num,
&mut den,
&mut valid,
))?;
(*out).timeline_in_num = num;
(*out).timeline_in_den = den;
(*out).valid = valid;
Ok(())
})
}
// ---------------------------------------------------------------------------
// Audio processor
// ---------------------------------------------------------------------------
/// `oakengine_audio_processor_create`.
#[no_mangle]
pub extern "C" fn oakengine_audio_processor_create() -> *mut OakEngineAudioProcessor {
crate::handle::guard_ptr(|| {
let p = unsafe { a::oakaudio_processor_init() };
if p.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_handle::<OakEngineAudioProcessor>(p))
})
}
/// `oakengine_audio_processor_free` — NULL no-op.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_processor_free(p: *mut OakEngineAudioProcessor) {
guard_void(|| unsafe {
free_box(p);
})
}
/// `oakengine_audio_processor_open` — open the conversion graph; `from`/
/// `to` are borrowed `OakAudioParams*` handles (read via oakcore).
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_processor_open(
p: *mut OakEngineAudioProcessor,
from: *const c_void,
to: *const c_void,
tempo: c_double,
) -> c_int {
guard(|| unsafe {
let handle = unbox(p)?;
if from.is_null() || to.is_null() {
return Err(Error::Invalid);
}
let in_rate = a::oakcore_audioparams_sample_rate(from);
let in_layout = a::oakcore_audioparams_channel_layout(from);
let in_format = a::oakcore_audioparams_format(from);
let out_rate = a::oakcore_audioparams_sample_rate(to);
let out_layout = a::oakcore_audioparams_channel_layout(to);
let out_format = a::oakcore_audioparams_format(to);
Error::from_module(a::oakaudio_processor_open(
handle,
in_rate,
in_layout,
in_format,
out_rate,
out_layout,
out_format,
tempo,
))
})
}
/// `oakengine_audio_processor_close` — NULL/not-open no-op.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_processor_close(p: *mut OakEngineAudioProcessor) -> c_int {
guard(|| unsafe {
if p.is_null() {
return Ok(());
}
let handle = unbox(p)?;
Error::from_module(a::oakaudio_processor_close(handle))
})
}
/// `oakengine_audio_processor_is_open` — 1 when open, 0 when NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_processor_is_open(p: *mut OakEngineAudioProcessor) -> c_int {
crate::handle::guard_int(|| unsafe {
if p.is_null() {
return Ok(0);
}
let handle = unbox(p)?;
Ok(a::oakaudio_processor_is_open(handle))
})
}
/// `oakengine_audio_processor_convert` — **not backed**: the oakaudio
/// module's processor converts planar→planar, while the engine contract
/// is planar→packed with an owned output buffer. Returns
/// `OAKENGINE_E_FAILED` until the module exposes a packed-output
/// converter.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_processor_convert(
_p: *mut OakEngineAudioProcessor,
_in: *mut *mut f32,
_nb_in_samples: c_int,
_out_data: *mut *const c_void,
_out_size: *mut c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_audio_processor_output_params` — **not backed**: the
/// oakaudio module has no output-params getter. Returns NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_processor_output_params(
_p: *mut OakEngineAudioProcessor,
) -> *mut c_void {
std::ptr::null_mut()
}
/// `engine/include/oakengine/audio.h` — envelope-offset result.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakAudioWaveformOffset {
/// Offset in samples.
pub offset_samples: i64,
/// Correlation confidence.
pub confidence: c_double,
/// 1 when usable.
pub valid: c_int,
}
/// `engine/include/oakengine/audio.h` — rate+offset result.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakAudioWaveformStretchOffset {
/// Playback rate.
pub rate: c_double,
/// Offset in samples.
pub offset_samples: i64,
/// Correlation confidence.
pub confidence: c_double,
/// 1 when usable.
pub valid: c_int,
}
/// `engine/include/oakengine/audio.h` — source-clip description.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakAudioSyncSourceClip {
/// Source start time num.
pub source_start_time_num: i64,
/// Source start time den.
pub source_start_time_den: i64,
/// Media in num.
pub media_in_num: i64,
/// Media in den.
pub media_in_den: i64,
/// 1 when source start time is meaningful.
pub has_source_start_time: c_int,
}
/// `engine/include/oakengine/audio.h` — timeline placement result.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakAudioSyncPlacement {
/// Timeline in-point num.
pub timeline_in_num: i64,
/// Timeline in-point den.
pub timeline_in_den: i64,
/// 1 when usable.
pub valid: c_int,
}
+302
View File
@@ -0,0 +1,302 @@
// 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/>.
//! oakaudio C ABI bridge: direct Rust calls into the `oakaudio` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oakaudio`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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/>.
//! oakaudio C ABI imports, mirroring the oakaudio crate's exports
//! (`src/audio/rust/src/ffi.rs`; headers `include/audio/*.h`), plus the
//! liboakcore `oakcore_audioparams_*` readers used to convert the
//! engine's borrowed `OakAudioParams*` handles.
use std::ffi::{c_char, c_double, c_int, c_void};
use crate::handle::CHandle;
/// `oakaudio_offsetresult` C ABI POD. Single-lib unification: aliases
/// the oakaudio crate's struct (identical layout).
pub type OffsetResult = oakaudio::ffi::sync::OffsetResult;
/// `oakaudio_stretchoffsetresult` C ABI POD. Single-lib unification: aliases
/// the oakaudio crate's struct (identical layout).
pub type StretchOffsetResult = oakaudio::ffi::sync::StretchOffsetResult;
/// `oakaudio_sourceclip` C ABI POD. Single-lib unification: aliases
/// the oakaudio crate's struct (identical layout).
pub type SourceClip = oakaudio::ffi::sync::SourceClip;
/// `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;
pub fn oakcore_audioparams_free(params: *mut c_void);
pub fn oakcore_audioparams_sample_rate(params: *const c_void) -> c_int;
pub fn oakcore_audioparams_channel_layout(params: *const c_void) -> u64;
pub fn oakcore_audioparams_format(params: *const c_void) -> c_int;
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_create_instance() -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_create_instance() }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_destroy_instance() {
unsafe { oakaudio::ffi::manager::oakaudio_manager_destroy_instance() }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_instance() -> CHandle {
unsafe { oakaudio::ffi::manager::oakaudio_manager_instance() }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_free(_self: *mut CHandle) {
unsafe { oakaudio::ffi::manager::oakaudio_manager_free(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_set_output_notify_interval(_self: CHandle, bytes: i64) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_set_output_notify_interval(_self, bytes) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_push_to_output(
_self: CHandle,
rate: c_int,
layout: u64,
format: c_int,
samples: *const c_char,
samples_size: i64,
error_buf: *mut c_char,
error_buf_size: c_int,
) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_push_to_output(_self, rate, layout, format, samples, samples_size, error_buf, error_buf_size) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_clear_buffered_output(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_clear_buffered_output(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_stop_output(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_stop_output(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_reset_output_clock(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_reset_output_clock(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_get_output_device(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_get_output_device(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_set_output_device(_self: CHandle, device: c_int) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_set_output_device(_self, device) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_get_input_device(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_get_input_device(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_set_input_device(_self: CHandle, device: c_int) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_set_input_device(_self, device) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_hard_reset(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_hard_reset(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
/// `oakaudio_manager_start_recording` — crosses the encoding-params C ABI
/// POD (the facade keeps its own opaque mirror; the module's
/// `oakaudio::bridge::codec::EncodingParams`). Kept as a link-time
/// `extern "C"` declaration against the frozen module C ABI.
pub fn oakaudio_manager_start_recording(
_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
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_stop_recording(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_stop_recording(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_manager_seconds(_self: CHandle, out: *mut c_double) -> c_int {
unsafe { oakaudio::ffi::manager::oakaudio_manager_seconds(_self, out) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_processor_init() -> CHandle {
unsafe { oakaudio::ffi::processor::oakaudio_processor_init() }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_processor_free(_self: *mut CHandle) {
unsafe { oakaudio::ffi::processor::oakaudio_processor_free(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_processor_open(
_self: CHandle,
in_rate: c_int,
in_layout: u64,
in_format: c_int,
out_rate: c_int,
out_layout: u64,
out_format: c_int,
speed: c_double,
) -> c_int {
unsafe { oakaudio::ffi::processor::oakaudio_processor_open(_self, in_rate, in_layout, in_format, out_rate, out_layout, out_format, speed) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_processor_close(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::processor::oakaudio_processor_close(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_processor_is_open(_self: CHandle) -> c_int {
unsafe { oakaudio::ffi::processor::oakaudio_processor_is_open(_self) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_sync_estimate_envelope_offset(
reference: *const c_double,
reference_len: c_int,
candidate: *const c_double,
candidate_len: c_int,
reference_valid: *const u8,
candidate_valid: *const u8,
window_samples: u64,
max_offset_windows: i64,
out: *mut OffsetResult,
) -> c_int {
unsafe { oakaudio::ffi::sync::oakaudio_sync_estimate_envelope_offset(reference, reference_len, candidate, candidate_len, reference_valid, candidate_valid, window_samples, max_offset_windows, out) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_sync_estimate_stretch_and_offset(
reference: *const c_double,
reference_len: c_int,
candidate: *const c_double,
candidate_len: c_int,
reference_valid: *const u8,
candidate_valid: *const u8,
window_samples: u64,
max_offset_windows: i64,
min_rate: c_double,
max_rate: c_double,
rate_step: c_double,
out: *mut StretchOffsetResult,
) -> c_int {
unsafe { oakaudio::ffi::sync::oakaudio_sync_estimate_stretch_and_offset(reference, reference_len, candidate, candidate_len, reference_valid, candidate_valid, window_samples, max_offset_windows, min_rate, max_rate, rate_step, out) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_sync_place_by_source_time(
reference: *const SourceClip,
candidate: *const SourceClip,
reference_timeline_in_num: i64,
reference_timeline_in_den: i64,
out_num: *mut i64,
out_den: *mut i64,
out_valid: *mut c_int,
) -> c_int {
unsafe { oakaudio::ffi::sync::oakaudio_sync_place_by_source_time(reference, candidate, reference_timeline_in_num, reference_timeline_in_den, out_num, out_den, out_valid) }
}
/// Direct call into the `oakaudio` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakaudio_sync_place_by_waveform_offset(
reference_timeline_in_num: i64,
reference_timeline_in_den: i64,
candidate_offset_samples: i64,
sample_rate: c_int,
out_num: *mut i64,
out_den: *mut i64,
out_valid: *mut c_int,
) -> c_int {
unsafe { oakaudio::ffi::sync::oakaudio_sync_place_by_waveform_offset(reference_timeline_in_num, reference_timeline_in_den, candidate_offset_samples, sample_rate, out_num, out_den, out_valid) }
}
+233
View File
@@ -0,0 +1,233 @@
// 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 bridge: direct Rust calls into the `oakcodec` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oakcodec`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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, mirroring the oakcodec crate's exports
//! (`src/codec/rust/src/ffi/{format,encoder}.rs`; headers
//! `include/codec/{format,encoder}.h`). Also carries the
//! `oakcodec_encoding_params` POD the facade's encoding-params handle
//! wraps, and the oakaudio recording-params pointer pass-through.
use std::ffi::{c_char, c_int};
use crate::handle::CHandle;
/// `include/codec/encoder.h` — the encoding-params POD, a complete mirror
/// `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() }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_count() -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_count() }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_name(format: c_int, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_name(format, buf, buf_size) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_extension(
format: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_extension(format, buf, buf_size) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_video_codec_count(format: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_video_codec_count(format) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_video_codec_at(format: c_int, index: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_video_codec_at(format, index) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_audio_codec_count(format: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_audio_codec_count(format) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_audio_codec_at(format: c_int, index: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_audio_codec_at(format, index) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_subtitle_codec_count(format: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_subtitle_codec_count(format) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_format_subtitle_codec_at(format: c_int, index: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_format_subtitle_codec_at(format, index) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_codec_name(codec: c_int, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_codec_name(codec, buf, buf_size) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_codec_is_still_image(codec: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_codec_is_still_image(codec) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_codec_is_lossless(codec: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_codec_is_lossless(codec) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_pix_fmt_count(format: c_int, codec: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_pix_fmt_count(format, codec) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_pix_fmt_at(
format: c_int,
codec: c_int,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_pix_fmt_at(format, codec, index, buf, buf_size) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_pix_fmt_index(codec: c_int, pix_fmt: *const c_char) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_pix_fmt_index(codec, pix_fmt) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_sample_format_count(format: c_int, codec: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_sample_format_count(format, codec) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_sample_format_at(format: c_int, codec: c_int, index: c_int) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_sample_format_at(format, codec, index) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_filename_contains_digit_placeholder(filename: *const c_char) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_filename_contains_digit_placeholder(filename) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_image_sequence_digit_count(filename: *const c_char) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_image_sequence_digit_count(filename) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoding_filename_remove_digit_placeholder(
filename: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcodec::ffi::format::oakcodec_encoding_filename_remove_digit_placeholder(filename, buf, buf_size) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
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 {
unsafe { oakcodec::ffi::encoder::oakcodec_encoding_generate_matrix(method, src_width, src_height, dst_width, dst_height, out_matrix) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
/// `oakcodec_encoder_init` — crosses the encoding-params C ABI POD
/// (`oakcodec_encoding_params`; the facade keeps its own POD mirror).
/// 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::ffi::encoder::oakcodec_encoder_init(params) }
}
/// Direct call into the `oakcodec` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcodec_encoder_free(encoder: *mut CHandle) {
unsafe { oakcodec::ffi::encoder::oakcodec_encoder_free(encoder) }
}
+739
View File
@@ -0,0 +1,739 @@
// 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 bridge: direct Rust calls into the `oakcommon` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oakcommon`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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, mirroring the oakcommon crate's exports
//! (`src/common/rust/src/ffi.rs`; headers `include/common/*.h`). Only the
//! families the facade wraps: config, videoparams, colortransform, xml
//! reader/writer and the decibel helpers.
use std::ffi::{c_char, c_int, c_void};
use crate::handle::CHandle;
/// `include/common/config.h` — error handler callback.
pub type ConfigErrorHandler =
Option<unsafe extern "C" fn(title: *const c_char, message: *const c_char, userdata: *mut c_void)>;
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_load() -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_load() }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_save() -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_save() }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_reset_defaults() -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_reset_defaults() }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_set(group: *const c_char, key: *const c_char, value: *const c_char) {
unsafe { oakcommon::ffi::config::oakcommon_config_set(group, key, value) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_get(
group: *const c_char,
key: *const c_char,
buf: *mut c_char,
buf_size: 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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_get_int(group: *const c_char, key: *const c_char, fallback: c_int) -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_get_int(group, key, fallback) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_get_int64(
group: *const c_char,
key: *const c_char,
fallback: i64,
) -> i64 {
unsafe { oakcommon::ffi::config::oakcommon_config_get_int64(group, key, fallback) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_get_double(group: *const c_char, key: *const c_char, fallback: f64) -> f64 {
unsafe { oakcommon::ffi::config::oakcommon_config_get_double(group, key, fallback) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_get_bool(group: *const c_char, key: *const c_char, fallback: c_int) -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_get_bool(group, key, fallback) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_set_int(group: *const c_char, key: *const c_char, value: c_int) {
unsafe { oakcommon::ffi::config::oakcommon_config_set_int(group, key, value) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_set_int64(group: *const c_char, key: *const c_char, value: i64) {
unsafe { oakcommon::ffi::config::oakcommon_config_set_int64(group, key, value) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_set_double(group: *const c_char, key: *const c_char, value: f64) {
unsafe { oakcommon::ffi::config::oakcommon_config_set_double(group, key, value) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_set_bool(group: *const c_char, key: *const c_char, value: c_int) {
unsafe { oakcommon::ffi::config::oakcommon_config_set_bool(group, key, value) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_entry_type(group: *const c_char, key: *const c_char) -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_entry_type(group, key) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_config_set_error_handler(handler: ConfigErrorHandler, userdata: *mut c_void) -> c_int {
unsafe { oakcommon::ffi::config::oakcommon_config_set_error_handler(handler, userdata) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_init() -> CHandle {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_init() }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_init_basic(
width: c_int,
height: c_int,
pixel_format: c_int,
nb_channels: c_int,
pixel_aspect_num: c_int,
pixel_aspect_den: c_int,
interlacing: c_int,
divider: c_int,
) -> CHandle {
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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_init_with_time_base(
width: c_int,
height: c_int,
time_base_num: c_int,
time_base_den: c_int,
pixel_format: c_int,
nb_channels: c_int,
pixel_aspect_num: c_int,
pixel_aspect_den: c_int,
interlacing: c_int,
divider: c_int,
) -> CHandle {
unsafe {
oakcommon::ffi::videoparams::oakcommon_videoparams_init_with_time_base(
width, height, time_base_num, time_base_den, pixel_format, nb_channels,
pixel_aspect_num, pixel_aspect_den, interlacing, divider,
)
}
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_free(params: *mut CHandle) {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_free(params) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_width(params: CHandle, 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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_width(params: CHandle, width: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_width(params, width) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_height(params: CHandle, 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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_height(params: CHandle, height: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_height(params, height) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_time_base(
params: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_time_base(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_time_base(
params: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_time_base(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_frame_rate(
params: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_frame_rate(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_frame_rate(
params: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_frame_rate(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_pixel_aspect_ratio(
params: CHandle,
numerator: *mut c_int,
denominator: *mut c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_pixel_aspect_ratio(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_pixel_aspect_ratio(
params: CHandle,
numerator: c_int,
denominator: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_pixel_aspect_ratio(params, numerator, denominator) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_format(params: CHandle, 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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_format(params: CHandle, format: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_format(params, format) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_interlacing(params: CHandle, interlacing: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_interlacing(params, interlacing) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_interlacing(params: CHandle, interlacing: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_interlacing(params, interlacing) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_divider(params: CHandle, divider: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_divider(params, divider) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_divider(params: CHandle, divider: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_divider(params, divider) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_video_type(params: CHandle, type_: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_video_type(params, type_) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_video_type(params: CHandle, type_: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_video_type(params, type_) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_premultiplied_alpha(
params: CHandle,
premultiplied: *mut c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_premultiplied_alpha(params, premultiplied) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_premultiplied_alpha(params: CHandle, premultiplied: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_premultiplied_alpha(params, premultiplied) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_color_range(params: CHandle, color_range: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_color_range(params, color_range) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_set_color_range(params: CHandle, color_range: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_set_color_range(params, color_range) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_is_valid(params: CHandle, valid: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_is_valid(params, valid) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_effective_width(params: CHandle, width: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_effective_width(params, width) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_effective_height(params: CHandle, height: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_effective_height(params, height) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_bytes_per_pixel(params: CHandle, bytes: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_bytes_per_pixel(params, bytes) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_equals(a: CHandle, b: CHandle, out_equal: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_equals(a, b, out_equal) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_format_is_float(pixel_format: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_format_is_float(pixel_format) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_format_name(
pixel_format: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_format_name(pixel_format, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_frame_rate_to_string(
numerator: c_int,
denominator: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_frame_rate_to_string(numerator, denominator, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_name_for_divider(
divider: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_name_for_divider(divider, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_scaled_dimension(dimension: c_int, divider: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_scaled_dimension(dimension, divider) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_generate_auto_divider(width: i64, height: i64) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_generate_auto_divider(width, height) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_divider_for_target_resolution(
src_width: c_int,
src_height: c_int,
target_width: c_int,
target_height: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_divider_for_target_resolution(src_width, src_height, target_width, target_height) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_bytes_per_channel_for_format(pixel_format: c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_bytes_per_channel_for_format(pixel_format) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_bytes_per_pixel_for_format(
pixel_format: c_int,
channels: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_bytes_per_pixel_for_format(pixel_format, channels) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_static_get_bytes_per_pixel(
pixel_format: c_int,
channels: c_int,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_static_get_bytes_per_pixel(pixel_format, channels) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_buffer_size(params: CHandle, size: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_buffer_size(params, size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_videoparams_get_time_in_timebase_units(
params: CHandle,
time_num: c_int,
time_den: c_int,
timestamp: *mut i64,
) -> c_int {
unsafe { oakcommon::ffi::videoparams::oakcommon_videoparams_get_time_in_timebase_units(params, time_num, time_den, timestamp) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_init_output(output: *const c_char) -> CHandle {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_init_output(output) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_init_display(
display: *const c_char,
view: *const c_char,
look: *const c_char,
) -> CHandle {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_init_display(display, view, look) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_free(transform: *mut CHandle) {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_free(transform) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_is_display(transform: CHandle) -> c_int {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_is_display(transform) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_get_display(
transform: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_get_display(transform, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_get_output(
transform: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_get_output(transform, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_get_view(
transform: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_get_view(transform, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_colortransform_get_look(
transform: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_get_look(transform, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_init(data: *const c_char) -> CHandle {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_init(data) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_free(reader: *mut CHandle) {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_free(reader) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_read_next_start_element(reader: CHandle, found: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_next_start_element(reader, found) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_name(reader: CHandle, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_name(reader, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_read_element_text(
reader: CHandle,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_element_text(reader, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_skip_current_element(reader: CHandle) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_skip_current_element(reader) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_attribute_count(reader: CHandle, count: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_count(reader, count) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_attribute_name(
reader: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_name(reader, index, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_attribute_value(
reader: CHandle,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_value(reader, index, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_reader_has_error(reader: CHandle, has_error: *mut c_int) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_has_error(reader, has_error) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_init() -> CHandle {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_init() }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_free(writer: *mut CHandle) {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_free(writer) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_write_start_element(writer: CHandle, name: *const c_char) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_start_element(writer, name) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_write_attribute(
writer: CHandle,
name: *const c_char,
value: *const c_char,
) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_attribute(writer, name, value) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_write_characters(writer: CHandle, text: *const c_char) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_characters(writer, text) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_write_text_element(
writer: CHandle,
name: *const c_char,
text: *const c_char,
) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_text_element(writer, name, text) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_write_end_element(writer: CHandle) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_element(writer) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_write_end_document(writer: CHandle) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_document(writer) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_xml_writer_output(writer: CHandle, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_output(writer, buf, buf_size) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_decibel_from_linear(linear: f64, out_db: *mut f64) -> c_int {
unsafe { oakcommon::ffi::misc::oakcommon_decibel_from_linear(linear, out_db) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_decibel_to_linear(db: f64, out_linear: *mut f64) -> c_int {
unsafe { oakcommon::ffi::misc::oakcommon_decibel_to_linear(db, out_linear) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_decibel_from_logarithmic(logarithmic: f64, out_db: *mut f64) -> c_int {
unsafe { oakcommon::ffi::misc::oakcommon_decibel_from_logarithmic(logarithmic, out_db) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_decibel_to_logarithmic(db: f64, out_logarithmic: *mut f64) -> c_int {
unsafe { oakcommon::ffi::misc::oakcommon_decibel_to_logarithmic(db, out_logarithmic) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_decibel_linear_to_logarithmic(linear: f64, out_logarithmic: *mut f64) -> c_int {
unsafe { oakcommon::ffi::misc::oakcommon_decibel_linear_to_logarithmic(linear, out_logarithmic) }
}
/// Direct call into the `oakcommon` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakcommon_decibel_logarithmic_to_linear(logarithmic: f64, out_linear: *mut f64) -> c_int {
unsafe { oakcommon::ffi::misc::oakcommon_decibel_logarithmic_to_linear(logarithmic, out_linear) }
}
+43
View File
@@ -0,0 +1,43 @@
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! C ABI imports from the oak modules, one submodule per module crate.
//!
//! The facade consumes the module C ABIs (`include/<mod>/*.h`) purely as
//! `extern "C"` imports — it never links the module crates at build time.
//! At the final app link the symbols resolve against the module shared
//! libraries; `cargo test` resolves them against the module crates' rlibs
//! (dev-dependencies, see Cargo.toml).
//!
//! **Signatures are declared from the module crates' actual `#[no_mangle]`
//! exports** (their `src/*/ffi*` modules), not from memory of the include
//! headers — module bridges in sibling crates have drifted from the real
//! ABI before. Every handle crosses the boundary as [`crate::handle::CHandle`]
//! (structurally identical to every `Oak<Mod><Type>` value handle).
//!
//! Only functions the module crates actually implement are declared here;
//! engine functions whose backing is still C++-only are facade stubs (see
//! the area modules) and never reach this module.
pub mod audio;
pub mod codec;
pub mod common;
pub mod undo;
pub mod plugin;
pub mod render;
pub mod node;
pub mod timeline;
pub mod task;
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
// 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/>.
//! oakplugin C ABI bridge: direct Rust calls into the `oakplugin` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oakplugin`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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/>.
//! oakplugin C ABI imports, mirroring the oakplugin crate's exports
//! (`src/plugin/rust/src/ffi.rs`; headers `include/plugin/*.h`).
use std::ffi::{c_char, c_int};
/// Direct call into the `oakplugin` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakplugin_host_scan(bundle_dirs: *const *const c_char, dir_count: c_int) -> c_int {
unsafe { oakplugin::ffi::oakplugin_host_scan(bundle_dirs, dir_count) }
}
/// Direct call into the `oakplugin` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakplugin_host_init() -> c_int {
unsafe { oakplugin::ffi::oakplugin_host_init() }
}
/// Direct call into the `oakplugin` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakplugin_host_plugin_count() -> c_int {
unsafe { oakplugin::ffi::oakplugin_host_plugin_count() }
}
/// Direct call into the `oakplugin` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakplugin_host_plugin_id_at(index: c_int, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakplugin::ffi::oakplugin_host_plugin_id_at(index, buf, buf_size) }
}
/// Direct call into the `oakplugin` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakplugin_host_plugin_label(plugin_id: *const c_char, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oakplugin::ffi::oakplugin_host_plugin_label(plugin_id, buf, buf_size) }
}
+315
View File
@@ -0,0 +1,315 @@
// 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 bridge: direct Rust calls into the `oakrender` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oakrender`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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, mirroring the oakrender crate's exports
//! (`src/render/rust/src/ffi.rs`; headers `include/render/*.h`).
use std::ffi::{c_char, c_double, c_int, c_void};
use crate::handle::CHandle;
/// `include/render/ticket.h` — video render ticket params. Single-lib
/// unification: aliases the oakrender crate's POD (same `repr(C)`
/// layout; all handle fields are the shared [`CHandle`]).
pub type OakVideoTicketParams = oakrender::ffi::OakVideoTicketParams;
/// `include/render/renderer.h` — frame video-params POD returned by
/// `oakrender_codec_frame_get_params`. Single-lib unification: aliases
/// the oakrender crate's POD.
pub type OakRenderVideoParams = oakrender::ffi::OakRenderVideoParams;
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_display_renderer_create_dynamic(backend_id: *const c_char) -> CHandle {
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_create_dynamic(backend_id) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_display_renderer_create_opengl() -> CHandle {
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_create_opengl() }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_display_renderer_init(renderer: CHandle, gl_context: *mut c_void) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_init(renderer, gl_context) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_display_renderer_destroy(renderer: *mut CHandle) {
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_destroy(renderer) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_display_renderer_is_open_gl(renderer: CHandle) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_is_open_gl(renderer) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_display_renderer_is_vulkan(renderer: CHandle) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_display_renderer_is_vulkan(renderer) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_set_cacher_multicam(multicam_or_null: CHandle) -> c_int {
unsafe { oakrender::ffi::manager::oakrender_set_cacher_multicam(multicam_or_null) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_set_display_color_processor(p_or_null: CHandle) -> c_int {
unsafe { oakrender::ffi::manager::oakrender_set_display_color_processor(p_or_null) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_ticket_render_frame(
params: *const OakVideoTicketParams,
cb: Option<unsafe extern "C" fn(CHandle, *mut c_void)>,
userdata: *mut c_void,
) -> CHandle {
unsafe { oakrender::ffi::ticket::oakrender_ticket_render_frame(params, cb, userdata) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_ticket_render_audio(
output_node: CHandle,
in_num: i64,
in_den: i64,
out_num: i64,
out_den: i64,
params: *const c_void,
mode: c_int,
cb: Option<unsafe extern "C" fn(CHandle, *mut c_void)>,
userdata: *mut c_void,
) -> CHandle {
unsafe {
oakrender::ffi::ticket::oakrender_ticket_render_audio(
output_node, in_num, in_den, out_num, out_den, params as *const CHandle, mode, cb,
userdata,
)
}
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_ticket_wait(ticket: CHandle) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_wait(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_ticket_cancel(ticket: CHandle) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_cancel(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_ticket_get_frame(ticket: CHandle, out: *mut CHandle) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_get_frame(ticket, out) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_ticket_get_samples(ticket: CHandle, out: *mut *mut c_void) -> c_int {
unsafe { oakrender::ffi::ticket::oakrender_ticket_get_samples(ticket, out) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_ticket_free(ticket: *mut CHandle) {
unsafe { oakrender::ffi::ticket::oakrender_ticket_free(ticket) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_create() -> CHandle {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_create() }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_retain(frame: CHandle) -> CHandle {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_retain(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_free(frame: *mut CHandle) {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_free(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_width(frame: CHandle) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_width(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_height(frame: CHandle) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_height(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_linesize_bytes(frame: CHandle) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_linesize_bytes(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_data(frame: CHandle) -> *mut c_void {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_data(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_const_data(frame: CHandle) -> *const c_void {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_const_data(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_is_allocated(frame: CHandle) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_is_allocated(frame) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_codec_frame_get_params(frame: CHandle, out: *mut OakRenderVideoParams) -> c_int {
unsafe { oakrender::ffi::renderer::oakrender_codec_frame_get_params(frame, out) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_color_processor_create(
src_space: *const c_char,
dst_transform: *const c_char,
direction: c_int,
) -> CHandle {
unsafe { oakrender::ffi::color::oakrender_color_processor_create(src_space, dst_transform, direction) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_color_processor_free(processor: *mut CHandle) {
unsafe { oakrender::ffi::color::oakrender_color_processor_free(processor) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_color_processor_is_valid(processor: CHandle) -> c_int {
unsafe { oakrender::ffi::color::oakrender_color_processor_is_valid(processor) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_color_processor_create_transform(
manager: CHandle,
input: *const c_char,
dest: CHandle,
direction: c_int,
) -> CHandle {
unsafe { oakrender::ffi::color::oakrender_color_processor_create_transform(manager, input, dest, direction) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_color_processor_convert(
processor: CHandle,
ir: c_double,
ig: c_double,
ib: c_double,
ia: c_double,
out_r: *mut c_double,
out_g: *mut c_double,
out_b: *mut c_double,
out_a: *mut c_double,
) -> c_int {
unsafe { oakrender::ffi::color::oakrender_color_processor_convert(processor, ir, ig, ib, ia, out_r, out_g, out_b, out_a) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_color_manager_set_up_default_config() -> c_int {
unsafe { oakrender::ffi::color::oakrender_color_manager_set_up_default_config() }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_color_manager_get_config(buf: *mut c_char, n: c_int) -> c_int {
unsafe { oakrender::ffi::color::oakrender_color_manager_get_config(buf, n) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_lut_is_supported_extension(extension: *const c_char) -> c_int {
unsafe { oakrender::ffi::color::oakrender_lut_is_supported_extension(extension) }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_lut_supported_extensions_count() -> c_int {
unsafe { oakrender::ffi::color::oakrender_lut_supported_extensions_count() }
}
/// Direct call into the `oakrender` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakrender_lut_supported_extension_at(i: c_int, buf: *mut c_char, n: c_int) -> c_int {
unsafe { oakrender::ffi::color::oakrender_lut_supported_extension_at(i, buf, n) }
}
+275
View File
@@ -0,0 +1,275 @@
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! oaktask C ABI bridge: direct Rust calls into the `oaktask` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oaktask`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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/>.
//! oaktask C ABI imports, mirroring the oaktask crate's exports
//! (`src/task/rust/src/ffi/{manager,task,project}.rs`; headers
//! `include/task/*.h`).
//!
//! Every handle crosses as [`crate::handle::CHandle`]. `oaktask_create_export`
//! takes the encoding-params POD ([`crate::bridge::codec::EncodingParamsPOD`],
//! field-identical to the task crate's `OakCodecEncodingParams`). String
//! getters report the size **including** the NUL; the facade converts with
//! [`crate::handle::string_result`].
use std::ffi::{c_char, c_int, c_void};
use crate::handle::CHandle;
/// `oaktask_event_fn` callback (`include/task/task.h`): `event_id` is an
/// `OakTaskEvent` (0=started, 1=progress, 2=finished), `value` 0..1 (or
/// start-ms / success flag), `userdata` the subscription token.
pub type OakTaskEventFn = unsafe extern "C" fn(event_id: c_int, value: f64, userdata: *mut c_void);
/// `oaktask_otio_import_confirm_fn` (`include/task/project.h`).
pub type OakTaskOtioImportConfirmFn =
unsafe extern "C" fn(sequence_names: *const *const c_char, count: c_int, userdata: *mut c_void) -> c_int;
/// `oaktask_image_sequence_confirm_fn` (`include/task/project.h`).
pub type OakTaskImageSequenceConfirmFn =
unsafe extern "C" fn(filename: *const c_char, userdata: *mut c_void) -> c_int;
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_manager_init() -> c_int {
unsafe { oaktask::ffi::manager::oaktask_manager_init() }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_manager_shutdown() {
unsafe { oaktask::ffi::manager::oaktask_manager_shutdown() }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_register_codec_submitter() -> c_int {
unsafe { oaktask::ffi::manager::oaktask_register_codec_submitter() }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_manager_count() -> c_int {
unsafe { oaktask::ffi::manager::oaktask_manager_count() }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_manager_at(i: c_int) -> CHandle {
unsafe { oaktask::ffi::manager::oaktask_manager_at(i) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_manager_delete_finished() {
unsafe { oaktask::ffi::manager::oaktask_manager_delete_finished() }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_free(t: *mut CHandle) {
unsafe { oaktask::ffi::task::oaktask_task_free(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_start_sync(t: CHandle) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_start_sync(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_start(t: CHandle) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_start(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_cancel(t: CHandle) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_cancel(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_wait(t: CHandle) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_wait(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_is_finished(t: CHandle) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_is_finished(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_succeeded(t: CHandle) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_succeeded(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_title(t: CHandle, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_title(t, buf, buf_size) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_error(t: CHandle, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oaktask::ffi::task::oaktask_task_error(t, buf, buf_size) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_task_subscribe(t: CHandle, cb: Option<OakTaskEventFn>, userdata: *mut c_void) -> i64 {
unsafe { oaktask::ffi::task::oaktask_task_subscribe(t, cb, userdata) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_debug_alive_count() -> c_int {
unsafe { oaktask::ffi::task::oaktask_debug_alive_count() }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_create_project_load(filename: *const c_char) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_create_project_load(filename) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_load_take_project(t: CHandle) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_load_take_project(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_create_project_save(project: CHandle, filename_or_null: *const c_char, use_compression: c_int) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_create_project_save(project, filename_or_null, use_compression) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_create_project_import(folder: CHandle, project: CHandle, urls: *const *const c_char, url_count: c_int) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_create_project_import(folder, project, urls, url_count) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_import_take_command(t: CHandle) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_import_take_command(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_import_footage_count(t: CHandle) -> c_int {
unsafe { oaktask::ffi::project::oaktask_import_footage_count(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_import_footage_at(t: CHandle, index: c_int) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_import_footage_at(t, index) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_import_invalid_count(t: CHandle) -> c_int {
unsafe { oaktask::ffi::project::oaktask_import_invalid_count(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_import_invalid_at(t: CHandle, index: c_int, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe { oaktask::ffi::project::oaktask_import_invalid_at(t, index, buf, buf_size) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_create_project_load_otio(filename: *const c_char) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_create_project_load_otio(filename) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_load_otio_take_project(t: CHandle) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_load_otio_take_project(t) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_create_project_save_otio(project: CHandle, filename: *const c_char) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_create_project_save_otio(project, filename) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_load_otio_set_confirm_cb(cb: Option<OakTaskOtioImportConfirmFn>, userdata: *mut c_void) {
unsafe { oaktask::ffi::project::oaktask_load_otio_set_confirm_cb(cb, userdata) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_create_precache(footage: CHandle, index: c_int, sequence: CHandle) -> CHandle {
unsafe { oaktask::ffi::project::oaktask_create_precache(footage, index, sequence) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external 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::ffi::project::oaktask_create_export(viewer, color_manager, params) }
}
/// Direct call into the `oaktask` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktask_import_set_image_sequence_confirm_cb(cb: Option<OakTaskImageSequenceConfirmFn>, userdata: *mut c_void) {
unsafe { oaktask::ffi::project::oaktask_import_set_image_sequence_confirm_cb(cb, userdata) }
}
+376
View File
@@ -0,0 +1,376 @@
// 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 bridge: direct Rust calls into the `oaktimeline` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oaktimeline`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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, mirroring the oaktimeline crate's exports
//! (`src/timeline/rust/src/ffi.rs`; headers `include/timeline/*.h`).
//!
//! Every handle crosses as [`crate::handle::CHandle`]. The marker/workarea
//! time quantities cross as `c_int` num/den pairs; the edit commands take
//! `i64` rationals. String getters report the size **including** the NUL;
//! the facade converts with [`crate::handle::string_result`].
use std::ffi::{c_char, c_int};
use crate::handle::CHandle;
// `include/timeline/marker.h` exports (complete inventory):
// oaktimeline_marker_list_create / free / of / add / count / at /
// add_command / remove_at_command / set_time_command /
// set_props_command / list_load / list_save.
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_list_create() -> CHandle {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_list_create() }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_list_of(owner: CHandle) -> CHandle {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_list_of(owner) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_list_free(list: *mut CHandle) {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_list_free(list) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_add(
list: CHandle,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
name: *const c_char,
color: c_int,
) -> c_int {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_add(list, in_num, in_den, out_num, out_den, name, color) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_count(list: CHandle, out_count: *mut c_int) -> c_int {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_count(list, out_count) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_at(
list: CHandle,
index: c_int,
in_num: *mut c_int,
in_den: *mut c_int,
out_num: *mut c_int,
out_den: *mut c_int,
color: *mut c_int,
name_buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_at(list, index, in_num, in_den, out_num, out_den, color, name_buf, buf_size) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_add_command(
list: CHandle,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
name: *const c_char,
color: c_int,
) -> CHandle {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_add_command(list, in_num, in_den, out_num, out_den, name, color) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_remove_at_command(list: CHandle, index: c_int) -> CHandle {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_remove_at_command(list, index) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_set_time_command(
list: CHandle,
index: c_int,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
) -> CHandle {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_set_time_command(list, index, in_num, in_den, out_num, out_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_set_props_command(
list: CHandle,
index: c_int,
color: c_int,
name: *const c_char,
) -> CHandle {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_set_props_command(list, index, color, name) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_list_load(list: CHandle, reader: CHandle) -> c_int {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_list_load(list, reader) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_marker_list_save(list: CHandle, writer: CHandle) -> c_int {
unsafe { oaktimeline::ffi::marker::oaktimeline_marker_list_save(list, writer) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_create() -> CHandle {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_create() }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_of(owner: CHandle) -> CHandle {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_of(owner) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_free(w: *mut CHandle) {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_free(w) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_set_enabled(w: CHandle, enabled: c_int) -> c_int {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_set_enabled(w, enabled) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_get(
w: CHandle,
in_num: *mut c_int,
in_den: *mut c_int,
out_num: *mut c_int,
out_den: *mut c_int,
enabled: *mut c_int,
) -> c_int {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_get(w, in_num, in_den, out_num, out_den, enabled) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_set_range(
w: CHandle,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
) -> c_int {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_set_range(w, in_num, in_den, out_num, out_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_set_range_command(
w: CHandle,
in_num: c_int,
in_den: c_int,
out_num: c_int,
out_den: c_int,
old_in_num: c_int,
old_in_den: c_int,
old_out_num: c_int,
old_out_den: c_int,
) -> CHandle {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_set_range_command(w, in_num, in_den, out_num, out_den, old_in_num, old_in_den, old_out_num, old_out_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_set_enabled_command(w: CHandle, enabled: c_int) -> CHandle {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_set_enabled_command(w, enabled) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_reset(
in_num: *mut c_int,
in_den: *mut c_int,
out_num: *mut c_int,
out_den: *mut c_int,
) -> c_int {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_reset(in_num, in_den, out_num, out_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_load(w: CHandle, reader: CHandle) -> c_int {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_load(w, reader) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_workarea_save(w: CHandle, writer: CHandle) -> c_int {
unsafe { oaktimeline::ffi::workarea::oaktimeline_workarea_save(w, writer) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_add_track_command(list: CHandle) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_add_track_command(list) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_remove_track_command(track: CHandle) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_remove_track_command(track) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_place_block_command(
list: CHandle,
track_index: c_int,
block: CHandle,
in_num: i64,
in_den: i64,
) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_place_block_command(list, track_index, block, in_num, in_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_replace_block_with_gap_command(track: CHandle, block: CHandle) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_replace_block_with_gap_command(track, block) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_trim_command(
track: CHandle,
block: CHandle,
new_length_num: i64,
new_length_den: i64,
mode: c_int,
) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_trim_command(track, block, new_length_num, new_length_den, mode) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_split_command(blocks: *const CHandle, count: c_int, point_num: i64, point_den: i64) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_split_command(blocks, count, point_num, point_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_split_preserving_links_command(
blocks: *const CHandle,
count: c_int,
point_nums: *const i64,
point_dens: *const i64,
time_count: c_int,
) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_split_preserving_links_command(blocks, count, point_nums, point_dens, time_count) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_ripple_delete_gaps_command(
sequence: CHandle,
in_nums: *const i64,
in_dens: *const i64,
out_nums: *const i64,
out_dens: *const i64,
tracks: *const CHandle,
range_count: c_int,
) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_ripple_delete_gaps_command(sequence, in_nums, in_dens, out_nums, out_dens, tracks, range_count) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_slide_command(
track: CHandle,
blocks: *const CHandle,
block_count: c_int,
in_adjacent: CHandle,
out_adjacent: CHandle,
movement_num: i64,
movement_den: i64,
) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_slide_command(track, blocks, block_count, in_adjacent, out_adjacent, movement_num, movement_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_ripple_remove_area_command(
track: CHandle,
in_num: i64,
in_den: i64,
out_num: i64,
out_den: i64,
) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_ripple_remove_area_command(track, in_num, in_den, out_num, out_den) }
}
/// Direct call into the `oaktimeline` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oaktimeline_insert_gaps_command(
list: CHandle,
point_num: i64,
point_den: i64,
length_num: i64,
length_den: i64,
) -> CHandle {
unsafe { oaktimeline::ffi::edit::oaktimeline_insert_gaps_command(list, point_num, point_den, length_num, length_den) }
}
+208
View File
@@ -0,0 +1,208 @@
// 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 bridge: direct Rust calls into the `oakundo` crate.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): every
//! call below is a compile-time Rust call into `oakundo`'s `ffi` (the
//! `#[no_mangle]` exports stay in the dylib for the external C ABI;
//! internal callers bypass them). Handles cross as the shared
//! [`crate::handle::CHandle`]. Exceptions that keep an `extern "C"`
//! declaration (resolved at link time against the sibling crate in the
//! same dylib) are the host `oakcore_*` symbols and the encoding-params
//! C ABI POD crossings (the facade keeps its own POD mirrors there).
// 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, mirroring the oakundo crate's exports
//! (`src/undo/rust/src/ffi.rs`; headers `include/undo/*.h`).
use std::ffi::{c_char, c_int};
use crate::handle::CHandle;
/// `include/undo/undocommand.h` — callback table backing a
/// caller-defined undo command. 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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_init(
vtable: *const OakUndoCommandVtable,
userdata: *mut std::ffi::c_void,
) -> CHandle {
unsafe { oakundo::ffi::command::oakundo_command_init(vtable, userdata) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_init_multi() -> CHandle {
unsafe { oakundo::ffi::command::oakundo_command_init_multi() }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_multi_add_child(multi: CHandle, child: CHandle) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_multi_add_child(multi, child) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_multi_child_count(multi: CHandle, 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; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_multi_child(
multi: CHandle,
index: c_int,
out_child: *mut CHandle,
) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_multi_child(multi, index, out_child) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_redo_now(command: CHandle) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_redo_now(command) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_undo_now(command: CHandle) -> c_int {
unsafe { oakundo::ffi::command::oakundo_command_undo_now(command) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_command_free(command: *mut CHandle) {
unsafe { oakundo::ffi::command::oakundo_command_free(command) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_init() -> CHandle {
unsafe { oakundo::ffi::undostack::oakundo_undostack_init() }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_free(stack: *mut CHandle) {
unsafe { oakundo::ffi::undostack::oakundo_undostack_free(stack) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_push(stack: CHandle, command: CHandle, name: *const c_char) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_push(stack, command, name) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_push_pre_executed(
stack: CHandle,
command: CHandle,
name: *const c_char,
) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_push_pre_executed(stack, command, name) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_undo(stack: CHandle) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_undo(stack) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_redo(stack: CHandle) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_redo(stack) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_jump(stack: CHandle, index: i64) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_jump(stack, index) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_clear(stack: CHandle) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_clear(stack) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_can_undo(stack: CHandle, out_value: *mut c_int) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_can_undo(stack, out_value) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_can_redo(stack: CHandle, out_value: *mut c_int) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_can_redo(stack, out_value) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_count(stack: CHandle, out_count: *mut i64) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_count(stack, out_count) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_index(stack: CHandle, out_index: *mut i64) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_index(stack, out_index) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_command_text(
stack: CHandle,
row: i64,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_command_text(stack, row, buf, buf_size) }
}
/// Direct call into the `oakundo` crate (single-lib unification; the
/// `#[no_mangle]` export stays for the external C ABI).
pub fn oakundo_undostack_command_is_done(
stack: CHandle,
row: i64,
out_value: *mut c_int,
) -> c_int {
unsafe { oakundo::ffi::undostack::oakundo_undostack_command_is_done(stack, row, out_value) }
}
File diff suppressed because it is too large Load Diff
+611
View File
@@ -0,0 +1,611 @@
// 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/>.
//! `engine/include/oakengine/config.h` and
//! `engine/include/oakengine/videoparams.h` over the oakcommon module.
//!
//! The engine config family uses flat keys; the oakcommon store is
//! `(group, key)` — the facade passes group = NULL. Engine semantics that
//! differ from the module are honored here (a missing key reads as an
//! empty string / 0, not a module error).
//!
//! The engine videoparams family is mostly **facade-local static data**
//! (the standard frame-rate / pixel-aspect / divider tables from
//! `engine/render/videoparams.cpp`) plus POD↔handle conversion over the
//! oakcommon `OakVideoParams` handle; see the mapping notes per function.
use std::ffi::{c_char, c_int, c_void};
use std::sync::{Mutex, OnceLock};
use crate::bridge::common as c;
use crate::error::Error;
use crate::handle::{
box_handle, free_box, guard, guard_int, guard_void, string_result, OakEngineClipboard,
};
// ---------------------------------------------------------------------------
// config.h
// ---------------------------------------------------------------------------
/// Facade copy of the registered config error handler (the module keeps
/// its own copy for load/save errors; this one backs
/// `oakengine_config_report_error`). The userdata pointer is stored as
/// `usize` so the static stays Send/Sync.
static ERROR_FN: OnceLock<Mutex<Option<(Option<ConfigErrorFn>, usize)>>> = OnceLock::new();
/// `engine/include/oakengine/config.h` error callback.
pub type ConfigErrorFn = unsafe extern "C" fn(title: *const c_char, message: *const c_char, userdata: *mut c_void);
fn error_fn_slot() -> &'static Mutex<Option<(Option<ConfigErrorFn>, usize)>> {
ERROR_FN.get_or_init(|| Mutex::new(None))
}
/// `oakengine_config_load` — load configuration from disk.
#[no_mangle]
pub extern "C" fn oakengine_config_load() -> c_int {
guard(|| Error::from_module(unsafe { c::oakcommon_config_load() }))
}
/// `oakengine_config_save` — save configuration to disk.
#[no_mangle]
pub extern "C" fn oakengine_config_save() -> c_int {
guard(|| Error::from_module(unsafe { c::oakcommon_config_save() }))
}
/// `oakengine_config_get_string` — read a string value (buf/size).
/// Returns the string length, 0 when the key is missing or empty.
#[no_mangle]
pub unsafe extern "C" fn oakengine_config_get_string(
key: *const c_char,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
if key.is_null() {
return Err(Error::Invalid);
}
let rc = c::oakcommon_config_get(std::ptr::null(), key, buf, buf_size);
// Engine contract: a missing key reads as an empty string.
if rc == -10004 {
Ok(0)
} else if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
/// `oakengine_config_set_string` — write a string value.
#[no_mangle]
pub unsafe extern "C" fn oakengine_config_set_string(
key: *const c_char,
value: *const c_char,
) -> c_int {
guard(|| unsafe {
if key.is_null() {
return Err(Error::Invalid);
}
let value = if value.is_null() { empty_cstr() } else { value };
c::oakcommon_config_set(std::ptr::null(), key, value);
Ok(())
})
}
/// `oakengine_config_get_int` — read an integer value (fallback when the
/// key is missing or not convertible).
#[no_mangle]
pub extern "C" fn oakengine_config_get_int(key: *const c_char, default_value: i64) -> i64 {
crate::handle::guard_i64(|| unsafe {
if key.is_null() {
return Ok(default_value);
}
Ok(c::oakcommon_config_get_int64(std::ptr::null(), key, default_value))
})
}
/// `oakengine_config_set_int` — write an integer value.
#[no_mangle]
pub unsafe extern "C" fn oakengine_config_set_int(key: *const c_char, value: i64) -> c_int {
guard(|| unsafe {
if key.is_null() {
return Err(Error::Invalid);
}
c::oakcommon_config_set_int64(std::ptr::null(), key, value);
Ok(())
})
}
/// `oakengine_config_set_error_handler` — register the error callback
/// (NULL clears it). Forwards to oakcommon and keeps a facade copy for
/// `oakengine_config_report_error`.
#[no_mangle]
pub extern "C" fn oakengine_config_set_error_handler(
fn_: Option<ConfigErrorFn>,
userdata: *mut c_void,
) -> c_int {
guard(|| {
let mut slot = error_fn_slot().lock().unwrap_or_else(|e| e.into_inner());
*slot = Some((fn_, userdata as usize));
let rc = unsafe { c::oakcommon_config_set_error_handler(fn_, userdata) };
if rc != 0 {
return Err(Error::Module(rc));
}
Ok(())
})
}
/// `oakengine_config_report_error` — report an error through the
/// registered handler (logged and discarded when none is set).
#[no_mangle]
pub unsafe extern "C" fn oakengine_config_report_error(title: *const c_char, message: *const c_char) -> c_int {
guard(|| unsafe {
let slot = error_fn_slot().lock().unwrap_or_else(|e| e.into_inner());
if let Some((Some(fn_), userdata)) = *slot {
let title = if title.is_null() { empty_cstr() } else { title };
let message = if message.is_null() { empty_cstr() } else { message };
fn_(title, message, userdata as *mut c_void);
}
Ok(())
})
}
/// Static empty C string used where the engine treats NULL as "".
static EMPTY_CSTR: std::ffi::c_char = 0;
/// Pointer to the static empty C string.
pub(crate) fn empty_cstr() -> *const c_char {
&EMPTY_CSTR as *const c_char
}
// ---------------------------------------------------------------------------
// videoparams.h — static tables (ported from engine/render/videoparams.cpp)
// ---------------------------------------------------------------------------
/// Standard frame rates as num/den
/// (`VideoParams::k_supported_frame_rates`).
const SUPPORTED_FRAME_RATES: &[(c_int, c_int)] = &[
(10, 1),
(15, 1),
(24000, 1001),
(24, 1),
(25, 1),
(30000, 1001),
(30, 1),
(48000, 1001),
(48, 1),
(50, 1),
(60000, 1001),
(60, 1),
];
/// Standard pixel aspect ratios as num/den
/// (`VideoParams::k_standard_pixel_aspects`).
const STANDARD_PIXEL_ASPECTS: &[(c_int, c_int)] = &[
(1, 1),
(8, 9),
(32, 27),
(16, 15),
(64, 45),
(4, 3),
];
/// Supported preview dividers (`VideoParams::k_supported_dividers`).
const SUPPORTED_DIVIDERS: &[c_int] = &[1, 2, 3, 4, 6, 8, 12, 16];
/// Engine-internal video channel count (RGBA).
const INTERNAL_CHANNEL_COUNT: c_int = 4;
/// `oakengine_video_params_supported_frame_rate_count`.
#[no_mangle]
pub extern "C" fn oakengine_video_params_supported_frame_rate_count() -> c_int {
guard_int(|| Ok(SUPPORTED_FRAME_RATES.len() as c_int))
}
/// `oakengine_video_params_supported_frame_rate_at` — num/den at `index`.
#[no_mangle]
pub extern "C" fn oakengine_video_params_supported_frame_rate_at(
index: c_int,
num: *mut c_int,
den: *mut c_int,
) -> c_int {
guard(|| unsafe {
if num.is_null() || den.is_null() {
return Err(Error::Invalid);
}
match SUPPORTED_FRAME_RATES.get(index as usize) {
Some((n, d)) => {
*num = *n;
*den = *d;
Ok(())
}
None => Err(Error::Invalid),
}
})
}
/// `oakengine_video_params_frame_rate_to_string` — label of a frame rate.
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_frame_rate_to_string(
num: c_int,
den: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
let rc = c::oakcommon_videoparams_frame_rate_to_string(num, den, buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
/// `oakengine_video_params_standard_pixel_aspect_count`.
#[no_mangle]
pub extern "C" fn oakengine_video_params_standard_pixel_aspect_count() -> c_int {
guard_int(|| Ok(STANDARD_PIXEL_ASPECTS.len() as c_int))
}
/// `oakengine_video_params_standard_pixel_aspect_at` — num/den at `index`.
#[no_mangle]
pub extern "C" fn oakengine_video_params_standard_pixel_aspect_at(
index: c_int,
num: *mut c_int,
den: *mut c_int,
) -> c_int {
guard(|| unsafe {
if num.is_null() || den.is_null() {
return Err(Error::Invalid);
}
match STANDARD_PIXEL_ASPECTS.get(index as usize) {
Some((n, d)) => {
*num = *n;
*den = *d;
Ok(())
}
None => Err(Error::Invalid),
}
})
}
/// `oakengine_video_params_standard_pixel_aspect_name` — display name of
/// the `index`-th standard pixel aspect. Built from the table the way the
/// C++ `VideoParams::standard_pixel_aspect_list()` populates the combo:
/// square = "Square", others = "num:den".
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_standard_pixel_aspect_name(
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
match STANDARD_PIXEL_ASPECTS.get(index as usize) {
Some((1, 1)) => Ok(crate::handle::write_string("Square", buf, buf_size)),
Some((n, d)) => Ok(crate::handle::write_string(
&format!("{n}:{d}"),
buf,
buf_size,
)),
None => Err(Error::Invalid),
}
})
}
/// `oakengine_video_params_format_pixel_aspect_ratio_string` — format a
/// printf-style template with the pixel aspect ratio.
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_format_pixel_aspect_ratio_string(
format: *const c_char,
num: c_int,
den: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
if format.is_null() {
return Err(Error::Invalid);
}
let template = crate::handle::read_cstr(format);
// The engine formats a single "%1" placeholder with num/den.
let rendered = if template.contains("%1") {
template.replace("%1", &format!("{num}:{den}"))
} else {
template
};
Ok(crate::handle::write_string(&rendered, buf, buf_size))
})
}
/// `oakengine_video_params_supported_divider_count`.
#[no_mangle]
pub extern "C" fn oakengine_video_params_supported_divider_count() -> c_int {
SUPPORTED_DIVIDERS.len() as c_int
}
/// `oakengine_video_params_supported_divider_at` — divider at `index`
/// (-1 when out of range).
#[no_mangle]
pub extern "C" fn oakengine_video_params_supported_divider_at(index: c_int) -> c_int {
guard_int(|| {
Ok(match SUPPORTED_DIVIDERS.get(index as usize) {
Some(d) => *d,
None => -1,
})
})
}
/// `oakengine_video_params_divider_name` — display name of a divider
/// (`VideoParams::get_name_for_divider`, ported).
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_divider_name(
divider: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
if divider <= 0 {
return Err(Error::Invalid);
}
let rc = c::oakcommon_videoparams_get_name_for_divider(divider, buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
/// `oakengine_video_params_format_is_float` — 1 when the format is float.
#[no_mangle]
pub extern "C" fn oakengine_video_params_format_is_float(format: c_int) -> c_int {
guard_int(|| Ok(unsafe { c::oakcommon_videoparams_format_is_float(format) }))
}
/// `oakengine_video_params_pixel_format_name` — display name of a format.
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_pixel_format_name(
format: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
let rc = c::oakcommon_videoparams_get_format_name(format, buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
/// `oakengine_video_params_effective_size` — divider-scaled dimensions.
#[no_mangle]
pub extern "C" fn oakengine_video_params_effective_size(
width: c_int,
height: c_int,
divider: c_int,
out_width: *mut c_int,
out_height: *mut c_int,
) -> c_int {
guard(|| unsafe {
if width <= 0 || height <= 0 || divider <= 0 {
return Err(Error::Invalid);
}
if !out_width.is_null() {
*out_width = c::oakcommon_videoparams_get_scaled_dimension(width, divider);
}
if !out_height.is_null() {
*out_height = c::oakcommon_videoparams_get_scaled_dimension(height, divider);
}
Ok(())
})
}
/// `oakengine_video_params_make` — fill an `oak_video_params` POD.
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_make(
p: *mut OakVideoParamsPod,
width: c_int,
height: c_int,
time_base_num: c_int,
time_base_den: c_int,
format: c_int,
pixel_aspect_num: c_int,
pixel_aspect_den: c_int,
interlacing: c_int,
color_range: c_int,
divider: c_int,
) -> c_int {
guard(|| unsafe {
if p.is_null() {
return Err(Error::Invalid);
}
(*p).width = width;
(*p).height = height;
(*p).time_base_num = time_base_num;
(*p).time_base_den = time_base_den;
(*p).format = format;
(*p).pixel_aspect_num = pixel_aspect_num;
(*p).pixel_aspect_den = pixel_aspect_den;
(*p).interlacing = interlacing;
(*p).color_range = color_range;
(*p).divider = divider;
(*p).video_type = 0;
(*p).premultiplied_alpha = 0;
Ok(())
})
}
/// `oakengine_video_params_create` — create an engine-side VideoParams
/// from a POD (returns an opaque engine pointer; free with
/// `oakengine_video_params_free`).
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_create(pod: *const OakVideoParamsPod) -> *mut c_void {
crate::handle::guard_ptr(|| unsafe {
if pod.is_null() {
return Ok(std::ptr::null_mut());
}
let params = c::oakcommon_videoparams_init();
if params.is_null() {
return Ok(std::ptr::null_mut());
}
let mut rc = c::oakcommon_videoparams_set_width(params, (*pod).width);
if rc == 0 {
rc = c::oakcommon_videoparams_set_height(params, (*pod).height);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_time_base(params, (*pod).time_base_num, (*pod).time_base_den);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_format(params, (*pod).format);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_pixel_aspect_ratio(
params,
(*pod).pixel_aspect_num,
(*pod).pixel_aspect_den,
);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_interlacing(params, (*pod).interlacing);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_color_range(params, (*pod).color_range);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_divider(params, (*pod).divider);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_video_type(params, (*pod).video_type);
}
if rc == 0 {
rc = c::oakcommon_videoparams_set_premultiplied_alpha(params, (*pod).premultiplied_alpha);
}
if rc != 0 {
let mut p = params;
c::oakcommon_videoparams_free(&mut p);
return Ok(std::ptr::null_mut());
}
Ok(box_handle::<OakEngineClipboard>(params).cast())
})
}
/// `oakengine_video_params_free` — free a params object.
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_free(params: *mut c_void) {
guard_void(|| unsafe {
free_box(params.cast::<OakEngineClipboard>());
})
}
/// `oakengine_video_params_equal` — 1 when all user-facing fields match.
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_equal(
a: *const OakVideoParamsPod,
b: *const OakVideoParamsPod,
) -> c_int {
crate::handle::guard_int(|| unsafe {
if a.is_null() || b.is_null() {
return Ok(0);
}
Ok(compare_pod(&*a, &*b))
})
}
fn compare_pod(a: &OakVideoParamsPod, b: &OakVideoParamsPod) -> c_int {
let same = a.width == b.width
&& a.height == b.height
&& a.time_base_num == b.time_base_num
&& a.time_base_den == b.time_base_den
&& a.format == b.format
&& a.pixel_aspect_num == b.pixel_aspect_num
&& a.pixel_aspect_den == b.pixel_aspect_den
&& a.interlacing == b.interlacing
&& a.color_range == b.color_range
&& a.divider == b.divider
&& a.video_type == b.video_type
&& a.premultiplied_alpha == b.premultiplied_alpha;
if same {
1
} else {
0
}
}
/// `oakengine_video_params_is_valid` — 1 when the POD describes a usable
/// video stream.
#[no_mangle]
pub unsafe extern "C" fn oakengine_video_params_is_valid(p: *const OakVideoParamsPod) -> c_int {
crate::handle::guard_int(|| unsafe {
if p.is_null() {
return Ok(0);
}
let pod = &*p;
let valid = pod.width > 0
&& pod.height > 0
&& pod.pixel_aspect_num > 0
&& pod.pixel_aspect_den > 0
&& pod.format >= 0
&& pod.time_base_den > 0;
Ok(if valid { 1 } else { 0 })
})
}
/// `oakengine_video_params_bytes_per_pixel` — bytes per pixel of
/// `format` with `channels` channels.
#[no_mangle]
pub extern "C" fn oakengine_video_params_bytes_per_pixel(format: c_int, channels: c_int) -> c_int {
guard_int(|| Ok(unsafe { c::oakcommon_videoparams_static_get_bytes_per_pixel(format, channels) }))
}
/// `oakengine_video_params_internal_channel_count` — RGBA.
#[no_mangle]
pub extern "C" fn oakengine_video_params_internal_channel_count() -> c_int {
guard_int(|| Ok(INTERNAL_CHANNEL_COUNT))
}
/// `engine/include/oakengine/videoparams.h` — POD mirror of VideoParams'
/// user-facing fields. Rust mirror of `oak_video_params`.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakVideoParamsPod {
/// Width.
pub width: c_int,
/// Height.
pub height: c_int,
/// Frame duration numerator (e.g. 1001/30000 s).
pub time_base_num: c_int,
/// Frame duration denominator.
pub time_base_den: c_int,
/// PixelFormat::Format value.
pub format: c_int,
/// Pixel aspect numerator.
pub pixel_aspect_num: c_int,
/// Pixel aspect denominator.
pub pixel_aspect_den: c_int,
/// Interlacing value.
pub interlacing: c_int,
/// ColorRange value.
pub color_range: c_int,
/// Preview resolution divider (1 = full).
pub divider: c_int,
/// VideoParams::Type value.
pub video_type: c_int,
/// 0/1 premultiplied alpha.
pub premultiplied_alpha: c_int,
}
+91
View File
@@ -0,0 +1,91 @@
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Deferred `oakengine_*` families and the reasons.
//!
//! This module exists purely as documentation: the areas below are in the
//! facade's scope (module-backed or assembly-layer) but are **not wrapped
//! yet**. Nothing here is exported.
//!
//! ## Genuinely facade-only areas (out of scope, per M9 §4)
//!
//! viewer/playback/preview/display/gizmo/app/events/exporter/disk/proxy/
//! serializer — the liboakengine assembly layer. No files for them in this
//! crate.
//!
//! worker and ipc were in this list too until the render-worker port
//! landed: [`worker`] (`engine/include/oakengine/worker.h`) and the
//! shared-memory frame-slot transport (`engine/include/oakengine/ipc.h`,
//! the shm/framepool half) now live in this crate — see `src/worker.rs`
//! and `src/ipc.rs`.
//!
//! The node/timeline/task families were deferred while the oaknode crate
//! was a `todo!()` skeleton and oaktimeline's test-stub mocks collided
//! with the real oakundo crate in one test binary. Both blockers are
//! cleared: oaknode now implements the module C ABI, and the facade links
//! oaknode/oaktimeline/oaktask WITHOUT their `test-stubs` features (see
//! README.md "Testing"), so the real exports resolve against the
//! dev-dependency rlibs. The families now live in [`node`]
//! (`engine/include/oakengine/{node,project,footage}.h`), [`timeline`]
//! (`engine/include/oakengine/timeline.h`) and [`task`]
//! (`engine/include/oakengine/task.h`).
//!
//! ## Partial coverage within wrapped families (documented stubs)
//!
//! The wrapped families still carry documented stubs where the module
//! crates lack the C ABI surface — each stub returns its header's
//! documented failure value:
//!
//! - **codec** (encoding.h, 81/85 wrapped): the preset path/count/name,
//! preset load/save and the sequence-bound export/last-used entry
//! points (`oakengine_encoding_preset_*`,
//! `oakengine_encoding_params_load_file/save_file`,
//! `oakengine_export_render_with_params`,
//! `oakengine_encoding_params_get/set_last_used`) are stubs — the
//! oakcodec crate has no preset API and those entry points need the
//! exporter/sequence families.
//! - **render color** (color.h, 19/31 wrapped): the color-manager list
//! queries (colorspace/display/view/look/compliant/luma), the
//! standalone config handle and `color_processor_id` /
//! `transform_job_set_processor` are stubs — the oakrender crate
//! exposes only `color_manager_get_config`/`set_up_default_config` and
//! the processor create/convert surface.
//! - **render lut** (lut.h, 0/5 wrapped): the directory/file library is
//! facade-level over FileFunctions; the crate only enumerates supported
//! LUT extensions.
//! - **render audio buffer** (renderer.h): the buffer accessors are
//! stubs because the crate's `ticket_get_samples` path is
//! unimplemented.
//! - **node** (node.h+project.h+footage.h, 226/327 wrapped): gizmo
//! accessors, plugin messages, the QBrush getter, input properties,
//! thumbnail/waveform caches, shape/subtitle blocks, keyframe
//! enumeration (count/at/easing/remove/batch/handles-on-track — the
//! oaknode keyframe C ABI is handle-only), input flags/array/data-type
//! introspection, category/flags metadata, effect-input lookup,
//! exclusive dependencies, `node_get_data`, transform-time, dependency
//! copy, project color reference space / alongside cache path, footage
//! audio-stream info, colorspace candidates, custom proxy params,
//! source start time, stream-enabled, proxy generate. Each stub body
//! carries the one-line reason.
//! - **timeline** (timeline.h, 126/139 wrapped): the ripple-tracks
//! command, default transitions, move-track/move-clip, standalone
//! marker creation, auto-cache accessors, clip cache invalidation and
//! the multicam find/switch helpers are stubs — the oaktimeline/oaknode
//! module surfaces for them do not exist (see the stub bodies).
//! - **task** (task.h, 27/27 wrapped): `oakengine_task_create_proxy` is
//! stubbed (the oaktask crate exposes no proxy-task C creator);
//! `oakengine_task_start_time`/`is_cancelled` are facade-approximated
//! (the module has no getters).
+87
View File
@@ -0,0 +1,87 @@
// 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/>.
//! Facade error codes, mirroring `engine/include/oakengine/init.h`.
//!
//! The facade is module 00 of the project-wide -MMCCCC scheme
//! (see `include/common/error.h`): its own codes are `-(0*10000 + CCCC)`,
//! i.e. -1..-6. Codes returned by a wrapped module call pass through
//! **untranslated** — the numeric module prefix preserves provenance
//! (e.g. -20004 is oakundo's NOT_FOUND, -30001 oaknode's INVALID) and the
//! facade never rewrites them.
/// Success.
pub const OAKENGINE_OK: i32 = 0;
/// Empty handle or invalid argument.
pub const OAKENGINE_E_INVALID: i32 = -1;
/// Call not valid in the current state.
pub const OAKENGINE_E_STATE: i32 = -2;
/// The underlying operation failed.
pub const OAKENGINE_E_FAILED: i32 = -3;
/// Index out of range / entry not found.
pub const OAKENGINE_E_NOT_FOUND: i32 = -4;
/// Allocation failed (reserved; mirrors the -MMCCCC reserved list).
pub const OAKENGINE_E_NOMEM: i32 = -5;
/// The operation was cancelled (reserved; mirrors the -MMCCCC reserved
/// list).
pub const OAKENGINE_E_CANCELLED: i32 = -6;
/// Crate-internal result type.
pub type Result<T> = std::result::Result<T, Error>;
/// Crate-internal error.
#[derive(Debug)]
pub enum Error {
/// Empty handle or invalid argument.
Invalid,
/// Wrong state.
State,
/// The underlying operation failed (context string is log-only).
Failed(String),
/// Not found.
NotFound,
/// Out of memory.
NoMem,
/// Cancelled.
Cancelled,
/// A module error code that must pass through untranslated.
Module(i32),
}
impl Error {
/// Map to the public error code. Module codes pass through verbatim.
pub fn code(&self) -> i32 {
match self {
Error::Invalid => OAKENGINE_E_INVALID,
Error::State => OAKENGINE_E_STATE,
Error::Failed(_) => OAKENGINE_E_FAILED,
Error::NotFound => OAKENGINE_E_NOT_FOUND,
Error::NoMem => OAKENGINE_E_NOMEM,
Error::Cancelled => OAKENGINE_E_CANCELLED,
Error::Module(code) => *code,
}
}
/// Wrap a module return code. `0` (OK) never becomes an error; any
/// negative code is kept as a pass-through [`Error::Module`].
pub fn from_module(code: i32) -> Result<()> {
if code == 0 {
Ok(())
} else {
Err(Error::Module(code))
}
}
}
+255
View File
@@ -0,0 +1,255 @@
// 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/>.
//! Facade scaffolding: engine opaque pointers as thin newtype wrappers
//! around module [`CHandle`] values.
//!
//! Every `OakEngine*` opaque type from `engine/include/oakengine/*.h`
//! is a `#[repr(C)]` struct holding one [`CHandle`] (the module C ABI's
//! `{ctx, addref, release, abi_version}` value handle, see
//! `include/common/handle.h`). The C caller only ever sees an opaque
//! pointer, so the field layout is ours to choose; the wrappers exist so
//! the exported `oakengine_*` signatures match the frozen headers
//! verbatim.
//!
//! A box is created by [`box_handle`] and freed by [`free_box`]: freeing
//! calls the handle's `release` (for a module-borrowed handle that only
//! releases the handle shell, never the graph-owned object) and then
//! deallocates the box. Consuming exports (`oakengine_*_free`,
//! `oakengine_undo_push`, ...) call [`free_box`].
//!
//! String output follows the engine's buf/size convention (see
//! [`write_string`]): the return value is the required length including
//! the terminating NUL; negative values are error codes.
use std::ffi::{c_char, c_int, c_void};
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::error::{Error, Result};
/// The shared ABI value-handle type (single-lib unification, see
/// `docs/zh/plans/riir/single-lib.md`): one canonical
/// `{ctx, addref, release, abi_version}` type in `oakcore-rs`, re-exported
/// by every module crate, so the facade can pass a handle straight into a
/// module's `pub` Rust functions without an `extern "C"` declaration.
/// `Clone + Copy + Send + Sync` come from the shared type.
pub use oakcore_rs::handle::CHandle;
/// Engine opaque handle types, one per `typedef struct OakEngine*` in
/// `engine/include/oakengine/*.h`. All are thin newtype wrappers around a
/// [`CHandle`] value with a uniform extraction surface ([`EngineBox`]).
macro_rules! engine_handle {
($($name:ident),* $(,)?) => {
$(
/// Opaque engine handle: thin newtype wrapper around a module
/// [`CHandle`] value.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct $name {
/// The wrapped module handle.
pub handle: CHandle,
}
impl EngineBox for $name {
fn boxed_new(handle: CHandle) -> Self {
$name { handle }
}
fn handle(&self) -> CHandle {
self.handle
}
}
)*
};
}
engine_handle! {
OakEngineAudioBuffer,
OakEngineAudioProcessor,
OakEngineBlock,
OakEngineClip,
OakEngineClipboard,
OakEngineColorConfig,
OakEngineColorManager,
OakEngineColorProcessor,
OakEngineEncodingParams,
OakEngineFootage,
OakEngineFrame,
OakEngineFrameCache,
OakEngineKeyframe,
OakEngineMarker,
OakEngineMarkerList,
OakEngineNode,
OakEngineNodeDragger,
OakEnginePlayback,
OakEnginePlaybackCache,
OakEnginePreviewRequest,
OakEngineProject,
OakEngineRenderer,
OakEngineSequence,
OakEngineTask,
OakEngineThumbnailCache,
OakEngineTrack,
OakEngineTrackList,
OakEngineTraverseDb,
OakEngineWaveformCache,
OakEngineWorkarea,
}
/// Uniform construction/extraction surface of the engine opaque types.
pub trait EngineBox: Sized {
/// Build the wrapper from a module handle.
fn boxed_new(handle: CHandle) -> Self;
/// Extract the wrapped module handle (copy).
fn handle(&self) -> CHandle;
}
/// Allocate a heap box for a module handle and return its raw pointer.
/// The box must later be released with [`free_box`].
pub fn box_handle<T: EngineBox>(handle: CHandle) -> *mut T {
Box::into_raw(Box::new(T::boxed_new(handle)))
}
/// Dereference an engine opaque pointer and copy out its module handle.
/// Returns [`Error::Invalid`] for a NULL pointer or an empty handle.
///
/// # Safety
/// `ptr` must point to a live box created by [`box_handle`] (or be
/// NULL).
pub unsafe fn unbox<T: EngineBox>(ptr: *const T) -> Result<CHandle> {
unsafe {
if ptr.is_null() {
return Err(Error::Invalid);
}
let h = (*ptr).handle();
if h.is_null() {
return Err(Error::Invalid);
}
Ok(h)
}
}
/// Free a box created by [`box_handle`]: release the module handle (via
/// its `release` function pointer) and deallocate the box. NULL and
/// empty handles are no-ops. After the call `ptr` is dangling; the
/// caller must not use it again.
///
/// # Safety
/// `ptr` must be a pointer previously returned by [`box_handle`] (or
/// NULL) and must not be freed twice.
pub unsafe fn free_box<T: EngineBox>(ptr: *mut T) {
unsafe {
if ptr.is_null() {
return;
}
let handle = (*ptr).handle();
if let Some(release) = handle.release {
release(handle.ctx);
}
drop(Box::from_raw(ptr));
}
}
/// Panic-catching FFI wrapper for `i32`-returning exports.
pub fn guard<F: FnOnce() -> Result<()>>(f: F) -> c_int {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(())) => crate::error::OAKENGINE_OK,
Ok(Err(e)) => e.code(),
Err(_) => crate::error::OAKENGINE_E_FAILED,
}
}
/// Panic-catching FFI wrapper for pointer-returning exports.
pub fn guard_ptr<T, F: FnOnce() -> Result<*mut T>>(f: F) -> *mut T {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(p)) => p,
_ => std::ptr::null_mut(),
}
}
/// Panic-catching FFI wrapper for `int64_t`-returning exports
/// (`OAKENGINE_E_INVALID` sentinel on error, matching the engine's
/// "no application core exists" convention).
pub fn guard_i64<F: FnOnce() -> Result<i64>>(f: F) -> i64 {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(v)) => v,
Ok(Err(_)) => crate::error::OAKENGINE_E_INVALID as i64,
Err(_) => crate::error::OAKENGINE_E_FAILED as i64,
}
}
/// Panic-catching FFI wrapper for void exports.
pub fn guard_void<F: FnOnce()>(f: F) {
let _ = catch_unwind(AssertUnwindSafe(f));
}
/// Panic-catching FFI wrapper for exports whose return value IS the
/// result (a count, a 1/0 flag, a required string length): the closure
/// returns the positive payload, errors are returned as negative codes.
pub fn guard_int<F: FnOnce() -> Result<c_int>>(f: F) -> c_int {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(v)) => v,
Ok(Err(e)) => e.code(),
Err(_) => crate::error::OAKENGINE_E_FAILED,
}
}
/// Write `s` into `buf` following the engine buf/size convention and
/// return the string length **excluding** the terminating NUL (the engine
/// headers' "would-be length"; module getters report len+1 and are
/// converted with [`string_result`]). A NULL `buf` or `buf_size <= 0`
/// only reports the length. `s` is truncated to `buf_size - 1` bytes when
/// it does not fit.
///
/// # Safety
/// `buf` must point to `buf_size` writable bytes when non-NULL and
/// `buf_size > 0`.
pub unsafe fn write_string(s: &str, buf: *mut c_char, buf_size: c_int) -> c_int {
unsafe {
if !buf.is_null() && buf_size > 0 {
let copy_len = s.len().min((buf_size as usize).saturating_sub(1));
std::ptr::copy_nonoverlapping(s.as_ptr(), buf as *mut u8, copy_len);
*buf.add(copy_len) = 0;
}
}
s.len() as c_int
}
/// Read a NUL-terminated C string; NULL yields an empty string.
///
/// # Safety
/// `s` must be a valid NUL-terminated string, or NULL.
pub unsafe fn read_cstr(s: *const c_char) -> String {
unsafe {
if s.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
}
}
}
/// Convert a module two-stage getter result to the engine convention.
/// Module getters report the required buffer size **including** the
/// terminating NUL; the engine headers' buf/size convention reports the
/// string **length** (excluding the NUL, mirroring the C++ capi
/// `write_string`). Negative codes pass through untranslated.
pub fn string_result(module_ret: c_int) -> c_int {
if module_ret > 0 {
module_ret - 1
} else {
module_ret
}
}
File diff suppressed because it is too large Load Diff
+96
View File
@@ -0,0 +1,96 @@
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! # oakengine — the `liboakengine` facade (Rust)
//!
//! Re-exports the frozen `oakengine_*` C ABI
//! (`engine/include/oakengine/*.h`) verbatim on top of the module C ABIs
//! (`include/<mod>/*.h`, implemented by the oakundo/oaknode/oaktimeline/
//! oakcodec/oakaudio/oakrender/oaktask/oakcommon/oakplugin crates). It is
//! the M9 §4 assembly layer: every module call crosses the module C ABI as
//! an `extern "C"` import (see [`bridge`]); the facade itself owns only
//! cross-cutting state (the process-wide undo stack and the open undo
//! group, see [`undo`]).
//!
//! ## Handle mapping
//!
//! The engine headers' opaque pointers (`OakEngineNode*`, `OakEngineTrack*`,
//! ...) become thin newtype wrappers around module [`handle::CHandle`]
//! values (see [`handle`]). Each exported function keeps the exact
//! signature from the engine header; inside, it unboxes the module handle,
//! calls the module C ABI and boxes the result.
//!
//! ## FFI discipline
//!
//! Every export goes through a `catch_unwind` guard ([`handle::guard*`]),
//! `free` functions are NULL no-ops, strings use the two-stage buf/size
//! convention ([`handle::write_string`]), and module error codes pass
//! through untranslated ([`error`], facade module 00 → -1..-6).
//!
//! ## Testing
//!
//! The module crates are real dependencies (see Cargo.toml) and
//! [`linkage`] anchors them into every link of this crate, so the module
//! C ABIs are embedded in the `liboakengine` cdylib next to the facade's
//! own exports. `cargo test` links the same crates' rlibs (plus the
//! `test-stubs` feature union declared in the dev-dependencies, which
//! compiles the oakcommon/oakplugin in-crate mocks); `tests/linkage.rs`
//! additionally references every crate for the integration-test binaries
//! and `test_link` (below) covers the unit-test binary. Where a wrapped
//! family needs module behavior the crates do not implement yet, the
//! engine function is a documented stub and its test carries `#[ignore]`
//! with a reason (see README.md).
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)]
pub mod audio;
pub mod bridge;
pub mod codec;
pub mod common;
pub mod deferred;
pub mod error;
pub mod handle;
pub mod ipc;
#[cfg(not(test))]
pub mod linkage;
pub mod node;
pub mod plugin;
pub mod render;
pub mod task;
pub mod timeline;
pub mod undo;
pub mod worker;
#[cfg(test)]
mod test_link {
// The lib's own unit-test binary must link the module crates' rlibs to
// satisfy the facade's `extern "C"` imports that the unit tests compile
// in — e.g. the worker session's oakrender display renderer (src/worker.rs).
// The integration tests do the same through tests/common/mod.rs
// `force_link()`; this covers the `cargo test` unit-test binary.
#![allow(dead_code)]
fn force_link() -> usize {
let fns: [usize; 4] = [
oakrender::ffi::renderer::oakrender_display_renderer_create_opengl
as *const () as usize,
oaknode::ffi::project::oaknode_project_init as *const () as usize,
oaktimeline::ffi::marker::oaktimeline_marker_list_create as *const () as usize,
oaktask::ffi::manager::oaktask_manager_init as *const () as usize,
];
fns.iter().sum()
}
}
+66
View File
@@ -0,0 +1,66 @@
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Linkage anchors — force the module crates' rlibs into every link.
//!
//! The facade talks to the modules exclusively through `extern "C"`
//! imports (src/bridge/), so rustc would otherwise consider the module
//! crates unused and prune their rlibs from the link. This module
//! references one `#[no_mangle]` export of every module crate (and
//! `oakcore-rs`) from a `#[used]` static, which (a) marks each crate as
//! used so its rlib reaches the linker and (b) keeps the anchor alive so
//! the referenced object files are pulled. For the `liboakengine` cdylib
//! this is what actually embeds the module C ABIs (oakundo_*,
//! oakcommon_*, ...) into the dylib next to the facade's own oakengine_*
//! exports.
//!
//! The per-crate symbol mirrors the test-force-link in
//! tests/common/mod.rs (same paths, same `as usize` cast idiom), so the
//! crate/module paths are proven against the current module layouts.
#![allow(dead_code)]
/// Pull every module crate into the link. Mirrors
/// `tests/common/mod.rs::force_link`; the oakcommon XML/undo anchors are
/// repeated because oaknode's serializer resolves those C ABI symbols at
/// runtime via dlsym(RTLD_DEFAULT) and they must be present in the dylib
/// for that lookup to succeed.
fn force_link() -> usize {
let fns: [usize; 13] = [
// oakcore-rs (pure value types; referenced so its rlib is linked).
oakcore_rs::Rational::new(1, 2).numerator() as usize,
// One exported C ABI symbol per module crate.
oakundo::ffi::undostack::oakundo_undostack_init as usize,
oakcommon::ffi::config::oakcommon_config_get_int as usize,
oaktimeline::ffi::marker::oaktimeline_marker_list_create as usize,
oakcodec::ffi::format::oakcodec_encoding_format_count as usize,
oakaudio::ffi::waveform::oakaudio_waveform_length as usize,
oakrender::ffi::cache::oakrender_cache_indicator_height as usize,
oaktask::ffi::manager::oaktask_manager_init as usize,
oakplugin::ffi::oakplugin_host_plugin_count as usize,
oaknode::ffi::project::oaknode_project_init as usize,
// oaknode's dlsym(RTLD_DEFAULT) targets (see tests/common/mod.rs).
oakcommon::ffi::xmlutils::oakcommon_xml_writer_init as usize,
oakcommon::ffi::xmlutils::oakcommon_xml_reader_init as usize,
oakundo::ffi::command::oakundo_command_init as usize,
];
fns.iter().sum()
}
/// Keeps [`force_link`] (and through it every referenced export) alive in
/// the cdylib/staticlib even though nothing calls it directly.
#[used]
static FORCE_LINK_ANCHOR: fn() -> usize = force_link;
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! `engine/include/oakengine/plugin.h` over the oakplugin module.
//!
//! The active-viewer provider and progress-reporter factory are pure
//! facade state (module 00 analogues of the C++ capi's statics): the UI
//! registers C callbacks here, and the plugin host consumes them once the
//! module exposes the corresponding registration points
//! (`oakplugin_*_set_*_provider`). Until then the callbacks are stored
//! and reported as registered.
use std::ffi::{c_char, c_int, c_void};
use std::sync::{Mutex, OnceLock};
use crate::bridge::plugin as p;
use crate::error::Error;
use crate::handle::guard;
/// `oakengine_plugin_active_viewer_fn` — returns the active viewer node.
pub type ActiveViewerFn =
unsafe extern "C" fn(userdata: *mut c_void) -> *mut crate::handle::OakEngineNode;
/// `oakengine_plugin_reporter_create_fn` — creates a UI progress reporter.
pub type ReporterCreateFn =
unsafe extern "C" fn(message: *const c_char, title: *const c_char, userdata: *mut c_void) -> *mut c_void;
/// `oakengine_plugin_reporter_destroy_fn` — destroys a reporter.
pub type ReporterDestroyFn = unsafe extern "C" fn(reporter: *mut c_void, userdata: *mut c_void);
/// `oakengine_plugin_reporter_is_cancelled_fn` — 1 when cancelled.
pub type ReporterIsCancelledFn = unsafe extern "C" fn(reporter: *mut c_void, userdata: *mut c_void) -> c_int;
/// `oakengine_plugin_reporter_set_progress_fn` — progress update.
pub type ReporterSetProgressFn =
unsafe extern "C" fn(reporter: *mut c_void, progress: f64, userdata: *mut c_void);
struct ProviderState {
active_viewer: Option<(Option<ActiveViewerFn>, usize)>,
reporter: Option<(
Option<ReporterCreateFn>,
Option<ReporterDestroyFn>,
Option<ReporterIsCancelledFn>,
Option<ReporterSetProgressFn>,
usize,
)>,
}
fn state() -> &'static Mutex<ProviderState> {
static STATE: OnceLock<Mutex<ProviderState>> = OnceLock::new();
STATE.get_or_init(|| {
Mutex::new(ProviderState {
active_viewer: None,
reporter: None,
})
})
}
/// `oakengine_plugin_set_active_viewer_provider` — register the active
/// viewer callback (NULL clears it).
#[no_mangle]
pub extern "C" fn oakengine_plugin_set_active_viewer_provider(
fn_: Option<ActiveViewerFn>,
userdata: *mut c_void,
) -> c_int {
guard(|| {
let mut s = state().lock().unwrap_or_else(|e| e.into_inner());
s.active_viewer = Some((fn_, userdata as usize));
Ok(())
})
}
/// `oakengine_plugin_set_progress_reporter_factory` — register the
/// progress-reporter factory callbacks (NULL clears them).
#[no_mangle]
pub extern "C" fn oakengine_plugin_set_progress_reporter_factory(
create: Option<ReporterCreateFn>,
destroy: Option<ReporterDestroyFn>,
is_cancelled: Option<ReporterIsCancelledFn>,
set_progress: Option<ReporterSetProgressFn>,
userdata: *mut c_void,
) -> c_int {
guard(|| {
let mut s = state().lock().unwrap_or_else(|e| e.into_inner());
s.reporter = Some((create, destroy, is_cancelled, set_progress, userdata as usize));
Ok(())
})
}
/// `oakengine_plugin_load_plugins` — scan the plugin bundle directory
/// `path` (oakplugin_host_scan).
#[no_mangle]
pub unsafe extern "C" fn oakengine_plugin_load_plugins(path: *const c_char) -> c_int {
guard(|| unsafe {
if path.is_null() {
return Err(Error::Invalid);
}
let dirs: [*const c_char; 1] = [path];
Error::from_module(p::oakplugin_host_scan(dirs.as_ptr(), 1))
})
}
/// `oakengine_plugin_node_push_button_clicked` — not yet backed: the
/// oakplugin crate exposes no push-button API (the OFX button-param
/// trigger is C++-only). Returns `OAKENGINE_E_FAILED`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_plugin_node_push_button_clicked(
_node: *mut crate::handle::OakEngineNode,
_button_id: *const c_char,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
+958
View File
@@ -0,0 +1,958 @@
// 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/>.
//! `engine/include/oakengine/{renderer,color,lut}.h` over the oakrender
//! module.
//!
//! - The **renderer** is a facade-owned box binding a sequence handle to
//! an output geometry; each render call submits an oakrender ticket
//! (`OakVideoTicketParams`), waits for it and returns the produced
//! frame (`OakCodecFrame` wrapped in `OakEngineFrame`). Audio rendering
//! submits the ticket but the crate's samples path is unimplemented, so
//! it fails with the reason in `last_error`.
//! - The **frame accessors** read the wrapped `OakCodecFrame`
//! (`channel_count` has no crate accessor and reports 0).
//! - The **color processor** family maps onto
//! `oakrender_color_processor_*`; the engine's `oak_color_transform`
//! POD is converted into an oakcommon colortransform handle for
//! `create_transform`. The color-manager list queries, standalone
//! config handle and LUT directory/file library have no crate backing
//! and are documented stubs (see `deferred.rs`).
use std::cell::RefCell;
use std::ffi::{c_char, c_double, c_int, c_void};
use crate::bridge::render as r;
use crate::bridge::render::{OakRenderVideoParams, OakVideoTicketParams};
use crate::error::{Error, Result};
use crate::handle::{
box_handle, free_box, guard, guard_int, guard_ptr, guard_void, string_result, unbox, CHandle,
EngineBox, OakEngineAudioBuffer, OakEngineColorProcessor, OakEngineFrame, OakEngineNode,
OakEngineRenderer, OakEngineSequence,
};
// ---------------------------------------------------------------------------
// Render manager / cacher
// ---------------------------------------------------------------------------
/// `oakengine_render_manager_set_aggressive_garbage_collection`.
#[no_mangle]
pub extern "C" fn oakengine_render_manager_set_aggressive_garbage_collection(
aggressive: c_int,
) -> c_int {
guard(|| Error::from_module(unsafe { r::oakrender_manager_set_aggressive_gc(aggressive) }))
}
/// `oakengine_render_manager_requested_backend` — **not backed** (the
/// oakrender crate exposes the current backend, not the requested one).
/// Returns 0 (k_open_gl).
#[no_mangle]
pub extern "C" fn oakengine_render_manager_requested_backend() -> c_int {
0
}
/// `oakengine_render_manager_backend_to_string` — **not backed** (the
/// crate enumerates backend ids, not enum→string). Returns
/// OAKENGINE_E_FAILED.
#[no_mangle]
pub unsafe extern "C" fn oakengine_render_manager_backend_to_string(
_backend: c_int,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_render_cache_set_display_color_processor` — NULL clears.
#[no_mangle]
pub unsafe extern "C" fn oakengine_render_cache_set_display_color_processor(
processor: *mut c_void,
) -> c_int {
guard(|| unsafe {
let proc = if processor.is_null() {
CHandle::null()
} else {
unbox(processor.cast::<OakEngineColorProcessor>())?
};
Error::from_module(r::oakrender_set_display_color_processor(proc))
})
}
/// `oakengine_render_cache_set_multicam_node` — NULL clears.
#[no_mangle]
pub unsafe extern "C" fn oakengine_render_cache_set_multicam_node(node: *mut OakEngineNode) -> c_int {
guard(|| unsafe {
let n = if node.is_null() {
CHandle::null()
} else {
unbox(node)?
};
Error::from_module(r::oakrender_set_cacher_multicam(n))
})
}
// ---------------------------------------------------------------------------
// Renderer
// ---------------------------------------------------------------------------
/// Facade-side renderer box: the bound sequence + output geometry.
struct RendererBox {
/// Unboxed sequence node handle (borrowed).
seq: CHandle,
/// Output width.
width: c_int,
/// Output height.
height: c_int,
/// Output pixel format (`PixelFormat::Format`).
pixel_format: c_int,
/// Frame-rate numerator.
frame_rate_num: c_int,
/// Frame-rate denominator.
frame_rate_den: c_int,
/// Render mode (0 offline / 1 online).
mode: c_int,
/// Last failure reason.
last_error: String,
}
/// Borrow the renderer box (NULL → Invalid).
unsafe fn renderer(ptr: *const OakEngineRenderer) -> Result<&'static RendererBox> {
unsafe {
if ptr.is_null() {
return Err(Error::Invalid);
}
Ok(&*(ptr as *const RendererBox))
}
}
/// Borrow the renderer box mutably.
unsafe fn renderer_mut(ptr: *mut OakEngineRenderer) -> Result<&'static mut RendererBox> {
unsafe {
if ptr.is_null() {
return Err(Error::Invalid);
}
Ok(&mut *(ptr as *mut RendererBox))
}
}
/// Build an oakcommon video-params handle for the renderer's geometry.
unsafe fn make_video_params(b: &RendererBox) -> Result<CHandle> {
unsafe {
let params = crate::bridge::common::oakcommon_videoparams_init();
if params.is_null() {
return Err(Error::Failed("video params allocation failed".into()));
}
let mut rc = crate::bridge::common::oakcommon_videoparams_set_width(params, b.width);
if rc == 0 {
rc = crate::bridge::common::oakcommon_videoparams_set_height(params, b.height);
}
if rc == 0 {
rc = crate::bridge::common::oakcommon_videoparams_set_format(params, b.pixel_format);
}
if rc == 0 {
rc = crate::bridge::common::oakcommon_videoparams_set_time_base(
params,
b.frame_rate_den,
b.frame_rate_num,
);
}
if rc != 0 {
let mut p = params;
crate::bridge::common::oakcommon_videoparams_free(&mut p);
return Err(Error::Failed("video params setup failed".into()));
}
Ok(params)
}
}
/// `oakengine_renderer_create` — NULL for invalid arguments.
#[no_mangle]
pub unsafe extern "C" fn oakengine_renderer_create(
seq: *mut OakEngineSequence,
width: c_int,
height: c_int,
pixel_format: c_int,
frame_rate_num: c_int,
frame_rate_den: c_int,
output_colorspace: *const c_char,
) -> *mut OakEngineRenderer {
guard_ptr(|| unsafe {
if seq.is_null() || width <= 0 || height <= 0 || frame_rate_num <= 0 || frame_rate_den <= 0 {
return Ok(std::ptr::null_mut());
}
// Validate the pixel format against the oakcommon format enum.
if crate::bridge::common::oakcommon_videoparams_get_format_name(
pixel_format,
std::ptr::null_mut(),
0,
) < 0
{
return Ok(std::ptr::null_mut());
}
let _ = crate::handle::read_cstr(output_colorspace); // resolved by the module at render time
let seq_handle = unbox(seq)?;
let boxed = Box::new(RendererBox {
seq: seq_handle,
width,
height,
pixel_format,
frame_rate_num,
frame_rate_den,
mode: 0,
last_error: String::new(),
});
Ok(Box::into_raw(boxed) as *mut OakEngineRenderer)
})
}
/// `oakengine_renderer_free` — NULL no-op.
#[no_mangle]
pub unsafe extern "C" fn oakengine_renderer_free(self_: *mut OakEngineRenderer) {
guard_void(|| unsafe {
if self_.is_null() {
return;
}
drop(Box::from_raw(self_ as *mut RendererBox));
})
}
/// `oakengine_renderer_set_mode` — 0/1 only.
#[no_mangle]
pub unsafe extern "C" fn oakengine_renderer_set_mode(
self_: *mut OakEngineRenderer,
mode: c_int,
) -> c_int {
guard(|| unsafe {
let b = renderer_mut(self_)?;
if mode != 0 && mode != 1 {
return Err(Error::Invalid);
}
b.mode = mode;
Ok(())
})
}
/// `oakengine_renderer_last_error` (buf/size).
#[no_mangle]
pub unsafe extern "C" fn oakengine_renderer_last_error(
self_: *const OakEngineRenderer,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
let b = renderer(self_)?;
Ok(crate::handle::write_string(&b.last_error, buf, buf_size))
})
}
/// `oakengine_renderer_render_frame` — synchronous frame render.
#[no_mangle]
pub unsafe extern "C" fn oakengine_renderer_render_frame(
self_: *mut OakEngineRenderer,
timestamp: i64,
) -> *mut OakEngineFrame {
guard_ptr(|| unsafe {
let b = renderer_mut(self_)?;
let video_params = make_video_params(b)?;
let params = OakVideoTicketParams {
output_node: b.seq,
video_params,
audio_params: std::ptr::null(),
time_num: timestamp * i64::from(b.frame_rate_den),
time_den: i64::from(b.frame_rate_num),
color_manager: CHandle::null(),
mode: b.mode,
force_width: 0,
force_height: 0,
force_matrix: [0.0; 16],
has_force_matrix: 0,
force_format: -1,
force_channel_count: 0,
force_color_output: CHandle::null(),
force_color_transform: CHandle::null(),
cache: CHandle::null(),
};
let ticket = r::oakrender_ticket_render_frame(&params, None, std::ptr::null_mut());
let mut vp = video_params;
crate::bridge::common::oakcommon_videoparams_free(&mut vp);
if ticket.is_null() {
b.last_error = "render ticket submission failed".into();
return Ok(std::ptr::null_mut());
}
let wait_rc = r::oakrender_ticket_wait(ticket);
let mut frame = CHandle::null();
let get_rc = r::oakrender_ticket_get_frame(ticket, &mut frame);
let mut t = ticket;
r::oakrender_ticket_free(&mut t);
if wait_rc != 0 || get_rc != 0 || frame.is_null() {
b.last_error = "render failed or timed out".into();
return Ok(std::ptr::null_mut());
}
b.last_error.clear();
Ok(box_handle::<OakEngineFrame>(frame))
})
}
/// `oakengine_renderer_render_audio` — synchronous audio render. The
/// oakrender crate's samples path is unimplemented, so this submits the
/// ticket and reports the failure reason.
#[no_mangle]
pub unsafe extern "C" fn oakengine_renderer_render_audio(
self_: *mut OakEngineRenderer,
start_timestamp: i64,
length_timestamp: i64,
) -> *mut OakEngineAudioBuffer {
guard_ptr(|| unsafe {
let b = renderer_mut(self_)?;
let start_num = start_timestamp * i64::from(b.frame_rate_den);
let end_num = (start_timestamp + length_timestamp) * i64::from(b.frame_rate_den);
let den = i64::from(b.frame_rate_num);
let ticket = r::oakrender_ticket_render_audio(
b.seq,
start_num,
den,
end_num,
den,
std::ptr::null(),
b.mode,
None,
std::ptr::null_mut(),
);
if ticket.is_null() {
b.last_error = "audio render ticket submission failed".into();
return Ok(std::ptr::null_mut());
}
let wait_rc = r::oakrender_ticket_wait(ticket);
let mut samples: *mut c_void = std::ptr::null_mut();
let get_rc = r::oakrender_ticket_get_samples(ticket, &mut samples);
let mut t = ticket;
r::oakrender_ticket_free(&mut t);
let _ = (wait_rc, get_rc, samples);
b.last_error = "audio rendering is not implemented by the render module".into();
Ok(std::ptr::null_mut())
})
}
/// `oakengine_renderer_cancel` — cancel the in-flight render call.
#[no_mangle]
pub unsafe extern "C" fn oakengine_renderer_cancel(self_: *mut OakEngineRenderer) {
guard_void(|| unsafe {
if let Ok(b) = renderer_mut(self_) {
let _ = b; // the crate tracks in-flight tickets internally
}
})
}
// ---------------------------------------------------------------------------
// OakEngineFrame accessors (wraps the module's OakCodecFrame)
// ---------------------------------------------------------------------------
/// Borrow the frame's module handle; `None` for NULL/empty (the engine
/// contract: NULL is a no-op yielding zero results).
unsafe fn frame_handle(ptr: *const OakEngineFrame) -> Option<CHandle> {
unsafe {
if ptr.is_null() {
return None;
}
let h = (*ptr).handle();
if h.is_null() {
None
} else {
Some(h)
}
}
}
/// `oakengine_frame_width`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_frame_width(self_: *const OakEngineFrame) -> c_int {
guard_int(|| unsafe {
Ok(match frame_handle(self_) {
Some(f) => r::oakrender_codec_frame_width(f),
None => 0,
})
})
}
/// `oakengine_frame_height`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_frame_height(self_: *const OakEngineFrame) -> c_int {
guard_int(|| unsafe {
Ok(match frame_handle(self_) {
Some(f) => r::oakrender_codec_frame_height(f),
None => 0,
})
})
}
/// `oakengine_frame_format` — the frame's params POD format
/// (`PixelFormat::Format`).
#[no_mangle]
pub unsafe extern "C" fn oakengine_frame_format(self_: *const OakEngineFrame) -> c_int {
guard_int(|| unsafe {
let Some(f) = frame_handle(self_) else {
return Ok(0);
};
let mut params = OakRenderVideoParams {
width: 0,
height: 0,
time_base_num: 0,
time_base_den: 0,
format: 0,
pixel_aspect_num: 0,
pixel_aspect_den: 0,
interlacing: 0,
color_range: 0,
divider: 0,
video_type: 0,
premultiplied_alpha: 0,
};
Error::from_module(r::oakrender_codec_frame_get_params(f, &mut params))?;
Ok(params.format)
})
}
/// `oakengine_frame_channel_count` — **not backed** (the oakrender crate
/// exposes no frame channel count). Returns 0.
#[no_mangle]
pub unsafe extern "C" fn oakengine_frame_channel_count(self_: *const OakEngineFrame) -> c_int {
let _ = self_;
0
}
/// `oakengine_frame_linesize_bytes`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_frame_linesize_bytes(self_: *const OakEngineFrame) -> c_int {
guard_int(|| unsafe {
Ok(match frame_handle(self_) {
Some(f) => r::oakrender_codec_frame_linesize_bytes(f),
None => 0,
})
})
}
/// `oakengine_frame_data` — borrowed pixel data.
#[no_mangle]
pub unsafe extern "C" fn oakengine_frame_data(self_: *const OakEngineFrame) -> *const c_void {
guard_ptr(|| unsafe {
Ok(match frame_handle(self_) {
Some(f) => r::oakrender_codec_frame_const_data(f) as *mut c_void,
None => std::ptr::null_mut(),
})
})
}
/// `oakengine_frame_free` — NULL no-op.
#[no_mangle]
pub unsafe extern "C" fn oakengine_frame_free(self_: *mut OakEngineFrame) {
guard_void(|| unsafe {
if self_.is_null() {
return;
}
let handle = (*self_).handle();
let mut h = handle;
r::oakrender_codec_frame_free(&mut h);
drop(Box::from_raw(self_));
})
}
// ---------------------------------------------------------------------------
// OakEngineAudioBuffer accessors (no crate backing — always empty)
// ---------------------------------------------------------------------------
/// `oakengine_audio_sample_rate` — 0 (no crate samples accessor).
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_sample_rate(_self_: *const OakEngineAudioBuffer) -> c_int {
0
}
/// `oakengine_audio_channel_count` — 0.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_channel_count(_self_: *const OakEngineAudioBuffer) -> c_int {
0
}
/// `oakengine_audio_sample_count` — 0.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_sample_count(_self_: *const OakEngineAudioBuffer) -> i64 {
0
}
/// `oakengine_audio_data` — NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_data(
_self_: *const OakEngineAudioBuffer,
_channel: c_int,
) -> *const f32 {
std::ptr::null()
}
/// `oakengine_audio_free` — NULL no-op.
#[no_mangle]
pub unsafe extern "C" fn oakengine_audio_free(_self_: *mut OakEngineAudioBuffer) {}
// ---------------------------------------------------------------------------
// Color management
// ---------------------------------------------------------------------------
// Thread-local reason of the last failed color call.
thread_local! {
static LAST_COLOR_ERROR: RefCell<String> = const { RefCell::new(String::new()) };
}
/// `oakengine_color_last_error` (buf/size).
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_last_error(buf: *mut c_char, buf_size: c_int) -> c_int {
crate::handle::guard_int(|| {
Ok(LAST_COLOR_ERROR.with(|e| {
// SAFETY: `buf` is the caller's buf/size buffer.
unsafe { crate::handle::write_string(&e.borrow(), buf, buf_size) }
}))
})
}
/// `oakengine_color_manager_from_project` — **not backed** (needs the
/// deferred oaknode project family). Returns NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_from_project(
_project: *mut crate::handle::OakEngineProject,
) -> *mut crate::handle::OakEngineColorManager {
std::ptr::null_mut()
}
/// `oakengine_color_manager_get_config_filename` (buf/size).
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_get_config_filename(
_mgr: *const crate::handle::OakEngineColorManager,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
let rc = r::oakrender_color_manager_get_config(buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
/// `oakengine_color_manager_set_config_filename` — **not backed** (the
/// crate only reads the config). Returns OAKENGINE_E_FAILED.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_set_config_filename(
_mgr: *mut crate::handle::OakEngineColorManager,
_filename: *const c_char,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
macro_rules! color_manager_stub {
($($name:ident),* $(,)?) => {
$(
/// Documented stub: the oakrender crate does not implement the
/// color-manager list queries (see `deferred.rs`).
#[no_mangle]
pub unsafe extern "C" fn $name() -> c_int {
crate::error::OAKENGINE_E_FAILED
}
)*
};
}
color_manager_stub! {
oakengine_color_manager_colorspace_count,
oakengine_color_manager_display_count,
oakengine_color_manager_look_count,
}
macro_rules! color_manager_stub_arg {
($($name:ident),* $(,)?) => {
$(
/// Documented stub: the oakrender crate does not implement the
/// color-manager list queries (see `deferred.rs`).
#[no_mangle]
pub unsafe extern "C" fn $name(
_mgr: *const crate::handle::OakEngineColorManager,
_index: c_int,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
)*
};
}
color_manager_stub_arg! {
oakengine_color_manager_colorspace_at,
oakengine_color_manager_display_at,
}
/// `oakengine_color_manager_view_count` — **not backed**. -1.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_view_count(
_mgr: *const crate::handle::OakEngineColorManager,
_display: *const c_char,
) -> c_int {
-1
}
/// `oakengine_color_manager_view_at` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_view_at(
_mgr: *const crate::handle::OakEngineColorManager,
_display: *const c_char,
_index: c_int,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_look_at` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_look_at(
_mgr: *const crate::handle::OakEngineColorManager,
_index: c_int,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_default_display` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_default_display(
_mgr: *const crate::handle::OakEngineColorManager,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_default_view` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_default_view(
_mgr: *const crate::handle::OakEngineColorManager,
_display: *const c_char,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_default_input_color_space` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_default_input_color_space(
_mgr: *const crate::handle::OakEngineColorManager,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_set_default_input_color_space` — **not
/// backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_set_default_input_color_space(
_mgr: *mut crate::handle::OakEngineColorManager,
_colorspace: *const c_char,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_reference_color_space` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_reference_color_space(
_mgr: *const crate::handle::OakEngineColorManager,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_default_luma_coefs` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_default_luma_coefs(
_mgr: *const crate::handle::OakEngineColorManager,
_rgb: *mut c_double,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_compliant_color_space` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_compliant_color_space(
_mgr: *const crate::handle::OakEngineColorManager,
_name: *const c_char,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_manager_compliant_transform` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_manager_compliant_transform(
_mgr: *const crate::handle::OakEngineColorManager,
_in: *const OakColorTransformPod,
_force_display: c_int,
_out_is_display: *mut c_int,
_out_output: *mut c_char,
_output_size: c_int,
_out_view: *mut c_char,
_view_size: c_int,
_out_look: *mut c_char,
_look_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
// ---------------------------------------------------------------------------
// Color config handle (not backed)
// ---------------------------------------------------------------------------
/// `oakengine_color_config_load_default` — **not backed**. NULL.
#[no_mangle]
pub extern "C" fn oakengine_color_config_load_default() -> *mut crate::handle::OakEngineColorConfig {
std::ptr::null_mut()
}
/// `oakengine_color_config_load_file` — **not backed**. NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_config_load_file(
_filename: *const c_char,
) -> *mut crate::handle::OakEngineColorConfig {
std::ptr::null_mut()
}
/// `oakengine_color_config_free` — NULL no-op.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_config_free(
_config: *mut crate::handle::OakEngineColorConfig,
) {
}
/// `oakengine_color_config_colorspace_count` — **not backed**. 0.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_config_colorspace_count(
_config: *const crate::handle::OakEngineColorConfig,
) -> c_int {
0
}
/// `oakengine_color_config_colorspace_at` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_config_colorspace_at(
_config: *const crate::handle::OakEngineColorConfig,
_index: c_int,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
// ---------------------------------------------------------------------------
// Color processor
// ---------------------------------------------------------------------------
/// `engine/include/oakengine/color.h` — `oak_color_transform` POD mirror.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct OakColorTransformPod {
/// 0: `output` is a colorspace; 1: display/view/look.
pub is_display: c_int,
/// Colorspace name, or display device when is_display.
pub output: *const c_char,
/// Display view (is_display only).
pub view: *const c_char,
/// Display look (is_display only).
pub look: *const c_char,
}
/// `oakengine_color_processor_create` — convert the `oak_color_transform`
/// POD into an oakcommon colortransform handle and hand it to
/// `oakrender_color_processor_create_transform`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_processor_create(
mgr: *const crate::handle::OakEngineColorManager,
input: *const c_char,
dest: *const OakColorTransformPod,
direction: c_int,
) -> *mut OakEngineColorProcessor {
guard_ptr(|| unsafe {
if input.is_null() || dest.is_null() {
return Ok(std::ptr::null_mut());
}
let empty = crate::common::empty_cstr();
let ct = if (*dest).is_display != 0 {
crate::bridge::common::oakcommon_colortransform_init_display(
if (*dest).output.is_null() { empty } else { (*dest).output },
if (*dest).view.is_null() { empty } else { (*dest).view },
if (*dest).look.is_null() { empty } else { (*dest).look },
)
} else {
crate::bridge::common::oakcommon_colortransform_init_output(
if (*dest).output.is_null() { empty } else { (*dest).output },
)
};
if ct.is_null() {
LAST_COLOR_ERROR.with(|e| *e.borrow_mut() = "invalid color transform".into());
return Ok(std::ptr::null_mut());
}
let mgr_handle = if mgr.is_null() {
CHandle::null()
} else {
unbox(mgr.cast::<crate::handle::OakEngineColorManager>())?
};
let proc = r::oakrender_color_processor_create_transform(
mgr_handle,
input,
ct,
direction,
);
let mut ct_handle = ct;
crate::bridge::common::oakcommon_colortransform_free(&mut ct_handle);
if proc.is_null() {
LAST_COLOR_ERROR.with(|e| *e.borrow_mut() = "could not create color processor".into());
return Ok(std::ptr::null_mut());
}
LAST_COLOR_ERROR.with(|e| e.borrow_mut().clear());
Ok(box_handle::<OakEngineColorProcessor>(proc))
})
}
/// `oakengine_color_processor_free` — NULL no-op.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_processor_free(proc: *mut OakEngineColorProcessor) {
guard_void(|| unsafe {
free_box(proc);
})
}
/// `oakengine_color_processor_is_valid` (1/0).
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_processor_is_valid(
proc: *const OakEngineColorProcessor,
) -> c_int {
guard_int(|| unsafe {
if proc.is_null() {
return Ok(0);
}
let p = unbox(proc)?;
Ok(r::oakrender_color_processor_is_valid(p))
})
}
/// `oakengine_color_processor_convert_color` — single RGBA color.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_processor_convert_color(
proc: *const OakEngineColorProcessor,
in_rgba: *const c_double,
out_rgba: *mut c_double,
) -> c_int {
guard(|| unsafe {
if in_rgba.is_null() || out_rgba.is_null() {
return Err(Error::Invalid);
}
let p = unbox(proc)?;
let rc = r::oakrender_color_processor_convert(
p,
*in_rgba,
*in_rgba.add(1),
*in_rgba.add(2),
*in_rgba.add(3),
out_rgba,
out_rgba.add(1),
out_rgba.add(2),
out_rgba.add(3),
);
Error::from_module(rc)
})
}
/// `oakengine_color_processor_id` — **not backed** (the crate exposes no
/// processor cache id). Returns OAKENGINE_E_FAILED.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_processor_id(
_proc: *const OakEngineColorProcessor,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_color_transform_job_set_processor` — **not backed** (the
/// job is a C++ type). Returns OAKENGINE_E_INVALID for a NULL job.
#[no_mangle]
pub unsafe extern "C" fn oakengine_color_transform_job_set_processor(
job: *mut c_void,
_proc: *const OakEngineColorProcessor,
) -> c_int {
if job.is_null() {
crate::error::OAKENGINE_E_INVALID
} else {
crate::error::OAKENGINE_E_FAILED
}
}
// ---------------------------------------------------------------------------
// LUT library (not backed)
// ---------------------------------------------------------------------------
/// `oakengine_lut_directory_count` — **not backed** (the LUT directory/
/// file library is facade-level over FileFunctions; the crate only
/// enumerates supported extensions). Returns 0.
#[no_mangle]
pub extern "C" fn oakengine_lut_directory_count() -> c_int {
0
}
/// `oakengine_lut_directory_at` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_lut_directory_at(
_index: c_int,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_lut_file_count` — **not backed**. Returns 0.
#[no_mangle]
pub extern "C" fn oakengine_lut_file_count() -> c_int {
0
}
/// `oakengine_lut_file_at` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_lut_file_at(
_index: c_int,
_buf: *mut c_char,
_buf_size: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
/// `oakengine_lut_set_directories` — **not backed**.
#[no_mangle]
pub unsafe extern "C" fn oakengine_lut_set_directories(
_dirs: *const *const c_char,
_count: c_int,
) -> c_int {
crate::error::OAKENGINE_E_FAILED
}
+822
View File
@@ -0,0 +1,822 @@
// 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/>.
//! `engine/include/oakengine/task.h` — the engine background-task system
//! (the C++ `olive::Task` / `olive::TaskManager`) over the oaktask module.
//!
//! Task ownership follows the header: `oakengine_task_create_*` returns an
//! OWNED task; `oakengine_task_manager_add` hands it to the manager (which
//! deletes it when done, so the handle becomes borrowed);
//! `oakengine_task_free` deletes a task that never reached the manager.
//! A task run with `oakengine_task_start_sync` stays owned by the caller.
//!
//! The facade owns the global task manager (module-00 analogue of the C++
//! app-startup `TaskManager`): it is initialized lazily on the first
//! manager-family call, mirroring the undo family's process-wide stack.
//!
//! The oaktask module exposes no getters for the C++ `Task::get_start_time`
//! / `Task::is_cancelled` / `ProjectSaveTask::get_project`; those three are
//! answered from facade-side state recorded at creation/cancel
//! ([`TaskMeta`], see the per-export notes).
//!
//! String output follows the engine buf/size convention: the return value
//! is the would-be length **excluding** the NUL. The module reports the
//! size **including** the NUL, converted with
//! [`crate::handle::string_result`]; module error codes (-80001..) pass
//! through untranslated.
use std::collections::HashMap;
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, zeroed_encoding_params};
use crate::bridge::node as n;
use crate::bridge::task as t;
use crate::codec::OakEngineEncodingParams;
use crate::common::OakVideoParamsPod;
use crate::error::{Error, Result};
use crate::handle::{
box_handle, free_box, guard, guard_i64, guard_int, guard_ptr, string_result, unbox, CHandle,
OakEngineClipboard, OakEngineNode, OakEngineProject, OakEngineSequence, OakEngineTask,
};
// ---------------------------------------------------------------------------
// Facade-side task state
// ---------------------------------------------------------------------------
/// Facade-side sidecars for tasks created through this module, keyed by the
/// module task handle's `ctx` (the stable identity of the underlying task;
/// see [`crate::handle::CHandle`]). Entries are dropped by
/// [`oakengine_task_free`]; a task handed to the manager keeps its entry
/// until free — the header forbids touching a borrowed handle after the
/// task is removed, so an entry left behind by a manager-run task is an
/// intentional, documented process-lifetime leak.
#[derive(Clone)]
struct TaskMeta {
/// Epoch-millisecond creation stamp, returned by
/// [`oakengine_task_start_time`] once the task has been started through
/// the facade (the module has no start-time getter; the C++ reports the
/// real `Task::get_start_time`).
created_at_ms: u64,
/// Whether the task was started through the facade
/// (`oakengine_task_start_sync` / `oakengine_task_manager_add` /
/// `oakengine_cli_task_dialog_run`).
started: bool,
/// Facade-initiated cancel flag (the module has no `is_cancelled`
/// getter; only cancels made through this facade are visible).
cancelled: bool,
/// The project a save task writes (addref'd at creation, released at
/// free) — the module has no save-project getter.
save_project: Option<CHandle>,
/// The encoding-params box an export task owns, dropped at free
/// (mirrors the C++ `FacadeExportTask` destructor; stored as `usize` so
/// the map stays `Send`).
export_params: Option<usize>,
/// The color manager an export task owns, released at free.
export_color_manager: Option<CHandle>,
}
impl TaskMeta {
fn new() -> Self {
TaskMeta {
created_at_ms: now_millis(),
started: false,
cancelled: false,
save_project: None,
export_params: None,
export_color_manager: None,
}
}
}
/// Epoch milliseconds (0 when the clock is before the epoch; never in
/// practice).
fn now_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
static META: OnceLock<Mutex<HashMap<usize, TaskMeta>>> = OnceLock::new();
fn meta_lock() -> std::sync::MutexGuard<'static, HashMap<usize, TaskMeta>> {
META.get_or_init(|| Mutex::new(HashMap::new()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}
fn meta_insert(key: usize, meta: TaskMeta) {
meta_lock().insert(key, meta);
}
fn meta_get(key: usize) -> Option<TaskMeta> {
meta_lock().get(&key).cloned()
}
fn meta_set_started(key: usize) {
if let Some(m) = meta_lock().get_mut(&key) {
m.started = true;
}
}
fn meta_set_cancelled(key: usize) {
if let Some(m) = meta_lock().get_mut(&key) {
m.cancelled = true;
}
}
/// Release every facade-side sidecar of a task (called by
/// [`oakengine_task_free`]): the addref'd save project, the owned
/// encoding-params box and the derived color manager of an export task.
fn drop_task_meta(key: usize) {
if let Some(meta) = meta_lock().remove(&key) {
if let Some(mut project) = meta.save_project {
unsafe { n::oaknode_project_free(&mut project) };
}
if let Some(ptr) = meta.export_params {
unsafe {
crate::codec::oakengine_encoding_params_destroy(ptr as *mut OakEngineEncodingParams)
};
}
if let Some(mut manager) = meta.export_color_manager {
unsafe { n::oaknode_colormanager_free(&mut manager) };
}
}
}
/// Box an owned module task handle as an engine handle, registering its
/// facade sidecars. NULL/empty handles stay NULL.
fn box_task(h: CHandle) -> *mut OakEngineTask {
if h.is_null() {
return std::ptr::null_mut();
}
meta_insert(h.ctx as usize, TaskMeta::new());
box_handle::<OakEngineTask>(h)
}
// ---------------------------------------------------------------------------
// Global task manager
// ---------------------------------------------------------------------------
/// Lazily initialize the global task manager on first facade use
/// (module-00 analogue of the C++ app-startup `TaskManager` creation; the
/// same pattern as the undo family's `global_stack`). The manager lives for
/// the process. `oaktask_manager_init` only fails when already initialized,
/// which the `OnceLock` prevents, so this always succeeds.
fn manager_ensure() -> Result<()> {
static INIT: OnceLock<()> = OnceLock::new();
let _ = INIT.get_or_init(|| unsafe {
t::oaktask_manager_init();
});
Ok(())
}
/// Stable opaque token for `oakengine_task_manager_handle`: a boxed
/// [`CHandle`] whose `ctx` is the address of a facade static (never
/// dereferenced). The oaktask module exposes no manager handle, so the
/// token exists purely to give the (out-of-scope, per README)
/// `OAKENGINE_EVENT_TASK_MANAGER_*` subscription an ABI-ready handle. The
/// box is leaked for the process, like the C++ `TaskManager::instance()`.
fn manager_token() -> *mut c_void {
static TOKEN: OnceLock<usize> = OnceLock::new();
// Stored as `usize` so the `OnceLock` stays `Sync`.
let boxed = TOKEN.get_or_init(|| {
box_handle::<OakEngineTask>(CHandle {
ctx: &MANAGER_TOKEN as *const u8 as *mut c_void,
addref: None,
release: None,
abi_version: 0,
}) as usize
});
*boxed as *mut OakEngineTask as *mut c_void
}
static MANAGER_TOKEN: u8 = 0;
/// `oakengine_task_manager_handle` — borrowed token of the global task
/// manager (NULL never: the facade initializes the manager lazily on first
/// use, see [`manager_ensure`]; the C++ engine creates it at app startup).
#[no_mangle]
pub extern "C" fn oakengine_task_manager_handle() -> *mut c_void {
guard_ptr(|| {
manager_ensure()?;
Ok(manager_token())
})
}
/// `oakengine_task_manager_count` — number of tasks known to the manager
/// (running plus failed-but-kept). The manager is created on first use, so
/// the header's "no manager exists" state is unreachable (0 when empty).
#[no_mangle]
pub extern "C" fn oakengine_task_manager_count() -> c_int {
guard_int(|| {
manager_ensure()?;
Ok(unsafe { t::oaktask_manager_count() })
})
}
/// `oakengine_task_manager_first` — borrowed handle of the manager's first
/// task (NULL when the queue is empty).
#[no_mangle]
pub extern "C" fn oakengine_task_manager_first() -> *mut OakEngineTask {
guard_ptr(|| {
manager_ensure()?;
let h = unsafe { t::oaktask_manager_at(0) };
if h.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_handle::<OakEngineTask>(h))
})
}
/// `oakengine_task_manager_add` — hand `task` to the manager queue
/// (transfers ownership; the manager deletes the task when done). The
/// module's `oaktask_task_start` performs the transfer; a task already
/// running on the manager reports the module's `OAKTASK_E_STATE`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_manager_add(task: *mut OakEngineTask) -> c_int {
guard(|| unsafe {
let h = unbox(task)?;
manager_ensure()?;
Error::from_module(t::oaktask_task_start(h))?;
meta_set_started(h.ctx as usize);
Ok(())
})
}
/// `oakengine_task_manager_cancel` — ask the manager to cancel `task`. The
/// module's `oaktask_task_cancel` signals the running task's cancellation
/// atom; the "failed-but-kept task is removed and deleted" half is managed
/// by the module's own bookkeeping (`oaktask_manager_delete_finished`) and
/// has no engine export, so it is not mirrored here.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_manager_cancel(task: *mut OakEngineTask) -> c_int {
guard(|| unsafe {
let h = unbox(task)?;
manager_ensure()?;
Error::from_module(t::oaktask_task_cancel(h))?;
meta_set_cancelled(h.ctx as usize);
Ok(())
})
}
// ---------------------------------------------------------------------------
// Task accessors
// ---------------------------------------------------------------------------
/// `oakengine_task_title` (buf/size; E_INVALID for NULL).
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_title(
task: *mut OakEngineTask,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
let rc = t::oaktask_task_title(h, buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
/// `oakengine_task_error` (buf/size; E_INVALID for NULL). Meaningful after
/// a failed run.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_error(
task: *mut OakEngineTask,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
let rc = t::oaktask_task_error(h, buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
/// `oakengine_task_start_time` — start timestamp in epoch milliseconds.
///
/// The module has no start-time getter, so the facade reports the
/// **creation** stamp (see [`TaskMeta`]) once the task has been started
/// through the facade; 0 before then (matching "0 when the task never
/// started"). Deviation from the C++ `Task::get_start_time`, which records
/// the actual start instant.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_start_time(task: *mut OakEngineTask) -> i64 {
guard_i64(|| unsafe {
let h = unbox(task)?;
Ok(match meta_get(h.ctx as usize) {
Some(m) if m.started => m.created_at_ms as i64,
_ => 0,
})
})
}
/// `oakengine_task_is_cancelled` — 1 when the task was asked to cancel.
///
/// The module has no `is_cancelled` getter, so only cancels issued through
/// [`oakengine_task_cancel`] / [`oakengine_task_manager_cancel`] on this
/// facade are visible (0 otherwise). Deviation from the C++
/// `Task::is_cancelled`, which reflects the task's own cancellation atom.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_is_cancelled(task: *mut OakEngineTask) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
Ok(if meta_get(h.ctx as usize).map(|m| m.cancelled).unwrap_or(false) {
1
} else {
0
})
})
}
/// `oakengine_task_cancel` — signal the task to cancel as soon as possible
/// (module `Task::cancel`, the `Task::Cancel` analogue).
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_cancel(task: *mut OakEngineTask) -> c_int {
guard(|| unsafe {
let h = unbox(task)?;
Error::from_module(t::oaktask_task_cancel(h))?;
meta_set_cancelled(h.ctx as usize);
Ok(())
})
}
/// `oakengine_task_start_sync` — run on the calling thread; 1 = succeeded,
/// 0 = failed or cancelled, E_INVALID for NULL. Ownership stays with the
/// caller.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_start_sync(task: *mut OakEngineTask) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
let rc = t::oaktask_task_start_sync(h);
meta_set_started(h.ctx as usize);
Ok(rc)
})
}
/// `oakengine_task_free` — delete a task that was never added to the
/// manager (releases the module task handle, which drops an owned task).
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_free(task: *mut OakEngineTask) -> c_int {
guard(|| unsafe {
if task.is_null() {
return Err(Error::Invalid);
}
let h = (*task).handle;
if h.is_null() {
return Err(Error::Invalid);
}
drop_task_meta(h.ctx as usize);
free_box::<OakEngineTask>(task);
Ok(())
})
}
/// `oakengine_cli_task_dialog_run` — run `task` through the engine's CLI
/// modal progress dialog; 1 on success, 0 on failure/cancellation.
///
/// The C++ `CLITaskDialog` renders a terminal progress dialog around a
/// synchronous run; the facade ports the observable behavior (sync run,
/// 1/0 result) with the dialog chrome itself stubbed. `parent` is unused.
/// The capi returns 0 (not E_INVALID) for a NULL task, so this mirrors it.
#[no_mangle]
pub unsafe extern "C" fn oakengine_cli_task_dialog_run(
task: *mut OakEngineTask,
_parent_or_null: *mut c_void,
) -> c_int {
guard_int(|| unsafe {
if task.is_null() {
return Ok(0);
}
let h = unbox(task)?;
let rc = t::oaktask_task_start_sync(h);
meta_set_started(h.ctx as usize);
Ok(rc)
})
}
// ---------------------------------------------------------------------------
// Task creators (all return OWNED tasks, NULL on invalid input)
// ---------------------------------------------------------------------------
/// `oakengine_task_create_project_load` — task that loads an OVE project
/// from `filename`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_create_project_load(
filename: *const c_char,
) -> *mut OakEngineTask {
guard_ptr(|| unsafe {
if filename.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_task(t::oaktask_create_project_load(filename)))
})
}
/// `oakengine_task_create_project_load_otio` — task that loads an
/// OpenTimelineIO project. The module always supports OTIO (the interchange
/// format is inferred from the filename extension), so valid input never
/// yields the header's "built without OTIO support" NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_create_project_load_otio(
filename: *const c_char,
) -> *mut OakEngineTask {
guard_ptr(|| unsafe {
if filename.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_task(t::oaktask_create_project_load_otio(filename)))
})
}
/// `oakengine_task_create_project_save` — task that saves `project`.
///
/// `use_compression` selects the compressed `.ove` writer; `override_filename`
/// may be NULL to save to the project's own filename. `layout` (an opaque
/// `SerializedLayoutInfo *` in the engine) is **ignored**: the module's
/// `ProjectSaveTask` has no layout slot, so a non-NULL layout is accepted
/// but not copied into the file.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_create_project_save(
project: *mut OakEngineProject,
use_compression: c_int,
override_filename: *const c_char,
_layout: *const c_void,
) -> *mut OakEngineTask {
guard_ptr(|| unsafe {
let ph = unbox(project)?;
let h = t::oaktask_create_project_save(ph, override_filename, use_compression);
if h.is_null() {
return Ok(std::ptr::null_mut());
}
// Keep the project borrowed for the task's lifetime so
// `oakengine_task_save_get_project` can answer from facade state
// (the module has no save-project getter).
let mut meta = TaskMeta::new();
meta.save_project = Some(ph.addref());
meta_insert(h.ctx as usize, meta);
Ok(box_handle::<OakEngineTask>(h))
})
}
/// `oakengine_task_create_project_save_otio` — task that saves `project` in
/// OpenTimelineIO format.
///
/// The engine header passes only the project, but the module's creator
/// requires the output filename; the facade derives it from the project's
/// own filename (the OTIO save of the current project file) and returns
/// NULL when the project has no filename. The module always supports OTIO,
/// so a valid input never yields the "built without OTIO support" NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_create_project_save_otio(
project: *mut OakEngineProject,
) -> *mut OakEngineTask {
guard_ptr(|| unsafe {
let ph = unbox(project)?;
let filename = project_filename_of(ph)?;
if filename.is_empty() {
return Ok(std::ptr::null_mut());
}
let c_filename =
std::ffi::CString::new(filename).map_err(|_| Error::Failed("invalid filename".into()))?;
Ok(box_task(t::oaktask_create_project_save_otio(ph, c_filename.as_ptr())))
})
}
/// Two-stage read of the project's filename (empty when unset).
fn project_filename_of(project: CHandle) -> Result<String> {
let needed = unsafe { n::oaknode_project_filename(project, std::ptr::null_mut(), 0) };
if needed <= 0 {
return Ok(String::new());
}
let mut buf = vec![0 as c_char; needed as usize];
let rc = unsafe { n::oaknode_project_filename(project, buf.as_mut_ptr(), needed) };
if rc < 0 {
return Err(Error::Module(rc));
}
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
Ok(String::from_utf8_lossy(unsafe {
std::slice::from_raw_parts(buf.as_ptr() as *const u8, len)
})
.into_owned())
}
/// `oakengine_task_create_project_import` — task that imports `url_count`
/// media files into `folder` (a folder node of the target project).
///
/// The URL array is copied by the module during the call. The engine header
/// passes only the folder; the module creator needs the owning project,
/// derived here via `oaknode_node_get_project`. Unlike the capi (which
/// rejects `url_count <= 0`), a zero-count task IS created — the header's
/// `oakengine_task_import_file_count` documents 0 as "nothing to import,
/// free instead of run". `url_count < 0`, a NULL URL inside the array, or a
/// folder with no project yield NULL.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_create_project_import(
folder: *mut OakEngineNode,
urls: *const *const c_char,
url_count: c_int,
) -> *mut OakEngineTask {
guard_ptr(|| unsafe {
let fh = unbox(folder)?;
if url_count < 0 || (urls.is_null() && url_count > 0) {
return Ok(std::ptr::null_mut());
}
let mut project = CHandle::null();
Error::from_module(n::oaknode_node_get_project(fh, &mut project))?;
if project.is_null() {
return Ok(std::ptr::null_mut());
}
let h = t::oaktask_create_project_import(fh, project, urls, url_count);
// Release the transient borrowed project handle (the import task
// keeps its own copy).
n::oaknode_project_free(&mut project);
Ok(box_task(h))
})
}
/// `oakengine_task_create_proxy` — **not backed** (stub, always NULL).
///
/// The oaktask crate's `ProxyTask` is driven by a codec task request and no
/// proxy-task creator exists on the module C ABI (`oaktask_create_precache`
/// is a different task). The engine's `FacadeProxyTask` would need
/// `oakengine_footage_proxy_generate`, which lives in the deferred exporter
/// family (see `deferred.rs`). Returns NULL per the creators' "NULL on
/// invalid input" contract.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_create_proxy(
_footage: *mut OakEngineNode,
) -> *mut OakEngineTask {
std::ptr::null_mut()
}
/// `oakengine_task_create_export` — task that renders an export of
/// `sequence` with `params`.
///
/// Takes ownership of `params` (destroyed with the task, mirroring the C++
/// `FacadeExportTask` destructor; the module copies the POD it needs at
/// creation, so the retained box is a lifetime guarantee for C callers).
/// The color manager is derived from the sequence's owning project
/// (`oaknode_colormanager_init`), mirroring how the C++ exporter obtains
/// its manager; a sequence without a project exports with an empty manager.
/// The module creator requires a POD pointer, so the facade's opaque params
/// handle is copied out through the public `oakengine_encoding_params_*`
/// getters ([`export_params_pod`]) — its backing `ParamsBox` (POD + option
/// map) is private to `codec.rs` and cannot be read here.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_create_export(
sequence: *mut OakEngineSequence,
params: *mut OakEngineEncodingParams,
) -> *mut OakEngineTask {
guard_ptr(|| unsafe {
let vh = unbox(sequence)?;
if params.is_null() {
return Ok(std::ptr::null_mut());
}
let pod = export_params_pod(params)?;
let color_manager = export_color_manager(vh)?;
let h = t::oaktask_create_export(vh, color_manager, &pod);
if h.is_null() {
// Creation failed: release the color manager we derived.
let mut manager = color_manager;
n::oaknode_colormanager_free(&mut manager);
return Ok(std::ptr::null_mut());
}
let mut meta = TaskMeta::new();
meta.export_params = Some(params as usize);
meta.export_color_manager = Some(color_manager);
meta_insert(h.ctx as usize, meta);
Ok(box_handle::<OakEngineTask>(h))
})
}
/// Copy the encoding-params POD the oaktask export creator reads out of the
/// facade's opaque params handle via its public getters.
///
/// The oaktask crate's `convert_encoding_params` consumes exactly these
/// fields (filename, format, video/audio/subtitle enables, codecs,
/// dimensions, time base, pixel format, export length), so a POD carrying
/// 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 = zeroed_encoding_params();
// filename (two-stage; writes NUL-terminated into `buf`)
let mut buf = [0 as c_char; 1024];
let rc = unsafe {
crate::codec::oakengine_encoding_params_filename(params, buf.as_mut_ptr(), buf.len() as c_int)
};
if rc < 0 {
return Err(Error::Invalid);
}
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.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) };
pod.video_codec = unsafe { crate::codec::oakengine_encoding_params_video_codec(params) };
pod.audio_enabled = unsafe { crate::codec::oakengine_encoding_params_audio_enabled(params) };
pod.audio_codec = unsafe { crate::codec::oakengine_encoding_params_audio_codec(params) };
pod.subtitles_enabled = unsafe {
crate::codec::oakengine_encoding_params_subtitles_enabled(params)
};
unsafe {
crate::codec::oakengine_encoding_params_get_export_length(
params,
&mut pod.export_length_num,
&mut pod.export_length_den,
);
}
if pod.video_enabled != 0 {
let mut video = std::mem::MaybeUninit::<OakVideoParamsPod>::uninit();
let rc = unsafe {
crate::codec::oakengine_encoding_params_get_video_params(params, video.as_mut_ptr())
};
if rc == 0 {
let v = unsafe { video.assume_init() };
pod.video_width = v.width;
pod.video_height = v.height;
pod.video_time_base_num = v.time_base_num;
pod.video_time_base_den = v.time_base_den;
pod.video_pixel_format = v.format;
}
}
Ok(pod)
}
/// Derive a color manager for an export task from the sequence's owning
/// project (borrowed project handle released after the manager is created).
/// Empty when the sequence has no project — the module export accepts an
/// empty manager.
fn export_color_manager(sequence: CHandle) -> Result<CHandle> {
let mut project = CHandle::null();
Error::from_module(unsafe { n::oaknode_node_get_project(sequence, &mut project) })?;
if project.is_null() {
return Ok(CHandle::null());
}
let manager = unsafe { n::oaknode_colormanager_init(project) };
unsafe { n::oaknode_project_free(&mut project) };
Ok(manager)
}
// ---------------------------------------------------------------------------
// Import task results
// ---------------------------------------------------------------------------
/// `oakengine_task_import_file_count` — number of files the import task
/// will process.
///
/// The module's only import count export is `oaktask_import_footage_count`,
/// which reports the **imported-footage** list length — 0 before the task
/// runs even when files were supplied. Deviation from the C++
/// `get_file_count` (the construction-time count); "0 means nothing to
/// import yet" holds before a run either way.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_import_file_count(task: *mut OakEngineTask) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
let rc = t::oaktask_import_footage_count(h);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(rc)
}
})
}
/// `oakengine_task_import_get_command` — the undo command built by a
/// successful import run as an opaque `OakEngineClipboard` (NULL before the
/// run, after a cancelled run, or on a second call). Ownership detaches
/// from the task; push it with `oakengine_undo_push` or free it.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_import_get_command(task: *mut OakEngineTask) -> *mut c_void {
guard_ptr(|| unsafe {
let h = unbox(task)?;
let cmd = t::oaktask_import_take_command(h);
if cmd.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_handle::<OakEngineClipboard>(cmd).cast())
})
}
/// `oakengine_task_import_footage_count` — number of footage items a
/// successful import run created (the module's `oaktask_import_footage_count`,
/// the same count reported by `oakengine_task_import_file_count`).
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_import_footage_count(task: *mut OakEngineTask) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
let rc = t::oaktask_import_footage_count(h);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(rc)
}
})
}
/// `oakengine_task_import_footage_at` — borrowed node handle of the
/// imported footage at `index` (NULL when out of range or not an import
/// task). The module addrefs the footage handle; the caller releases it
/// with `oakengine_node_free`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_import_footage_at(
task: *mut OakEngineTask,
index: c_int,
) -> *mut OakEngineNode {
guard_ptr(|| unsafe {
let h = unbox(task)?;
let fh = t::oaktask_import_footage_at(h, index);
if fh.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_handle::<OakEngineNode>(fh))
})
}
/// `oakengine_task_import_invalid_files_count` — number of files the import
/// task rejected.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_import_invalid_files_count(
task: *mut OakEngineTask,
) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
let rc = t::oaktask_import_invalid_count(h);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(rc)
}
})
}
/// `oakengine_task_import_invalid_file_at` — rejected file path at `index`
/// (buf/size). Out-of-range reports the module's `OAKTASK_E_NOT_FOUND`
/// (-80004) pass-through, the header's "E_INVALID for other tasks" being
/// covered by the NULL-task `OAKENGINE_E_INVALID` path.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_import_invalid_file_at(
task: *mut OakEngineTask,
index: c_int,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
guard_int(|| unsafe {
let h = unbox(task)?;
let rc = t::oaktask_import_invalid_at(h, index, buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(string_result(rc))
}
})
}
// ---------------------------------------------------------------------------
// Save task results
// ---------------------------------------------------------------------------
/// `oakengine_task_save_get_project` — borrowed handle of the project a
/// save task wrote (NULL for other tasks).
///
/// The module has no save-project getter, so the project is kept borrowed
/// from creation in [`TaskMeta`] (mirroring the C++ `ProjectSaveTask::
/// get_project`, which returns the project the task was created with).
/// Each call returns a fresh borrowed handle the caller releases with
/// `oakengine_project_free`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_task_save_get_project(task: *mut OakEngineTask) -> *mut OakEngineProject {
guard_ptr(|| unsafe {
let h = unbox(task)?;
match meta_get(h.ctx as usize).and_then(|m| m.save_project) {
Some(p) => Ok(box_handle::<OakEngineProject>(p.addref())),
None => Ok(std::ptr::null_mut()),
}
})
}
File diff suppressed because it is too large Load Diff
+402
View File
@@ -0,0 +1,402 @@
// 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/>.
//! `engine/include/oakengine/undo.h` — the process-wide undo stack,
//! undo groups and command lifecycle over the oakundo module.
//!
//! The facade owns the process-wide undo stack (module 00 analogue of
//! `EngineCore::undo_stack()`): it is created lazily on first use and
//! lives for the process (mirroring the C++ EngineCore shell, which is
//! also leaked intentionally). The open undo group is facade state too:
//! while a group is open, every command a wrapped family hands to
//! [`push_or_run`] is added to the group instead of the stack.
//!
//! Command creators declared in undo.h but backed by other modules
//! (`oakengine_node_*_command`, `oakengine_track_*_command`,
//! `oakengine_block_*_command`, `oakengine_timeline_*_command`) live in
//! the corresponding family modules, mirroring the C++ capi layout.
use std::ffi::{c_char, c_int, c_void};
use std::sync::{Mutex, OnceLock};
use crate::bridge::undo as u;
use crate::error::{Error, Result};
use crate::handle::{
box_handle, free_box, guard, guard_void, unbox, CHandle, OakEngineClipboard,
};
/// The process-wide undo stack handle (oakundo `OakUndoStack`), created
/// lazily and kept for the process lifetime.
fn global_stack() -> &'static CHandle {
static STACK: OnceLock<CHandle> = OnceLock::new();
STACK.get_or_init(|| unsafe { u::oakundo_undostack_init() })
}
/// Stable opaque token for `oakengine_undo_handle`: the module stack's
/// `ctx` pointer (never dereferenced by the facade; lives for the
/// process).
fn stack_token() -> *mut c_void {
global_stack().ctx
}
/// The currently open undo group (a multi command handle) plus its name.
struct OpenGroup {
/// Multi command handle; owned by this state until end/abort.
multi: CHandle,
/// Group label.
#[allow(dead_code)]
name: String,
}
static GROUP: Mutex<Option<OpenGroup>> = Mutex::new(None);
fn group_lock() -> std::sync::MutexGuard<'static, Option<OpenGroup>> {
GROUP.lock().unwrap_or_else(|e| e.into_inner())
}
/// Push `command` onto the stack, add it to the open group, or run it
/// directly — whichever applies (module 00 analogue of the C++ capi's
/// `oakengine_undo_push_or_run`). `command_box` is consumed.
///
/// # Safety
/// `command_box` must be a live box created by a facade command creator.
pub(crate) unsafe fn push_or_run(command_box: *mut OakEngineClipboard, name: *const c_char) -> Result<()> {
let cmd = unsafe { unbox(command_box)? };
let label = unsafe { crate::handle::read_cstr(name) };
let g = group_lock();
if let Some(group) = g.as_ref() {
// The module's `oakundo_command_multi_add_child` consumes the
// child's command value (command_take), so the eager redo must
// happen on the still-owned handle FIRST — the group takes the
// already-done command (C++ semantics: add_child + redo_now, net
// effect identical for the group's reverse-order undo).
let rc = unsafe { u::oakundo_command_redo_now(cmd) };
if rc != 0 {
return Err(Error::Module(rc));
}
let rc = unsafe { u::oakundo_command_multi_add_child(group.multi, cmd) };
drop(g);
unsafe { free_box(command_box) };
return if rc == 0 { Ok(()) } else { Err(Error::Module(rc)) };
}
let stack = *global_stack();
let rc = unsafe { u::oakundo_undostack_push(stack, cmd, label.as_ptr() as *const c_char) };
if rc == 0 {
// Stack took a reference; release ours by freeing the box.
unsafe { free_box(command_box) };
Ok(())
} else {
// Push failed (e.g. empty multi): the module deleted the command;
// release the box shell without touching the (already consumed)
// handle.
unsafe { free_box(command_box) };
Err(Error::Module(rc))
}
}
/// `oakengine_undo_handle` — borrowed token of the global undo stack
/// (NULL never: the facade creates the stack lazily).
#[no_mangle]
pub extern "C" fn oakengine_undo_handle() -> *mut c_void {
crate::handle::guard_ptr(|| Ok(stack_token()))
}
/// `oakengine_undo_push` — push `command` onto the stack and execute its
/// redo (or add it to the open group). Takes ownership of `command`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_push(command: *mut c_void, name: *const c_char) -> c_int {
guard(|| unsafe {
if command.is_null() {
return Err(Error::Invalid);
}
push_or_run(command.cast::<OakEngineClipboard>(), name)
})
}
/// `oakengine_undo_group_begin` — start collecting commands into a group.
#[no_mangle]
pub extern "C" fn oakengine_undo_group_begin(name: *const c_char) -> c_int {
guard(|| {
let mut g = group_lock();
if g.is_some() {
return Err(Error::State);
}
let multi = unsafe { u::oakundo_command_init_multi() };
if multi.is_null() {
return Err(Error::Failed("undo group allocation failed".into()));
}
*g = Some(OpenGroup {
multi,
name: unsafe { crate::handle::read_cstr(name) },
});
Ok(())
})
}
/// `oakengine_undo_group_end` — close the group and push it as one entry.
/// An empty group is discarded (no undo entry).
#[no_mangle]
pub extern "C" fn oakengine_undo_group_end() -> c_int {
guard(|| {
let mut g = group_lock();
let open = g.take().ok_or(Error::State)?;
let multi = open.multi;
let name = open.name;
drop(g);
// push_pre_executed discards an empty multi command. Either way
// the stack took (or destroyed) the command; release our own
// reference to the multi handle.
let stack = *global_stack();
let rc = unsafe {
u::oakundo_undostack_push_pre_executed(stack, multi, name.as_ptr() as *const c_char)
};
let mut multi_handle = multi;
unsafe { u::oakundo_command_free(&mut multi_handle) };
if rc == 0 {
Ok(())
} else {
Err(Error::Module(rc))
}
})
}
/// `oakengine_undo_group_abort` — undo all executed children and discard
/// the group.
#[no_mangle]
pub extern "C" fn oakengine_undo_group_abort() -> c_int {
guard(|| {
let mut g = group_lock();
let open = g.take().ok_or(Error::State)?;
drop(g);
let rc = unsafe { u::oakundo_command_undo_now(open.multi) };
if rc != 0 {
return Err(Error::Module(rc));
}
let mut multi = open.multi;
unsafe { u::oakundo_command_free(&mut multi) };
Ok(())
})
}
/// `oakengine_undo_command_redo_now` — execute the redo of `command`
/// without taking ownership.
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_command_redo_now(command: *mut c_void) -> c_int {
guard(|| unsafe {
let cmd = unbox(command.cast::<OakEngineClipboard>())?;
Error::from_module(u::oakundo_command_redo_now(cmd))
})
}
/// `oakengine_undo_command_undo_now` — execute the undo of `command`
/// without taking ownership.
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_command_undo_now(command: *mut c_void) -> c_int {
guard(|| unsafe {
let cmd = unbox(command.cast::<OakEngineClipboard>())?;
Error::from_module(u::oakundo_command_undo_now(cmd))
})
}
/// Engine-side callback types for app-defined undo commands
/// (`engine/include/oakengine/undo.h`).
type UndoRedoFn = unsafe extern "C" fn(userdata: *mut c_void);
type UndoFreeFn = unsafe extern "C" fn(userdata: *mut c_void);
/// `oakengine_undo_command_create` — create an app-defined undo command
/// backed by C callbacks. Takes ownership of `userdata`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_command_create(
name: *const c_char,
redo: Option<UndoRedoFn>,
undo: Option<UndoRedoFn>,
free_fn: Option<UndoFreeFn>,
userdata: *mut c_void,
) -> *mut c_void {
crate::handle::guard_ptr(|| unsafe {
let _ = crate::handle::read_cstr(name);
let vtable = crate::bridge::undo::OakUndoCommandVtable { redo, undo, free_fn };
let cmd = u::oakundo_command_init(&vtable, userdata);
if cmd.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_handle::<OakEngineClipboard>(cmd).cast())
})
}
/// `oakengine_undo_command_create_multi` — create an empty
/// MultiUndoCommand as an opaque command pointer.
#[no_mangle]
pub extern "C" fn oakengine_undo_command_create_multi() -> *mut c_void {
crate::handle::guard_ptr(|| {
let cmd = unsafe { u::oakundo_command_init_multi() };
if cmd.is_null() {
return Ok(std::ptr::null_mut());
}
Ok(box_handle::<OakEngineClipboard>(cmd).cast())
})
}
/// `oakengine_undo_command_multi_add_child` — add `child` to `multi`
/// (the multi takes one reference; `child`'s box is consumed).
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_command_multi_add_child(
multi: *mut c_void,
child: *mut c_void,
) -> c_int {
guard(|| unsafe {
if multi.is_null() || child.is_null() {
return Err(Error::Invalid);
}
let m = unbox(multi.cast::<OakEngineClipboard>())?;
let c = unbox(child.cast::<OakEngineClipboard>())?;
let rc = u::oakundo_command_multi_add_child(m, c);
free_box(child.cast::<OakEngineClipboard>());
if rc == 0 {
Ok(())
} else {
Err(Error::Module(rc))
}
})
}
/// `oakengine_undo_command_multi_child_count` — children of `multi`.
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_command_multi_child_count(multi: *mut c_void) -> c_int {
crate::handle::guard_int(|| unsafe {
let m = unbox(multi.cast::<OakEngineClipboard>())?;
let mut count: c_int = 0;
Error::from_module(u::oakundo_command_multi_child_count(m, &mut count))?;
Ok(count)
})
}
/// `oakengine_undo_command_free` — destroy a command without pushing it.
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_command_free(command: *mut c_void) {
guard_void(|| unsafe {
free_box(command.cast::<OakEngineClipboard>());
})
}
/// `oakengine_undo_count` — total number of history rows.
#[no_mangle]
pub extern "C" fn oakengine_undo_count() -> i64 {
crate::handle::guard_i64(|| unsafe {
let mut count: i64 = 0;
Error::from_module(u::oakundo_undostack_count(*global_stack(), &mut count))?;
Ok(count)
})
}
/// `oakengine_undo_index` — current position in the history.
#[no_mangle]
pub extern "C" fn oakengine_undo_index() -> i64 {
crate::handle::guard_i64(|| unsafe {
let mut index: i64 = 0;
Error::from_module(u::oakundo_undostack_index(*global_stack(), &mut index))?;
Ok(index)
})
}
/// `oakengine_undo_command_text` — label of the row at `row`
/// (buf/size; OAKENGINE_E_NOT_FOUND for an invalid row).
#[no_mangle]
pub unsafe extern "C" fn oakengine_undo_command_text(
row: i64,
buf: *mut c_char,
buf_size: c_int,
) -> c_int {
// The oakundo getter is itself two-stage: it reports the required
// size when `buf` is NULL/too small and copies otherwise, so the
// module return value is returned verbatim (guarded against panic),
// converted to the engine's length-excluding-NUL convention.
crate::handle::guard_int(|| unsafe {
let rc = u::oakundo_undostack_command_text(*global_stack(), row, buf, buf_size);
if rc < 0 {
Err(Error::Module(rc))
} else {
Ok(crate::handle::string_result(rc))
}
})
}
/// `oakengine_undo_command_is_done` — 1 when the row is done, 0 when
/// undone, OAKENGINE_E_NOT_FOUND for an invalid row.
#[no_mangle]
pub extern "C" fn oakengine_undo_command_is_done(row: i64) -> c_int {
crate::handle::guard_int(|| unsafe {
let mut value: c_int = 0;
Error::from_module(u::oakundo_undostack_command_is_done(*global_stack(), row, &mut value))?;
Ok(value)
})
}
/// `oakengine_undo_jump` — undo/redo until the done-command count equals
/// `index`.
#[no_mangle]
pub extern "C" fn oakengine_undo_jump(index: i64) -> c_int {
guard(|| unsafe { Error::from_module(u::oakundo_undostack_jump(*global_stack(), index)) })
}
/// `oakengine_undo_clear` — delete all commands and push the fresh
/// "New/Open Project" empty command.
#[no_mangle]
pub extern "C" fn oakengine_undo_clear() -> c_int {
guard(|| unsafe { Error::from_module(u::oakundo_undostack_clear(*global_stack())) })
}
/// `oakengine_undo_update_actions` — no-op: the QAction members were
/// removed in the de-Qt pass (see notes.md), the app builds its own
/// undo/redo actions from `oakengine_undo_can_undo/redo`.
#[no_mangle]
pub extern "C" fn oakengine_undo_update_actions() -> c_int {
crate::error::OAKENGINE_OK
}
/// `oakengine_undo_can_undo` — 1/0.
#[no_mangle]
pub extern "C" fn oakengine_undo_can_undo() -> c_int {
crate::handle::guard_int(|| unsafe {
let mut value: c_int = 0;
Error::from_module(u::oakundo_undostack_can_undo(*global_stack(), &mut value))?;
Ok(value)
})
}
/// `oakengine_undo_can_redo` — 1/0.
#[no_mangle]
pub extern "C" fn oakengine_undo_can_redo() -> c_int {
crate::handle::guard_int(|| unsafe {
let mut value: c_int = 0;
Error::from_module(u::oakundo_undostack_can_redo(*global_stack(), &mut value))?;
Ok(value)
})
}
/// `oakengine_undo_undo_action` — Qt leftover: the de-Qt module world has
/// no QAction; returns NULL. The app builds its own action.
#[no_mangle]
pub extern "C" fn oakengine_undo_undo_action() -> *mut c_void {
std::ptr::null_mut()
}
/// `oakengine_undo_redo_action` — Qt leftover; returns NULL (see
/// `oakengine_undo_undo_action`).
#[no_mangle]
pub extern "C" fn oakengine_undo_redo_action() -> *mut c_void {
std::ptr::null_mut()
}
File diff suppressed because it is too large Load Diff