core: merge oak-common into oak-core
oak-common is gone; its modules (configstore, xmlutils, ocioutils,
oiioutils, colormath, colortransform, videoparams, ffmpegutils, ...)
now live in oak-core alongside the value types. The render value/GPU
types moved too: backend (wgpu context + DisplayRenderer), color
(ColorProcessor over ocio-rs), texture, frame, and the commonutil
config helpers.
Fix-ups to make the merged tree build and pass tests:
- oak-core Cargo.toml: wgpu back to 25 (the moved backend code is
written against that API generation); add the toml/quick-xml/image
deps oak-common carried.
- lib.rs: drop the duplicate 'pub mod error;'.
- error.rs: unified OAKCORE_* codes; restore Error::new() and
From<OcioError> from oak-common's error type.
- backend.rs/color.rs: oak_core::/oak_render:: self-references
rewritten to crate::; the shaderfx-dependent GPU effect test moved
to oak-render's shaderfx tests (shaderfx depends on oak-node and
cannot live in oak-core).
- oak-render's error module re-exports oak_core::error::{Error,
Result}; the OAKRENDER_* codes stay as the public-code contract.
- oak-node jobs.rs: ColorProcessor imported from oak_core::color.
- Integration tests repointed at oak_core::{texture, frame, backend,
color, colormath}.
- the display-ICC regression test treats an empty OAK_DISPLAY_ICC as
unset, matching displayicc::env_override_icc.
This commit is contained in:
Generated
+5
-19
@@ -4158,9 +4158,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.33"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"value-bag",
|
||||
@@ -4732,7 +4732,6 @@ dependencies = [
|
||||
"log",
|
||||
"oak-audio",
|
||||
"oak-codec",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-node",
|
||||
"oak-plugin",
|
||||
@@ -4753,7 +4752,6 @@ dependencies = [
|
||||
"cpal",
|
||||
"ffmpeg-next",
|
||||
"oak-codec",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-ffmpeg-link",
|
||||
"thiserror 2.0.20",
|
||||
@@ -4765,7 +4763,6 @@ version = "0.5.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"oak-codec",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-node",
|
||||
"oak-render",
|
||||
@@ -4778,29 +4775,24 @@ name = "oak-codec"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"ffmpeg-next",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-ffmpeg-link",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oak-common"
|
||||
name = "oak-core"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"oak-core",
|
||||
"ocio-rs",
|
||||
"quick-xml 0.41.0",
|
||||
"thiserror 2.0.20",
|
||||
"toml 0.8.23",
|
||||
"wgpu 25.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oak-core"
|
||||
version = "0.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "oak-ffmpeg-link"
|
||||
version = "0.5.0"
|
||||
@@ -4810,7 +4802,6 @@ name = "oak-node"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"oak-codec",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-undo",
|
||||
"thiserror 2.0.20",
|
||||
@@ -4846,7 +4837,6 @@ dependencies = [
|
||||
"libc",
|
||||
"naga 25.0.1",
|
||||
"oak-codec",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-node",
|
||||
"ocio-rs",
|
||||
@@ -4861,7 +4851,6 @@ name = "oak-storage"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-node",
|
||||
"oak-otio",
|
||||
@@ -4878,7 +4867,6 @@ name = "oak-task"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"oak-codec",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-node",
|
||||
"oak-otio",
|
||||
@@ -4891,7 +4879,6 @@ dependencies = [
|
||||
name = "oak-timeline"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-node",
|
||||
"oak-undo",
|
||||
@@ -4911,7 +4898,6 @@ version = "0.5.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"oak-codec",
|
||||
"oak-common",
|
||||
"oak-core",
|
||||
"oak-node",
|
||||
"oak-plugin",
|
||||
@@ -7566,7 +7552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand 2.5.0",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.61.2",
|
||||
|
||||
@@ -76,7 +76,6 @@ log = "0.4"
|
||||
# dylib, no C ABI, no build.rs link step, no host shims.
|
||||
oak-audio = { path = "../oak-audio" }
|
||||
oak-codec = { path = "../oak-codec" }
|
||||
oak-common = { path = "../oak-common" }
|
||||
oak-core = { path = "../oak-core" }
|
||||
oak-node = { path = "../oak-node" }
|
||||
oak-plugin = { path = "../oak-plugin" }
|
||||
|
||||
@@ -481,7 +481,7 @@ pub(crate) fn shortcuts_test_lock() -> &'static Mutex<()> {
|
||||
/// The path of the custom-shortcuts file: `<config>/shortcuts`, exactly like
|
||||
/// the C++ `MainWindow::get_custom_shortcuts_file`.
|
||||
pub fn custom_shortcuts_path() -> String {
|
||||
let dir = oak_common::filefunctions::FileFunctions::new()
|
||||
let dir = oak_core::filefunctions::FileFunctions::new()
|
||||
.get_configuration_location()
|
||||
.unwrap_or_else(|_| std::env::temp_dir().to_string_lossy().into_owned());
|
||||
format!("{}/shortcuts", dir.trim_end_matches('/'))
|
||||
|
||||
+24
-24
@@ -42,13 +42,13 @@ use std::time::Duration;
|
||||
use crate::oakui::component::menu::{self, Menu, MenuBar, MenuBarEntry, MenuBarEvent, MenuItem};
|
||||
use crate::oakui::component::text_input::install_text_input_bindings;
|
||||
use gpui::dock::{
|
||||
DockArea, DockEvent, DockLayout, DropTarget, DropZone, NodePath, PanelHandle, PanelId,
|
||||
PanelRegistry,
|
||||
DockArea, DockEvent, DockLayout, DropTarget, DropZone, NodePath, PanelHandle, PanelId,
|
||||
PanelRegistry,
|
||||
};
|
||||
use gpui::timeline::{ClipData, ClipId, Frame, FrameRange, TimelineEvent, TimelineView, TrackData};
|
||||
use gpui::{
|
||||
colors::DefaultColors, div, prelude::*, px, size, App, AsyncWindowContext, Bounds, Context,
|
||||
Entity, PathPromptOptions, Render, Window, WindowBounds, WindowOptions,
|
||||
colors::DefaultColors, div, prelude::*, px, size, App, AsyncWindowContext, Bounds, Context,
|
||||
Entity, PathPromptOptions, Render, Window, WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui_widgets::audio_meter::{AudioLevelMeter, MeterOrientation};
|
||||
use gpui_widgets::dialog::progress::{progress_dialog, ProgressContent};
|
||||
@@ -58,7 +58,7 @@ use gpui_widgets::viewer::PlaybackClock;
|
||||
|
||||
use crate::actions::{ActionId, TimelineToolExt, Tool};
|
||||
use crate::dialogs::{
|
||||
DropSequenceChoice, ExportDialogContent, PreferencesDialogContent, SequenceFormatSeed,
|
||||
DropSequenceChoice, ExportDialogContent, PreferencesDialogContent, SequenceFormatSeed,
|
||||
};
|
||||
use crate::oakui::{AppEngine, ExportSession, MockEngine, Monitor, RealEngine};
|
||||
use crate::panels::commands as panel_commands;
|
||||
@@ -78,9 +78,9 @@ use crate::panels::timeline::{FootageDropNeedsSequence, TimelinePanel};
|
||||
// registry is the single source; these keep the test call sites readable).
|
||||
#[cfg(test)]
|
||||
pub(crate) mod menu_ids {
|
||||
use crate::actions::ActionId;
|
||||
use crate::actions::ActionId;
|
||||
|
||||
pub const NEW_PROJECT: usize = ActionId::NewProject.menu_id();
|
||||
pub const NEW_PROJECT: usize = ActionId::NewProject.menu_id();
|
||||
pub const OPEN_PROJECT: usize = ActionId::OpenProject.menu_id();
|
||||
pub const EXPORT_PROJECT: usize = ActionId::SaveProject.menu_id();
|
||||
pub const CLOSE: usize = ActionId::CloseProject.menu_id();
|
||||
@@ -1035,8 +1035,8 @@ impl<E: AppEngine> OakApp<E> {
|
||||
/// preferences, tools, transport fallbacks — and the placeholder
|
||||
/// `println!` for the actions not wired yet.
|
||||
fn handle_global_action(&mut self, action: ActionId, cx: &mut Context<Self>) {
|
||||
use crate::actions::ActionId as A;
|
||||
match action {
|
||||
use crate::actions::ActionId as A;
|
||||
match action {
|
||||
// --- File ------------------------------------------------------
|
||||
A::NewProject => self.open_new_project(cx),
|
||||
A::OpenProject => self.open_file_dialog(FileAction::Open, cx),
|
||||
@@ -1746,8 +1746,8 @@ impl<E: AppEngine> OakApp<E> {
|
||||
/// close the dialog on success; the mutating actions reload the list;
|
||||
/// failures land in the dialog's status line.
|
||||
fn on_manager_event(&mut self, event: &crate::manager::ManagerEvent, cx: &mut Context<Self>) {
|
||||
use crate::manager::ManagerEvent as E;
|
||||
match event {
|
||||
use crate::manager::ManagerEvent as E;
|
||||
match event {
|
||||
E::Create => {
|
||||
let name = crate::i18n::tr("manager.new.default_name").to_string();
|
||||
let result = self
|
||||
@@ -3392,7 +3392,7 @@ impl MenuState {
|
||||
loop_playback: false,
|
||||
show_all: false,
|
||||
full_screen: false,
|
||||
use_proxy_media: oak_common::configstore::ConfigStore::instance().get_bool(
|
||||
use_proxy_media: oak_core::configstore::ConfigStore::instance().get_bool(
|
||||
None,
|
||||
"UseProxyMedia",
|
||||
1,
|
||||
@@ -3420,10 +3420,10 @@ fn menu_item(action: ActionId) -> MenuItem {
|
||||
/// the menu bar after a language switch repaints it in the new language;
|
||||
/// `state` drives the dynamic checkmarks (theme, tool, snapping, loop, …).
|
||||
fn make_menus(state: MenuState) -> Vec<MenuBarEntry> {
|
||||
use crate::actions::ActionId as A;
|
||||
use crate::i18n::tr;
|
||||
use crate::actions::ActionId as A;
|
||||
use crate::i18n::tr;
|
||||
|
||||
let theme_submenu = Menu::new(vec![
|
||||
let theme_submenu = Menu::new(vec![
|
||||
menu_item(A::ThemeDark).with_checked(state.dark),
|
||||
menu_item(A::ThemeLight).with_checked(!state.dark),
|
||||
]);
|
||||
@@ -3754,9 +3754,9 @@ fn run_with<E: AppEngine>(args: AppArgs) {
|
||||
// choice through the process environment. Read by gpui_wgpu as
|
||||
// OAK_DISPLAY_BIT_DEPTH ("8" opts into the 8-bit pair; anything
|
||||
// else requests 10-bit).
|
||||
let bit_depth = oak_render::backend::DisplayBitDepth::from_config_string(
|
||||
let bit_depth = oak_core::backend::DisplayBitDepth::from_config_string(
|
||||
&crate::oakui::real::config_get_string(
|
||||
oak_render::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH,
|
||||
oak_core::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH,
|
||||
),
|
||||
);
|
||||
// SAFETY: single-threaded startup, before any window exists.
|
||||
@@ -3830,14 +3830,14 @@ fn run_with<E: AppEngine>(args: AppArgs) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::oakui::EngineGateway as _;
|
||||
use gpui::timeline::TimelineDataSource as _;
|
||||
use gpui::{
|
||||
px, size, AnyWindowHandle, ExternalPaths, FileDropEvent, TestAppContext, VisualTestContext,
|
||||
};
|
||||
use super::*;
|
||||
use crate::oakui::EngineGateway as _;
|
||||
use gpui::timeline::TimelineDataSource as _;
|
||||
use gpui::{
|
||||
px, size, AnyWindowHandle, ExternalPaths, FileDropEvent, TestAppContext, VisualTestContext,
|
||||
};
|
||||
|
||||
/// The 视图/View menu carries a 语言/Language submenu whose items are
|
||||
/// The 视图/View menu carries a 语言/Language submenu whose items are
|
||||
/// labeled in their own language and whose checkmark follows the active
|
||||
/// language — and the whole menu bar flips language with `i18n`.
|
||||
#[test]
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
//! Each view owns its widgets and emits nothing itself — the host
|
||||
//! (`crate::app::OakApp`) reads the state (format / path) when a dialog
|
||||
//! button is clicked, and the preferences view writes its choices straight
|
||||
//! into the oakcommon config store on selection. Theme/language changes
|
||||
//! into the oak_core config store on selection. Theme/language changes
|
||||
//! additionally emit a [`PreferencesEvent`] so the host can re-apply the
|
||||
//! shell chrome immediately.
|
||||
|
||||
@@ -36,28 +36,28 @@ use gpui::colors::DefaultColors;
|
||||
use gpui::prelude::*;
|
||||
use gpui::timeline::FrameRate;
|
||||
use gpui::{
|
||||
div, px, App, ClickEvent, Context, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, Keystroke, PathPromptOptions, Render, SharedString, Window,
|
||||
div, px, App, ClickEvent, Context, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, Keystroke, PathPromptOptions, Render, SharedString, Window,
|
||||
};
|
||||
use gpui_elements::editable_text::{EditableTextState, StringStorage, TextChanged};
|
||||
|
||||
use crate::actions::ActionId;
|
||||
use crate::i18n;
|
||||
use crate::oakui::real::{
|
||||
audio_input_device, audio_input_devices, audio_output_device, audio_output_devices,
|
||||
config_get_bool, config_get_int, config_get_string, config_set_bool, config_set_int,
|
||||
config_set_string, encoding_formats, proxy_dividers, renderer_backends, set_audio_input_device,
|
||||
set_audio_output_device, set_theme_dark, theme_is_dark, CONFIG_KEY_DEFAULT_TRANSITION_SEC,
|
||||
CONFIG_KEY_DISK_CACHE_PATH, CONFIG_KEY_FFMPEG_PATH, CONFIG_KEY_PG_URL,
|
||||
CONFIG_KEY_PREVIEW_WINDOW, CONFIG_KEY_PROXY_DIVIDER, CONFIG_KEY_RENDERER_BACKEND,
|
||||
CONFIG_KEY_SNAPSHOT_INTERVAL_SEC, CONFIG_KEY_STORAGE_BACKEND, CONFIG_KEY_USE_PROXY,
|
||||
DEFAULT_PREVIEW_WINDOW_FORWARD, DEFAULT_SNAPSHOT_INTERVAL_SEC, DEFAULT_TRANSITION_SEC,
|
||||
EXPORT_FORMAT_MP4,
|
||||
audio_input_device, audio_input_devices, audio_output_device, audio_output_devices,
|
||||
config_get_bool, config_get_int, config_get_string, config_set_bool, config_set_int,
|
||||
config_set_string, encoding_formats, proxy_dividers, renderer_backends, set_audio_input_device,
|
||||
set_audio_output_device, set_theme_dark, theme_is_dark, CONFIG_KEY_DEFAULT_TRANSITION_SEC,
|
||||
CONFIG_KEY_DISK_CACHE_PATH, CONFIG_KEY_FFMPEG_PATH, CONFIG_KEY_PG_URL,
|
||||
CONFIG_KEY_PREVIEW_WINDOW, CONFIG_KEY_PROXY_DIVIDER, CONFIG_KEY_RENDERER_BACKEND,
|
||||
CONFIG_KEY_SNAPSHOT_INTERVAL_SEC, CONFIG_KEY_STORAGE_BACKEND, CONFIG_KEY_USE_PROXY,
|
||||
DEFAULT_PREVIEW_WINDOW_FORWARD, DEFAULT_SNAPSHOT_INTERVAL_SEC, DEFAULT_TRANSITION_SEC,
|
||||
EXPORT_FORMAT_MP4,
|
||||
};
|
||||
// The `DisplayBitDepth` config key lives with the format mapping it
|
||||
// drives (oak-render's backend); the preferences dropdown and the
|
||||
// window-layer consumer share the same key.
|
||||
use oak_render::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH;
|
||||
use oak_core::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Preferences
|
||||
@@ -373,13 +373,13 @@ impl PreferencesContent {
|
||||
})
|
||||
.detach();
|
||||
|
||||
// --- 色彩 Color: display ICC color management -----------------------
|
||||
// On by default: the viewer frames are transformed through the
|
||||
// display's ICC profile (system profile, or a custom file below).
|
||||
// A mode change re-evaluates the platform display policy and
|
||||
// retags the windows immediately (no restart).
|
||||
use crate::oakui::displaycolor::{CONFIG_KEY_COLOR_MODE, CONFIG_KEY_CUSTOM_ICC};
|
||||
let display_icc = cx.new(|cx| {
|
||||
// --- 色彩 Color: display ICC color management -----------------------
|
||||
// On by default: the viewer frames are transformed through the
|
||||
// display's ICC profile (system profile, or a custom file below).
|
||||
// A mode change re-evaluates the platform display policy and
|
||||
// retags the windows immediately (no restart).
|
||||
use crate::oakui::displaycolor::{CONFIG_KEY_COLOR_MODE, CONFIG_KEY_CUSTOM_ICC};
|
||||
let display_icc = cx.new(|cx| {
|
||||
let mode = config_get_string(CONFIG_KEY_COLOR_MODE);
|
||||
CheckBox::new(
|
||||
13,
|
||||
@@ -1685,8 +1685,8 @@ fn divider_label(divider: i32) -> String {
|
||||
|
||||
/// The display string of a proxy lifecycle state.
|
||||
fn proxy_state_label(state: crate::oakui::engine::ProxyMediaState) -> String {
|
||||
use crate::oakui::engine::ProxyMediaState;
|
||||
match state {
|
||||
use crate::oakui::engine::ProxyMediaState;
|
||||
match state {
|
||||
ProxyMediaState::Missing => i18n::tr("proxydialog.state.missing"),
|
||||
ProxyMediaState::Generating => i18n::tr("proxydialog.state.generating"),
|
||||
ProxyMediaState::Ready => i18n::tr("proxydialog.state.ready"),
|
||||
@@ -1918,20 +1918,20 @@ impl<E: crate::oakui::engine::AppEngine> ProjectPropertiesContent<E> {
|
||||
let (working, gamut, transfer) = engine.read(cx).project_color_settings();
|
||||
working_space.update(cx, |combo, cx| {
|
||||
combo.set_selected(
|
||||
Some(oak_common::colormath::WorkingColorSpace::from_setting(&working) as usize),
|
||||
cx,
|
||||
Some(oak_core::colormath::WorkingColorSpace::from_setting(&working) as usize),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
output_gamut.update(cx, |combo, cx| {
|
||||
combo.set_selected(
|
||||
Some(oak_common::colormath::OutputGamut::from_setting(&gamut) as usize),
|
||||
cx,
|
||||
Some(oak_core::colormath::OutputGamut::from_setting(&gamut) as usize),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
output_transfer.update(cx, |combo, cx| {
|
||||
combo.set_selected(
|
||||
Some(oak_common::colormath::OutputTransfer::from_setting(&transfer) as usize),
|
||||
cx,
|
||||
Some(oak_core::colormath::OutputTransfer::from_setting(&transfer) as usize),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -2015,8 +2015,8 @@ impl<E: crate::oakui::engine::AppEngine> ProjectPropertiesContent<E> {
|
||||
/// The color pipeline settings currently selected in the combos, as
|
||||
/// the canonical persisted strings.
|
||||
fn color_settings(&self, cx: &App) -> (String, String, String) {
|
||||
use oak_common::colormath::{OutputGamut, OutputTransfer, WorkingColorSpace};
|
||||
let working = self
|
||||
use oak_core::colormath::{OutputGamut, OutputTransfer, WorkingColorSpace};
|
||||
let working = self
|
||||
.working_space
|
||||
.read(cx)
|
||||
.selected()
|
||||
@@ -4289,8 +4289,8 @@ impl Render for NewProjectContent {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn keystroke(key: &str) -> Keystroke {
|
||||
use super::*;
|
||||
fn keystroke(key: &str) -> Keystroke {
|
||||
gpui::Keystroke::parse(key).unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
//! directly (M14 R3: project open/save through the oaknode serializer,
|
||||
//! timeline edits through the oaktimeline edit commands on the oakundo
|
||||
//! global stack, the oakrender ticket arena for the viewers, the oaktask
|
||||
//! export path, the oakcommon config store, and the oakstorage
|
||||
//! export path, the oak_core config store, and the oakstorage
|
||||
//! write-through library — no `liboakengine` dylib, no C ABI).
|
||||
//!
|
||||
//! # Layout
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
//! ## The content space
|
||||
//!
|
||||
//! The chain starts from the project's output spec
|
||||
//! ([`oak_render::color::pipeline_output_spec`]): sRGB content runs
|
||||
//! ([`oak_core::color::pipeline_output_spec`]): sRGB content runs
|
||||
//! through the named sRGB/Rec.709 space of the active OCIO config, and
|
||||
//! non-sRGB content (P3/BT.2020 gamuts, PQ/HLG transfers) is converted to
|
||||
//! CIE XYZ (D65, unit luminance) first and flows through the ICC's
|
||||
@@ -82,9 +82,9 @@
|
||||
use std::sync::{Arc, LazyLock, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use oak_common::colormath::{output_spec_to_xyz_d65, OutputColorSpec, OutputGamut, OutputTransfer};
|
||||
use oak_common::configstore::ConfigStore;
|
||||
use oak_render::color::{pipeline_output_spec, ColorProcessor};
|
||||
use oak_core::colormath::{output_spec_to_xyz_d65, OutputColorSpec, OutputGamut, OutputTransfer};
|
||||
use oak_core::configstore::ConfigStore;
|
||||
use oak_core::color::{pipeline_output_spec, ColorProcessor};
|
||||
|
||||
/// Config key: the display color management mode ("icc" / "off"). The
|
||||
/// preference only applies where the platform policy allows self-management
|
||||
@@ -188,7 +188,7 @@ pub fn display_policy() -> DisplayPolicy {
|
||||
// sRGB; a non-sRGB project output can't be honored through it — warn
|
||||
// once so the degradation is visible in the log.
|
||||
#[cfg(target_os = "windows")]
|
||||
if oak_common::displayicc::windows_acm_active() {
|
||||
if oak_core::displayicc::windows_acm_active() {
|
||||
if pipeline_output_spec() != OutputColorSpec::default() {
|
||||
static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
|
||||
WARNED.get_or_init(|| {
|
||||
@@ -280,7 +280,7 @@ fn current_monitor_fingerprint(window: &gpui::Window, cx: &gpui::App) -> Option<
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let id = u64::from(window.display(cx)?.id());
|
||||
return oak_common::displayicc::windows_monitor_fingerprint(id);
|
||||
return oak_core::displayicc::windows_monitor_fingerprint(id);
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@@ -288,7 +288,7 @@ fn current_monitor_fingerprint(window: &gpui::Window, cx: &gpui::App) -> Option<
|
||||
let center = window.bounds().center();
|
||||
let x = f64::from(center.x) * f64::from(window.scale_factor());
|
||||
let y = f64::from(center.y) * f64::from(window.scale_factor());
|
||||
return oak_common::displayicc::x11_monitor_fingerprint_at(x, y);
|
||||
return oak_core::displayicc::x11_monitor_fingerprint_at(x, y);
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
{
|
||||
@@ -414,9 +414,9 @@ fn current() -> Option<State> {
|
||||
/// inside `system_display_icc_for`); an unknown one (empty or malformed)
|
||||
/// falls back to the main-display profile — the pre-multi-monitor behavior.
|
||||
fn monitor_icc_path(monitor: &str) -> Option<String> {
|
||||
match oak_common::displayicc::monitor_ref_from_fingerprint(monitor) {
|
||||
Some(monitor) => oak_common::displayicc::system_display_icc_for(&monitor),
|
||||
None => oak_common::displayicc::system_display_icc(),
|
||||
match oak_core::displayicc::monitor_ref_from_fingerprint(monitor) {
|
||||
Some(monitor) => oak_core::displayicc::system_display_icc_for(&monitor),
|
||||
None => oak_core::displayicc::system_display_icc(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -829,7 +829,7 @@ pub trait AppEngine:
|
||||
/// The global "Use Proxy Media" switch (the C++ `UseProxyMedia`
|
||||
/// config; preview-only — exports always decode the original media).
|
||||
fn use_proxy_media(&self) -> bool {
|
||||
oak_common::configstore::ConfigStore::instance()
|
||||
oak_core::configstore::ConfigStore::instance()
|
||||
.get_bool(None, "UseProxyMedia", 1)
|
||||
!= 0
|
||||
}
|
||||
@@ -838,7 +838,7 @@ pub trait AppEngine:
|
||||
/// footage's rendered frames (the C++ toggles the config and
|
||||
/// re-renders; the preview path reads the switch on every montage).
|
||||
fn set_use_proxy_media(&mut self, enabled: bool, cx: &mut Context<Self>) {
|
||||
oak_common::configstore::ConfigStore::instance().set(
|
||||
oak_core::configstore::ConfigStore::instance().set(
|
||||
None,
|
||||
"UseProxyMedia",
|
||||
if enabled { "true" } else { "false" },
|
||||
@@ -851,7 +851,7 @@ pub trait AppEngine:
|
||||
/// size, 2/4/8 = progressively smaller preview renders for machines
|
||||
/// that cannot keep up. Preview-only; exports always render native.
|
||||
fn playback_divider(&self) -> i64 {
|
||||
oak_common::configstore::ConfigStore::instance()
|
||||
oak_core::configstore::ConfigStore::instance()
|
||||
.get_int(None, "PlaybackDivider", 1)
|
||||
.clamp(1, 8) as i64
|
||||
}
|
||||
@@ -859,7 +859,7 @@ pub trait AppEngine:
|
||||
/// Sets the playback resolution divider and invalidates the rendered
|
||||
/// frames so the next pull re-renders at the new geometry.
|
||||
fn set_playback_divider(&mut self, divider: i64, cx: &mut Context<Self>) {
|
||||
oak_common::configstore::ConfigStore::instance().set(
|
||||
oak_core::configstore::ConfigStore::instance().set(
|
||||
None,
|
||||
"PlaybackDivider",
|
||||
÷r.clamp(1, 8).to_string(),
|
||||
@@ -877,7 +877,7 @@ pub trait AppEngine:
|
||||
/// Whether playback stops at the last frame instead of looping (the C++
|
||||
/// viewer `Stop on Last` toggle / `StopOnLastFrame` config).
|
||||
fn stop_on_last(&self) -> bool {
|
||||
oak_common::configstore::ConfigStore::instance()
|
||||
oak_core::configstore::ConfigStore::instance()
|
||||
.get_bool(None, "StopOnLastFrame", 0)
|
||||
!= 0
|
||||
}
|
||||
@@ -885,7 +885,7 @@ pub trait AppEngine:
|
||||
/// Sets the `StopOnLastFrame` config (the next tick past the end either
|
||||
/// pauses at the last frame or wraps around).
|
||||
fn set_stop_on_last(&mut self, enabled: bool, cx: &mut Context<Self>) {
|
||||
oak_common::configstore::ConfigStore::instance().set(
|
||||
oak_core::configstore::ConfigStore::instance().set(
|
||||
None,
|
||||
"StopOnLastFrame",
|
||||
if enabled { "true" } else { "false" },
|
||||
@@ -897,14 +897,14 @@ pub trait AppEngine:
|
||||
/// value (`0` automatic / `1` only / `2` both), clamped to the valid
|
||||
/// range.
|
||||
fn waveform_mode(&self) -> i32 {
|
||||
oak_common::configstore::ConfigStore::instance()
|
||||
oak_core::configstore::ConfigStore::instance()
|
||||
.get_int(None, "ViewerWaveformMode", 0)
|
||||
.clamp(0, 2)
|
||||
}
|
||||
|
||||
/// Sets the `ViewerWaveformMode` config value (clamped to `0..=2`).
|
||||
fn set_waveform_mode(&mut self, mode: i32, cx: &mut Context<Self>) {
|
||||
oak_common::configstore::ConfigStore::instance().set(
|
||||
oak_core::configstore::ConfigStore::instance().set(
|
||||
None,
|
||||
"ViewerWaveformMode",
|
||||
&mode.clamp(0, 2).to_string(),
|
||||
@@ -972,15 +972,15 @@ pub trait AppEngine:
|
||||
|
||||
/// The project's color pipeline settings:
|
||||
/// `(working colorspace, output gamut, output transfer)` as the
|
||||
/// persisted setting strings (see `oak_common::colormath`). The
|
||||
/// persisted setting strings (see `oak_core::colormath`). The
|
||||
/// working colorspace is the pipeline's scene space (ACEScg by
|
||||
/// default, not hard-coded sRGB); the output pair is the delivery
|
||||
/// target for export and presentation.
|
||||
fn project_color_settings(&self) -> (String, String, String) {
|
||||
(
|
||||
oak_common::colormath::WorkingColorSpace::default().as_setting().to_string(),
|
||||
oak_common::colormath::OutputGamut::default().as_setting().to_string(),
|
||||
oak_common::colormath::OutputTransfer::default().as_setting().to_string(),
|
||||
oak_core::colormath::WorkingColorSpace::default().as_setting().to_string(),
|
||||
oak_core::colormath::OutputGamut::default().as_setting().to_string(),
|
||||
oak_core::colormath::OutputTransfer::default().as_setting().to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2450,7 +2450,7 @@ pub fn remove_node(p: &ProjectRef, node: NodeId) -> Result<(), String> {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A process-wide test lock: the app's tests share the oakundo global
|
||||
/// stack, the oakcommon config store and the codec decode sessions, so any
|
||||
/// stack, the oak_core config store and the codec decode sessions, so any
|
||||
/// test touching them serializes on this lock.
|
||||
#[cfg(test)]
|
||||
pub fn test_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||
|
||||
@@ -43,19 +43,19 @@ use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
|
||||
use gpui::effect_stack::{
|
||||
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
|
||||
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
|
||||
};
|
||||
use gpui::node_graph::{
|
||||
EdgeData, EdgeId, NodeData, NodeGraphDataSource, NodeGraphEvent, NodeId, PortData,
|
||||
PortDataType, PortId, PortKind,
|
||||
EdgeData, EdgeId, NodeData, NodeGraphDataSource, NodeGraphEvent, NodeId, PortData,
|
||||
PortDataType, PortId, PortKind,
|
||||
};
|
||||
use gpui::timeline::{
|
||||
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
|
||||
TrackData, TrackKind, TrimEdge,
|
||||
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
|
||||
TrackData, TrackKind, TrimEdge,
|
||||
};
|
||||
use gpui::{
|
||||
hsla, point, prelude::*, px, App, Context, Entity, Hsla, Pixels, Point, RenderImage,
|
||||
SharedString,
|
||||
hsla, point, prelude::*, px, App, Context, Entity, Hsla, Pixels, Point, RenderImage,
|
||||
SharedString,
|
||||
};
|
||||
use gpui_widgets::audio_meter::AudioMeterDataSource;
|
||||
use gpui_widgets::project_explorer::{ProjectDataSource, ProjectEntry};
|
||||
@@ -67,8 +67,8 @@ use oak_node::track::TrackType;
|
||||
use oak_timeline::util::{block_clip_create, track_append_block};
|
||||
|
||||
use super::engine::{
|
||||
AppEngine, EngineGateway, ExportEvent, ExportSession, LibraryProject, Monitor, MulticamState,
|
||||
Project, ScopeData, Sequence, VideoFormat, WizardFootage, WizardSyncOffset,
|
||||
AppEngine, EngineGateway, ExportEvent, ExportSession, LibraryProject, Monitor, MulticamState,
|
||||
Project, ScopeData, Sequence, VideoFormat, WizardFootage, WizardSyncOffset,
|
||||
};
|
||||
use super::graphops;
|
||||
use super::transport::TransportState;
|
||||
@@ -2060,7 +2060,7 @@ impl AppEngine for MockEngine {
|
||||
let trimmed = path.trim().to_string();
|
||||
// Validate like the real engine (a bogus path keeps the dialog open).
|
||||
if !trimmed.is_empty() {
|
||||
oak_render::color::set_up_default_config_from(Some(&trimmed))
|
||||
oak_core::color::set_up_default_config_from(Some(&trimmed))
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
self.ocio_config = trimmed;
|
||||
@@ -2582,11 +2582,11 @@ impl DemoMulticamGraph {
|
||||
/// tracks are built directly in the graph (no `Add Track` undo entries —
|
||||
/// the demo's initial state is not a user edit).
|
||||
fn build() -> Self {
|
||||
use oak_node::node::NodeCore;
|
||||
use oak_node::sequence::SequenceBehavior;
|
||||
use oak_node::track::{TrackBehavior, TrackListBehavior};
|
||||
use oak_node::node::NodeCore;
|
||||
use oak_node::sequence::SequenceBehavior;
|
||||
use oak_node::track::{TrackBehavior, TrackListBehavior};
|
||||
|
||||
let project = graphops::create_project();
|
||||
let project = graphops::create_project();
|
||||
let sequence = graphops::create_sequence(&project, "Multicam Demo");
|
||||
// A video track list with four tracks, wired into the sequence.
|
||||
{
|
||||
@@ -2863,10 +2863,10 @@ impl MockEngine {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
|
||||
fn demo_engine(app: &mut gpui::App) -> Entity<MockEngine> {
|
||||
fn demo_engine(app: &mut gpui::App) -> Entity<MockEngine> {
|
||||
app.new(|cx| MockEngine::demo(cx))
|
||||
}
|
||||
|
||||
@@ -2932,8 +2932,8 @@ mod tests {
|
||||
/// The stop-on-last tick pauses on the final frame instead of wrapping.
|
||||
#[test]
|
||||
fn clock_tick_stops_on_the_last_frame_when_asked() {
|
||||
use std::time::{Duration, Instant};
|
||||
let mut clock = MockClock::new(FrameRate::new(30, 1));
|
||||
use std::time::{Duration, Instant};
|
||||
let mut clock = MockClock::new(FrameRate::new(30, 1));
|
||||
clock.play();
|
||||
// 10 s at 30 fps = 300 frames into a 5-frame sequence: wrapped 60×.
|
||||
clock.started = Some((Instant::now() - Duration::from_secs(10), Frame(0)));
|
||||
@@ -2953,8 +2953,8 @@ mod tests {
|
||||
/// Without stop-on-last the tick wraps modulo the sequence length.
|
||||
#[test]
|
||||
fn clock_tick_loops_when_not_stopping() {
|
||||
use std::time::{Duration, Instant};
|
||||
let mut clock = MockClock::new(FrameRate::new(30, 1));
|
||||
use std::time::{Duration, Instant};
|
||||
let mut clock = MockClock::new(FrameRate::new(30, 1));
|
||||
clock.play();
|
||||
clock.started = Some((Instant::now() - Duration::from_secs(10), Frame(0)));
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
//! * [`real`] — [`RealEngine`](real::RealEngine) and
|
||||
//! [`RealClock`](real::RealClock), the real engine. M14 R3: it calls the
|
||||
//! oak* module crates' Rust APIs directly (oaknode / oaktimeline /
|
||||
//! oakundo / oakrender / oaktask / oakcodec / oakaudio / oakcommon /
|
||||
//! oakundo / oakrender / oaktask / oakcodec / oakaudio / oak_core /
|
||||
//! oakstorage — no `liboakengine` dylib, no C ABI) behind the same
|
||||
//! [`EngineGateway`](engine::EngineGateway) seam the mock implements.
|
||||
//! * [`graphops`] / [`effectchain`] / [`renderops`] — the app's assembly
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
//! listener and cancel atom.
|
||||
//! * **Config** — the preferences (renderer backend, language, theme,
|
||||
//! cache dir, proxy policy, snapshot interval, default transition,
|
||||
//! audio devices) round-trip through the oakcommon config store; the
|
||||
//! audio devices) round-trip through the oak_core config store; the
|
||||
//! audio device selection additionally applies live through oakaudio's
|
||||
//! manager.
|
||||
//! * **Storage** — the write-through library binds every opened project
|
||||
@@ -66,12 +66,12 @@ use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
|
||||
use gpui::effect_stack::{
|
||||
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
|
||||
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
|
||||
};
|
||||
use gpui::node_graph::{NodeGraphDataSource, NodeGraphEvent};
|
||||
use gpui::timeline::{
|
||||
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
|
||||
TrackData, TrackHeaderEvent, TrackKind, TrimEdge,
|
||||
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
|
||||
TrackData, TrackHeaderEvent, TrackKind, TrimEdge,
|
||||
};
|
||||
use gpui::{prelude::*, px, App, Context, Entity, Hsla, Pixels, RenderImage, SharedString};
|
||||
use gpui_widgets::audio_meter::AudioMeterDataSource;
|
||||
@@ -86,8 +86,8 @@ use oak_timeline::handle::CHandle;
|
||||
use oak_timeline::util::NodeRef;
|
||||
|
||||
use super::engine::{
|
||||
AppEngine, EngineGateway, ExportSession, LibraryProject, Monitor, MulticamState, Project,
|
||||
ScopeData, Sequence, SequenceParameters, VideoFormat, WizardFootage, WizardSyncOffset,
|
||||
AppEngine, EngineGateway, ExportSession, LibraryProject, Monitor, MulticamState, Project,
|
||||
ScopeData, Sequence, SequenceParameters, VideoFormat, WizardFootage, WizardSyncOffset,
|
||||
};
|
||||
use super::frames::{bgra_bytes_to_render_image, f32_rgba_to_bgra_image, synthetic_frame_samples};
|
||||
use super::graphops::{self, ProjectRef};
|
||||
@@ -714,10 +714,10 @@ fn rendered_to_owned_image(rendered: &super::renderops::RenderedFrame) -> Option
|
||||
/// The app-side output node for F32 frames (working colorspace → the
|
||||
/// project's output colorspace); pass-through in the legacy working space.
|
||||
fn apply_output_node_f32(samples: &mut [f32]) {
|
||||
oak_common::colormath::working_to_display_target(
|
||||
samples,
|
||||
oak_render::color::pipeline_working_space(),
|
||||
oak_render::color::pipeline_output_spec(),
|
||||
oak_core::colormath::working_to_display_target(
|
||||
samples,
|
||||
oak_core::color::pipeline_working_space(),
|
||||
oak_core::color::pipeline_output_spec(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2378,8 +2378,8 @@ impl RealEngine {
|
||||
/// mapped onto the widget's badge enum; folders and footage without
|
||||
/// proxy state get none.
|
||||
fn proxy_badge_of(&self, id: u64) -> Option<gpui_widgets::project_explorer::ProxyBadge> {
|
||||
use gpui_widgets::project_explorer::ProxyBadge;
|
||||
let project = self.project.as_ref()?;
|
||||
use gpui_widgets::project_explorer::ProxyBadge;
|
||||
let project = self.project.as_ref()?;
|
||||
let node = graphops::id_of(id)?;
|
||||
let guard = graphops::lock(project);
|
||||
let f = graphops::footage_behavior(&guard.graph, node)?;
|
||||
@@ -2547,7 +2547,7 @@ impl RealEngine {
|
||||
fn proxy_cache_path() -> String {
|
||||
let configured = config_get_string(CONFIG_KEY_DISK_CACHE_PATH);
|
||||
if configured.trim().is_empty() {
|
||||
oak_common::filefunctions::default_disk_cache_path()
|
||||
oak_core::filefunctions::default_disk_cache_path()
|
||||
} else {
|
||||
configured
|
||||
}
|
||||
@@ -2706,8 +2706,8 @@ impl RealEngine {
|
||||
f: &oak_node::footage::FootageBehavior,
|
||||
node: NodeId,
|
||||
) -> super::engine::ProxyMediaState {
|
||||
use super::engine::ProxyMediaState;
|
||||
if self.proxy_runs.iter().any(|run| run.footage == node) {
|
||||
use super::engine::ProxyMediaState;
|
||||
if self.proxy_runs.iter().any(|run| run.footage == node) {
|
||||
return ProxyMediaState::Generating;
|
||||
}
|
||||
if f.proxy.is_empty() {
|
||||
@@ -2733,9 +2733,9 @@ impl RealEngine {
|
||||
/// every clip is re-placed so its source head lines up with the
|
||||
/// reference's at the anchor (one multi-undo).
|
||||
fn sync_clips_by_source_time_internal(&mut self, clips: &[ClipId]) {
|
||||
use oak_audio::synchronizer::{place_by_source_time, SourceClip};
|
||||
use oak_audio::synchronizer::{place_by_source_time, SourceClip};
|
||||
|
||||
let Some(project) = self.project.clone() else {
|
||||
let Some(project) = self.project.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -2857,12 +2857,12 @@ impl RealEngine {
|
||||
/// offset triggers a rate search whose winner also rescales the clip
|
||||
/// speed (one multi-undo).
|
||||
fn sync_clips_by_waveform_internal(&mut self, clips: &[ClipId], allow_speed: bool) {
|
||||
use oak_audio::synchronizer::place_by_waveform_offset;
|
||||
use oak_audio::waveformsync::{
|
||||
estimate_envelope_offset_valid, estimate_stretch_and_offset,
|
||||
};
|
||||
use oak_audio::synchronizer::place_by_waveform_offset;
|
||||
use oak_audio::waveformsync::{
|
||||
estimate_envelope_offset_valid, estimate_stretch_and_offset,
|
||||
};
|
||||
|
||||
let Some(cache) = self.waveform_cache() else {
|
||||
let Some(cache) = self.waveform_cache() else {
|
||||
return;
|
||||
};
|
||||
let Some(project) = self.project.clone() else {
|
||||
@@ -3157,7 +3157,7 @@ impl RealEngine {
|
||||
// the render workers pick them up at graph-load time.
|
||||
{
|
||||
let guard = graphops::lock(&project);
|
||||
oak_render::color::set_pipeline_color_settings(
|
||||
oak_core::color::set_pipeline_color_settings(
|
||||
guard.working_color_space(),
|
||||
guard.output_color_spec(),
|
||||
);
|
||||
@@ -3263,12 +3263,12 @@ impl RealEngine {
|
||||
.cloned()
|
||||
});
|
||||
let applied = match stored.as_deref() {
|
||||
None => oak_render::color::set_up_default_config(),
|
||||
Some(path) => oak_render::color::set_up_default_config_from(Some(path)),
|
||||
None => oak_core::color::set_up_default_config(),
|
||||
Some(path) => oak_core::color::set_up_default_config_from(Some(path)),
|
||||
};
|
||||
if let Err(e) = applied {
|
||||
println!("[real engine] project OCIO config apply failed: {e}");
|
||||
let _ = oak_render::color::set_up_default_config();
|
||||
let _ = oak_core::color::set_up_default_config();
|
||||
}
|
||||
super::displaycolor::invalidate();
|
||||
}
|
||||
@@ -5422,7 +5422,7 @@ impl AppEngine for RealEngine {
|
||||
}
|
||||
|
||||
fn set_use_proxy_media(&mut self, enabled: bool, cx: &mut Context<Self>) {
|
||||
oak_common::configstore::ConfigStore::instance().set(
|
||||
oak_core::configstore::ConfigStore::instance().set(
|
||||
None,
|
||||
CONFIG_KEY_USE_PROXY,
|
||||
if enabled { "true" } else { "false" },
|
||||
@@ -5436,7 +5436,7 @@ impl AppEngine for RealEngine {
|
||||
/// Resolution ▸` menu): the preview geometry changes, so every cached
|
||||
/// and in-flight preview frame is stale.
|
||||
fn set_playback_divider(&mut self, divider: i64, cx: &mut Context<Self>) {
|
||||
oak_common::configstore::ConfigStore::instance().set(
|
||||
oak_core::configstore::ConfigStore::instance().set(
|
||||
None,
|
||||
"PlaybackDivider",
|
||||
÷r.clamp(1, 8).to_string(),
|
||||
@@ -5471,9 +5471,9 @@ impl AppEngine for RealEngine {
|
||||
// refuses an invalid config the same way). Applying is the
|
||||
// process-wide color config reload plus a full frame invalidation.
|
||||
if trimmed.is_empty() {
|
||||
oak_render::color::set_up_default_config().map_err(|e| e.to_string())?;
|
||||
oak_core::color::set_up_default_config().map_err(|e| e.to_string())?;
|
||||
} else {
|
||||
oak_render::color::set_up_default_config_from(Some(&trimmed))
|
||||
oak_core::color::set_up_default_config_from(Some(&trimmed))
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
{
|
||||
@@ -5531,9 +5531,9 @@ impl AppEngine for RealEngine {
|
||||
fn project_color_settings(&self) -> (String, String, String) {
|
||||
let Some(project) = self.project_ref() else {
|
||||
return (
|
||||
oak_common::colormath::WorkingColorSpace::default().as_setting().to_string(),
|
||||
oak_common::colormath::OutputGamut::default().as_setting().to_string(),
|
||||
oak_common::colormath::OutputTransfer::default().as_setting().to_string(),
|
||||
oak_core::colormath::WorkingColorSpace::default().as_setting().to_string(),
|
||||
oak_core::colormath::OutputGamut::default().as_setting().to_string(),
|
||||
oak_core::colormath::OutputTransfer::default().as_setting().to_string(),
|
||||
);
|
||||
};
|
||||
let guard = graphops::lock(project);
|
||||
@@ -5547,16 +5547,16 @@ impl AppEngine for RealEngine {
|
||||
};
|
||||
(
|
||||
get(
|
||||
oak_node::project::SETTING_WORKING_COLOR_SPACE,
|
||||
oak_common::colormath::WorkingColorSpace::default().as_setting(),
|
||||
oak_node::project::SETTING_WORKING_COLOR_SPACE,
|
||||
oak_core::colormath::WorkingColorSpace::default().as_setting(),
|
||||
),
|
||||
get(
|
||||
oak_node::project::SETTING_OUTPUT_GAMUT,
|
||||
oak_common::colormath::OutputGamut::default().as_setting(),
|
||||
oak_node::project::SETTING_OUTPUT_GAMUT,
|
||||
oak_core::colormath::OutputGamut::default().as_setting(),
|
||||
),
|
||||
get(
|
||||
oak_node::project::SETTING_OUTPUT_TRANSFER,
|
||||
oak_common::colormath::OutputTransfer::default().as_setting(),
|
||||
oak_node::project::SETTING_OUTPUT_TRANSFER,
|
||||
oak_core::colormath::OutputTransfer::default().as_setting(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -5572,8 +5572,8 @@ impl AppEngine for RealEngine {
|
||||
return;
|
||||
};
|
||||
// Normalize through the parsers so only canonical values persist.
|
||||
let working = oak_common::colormath::WorkingColorSpace::from_setting(&working);
|
||||
let spec = oak_common::colormath::OutputColorSpec::from_settings(&gamut, &transfer);
|
||||
let working = oak_core::colormath::WorkingColorSpace::from_setting(&working);
|
||||
let spec = oak_core::colormath::OutputColorSpec::from_settings(&gamut, &transfer);
|
||||
{
|
||||
let mut guard = graphops::lock(&project);
|
||||
guard.settings.insert(
|
||||
@@ -5592,7 +5592,7 @@ impl AppEngine for RealEngine {
|
||||
}
|
||||
// The app-side transforms read the process global; the workers pick
|
||||
// the new settings up with the next graph upload.
|
||||
oak_render::color::set_pipeline_color_settings(working, spec);
|
||||
oak_core::color::set_pipeline_color_settings(working, spec);
|
||||
// The workers derive their pipeline colors from the uploaded project
|
||||
// snapshot; a settings change alone does not bump the undo-stack
|
||||
// revision (the manager dedups re-uploads on it), so push an explicit
|
||||
@@ -7124,7 +7124,7 @@ pub fn encoding_formats() -> Vec<(i32, String, String)> {
|
||||
pub const EXPORT_FORMAT_MP4: i32 = 2;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config (preferences) — the oakcommon config store directly
|
||||
// Config (preferences) — the oak_core config store directly
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The config key selecting the renderer backend (worker `create_renderer`
|
||||
@@ -7134,7 +7134,7 @@ pub const CONFIG_KEY_RENDERER_BACKEND: &str = "GraphicsBackend";
|
||||
/// defaults to dark when the key is absent).
|
||||
pub const CONFIG_KEY_THEME: &str = "Theme";
|
||||
/// The config key overriding the disk cache directory (empty = the
|
||||
/// platform default `<config dir>/mediacache`; honored by oakcommon's
|
||||
/// platform default `<config dir>/mediacache`; honored by oak_core's
|
||||
/// `default_disk_cache_path`, so oakrender/oaknode caches follow it).
|
||||
pub const CONFIG_KEY_DISK_CACHE_PATH: &str = "DiskCachePath";
|
||||
/// The config key toggling proxy media use (`UseProxyMedia`, bool).
|
||||
@@ -7169,8 +7169,8 @@ pub const DEFAULT_SNAPSHOT_INTERVAL_SEC: i64 = 600;
|
||||
pub const DEFAULT_TRANSITION_SEC: &str = "0.5";
|
||||
|
||||
/// The process-wide config store.
|
||||
fn config_store() -> &'static oak_common::configstore::ConfigStore {
|
||||
oak_common::configstore::ConfigStore::instance()
|
||||
fn config_store() -> &'static oak_core::configstore::ConfigStore {
|
||||
oak_core::configstore::ConfigStore::instance()
|
||||
}
|
||||
|
||||
/// Loads the persisted configuration from disk (once at startup, before
|
||||
@@ -7358,11 +7358,11 @@ pub fn library_list() -> Result<Vec<LibraryProject>, String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::mpsc as std_mpsc;
|
||||
use std::time::Duration;
|
||||
use super::*;
|
||||
use std::sync::mpsc as std_mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Serializes the media/FFmpeg-heavy tests (the codec library is not
|
||||
/// Serializes the media/FFmpeg-heavy tests (the codec library is not
|
||||
/// thread-safe against concurrent decode sessions) and shares the
|
||||
/// process-global undo stack with the other app test modules.
|
||||
fn media_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||
@@ -7740,11 +7740,11 @@ mod tests {
|
||||
fn process_backend_preview_path_is_zero_copy() {
|
||||
let _media = media_lock();
|
||||
let _worker = WorkerBinGuard::set();
|
||||
use oak_render::manager::{RenderBackendChoice, RenderManager};
|
||||
use oak_render::procpool::{
|
||||
main_heap_frame_copies, reset_main_heap_frame_copies, DispatcherConfig,
|
||||
};
|
||||
RenderManager::shutdown();
|
||||
use oak_render::manager::{RenderBackendChoice, RenderManager};
|
||||
use oak_render::procpool::{
|
||||
main_heap_frame_copies, reset_main_heap_frame_copies, DispatcherConfig,
|
||||
};
|
||||
RenderManager::shutdown();
|
||||
let config = DispatcherConfig {
|
||||
worker_bin: Some(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use gpui::RenderImage;
|
||||
use oak_core::texture::Texture;
|
||||
use oak_core::{PixelFormat, Rational, TimeRange};
|
||||
use oak_node::id::NodeId;
|
||||
use oak_node::track::TrackType;
|
||||
use oak_render::manager::RenderManager;
|
||||
use oak_render::procpool::ShmFrameRef;
|
||||
use oak_render::texture::Texture;
|
||||
use oak_render::ticket::{AudioTicketParams, MontageClip, TicketPayload, VideoTicketParams};
|
||||
|
||||
use super::engine::{ExportEvent, ExportSession};
|
||||
@@ -84,7 +84,7 @@ pub fn ensure_render_manager() -> bool {
|
||||
/// `UseProxyMedia` config switch (C++ `Tools > Use Proxy Media`; the
|
||||
/// export path never consults it — exports always decode the original).
|
||||
pub fn use_proxy_media() -> bool {
|
||||
oak_common::configstore::ConfigStore::instance().get_bool(None, "UseProxyMedia", 1) != 0
|
||||
oak_core::configstore::ConfigStore::instance().get_bool(None, "UseProxyMedia", 1) != 0
|
||||
}
|
||||
|
||||
/// The preview media of a footage node with the three-level proxy switch
|
||||
@@ -606,10 +606,10 @@ impl RenderedFrame {
|
||||
/// the project's output colorspace, in place on tightly packed samples.
|
||||
/// Pass-through in the legacy sRGB working space.
|
||||
fn apply_output_node_f32(samples: &mut [f32]) {
|
||||
oak_common::colormath::working_to_display_target(
|
||||
samples,
|
||||
oak_render::color::pipeline_working_space(),
|
||||
oak_render::color::pipeline_output_spec(),
|
||||
oak_core::colormath::working_to_display_target(
|
||||
samples,
|
||||
oak_core::color::pipeline_working_space(),
|
||||
oak_core::color::pipeline_output_spec(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1437,9 +1437,9 @@ mod tests {
|
||||
// pixels; disabling restores them), not the color pipeline. Pin the
|
||||
// legacy sRGB pass-through so the pixel-value assertions hold
|
||||
// regardless of the ACEScg default.
|
||||
oak_render::color::set_pipeline_color_settings(
|
||||
oak_common::colormath::WorkingColorSpace::SrgbLegacy,
|
||||
oak_common::colormath::OutputColorSpec::default(),
|
||||
oak_core::color::set_pipeline_color_settings(
|
||||
oak_core::colormath::WorkingColorSpace::SrgbLegacy,
|
||||
oak_core::colormath::OutputColorSpec::default(),
|
||||
);
|
||||
oak_undo::global::clear().unwrap();
|
||||
let media =
|
||||
@@ -1697,7 +1697,7 @@ mod tests {
|
||||
let gdata;
|
||||
let goff;
|
||||
{
|
||||
let oak_render::texture::Texture::Cpu(ref gf) = &graph_frame else {
|
||||
let oak_core::texture::Texture::Cpu(ref gf) = &graph_frame else {
|
||||
panic!("graph render produced a non-CPU frame");
|
||||
};
|
||||
grow = gf.linesize_bytes();
|
||||
@@ -1805,7 +1805,7 @@ mod tests {
|
||||
oak_core::PixelFormat::F32,
|
||||
)
|
||||
.expect("graph render");
|
||||
let oak_render::texture::Texture::Cpu(ref gf) = texture else {
|
||||
let oak_core::texture::Texture::Cpu(ref gf) = texture else {
|
||||
panic!("non-CPU frame");
|
||||
};
|
||||
let stride = gf.linesize_bytes();
|
||||
@@ -2103,7 +2103,7 @@ mod tests {
|
||||
// Force the global proxy switch on and restore it afterwards (the
|
||||
// config store is process-global; the serialization lock held
|
||||
// above is the same one the other app test modules use).
|
||||
let store = oak_common::configstore::ConfigStore::instance();
|
||||
let store = oak_core::configstore::ConfigStore::instance();
|
||||
let old = store.get(None, "UseProxyMedia").unwrap_or_default();
|
||||
store.set_bool(None, "UseProxyMedia", 1);
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@
|
||||
|
||||
| C++ | Rust 落点 |
|
||||
|---|---|
|
||||
| `output_buffer_size()` | `config::output_buffer_size`(`oakcommon_config_get_int(nullptr,"AudioOutputBufferSize",0)`) |
|
||||
| `device_name(is_output_device)` | `config::device_name`(`oakcommon_config_get` 两阶段;key = "AudioOutput"/"AudioInput") |
|
||||
| `output_buffer_size()` | `config::output_buffer_size`(`oak_core_config_get_int(nullptr,"AudioOutputBufferSize",0)`) |
|
||||
| `device_name(is_output_device)` | `config::device_name`(`oak_core_config_get` 两阶段;key = "AudioOutput"/"AudioInput") |
|
||||
|
||||
## 9. 刻意不迁移(drop)
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -305,7 +305,7 @@ name = "oakaudio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oakcodec",
|
||||
"oakcommon",
|
||||
"oak_core",
|
||||
"oakcore-rs",
|
||||
]
|
||||
|
||||
@@ -318,7 +318,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oakcommon"
|
||||
name = "oak_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
|
||||
@@ -11,7 +11,6 @@ crate-type = ["staticlib", "rlib"]
|
||||
[dependencies]
|
||||
oak-ffmpeg-link = { path = "../oak-ffmpeg-link" }
|
||||
oak-core = { path = "../oak-core" }
|
||||
oak-common = { path = "../oak-common" }
|
||||
oak-codec = { path = "../oak-codec" }
|
||||
# Real resample/channel-convert/time-stretch filter graph. The C++
|
||||
# ffmpeg_bridge library existed only to absorb FFmpeg API churn; the Rust
|
||||
|
||||
@@ -47,8 +47,8 @@ functions) — frozen, implemented verbatim by `src/ffi.rs`.
|
||||
oakcodec encoder C ABI (`bridge::codec`) and waveform extraction
|
||||
decodes through the oakcodec decoder C ABI — exactly as the C++
|
||||
does. No direct ffmpeg_bridge use in the record path.
|
||||
7. **Config via oakcommon.** Device names and the output buffer size
|
||||
read through `bridge::common` (`oakcommon_config_*`), preserving the
|
||||
7. **Config via oak_core.** Device names and the output buffer size
|
||||
read through `bridge::common` (`oak_core_config_*`), preserving the
|
||||
`audio_config` namespace semantics as a `config.rs` free-function
|
||||
module.
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The `audio_config` namespace from `src/audio/src/configbridge.*`:
|
||||
//! audio-specific configuration read through the oakcommon C ABI.
|
||||
//! audio-specific configuration read through the oak_core C ABI.
|
||||
|
||||
use std::error::Error;
|
||||
use std::ffi::CString;
|
||||
use std::str::FromStr;
|
||||
use oak_common::configstore::*;
|
||||
use oak_core::configstore::*;
|
||||
/// PortAudio output buffer size in frames; 0 = let PortAudio choose.
|
||||
///
|
||||
/// `// CPP-PARITY: src/audio/src/configbridge.cpp:30`
|
||||
@@ -44,7 +44,7 @@ pub fn device_name(is_output_device: bool) -> Result<String, Box<dyn Error>> {
|
||||
let store = ConfigStore::instance();
|
||||
let size = i32::from_str(store.get(None, key)?.as_str())?;
|
||||
if size <= 1 {
|
||||
// Absent (OAKCOMMON_E_NOT_FOUND) or empty
|
||||
// Absent (oak_core_E_NOT_FOUND) or empty
|
||||
return Err(Box::new(crate::error::Error::NotFound));
|
||||
}
|
||||
let mut buf = vec![0u8; size as usize];
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
//! `include/audio/manager.h`). An empty handle reports `OAKAUDIO_E_STATE`.
|
||||
//!
|
||||
//! Recording goes through the oakcodec encoder C ABI ([`crate::bridge`]);
|
||||
//! device/config lookups go through oakcommon.
|
||||
//! device/config lookups go through oak_core.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
@@ -77,7 +77,7 @@ impl Default for ProcessorInner {
|
||||
}
|
||||
|
||||
/// Map an oakcore [`SampleFormat`] to the equivalent ffmpeg [`Sample`].
|
||||
/// Replaces `FFmpegUtils::get_ffmpeg_sample_format` crossing the oakcommon
|
||||
/// Replaces `FFmpegUtils::get_ffmpeg_sample_format` crossing the oak_core
|
||||
/// C ABI (`// CPP-PARITY: src/common/src/ffmpegutils.cpp:83`).
|
||||
fn to_ffmpeg_sample_format(fmt: SampleFormat) -> Sample {
|
||||
match fmt {
|
||||
|
||||
Generated
+2
-2
@@ -879,7 +879,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oakcommon"
|
||||
name = "oak_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
@@ -900,7 +900,7 @@ dependencies = [
|
||||
"libc",
|
||||
"oakaudio",
|
||||
"oakcodec",
|
||||
"oakcommon",
|
||||
"oak_core",
|
||||
"oakcore-rs",
|
||||
"oaknode",
|
||||
"oakplugin",
|
||||
|
||||
@@ -32,9 +32,8 @@ clap = { version = "4", features = ["derive"] }
|
||||
# direct Rust call into the oak* rlibs (oaknode for projects/footage,
|
||||
# oaktimeline for the track/clip commands, oakrender for the ticket arena,
|
||||
# oakcodec for the export formats/codecs, oaktask for the export task,
|
||||
# oakcommon/oakcore-rs for the shared value types). No liboakengine dylib,
|
||||
# oak_core/oakcore-rs for the shared value types). No liboakengine dylib,
|
||||
# no C ABI, no build.rs link step, no host shims.
|
||||
oak-common = { path = "../oak-common" }
|
||||
oak-core = { path = "../oak-core" }
|
||||
oak-node = { path = "../oak-node" }
|
||||
oak-timeline = { path = "../oak-timeline" }
|
||||
|
||||
@@ -20,7 +20,7 @@ cargo test # unit + integration tests
|
||||
|
||||
The crate is **self-contained** (M14 R2): it links the oak* module rlibs
|
||||
directly (`oaknode`, `oaktimeline`, `oakcodec`, `oakrender`, `oaktask`,
|
||||
`oakcommon`) — no `liboakengine` dylib, no C ABI, no build.rs link step.
|
||||
`oak_core`) — no `liboakengine` dylib, no C ABI, no build.rs link step.
|
||||
`cargo test -p oak-cli` stands alone.
|
||||
|
||||
## Subcommands
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//! Module-native engine helpers (M14 R2).
|
||||
//!
|
||||
//! oak-cli links the oak* module rlibs directly (oaknode / oaktimeline /
|
||||
//! oakcodec / oakrender / oaktask / oakcommon) instead of the built
|
||||
//! oakcodec / oakrender / oaktask / oak_core) instead of the built
|
||||
//! liboakengine dylib's C ABI. This module is the CLI's own assembly
|
||||
//! layer: it reproduces the facade operations the subcommands need —
|
||||
//! project load/create, footage probe, sequence + clip assembly, montage
|
||||
@@ -43,14 +43,14 @@ use oak_node::project::Project;
|
||||
use oak_node::sequence::SequenceBehavior;
|
||||
use oak_node::track::{TrackBehavior, TrackListBehavior, TrackType};
|
||||
use oak_node::value::VideoParams;
|
||||
use oak_timeline::undogeneral::TimelineAddTrackCommand;
|
||||
use oak_timeline::undopointer::TrackPlaceBlockCommand;
|
||||
use oak_timeline::util::NodeRef;
|
||||
use oak_render::manager::RenderManager;
|
||||
use oak_render::procpool::bgra8_to_rgba8;
|
||||
use oak_render::ticket::{
|
||||
AudioTicketParams, MontageClip, TicketPayload, VideoTicketParams,
|
||||
AudioTicketParams, MontageClip, TicketPayload, VideoTicketParams,
|
||||
};
|
||||
use oak_timeline::undogeneral::TimelineAddTrackCommand;
|
||||
use oak_timeline::undopointer::TrackPlaceBlockCommand;
|
||||
use oak_timeline::util::NodeRef;
|
||||
|
||||
/// The shared project reference (the modules' domain project handle).
|
||||
pub type ProjectRef = Arc<Mutex<Project>>;
|
||||
@@ -663,7 +663,7 @@ pub fn render_frame(
|
||||
m.tickets.wait(id).map_err(|e| e.to_string())?;
|
||||
let result = m.tickets.result(id).ok_or_else(|| "render ticket produced no result".to_string())?;
|
||||
match &result {
|
||||
Ok(TicketPayload::Video(oak_render::texture::Texture::Cpu(frame))) => {
|
||||
Ok(TicketPayload::Video(oak_core::texture::Texture::Cpu(frame))) => {
|
||||
Ok(RenderedFrame {
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
|
||||
@@ -9,7 +9,6 @@ license = "GPL-3.0-or-later"
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
oak-common = { path = "../oak-common" }
|
||||
oak-ffmpeg-link = { path = "../oak-ffmpeg-link" }
|
||||
oak-core = { path = "../oak-core" }
|
||||
# Real media decode/encode. The C++ ffmpeg_bridge library existed only to
|
||||
|
||||
@@ -42,15 +42,15 @@ and never blocks.
|
||||
virtual chain.
|
||||
3. **`Frame` owns its params by value.** `olive::Frame` wraps an
|
||||
`OakVideoParams` handle plus a `Vec<u8>` pixel buffer. In Rust the
|
||||
params are held as an `oakcommon::videoparams::VideoParams` value
|
||||
(single-lib unification dropped the refcounted oakcommon handle);
|
||||
params are held as an `oak_core::videoparams::VideoParams` value
|
||||
(single-lib unification dropped the refcounted oak_core handle);
|
||||
the buffer is a plain `Vec<u8>`.
|
||||
4. **No adapter layer.** Codec calls the other module crates directly
|
||||
(`oakcommon`, `oakcore-rs`, `oakffmpeg-link`), keeping the 2026-08
|
||||
(`oak_core`, `oakcore-rs`, `oakffmpeg-link`), keeping the 2026-08
|
||||
decision recorded in NOTES.md §6. Only genuinely repeated
|
||||
conversions survive as small module-local helpers.
|
||||
5. **XML stays on the C++ side.** `EncodingParams::load/save` use
|
||||
oakcommon's C++ `XmlStreamWriter/Reader` classes
|
||||
oak_core's C++ `XmlStreamWriter/Reader` classes
|
||||
(`src/common/src/xmlutils.h`), exactly as oaknode/oakrender do —
|
||||
the one C++-to-C++ coupling the bridge cannot cover (NOTES.md §7).
|
||||
6. **Threading.** `FrameManager` keeps its background GC thread behind
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
//! `Unavailable`. Deterministic per-channel filenames derive from the
|
||||
//! source + target audio params.
|
||||
|
||||
use oak_common::filefunctions::FileFunctions;
|
||||
use oak_core::filefunctions::FileFunctions;
|
||||
use std::path::Path;
|
||||
|
||||
/// Conform state of one audio stream.
|
||||
@@ -184,7 +184,7 @@ fn conform_filenames(
|
||||
out
|
||||
}
|
||||
|
||||
/// `oakcommon_filefunctions_get_unique_file_identifier` wrapper.
|
||||
/// `oak_core_filefunctions_get_unique_file_identifier` wrapper.
|
||||
fn unique_file_identifier(filename: &str) -> String {
|
||||
FileFunctions::new()
|
||||
.get_unique_file_identifier(filename)
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use oak_common::cancelatom::CancelAtom;
|
||||
use oak_core::cancelatom::CancelAtom;
|
||||
use oak_core::{Rational, TimeRange};
|
||||
|
||||
use crate::footagedescription::FootageDescription;
|
||||
@@ -113,7 +113,7 @@ pub enum RenderMode {
|
||||
|
||||
/// "Don't force a color range" sentinel for
|
||||
/// [`RetrieveVideoParams::force_range`] (the actual ranges are the
|
||||
/// `OAKCOMMON_COLOR_RANGE_*` values).
|
||||
/// `oak_core_COLOR_RANGE_*` values).
|
||||
pub const K_COLOR_RANGE_DEFAULT: i32 = -1;
|
||||
|
||||
/// `Decoder::RetrieveVideoParams` — what a video retrieve call needs.
|
||||
|
||||
@@ -269,10 +269,10 @@ impl EncodingParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load from a compact XML preset string (oakcommon C++ XmlStreamReader).
|
||||
/// Load from a compact XML preset string (oak_core C++ XmlStreamReader).
|
||||
///
|
||||
/// # CPP-PARITY
|
||||
/// `EncodingParams::load` — uses oakcommon's C++ `XmlStreamReader`
|
||||
/// `EncodingParams::load` — uses oak_core's C++ `XmlStreamReader`
|
||||
/// (`src/common/src/xmlutils.h`), a C++-to-C++ coupling the bridge
|
||||
/// cannot cover (NOTES.md §7). Preserves the load_v1 bug of not
|
||||
/// assigning `custom_range`.
|
||||
@@ -524,7 +524,7 @@ impl EncodingParams {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Minimal XML helpers for the round-trip `load`/`save_to_string`.
|
||||
//
|
||||
// CPP-PARITY: the C++ `load`/`save_to_string` go through oakcommon's
|
||||
// CPP-PARITY: the C++ `load`/`save_to_string` go through oak_core's
|
||||
// `XmlStreamReader`/`XmlStreamWriter` (a C++-to-C++ coupling the Rust bridge
|
||||
// cannot cover, NOTES.md §7). Rather than returning `Err`, this port keeps a
|
||||
// minimal but faithful round-trip for the fields representable without the
|
||||
|
||||
@@ -56,10 +56,10 @@ use ffmpeg::software::{resampling, scaling};
|
||||
use ffmpeg::{ChannelLayout, Dictionary, Error as FfmpegError, Rational as FfRational};
|
||||
use ffmpeg_next as ffmpeg;
|
||||
|
||||
use oak_common::cancelatom::CancelAtom;
|
||||
use oak_common::colormath::YuvMatrix;
|
||||
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_common::videoparams::{Interlacing, VideoParams, VideoType};
|
||||
use oak_core::cancelatom::CancelAtom;
|
||||
use oak_core::colormath::YuvMatrix;
|
||||
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_core::videoparams::{Interlacing, VideoParams, VideoType};
|
||||
use oak_core::{PixelFormat, Rational, SampleFormat, TimeRange};
|
||||
|
||||
use crate::audioparams::AudioParams;
|
||||
@@ -69,10 +69,10 @@ use crate::encodingparams::EncodingParams;
|
||||
use crate::footagedescription::{FootageDescription, StreamEntry};
|
||||
use crate::frame::Frame;
|
||||
|
||||
/// `OAKCOMMON_COLOR_RANGE_FULL`.
|
||||
const OAKCOMMON_COLOR_RANGE_FULL: i32 = 1;
|
||||
/// `OAKCOMMON_COLOR_RANGE_LIMITED`.
|
||||
const OAKCOMMON_COLOR_RANGE_LIMITED: i32 = 0;
|
||||
/// `oak_core_COLOR_RANGE_FULL`.
|
||||
const oak_core_COLOR_RANGE_FULL: i32 = 1;
|
||||
/// `oak_core_COLOR_RANGE_LIMITED`.
|
||||
const oak_core_COLOR_RANGE_LIMITED: i32 = 0;
|
||||
/// `AVCOL_RANGE_JPEG` (full range; AVCOL_RANGE_MPEG = 1 is limited).
|
||||
const AVCOL_RANGE_JPEG: i32 = 2;
|
||||
/// swscale colorspace ids (`SWS_CS_*`, libswscale/swscale.h).
|
||||
@@ -372,9 +372,9 @@ impl Decoder for FFmpegDecoder {
|
||||
params.set_color_primaries(color_meta.color_primaries);
|
||||
params.set_color_transfer(color_meta.color_trc);
|
||||
params.set_color_range(if color_meta.full_range {
|
||||
oak_common::videoparams::ColorRange::Full
|
||||
oak_core::videoparams::ColorRange::Full
|
||||
} else {
|
||||
oak_common::videoparams::ColorRange::Limited
|
||||
oak_core::videoparams::ColorRange::Limited
|
||||
});
|
||||
}
|
||||
Ok(Arc::new(frame))
|
||||
@@ -1099,9 +1099,9 @@ impl DecoderState {
|
||||
// frame's own metadata (YUVJ sources are full range). The old path
|
||||
// forced MPEG/limited for everything, crushing full-range screen
|
||||
// captures and JPEG-derived footage.
|
||||
let full_range = if force_range == OAKCOMMON_COLOR_RANGE_FULL {
|
||||
let full_range = if force_range == oak_core_COLOR_RANGE_FULL {
|
||||
true
|
||||
} else if force_range == OAKCOMMON_COLOR_RANGE_LIMITED {
|
||||
} else if force_range == oak_core_COLOR_RANGE_LIMITED {
|
||||
false
|
||||
} else {
|
||||
yuvj_full || raw_range == AVCOL_RANGE_JPEG
|
||||
@@ -1949,7 +1949,7 @@ fn convert_rgba8_to_f32(data: &[u8], w: u32, h: u32, stride: usize) -> Vec<u8> {
|
||||
/// colorspace tables and full ranges on both sides, so no matrix and no
|
||||
/// range recompression was applied. 10/12-bit sources arrive left-shifted
|
||||
/// to 16-bit (code << 6 / code << 4) — exactly the code-value scale
|
||||
/// [`oak_common::colormath::yuv444p16_to_rgb_f32`] expects.
|
||||
/// [`oak_core::colormath::yuv444p16_to_rgb_f32`] expects.
|
||||
fn convert_yuv444p16_to_rgba_f32(
|
||||
out: &ffmpeg::frame::Video,
|
||||
w: u32,
|
||||
@@ -1958,7 +1958,7 @@ fn convert_yuv444p16_to_rgba_f32(
|
||||
full_range: bool,
|
||||
) -> Vec<u8> {
|
||||
let mut rgba = vec![0.0f32; (w as usize) * (h as usize) * 4];
|
||||
oak_common::colormath::yuv444p16_to_rgb_f32(
|
||||
oak_core::colormath::yuv444p16_to_rgb_f32(
|
||||
out.data(0),
|
||||
out.stride(0),
|
||||
out.data(1),
|
||||
@@ -2111,9 +2111,9 @@ fn probe_file(filename: &str, cancelled: Option<&CancelAtom>) -> Option<FootageD
|
||||
vp.set_color_primaries((*raw).color_primaries as i32);
|
||||
vp.set_color_transfer((*raw).color_trc as i32);
|
||||
vp.set_color_range(if (*raw).color_range as i32 == AVCOL_RANGE_JPEG {
|
||||
oak_common::videoparams::ColorRange::Full
|
||||
oak_core::videoparams::ColorRange::Full
|
||||
} else {
|
||||
oak_common::videoparams::ColorRange::Limited
|
||||
oak_core::videoparams::ColorRange::Limited
|
||||
});
|
||||
}
|
||||
desc.push_stream(StreamEntry::Video(vp));
|
||||
@@ -3165,7 +3165,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn yuv_matrix_mapping_is_strict() {
|
||||
use oak_common::colormath::YuvMatrix;
|
||||
use oak_core::colormath::YuvMatrix;
|
||||
assert_eq!(yuv_matrix_for(AVCOL_SPC_BT709, 1920, 1080), YuvMatrix::Bt709);
|
||||
assert_eq!(yuv_matrix_for(AVCOL_SPC_BT470BG, 640, 480), YuvMatrix::Bt601);
|
||||
assert_eq!(yuv_matrix_for(AVCOL_SPC_SMPTE170M, 1920, 1080), YuvMatrix::Bt601);
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
//!
|
||||
//! Mirrors `src/codec/src/footagedescription.h`. A value type describing
|
||||
//! the streams a `Decoder::probe()` found in a file. Video and subtitle
|
||||
//! streams are stored as oakcommon by-value handles; audio streams as
|
||||
//! streams are stored as oak_core by-value handles; audio streams as
|
||||
//! `oak_core::TimeRangeList`/raw audio params. The original's
|
||||
//! `Track::Type` mapping and XML load/save are intentionally not reproduced
|
||||
//! (NOTES.md §4) — use [`FootageDescription::stream_is_video`] etc.
|
||||
|
||||
use oak_common::subtitleparams::SubtitleParams;
|
||||
use oak_common::videoparams::VideoParams;
|
||||
use oak_core::subtitleparams::SubtitleParams;
|
||||
use oak_core::videoparams::VideoParams;
|
||||
use oak_core::{Rational, TimeRange};
|
||||
|
||||
use crate::audioparams::AudioParams;
|
||||
@@ -214,14 +214,14 @@ mod tests {
|
||||
|
||||
fn video_params(index: i32) -> VideoParams {
|
||||
let mut vp = VideoParams::new_basic(
|
||||
1920,
|
||||
1080,
|
||||
oak_common::ocioutils::PixelFormat::from_code(0),
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1920,
|
||||
1080,
|
||||
oak_core::ocioutils::PixelFormat::from_code(0),
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
vp.set_stream_index(index);
|
||||
vp
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
//! `olive::Frame` — a CPU pixel buffer plus a [`VideoParams`] value.
|
||||
//!
|
||||
//! Mirrors `src/codec/src/frame.h`. The params are held as an oakcommon
|
||||
//! Mirrors `src/codec/src/frame.h`. The params are held as an oak_core
|
||||
//! [`VideoParams`] value (single-lib unification; the former refcounted
|
||||
//! oakcommon handle is gone, so copies are plain clones); the pixel data
|
||||
//! oak_core handle is gone, so copies are plain clones); the pixel data
|
||||
//! itself is a plain `Vec<u8>`. Line-size and pixel-format math lives
|
||||
//! here.
|
||||
|
||||
use oak_common::videoparams::VideoParams;
|
||||
use oak_core::videoparams::VideoParams;
|
||||
use oak_core::{PixelFormat, Rational};
|
||||
|
||||
/// Number of channels in the internal RGBA pipeline layout
|
||||
@@ -331,7 +331,7 @@ impl Frame {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
|
||||
|
||||
fn frame(w: i32, h: i32) -> Frame {
|
||||
let params = VideoParams::new_basic(w, h, OakPixelFormat::from_code(0), 4, 1, 1, 0, 1);
|
||||
@@ -422,7 +422,7 @@ mod tests {
|
||||
#[cfg(test)]
|
||||
mod tests_extra {
|
||||
use super::*;
|
||||
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
|
||||
|
||||
fn frame(w: i32, h: i32) -> Frame {
|
||||
let params = VideoParams::new_basic(w, h, OakPixelFormat::from_code(0), 4, 1, 1, 0, 1);
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use oak_common::videoparams::VideoParams;
|
||||
use oak_core::videoparams::VideoParams;
|
||||
use crate::frame::Frame;
|
||||
|
||||
/// `olive::FrameManager`: singleton frame pool with background GC.
|
||||
@@ -148,7 +148,7 @@ fn frame_matches(frame: &Frame, params: &VideoParams) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
|
||||
|
||||
fn test_params(w: i32, h: i32) -> VideoParams {
|
||||
VideoParams::new_basic(w, h, OakPixelFormat::from_code(0), 4, 1, 1, 0, 1)
|
||||
|
||||
@@ -106,7 +106,7 @@ pub fn hardware_decoding_enabled() -> bool {
|
||||
if let Ok(v) = std::env::var("OAK_HWACCEL") {
|
||||
return v != "0";
|
||||
}
|
||||
match oak_common::configstore::ConfigStore::instance()
|
||||
match oak_core::configstore::ConfigStore::instance()
|
||||
.get(None, CONFIG_KEY_HARDWARE_DECODING)
|
||||
{
|
||||
Ok(value) => value != "false",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//!
|
||||
//! Mirrors `src/codec/src/oiio/{oiiodecoder,oiioencoder}.{h,cpp}`. OIIO
|
||||
//! frame conversion goes through the local
|
||||
//! [`crate::oiioframebridge`] helpers plus oakcommon's OIIO mapping
|
||||
//! [`crate::oiioframebridge`] helpers plus oak_core's OIIO mapping
|
||||
//! functions.
|
||||
//!
|
||||
//! The OIIO dylib (`liboakoiio`) is not linked into this build, so every
|
||||
@@ -58,9 +58,9 @@ impl Decoder for OIIODecoder {
|
||||
}
|
||||
|
||||
fn probe(
|
||||
&self,
|
||||
_filename: &str,
|
||||
_cancelled: Option<&oak_common::cancelatom::CancelAtom>,
|
||||
&self,
|
||||
_filename: &str,
|
||||
_cancelled: Option<&oak_core::cancelatom::CancelAtom>,
|
||||
) -> Option<crate::footagedescription::FootageDescription> {
|
||||
// Probing is a dylib operation; without it we cannot report anything.
|
||||
None
|
||||
@@ -113,12 +113,12 @@ impl Decoder for OIIODecoder {
|
||||
}
|
||||
|
||||
fn conform_audio(
|
||||
&self,
|
||||
_output_filenames: &[String],
|
||||
_sample_rate: i32,
|
||||
_channel_layout: u64,
|
||||
_sample_format: i32,
|
||||
_cancelled: Option<&oak_common::cancelatom::CancelAtom>,
|
||||
&self,
|
||||
_output_filenames: &[String],
|
||||
_sample_rate: i32,
|
||||
_channel_layout: u64,
|
||||
_sample_format: i32,
|
||||
_cancelled: Option<&oak_core::cancelatom::CancelAtom>,
|
||||
) -> crate::error::Result<()> {
|
||||
Err(crate::error::Error::Failed(Self::NOT_AVAILABLE.to_string()))
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
//! frame <-> pixel-buffer conversion.
|
||||
//!
|
||||
//! Mirrors `src/codec/src/oiioframebridge.{h,cpp}`. These are internal C++
|
||||
//! functions that moved into codec from oakcommon (NOTES.md §oakcommon侧修复);
|
||||
//! oakcommon keeps its OIIO mapping functions; the frame conversion itself
|
||||
//! functions that moved into codec from oak_core (NOTES.md §oak_core侧修复);
|
||||
//! oak_core keeps its OIIO mapping functions; the frame conversion itself
|
||||
//! lives here.
|
||||
//!
|
||||
//! The C++ bridge copies pixels through the live OpenImageIO `ImageBuf`
|
||||
@@ -30,8 +30,8 @@
|
||||
//! timestamp and time base alongside the raw pixel rows, so a buffer can be
|
||||
//! turned back into an equivalent [`Frame`] without any external state.
|
||||
|
||||
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_common::videoparams::VideoParams;
|
||||
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_core::videoparams::VideoParams;
|
||||
use crate::frame::Frame;
|
||||
use oak_core::Rational;
|
||||
use std::ffi::c_int;
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
//! Mirrors `src/codec/src/proxymanager.h`. Stateless (NOTES.md): actual
|
||||
//! transcodes are delegated to the global task submit callback
|
||||
//! ([`crate::task`]); with no registrar, `get_or_start` reports the proxy
|
||||
//! as missing. `proxy_params_from_config` reads the oakcommon config store
|
||||
//! as missing. `proxy_params_from_config` reads the oak_core config store
|
||||
//! with the compiled-in defaults as fallback (1280x720 / divider 1 / crf 23
|
||||
//! / "mp4" / "veryfast" / audio included).
|
||||
|
||||
use oak_common::configstore::ConfigStore;
|
||||
use oak_common::filefunctions::FileFunctions;
|
||||
use oak_core::configstore::ConfigStore;
|
||||
use oak_core::filefunctions::FileFunctions;
|
||||
use std::path::Path;
|
||||
|
||||
/// Proxy state of a proxy file on disk.
|
||||
@@ -127,13 +127,13 @@ impl ProxyManager {
|
||||
ProxyParams::default()
|
||||
}
|
||||
|
||||
/// Proxy parameters read from the oakcommon config, with the compiled-in
|
||||
/// Proxy parameters read from the oak_core config, with the compiled-in
|
||||
/// defaults as fallback.
|
||||
///
|
||||
/// # CPP-PARITY
|
||||
/// `src/codec/src/proxymanager.h` `proxy_params_from_config` — reads
|
||||
/// ProxyWidth/ProxyHeight/ProxyDivider/ProxyCRF/ProxyPreset/
|
||||
/// ProxyIncludeAudio via `oakcommon_config_*`.
|
||||
/// ProxyIncludeAudio via `oak_core_config_*`.
|
||||
pub fn proxy_params_from_config() -> ProxyParams {
|
||||
let mut p = ProxyParams::default();
|
||||
p.width = config_get_int("ProxyWidth", p.width);
|
||||
@@ -387,17 +387,17 @@ fn cstr_slice(a: &[u8; 32]) -> &str {
|
||||
std::str::from_utf8(&a[..end]).unwrap_or("")
|
||||
}
|
||||
|
||||
/// `oakcommon_config_get_int` wrapper (null group).
|
||||
/// `oak_core_config_get_int` wrapper (null group).
|
||||
fn config_get_int(key: &str, default: i32) -> i32 {
|
||||
ConfigStore::instance().get_int(None, key, default)
|
||||
}
|
||||
|
||||
/// `oakcommon_config_get_bool` wrapper (null group).
|
||||
/// `oak_core_config_get_bool` wrapper (null group).
|
||||
fn config_get_bool(key: &str, default: i32) -> i32 {
|
||||
ConfigStore::instance().get_bool(None, key, default)
|
||||
}
|
||||
|
||||
/// `oakcommon_config_get` string read; `None` when the stored value is
|
||||
/// `oak_core_config_get` string read; `None` when the stored value is
|
||||
/// empty or absent.
|
||||
fn config_get_str(key: &str) -> Option<String> {
|
||||
match ConfigStore::instance().get(None, key) {
|
||||
@@ -406,14 +406,14 @@ fn config_get_str(key: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `oakcommon_filefunctions_get_unique_file_identifier` wrapper.
|
||||
/// `oak_core_filefunctions_get_unique_file_identifier` wrapper.
|
||||
fn unique_file_identifier(filename: &str) -> String {
|
||||
FileFunctions::new()
|
||||
.get_unique_file_identifier(filename)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// `oakcommon_filefunctions_get_application_path` read.
|
||||
/// `oak_core_filefunctions_get_application_path` read.
|
||||
fn application_path() -> String {
|
||||
FileFunctions::new()
|
||||
.get_application_path()
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
//! compiled without `#[cfg(test)]` and cannot resolve those symbols; see
|
||||
//! `tests/ffi_contract_test.rs`).
|
||||
|
||||
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_common::videoparams::VideoParams;
|
||||
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_core::videoparams::VideoParams;
|
||||
use crate::decoder::{
|
||||
CodecStream, Decoder, RenderMode, RetrieveAudioStatus, RetrieveVideoParams,
|
||||
K_COLOR_RANGE_DEFAULT,
|
||||
@@ -503,7 +503,7 @@ static HW_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
#[test]
|
||||
fn hardware_decode_matches_software_decode() {
|
||||
let _guard = HW_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let config = oak_common::configstore::ConfigStore::instance();
|
||||
let config = oak_core::configstore::ConfigStore::instance();
|
||||
let key = crate::hwdecode::CONFIG_KEY_HARDWARE_DECODING;
|
||||
|
||||
let decode_at = |time: i64| -> (Option<String>, Arc<Frame>) {
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_common::videoparams::VideoParams;
|
||||
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
|
||||
use oak_core::videoparams::VideoParams;
|
||||
use oak_core::{PixelFormat, Rational, SampleFormat};
|
||||
|
||||
use crate::encodingparams::EncodingParams;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
|
||||
# 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/>.
|
||||
#
|
||||
# `ocio-sys` (the FFI layer behind `ocio-rs`) does not probe the system for
|
||||
# OpenColorIO on its own: with no configuration it builds a *stub* bridge
|
||||
# whose calls all fail. These environment variables make it link the real
|
||||
# Homebrew OpenColorIO dylib:
|
||||
#
|
||||
# OCIO_RS_ENABLE_REAL - opt into the real (non-stub) bridge
|
||||
# OCIO_INSTALL_DIR - prefix whose include/ and lib/ hold OpenColorIO
|
||||
# OCIO_RS_LINK - Homebrew ships a dylib, so link dynamically
|
||||
#
|
||||
# See README.md "Build & test" for the bundled alternative.
|
||||
|
||||
[env]
|
||||
OCIO_RS_ENABLE_REAL = "1"
|
||||
OCIO_INSTALL_DIR = "/opt/homebrew"
|
||||
OCIO_RS_LINK = "dynamic"
|
||||
Generated
-332
@@ -1,332 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "fax"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oakcommon"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"oakcore-rs",
|
||||
"ocio-rs",
|
||||
"quick-xml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oakcore-rs"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "ocio-rs"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3492534019b59e29dba06014f907dd12824537ed4d293d4108c4bfc669de7fd"
|
||||
dependencies = [
|
||||
"ocio-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ocio-sys"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e63251d72d848de5eda39d59cd6490260cf031738ebd518ea37d76b5aae614ec"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"flate2",
|
||||
"half",
|
||||
"quick-error",
|
||||
"weezl",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "oak-common"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
description = "Oak Video Editor shared utilities (Rust)"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[features]
|
||||
# Compile the in-crate ffmpeg_bridge mock (fb_find_best_pix_fmt_of_list stub)
|
||||
# so the integration tests can link without libffmpeg_bridge. Without this
|
||||
# flag, `cargo test --lib` still works (the stub is active under `cfg(test)`),
|
||||
# but the C ABI wrapper tests in tests/ffi_ffmpegutils.rs need it. This
|
||||
# mirrors the `test-stubs` convention of oakplugin / oaktimeline.
|
||||
test-stubs = []
|
||||
|
||||
[dependencies]
|
||||
oak-core = { path = "../oak-core" }
|
||||
quick-xml = "0.41.0"
|
||||
log = "0.4"
|
||||
# TOML persistence for the application config (configstore.rs). Already in
|
||||
# Cargo.lock as a transitive dependency (0.8.23); promoted to a direct one.
|
||||
toml = "0.8"
|
||||
# OpenColorIO bindings (crates.io `ocio-rs`, BSD-3-Clause). ocioutils.rs maps
|
||||
# PixelFormat to the real `ocio_rs::BitDepth` enum and wraps
|
||||
# `ocio_rs::Config`/`CPUProcessor` for config loading and RGBA transforms.
|
||||
# Rationale registered in README.md. The `bundled` feature builds the vendored
|
||||
# OpenColorIO from source — all platforms build the SAME OCIO version (distro
|
||||
# packages are too old for the bridge's API floor, e.g. Ubuntu 24.04's 2.1).
|
||||
# An explicit OCIO_INSTALL_DIR still wins over the vendored build when set.
|
||||
ocio-rs = { version = "0.2", features = ["bundled"] }
|
||||
# Pure-Rust image I/O (crates.io `image`, MIT OR Apache-2.0); default features
|
||||
# off, TIFF enabled — the only format current callers need. oiioutils.rs
|
||||
# derives per-channel bit depths from its color-type tables and does float
|
||||
# image I/O through it. See README.md.
|
||||
image = { version = "0.25", default-features = false, features = ["tiff"] }
|
||||
thiserror = "2"
|
||||
@@ -1,104 +0,0 @@
|
||||
# oakcommon Rust crate
|
||||
|
||||
> Status: **implemented**. All `include/common/*.h` contracts are
|
||||
> implemented in Rust and covered by unit + C ABI integration tests
|
||||
> (see [Testing](#testing)).
|
||||
|
||||
## Scope
|
||||
|
||||
Replaces the C++ oakcommon module (`src/common/src`): config store,
|
||||
command-line parser, XML stream reader/writer, file functions,
|
||||
debug/logging, ffmpeg/OCIO/OIIO utility queries, video/subtitle
|
||||
params, color transform, misc utilities. Pure leaf module — depends
|
||||
only on `oakcore-rs`, `quick-xml`, `log`, `ocio-rs`, `image`, and
|
||||
system libraries.
|
||||
|
||||
Public contract: `include/common/*.h` (18 headers) — frozen,
|
||||
implemented verbatim by `src/ffi.rs`.
|
||||
|
||||
## Third-party crates
|
||||
|
||||
| Crate | Version | License | Status |
|
||||
|---|---|---|---|
|
||||
| `quick-xml` | 0.41 | MIT | adopted — XML reader/writer (`xmlutils.rs`) |
|
||||
| `log` | 0.4 | MIT / Apache-2.0 | adopted — logging facade (`debug.rs`); the stderr sink is retained as the always-available backend |
|
||||
| `ocio-rs` | 0.2.1 | BSD-3-Clause | adopted — real OpenColorIO access for `ocioutils.rs` (`OcioConfig`/`OcioProcessor`, `BitDepth` mapping). Pulled in via `ocio-sys` built with `OCIO_RS_ENABLE_REAL=1` against the Homebrew OCIO install (see `.cargo/config.toml`) |
|
||||
| `image` | 0.25 | MIT / Apache-2.0 | adopted — per-channel bit-depth tables and 32-bit float TIFF I/O for `oiioutils.rs` (`image_color_type_for`/`bits_per_channel`, `F32Image`). Default features off, `tiff` only |
|
||||
| `oakcore-rs` | path | GPL-3.0 | adopted — `Rational::from_double` (the C++ `Rational::from_double` port of FFmpeg's `av_d2q`) for `get_pixel_aspect_ratio`; a hand-written port kept in the leaf crate instead of pulling in `ffmpeg-next` |
|
||||
| `serde_json` | — | MIT / Apache-2.0 | **evaluated, not adopted** — the ConfigStore format is INI (QSettings-style `key=value` with `[group]` sections, `%g` doubles), not JSON; switching would break C++/Rust file interop |
|
||||
| `pico-args` / `clap` | — | MIT / Apache-2.0 | **evaluated, not adopted** — `commandlineparser.rs` must keep exact C++ quirks (case-insensitive names, first-match-wins, last-value-wins, argv[0] skipping, truncating getter copies, borrowed C ABI handles) that a generic parser cannot express without changing the C ABI shape |
|
||||
|
||||
## Architectural decisions
|
||||
|
||||
1. **Leaf module discipline**: no `bridge/` to other oak modules.
|
||||
FFmpeg is reached through `ffmpeg_bridge`'s C ABI (narrow
|
||||
`extern "C"` blocks in `ffmpegutils.rs`); OCIO and OIIO access is
|
||||
pure Rust via the crates.io bindings listed in the table above
|
||||
(`ocioutils.rs`, `oiioutils.rs`).
|
||||
2. **`olive::Variant` disappears**: it exists in C++ only because
|
||||
QVariant left a hole. Rust modules use closed enums; nothing in
|
||||
common needs it. `variant.{h,cpp}` (C++) is retired when all
|
||||
consumers are Rust.
|
||||
3. **XML**: `XmlStreamReader/Writer` keep the C++ streaming API shape
|
||||
(the C ABI is built on it), implemented over quick-xml — behavior
|
||||
(attribute order, error semantics) pinned by tests against the C++
|
||||
oracle.
|
||||
4. **Config**: the ConfigStore is **INI-backed** (QSettings-style
|
||||
`key=value` with `[group]` sections, `;`/`#` comments, `%g`
|
||||
double formatting), keeping the exact C++ file format and lookup
|
||||
order (user config → app defaults). It is *not* JSON — an earlier
|
||||
draft described it as JSON-backed, which was wrong; that claim was
|
||||
removed from this document.
|
||||
5. **Logging**: `debug.rs` provides the leveled logger
|
||||
(qWarning/qDebug/qCritical/qInfo replacement) with a printf-style C
|
||||
ABI. Every record is written to stderr (the C++ `stderr_sink`
|
||||
parity) and additionally forwarded to the `log` crate's global
|
||||
logger when the host has installed one. oakcommon never installs a
|
||||
global logger itself — the C ABI is loaded into hosts that set
|
||||
their own, and `log::set_logger` can only be called once per
|
||||
process.
|
||||
6. **OCIO / OIIO / FFmpeg**: `ocioutils.rs` talks to real OpenColorIO
|
||||
through the crates.io `ocio-rs` bindings (`OcioConfig`/`OcioProcessor`,
|
||||
`BitDepth` enum — no hand-written constant tables); `oiioutils.rs`
|
||||
derives its OIIO base-type mapping from the `image` crate's color-type
|
||||
tables (HALF pinned from the frozen OIIO table — `image` has no f16
|
||||
sample type) and converts aspect ratios with
|
||||
`oakcore_rs::Rational::from_double` — a hand-written port of FFmpeg's
|
||||
`av_d2q` matching the C++ `Rational::from_double` exactly, kept in the
|
||||
leaf crate rather than adding `ffmpeg-next`/`ffmpeg-sys-next` (narrow
|
||||
extern C discipline). 32-bit float image I/O (`F32Image`) is pure
|
||||
`image`. All adopted crates are registered in the table above.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
src/
|
||||
lib.rs crate doc + module map
|
||||
error.rs error codes (include/common/error.h)
|
||||
handle.rs refcounted-handle scaffolding
|
||||
configstore.rs INI config (include/common/config.h)
|
||||
commandlineparser.rs
|
||||
xmlutils.rs streaming XML reader/writer
|
||||
filefunctions.rs file/dir helpers
|
||||
debug.rs leveled logging
|
||||
ffmpegutils.rs pixfmt/samplefmt mapping (via ffmpeg_bridge C ABI)
|
||||
ocioutils.rs OCIO queries
|
||||
oiioutils.rs OIIO queries
|
||||
videoparams.rs VideoParams plain data + queries
|
||||
subtitleparams.rs SubtitleParams
|
||||
colortransform.rs ColorTransform plain data
|
||||
miscutils.rs misc (loop mode, drop behavior, power, current…)
|
||||
ffi.rs export layer (one submodule per public header)
|
||||
tests/ contract tests per module
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
`cargo test --release --features test-stubs` runs the full suite:
|
||||
unit tests, C ABI contract tests, and the integration tests (incl.
|
||||
`tests/ffi_ffmpegutils.rs`). The `test-stubs` feature substitutes the
|
||||
in-crate ffmpeg_bridge mock (`fb_find_best_pix_fmt_of_list` stub, see
|
||||
`src/ffmpegutils.rs`) so the C ABI tests link without
|
||||
libffmpeg_bridge; without the feature that symbol is imported from
|
||||
`ffmpeg_bridge` at link time. This mirrors the `test-stubs`
|
||||
convention of oakplugin / oaktimeline.
|
||||
@@ -1,189 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Error codes, mirroring `include/common/error.h`; project-wide
|
||||
//! -MMCCCC scheme (module 01), pass-through untranslated.
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Success.
|
||||
pub const OAKCOMMON_OK: i32 = 0;
|
||||
/// Empty handle or invalid argument.
|
||||
pub const OAKCOMMON_E_INVALID: i32 = -10001;
|
||||
/// Call not valid in the current state.
|
||||
pub const OAKCOMMON_E_STATE: i32 = -10002;
|
||||
/// The underlying operation failed.
|
||||
pub const OAKCOMMON_E_FAILED: i32 = -10003;
|
||||
/// Index out of range / entry not found.
|
||||
pub const OAKCOMMON_E_NOT_FOUND: i32 = -10004;
|
||||
/// Allocation failed.
|
||||
pub const OAKCOMMON_E_NOMEM: i32 = -10005;
|
||||
|
||||
/// Crate-internal result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Crate-internal error.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Empty handle or invalid argument.
|
||||
#[error("common: empty handle or invalid argument")]
|
||||
Invalid,
|
||||
/// Wrong state.
|
||||
#[error("common: call not valid in current state")]
|
||||
State,
|
||||
/// Operation failed (context string is log-only).
|
||||
#[error("common: operation failed: {0}")]
|
||||
Failed(String),
|
||||
/// Not found.
|
||||
#[error("common: entry not found")]
|
||||
NotFound,
|
||||
/// Out of memory.
|
||||
#[error("common: allocation failed")]
|
||||
NoMem,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Map to the public error code.
|
||||
pub fn code(&self) -> i32 {
|
||||
match self {
|
||||
Error::Invalid => OAKCOMMON_E_INVALID,
|
||||
Error::State => OAKCOMMON_E_STATE,
|
||||
Error::Failed(_) => OAKCOMMON_E_FAILED,
|
||||
Error::NotFound => OAKCOMMON_E_NOT_FOUND,
|
||||
Error::NoMem => OAKCOMMON_E_NOMEM,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Error::Failed`] carrying a context message (log-only).
|
||||
///
|
||||
/// Convenience constructor used by the OCIO/`image` wrappers in
|
||||
/// `ocioutils.rs` / `oiioutils.rs` when an underlying library call fails.
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Error::Failed(message.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ocio_rs::OcioError> for Error {
|
||||
fn from(e: ocio_rs::OcioError) -> Self {
|
||||
// The Display impl of `OcioError` always produces a non-empty message
|
||||
// (every variant carries text or a fixed phrase); it becomes the
|
||||
// log-only context of `Error::Failed`.
|
||||
Error::Failed(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn public_codes_match_header_values() {
|
||||
// Load-bearing values from include/common/error.h (module 01).
|
||||
assert_eq!(OAKCOMMON_OK, 0);
|
||||
assert_eq!(OAKCOMMON_E_INVALID, -10001);
|
||||
assert_eq!(OAKCOMMON_E_STATE, -10002);
|
||||
assert_eq!(OAKCOMMON_E_FAILED, -10003);
|
||||
assert_eq!(OAKCOMMON_E_NOT_FOUND, -10004);
|
||||
assert_eq!(OAKCOMMON_E_NOMEM, -10005);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_code_maps_each_variant() {
|
||||
assert_eq!(Error::Invalid.code(), OAKCOMMON_E_INVALID);
|
||||
assert_eq!(Error::State.code(), OAKCOMMON_E_STATE);
|
||||
assert_eq!(Error::Failed("boom".to_string()).code(), OAKCOMMON_E_FAILED);
|
||||
assert_eq!(Error::NotFound.code(), OAKCOMMON_E_NOT_FOUND);
|
||||
assert_eq!(Error::NoMem.code(), OAKCOMMON_E_NOMEM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_codes_are_all_distinct() {
|
||||
let codes = [
|
||||
Error::Invalid.code(),
|
||||
Error::State.code(),
|
||||
Error::Failed(String::new()).code(),
|
||||
Error::NotFound.code(),
|
||||
Error::NoMem.code(),
|
||||
];
|
||||
for (i, a) in codes.iter().enumerate() {
|
||||
for b in &codes[i + 1..] {
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
// Errors are strictly negative; OK stays zero.
|
||||
assert!(*a < 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_message_is_preserved_in_debug() {
|
||||
// The context string is log-only but must survive to the log.
|
||||
let e = Error::Failed("context info".to_string());
|
||||
let dbg = format!("{e:?}");
|
||||
assert!(dbg.contains("context info"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_alias_round_trips_ok_and_err() {
|
||||
let ok: Result<i32> = Ok(7);
|
||||
assert_eq!(ok.unwrap(), 7);
|
||||
let err: Result<i32> = Err(Error::NotFound);
|
||||
assert_eq!(err.unwrap_err().code(), OAKCOMMON_E_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_creates_failed_with_message() {
|
||||
let e = Error::new("context info");
|
||||
assert!(matches!(e, Error::Failed(_)));
|
||||
assert_eq!(e.code(), OAKCOMMON_E_FAILED);
|
||||
assert!(format!("{e:?}").contains("context info"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ocio_error_converts_to_failed() {
|
||||
let e = Error::from(ocio_rs::OcioError::InvalidInput(
|
||||
"bad colorspace".to_string(),
|
||||
));
|
||||
assert!(matches!(e, Error::Failed(_)));
|
||||
assert_eq!(e.code(), OAKCOMMON_E_FAILED);
|
||||
assert!(format!("{e:?}").contains("bad colorspace"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_is_non_empty_for_each_variant() {
|
||||
let variants = [
|
||||
Error::Invalid,
|
||||
Error::State,
|
||||
Error::Failed("context".to_string()),
|
||||
Error::NotFound,
|
||||
Error::NoMem,
|
||||
];
|
||||
for e in &variants {
|
||||
assert!(!e.to_string().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_display_includes_context() {
|
||||
let e = Error::Failed("context info".to_string());
|
||||
assert!(e.to_string().contains("context info"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_is_object_safe() {
|
||||
let e: Box<dyn std::error::Error> = Box::new(Error::NoMem);
|
||||
assert!(!e.to_string().is_empty());
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// 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 — shared utilities (Rust). Leaf module.
|
||||
//!
|
||||
//! Implements `include/common/*.h` verbatim. See README.md.
|
||||
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod cancelatom;
|
||||
pub mod colormath;
|
||||
pub mod colortransform;
|
||||
pub mod commandlineparser;
|
||||
pub mod configstore;
|
||||
pub mod debug;
|
||||
pub mod displayicc;
|
||||
pub mod error;
|
||||
pub mod ffmpegutils;
|
||||
pub mod filefunctions;
|
||||
pub mod miscutils;
|
||||
pub mod ocioutils;
|
||||
pub mod oiioutils;
|
||||
pub mod qtutils;
|
||||
pub mod subtitleparams;
|
||||
pub mod videoparams;
|
||||
pub mod xmlutils;
|
||||
|
||||
/// Test-only helpers shared across unit-test modules.
|
||||
///
|
||||
/// Several domain test modules (e.g. `configstore`, `filefunctions`) mutate
|
||||
/// process-global state — notably the `OAK_CONFIG_DIR` environment variable
|
||||
/// and shared temp paths — while exercising configuration-location logic.
|
||||
/// Rust runs tests in parallel, so all such tests must serialize on a single
|
||||
/// process-wide lock to avoid racing each other across module boundaries.
|
||||
#[cfg(test)]
|
||||
#[doc(hidden)]
|
||||
pub mod test_support {
|
||||
use std::sync::Mutex;
|
||||
|
||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Process-wide lock guarding tests that mutate global config/env state.
|
||||
///
|
||||
/// Hold this for the duration of any test (or helper) that sets/removes
|
||||
/// `OAK_CONFIG_DIR` or touches the shared configuration temp path.
|
||||
pub fn env_lock() -> &'static Mutex<()> {
|
||||
&ENV_LOCK
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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 contract tests. These assert the load-bearing constants, enum
|
||||
//! discriminants, and handle layout that the C headers rely on. They do
|
||||
//! NOT call any crate function, only public constants/types: these checks
|
||||
//! are compile-time/constant-only and document the ABI surface that must
|
||||
//! not drift from the headers.
|
||||
|
||||
use std::mem::{align_of, size_of};
|
||||
|
||||
use oak_common::error::{
|
||||
OAKCOMMON_E_FAILED, OAKCOMMON_E_INVALID, OAKCOMMON_E_NOMEM, OAKCOMMON_E_NOT_FOUND,
|
||||
OAKCOMMON_E_STATE, OAKCOMMON_OK,
|
||||
};
|
||||
use oak_common::ffmpegutils::{RGBA_CHANNEL_COUNT, RGB_CHANNEL_COUNT};
|
||||
use oak_common::miscutils::{DropWorkflowBehavior, LoopMode, DECIBEL_MINIMUM};
|
||||
use oak_common::ocioutils::PixelFormat;
|
||||
use oak_common::videoparams::{ColorRange, Interlacing, VideoType};
|
||||
|
||||
/// Error codes must match `include/common/error.h`.
|
||||
#[test]
|
||||
fn error_codes_match_header() {
|
||||
assert_eq!(OAKCOMMON_OK, 0);
|
||||
assert_eq!(OAKCOMMON_E_INVALID, -10001);
|
||||
assert_eq!(OAKCOMMON_E_STATE, -10002);
|
||||
assert_eq!(OAKCOMMON_E_FAILED, -10003);
|
||||
assert_eq!(OAKCOMMON_E_NOT_FOUND, -10004);
|
||||
assert_eq!(OAKCOMMON_E_NOMEM, -10005);
|
||||
}
|
||||
|
||||
/// Handle ABI version must match `include/common/handle.h`.
|
||||
|
||||
/// The handle struct must be a plain `{ctx, addref, release, abi_version}`
|
||||
/// `#[repr(C)]` record: 3 pointers + a u32, padded to pointer alignment.
|
||||
|
||||
/// Pixel-format codes must match `olive::core::PixelFormat`.
|
||||
#[test]
|
||||
fn pixel_format_discriminants() {
|
||||
assert_eq!(PixelFormat::Invalid as i32, -1);
|
||||
assert_eq!(PixelFormat::U8 as i32, 0);
|
||||
assert_eq!(PixelFormat::U10 as i32, 1);
|
||||
assert_eq!(PixelFormat::U16 as i32, 2);
|
||||
assert_eq!(PixelFormat::F16 as i32, 3);
|
||||
assert_eq!(PixelFormat::F32 as i32, 4);
|
||||
assert_eq!(PixelFormat::Count as i32, 5);
|
||||
}
|
||||
|
||||
/// Decibel minimum must match `include/common/miscutils.h`.
|
||||
#[test]
|
||||
fn decibel_minimum() {
|
||||
assert_eq!(DECIBEL_MINIMUM, -200.0);
|
||||
}
|
||||
|
||||
/// Channel-count constants must match `include/common/ffmpegutils.h`.
|
||||
#[test]
|
||||
fn channel_count_constants() {
|
||||
assert_eq!(RGB_CHANNEL_COUNT, 3);
|
||||
assert_eq!(RGBA_CHANNEL_COUNT, 4);
|
||||
}
|
||||
|
||||
/// Loop-mode codes must match `include/common/loopmode.h`.
|
||||
#[test]
|
||||
fn loop_mode_discriminants() {
|
||||
assert_eq!(LoopMode::Off as i32, 0);
|
||||
assert_eq!(LoopMode::Loop as i32, 1);
|
||||
assert_eq!(LoopMode::Clamp as i32, 2);
|
||||
}
|
||||
|
||||
/// Drop-workflow behavior codes must match `include/common/dropworkflowbehavior.h`.
|
||||
#[test]
|
||||
fn drop_workflow_behavior_discriminants() {
|
||||
assert_eq!(DropWorkflowBehavior::Ask as i32, 0);
|
||||
assert_eq!(DropWorkflowBehavior::Auto as i32, 1);
|
||||
assert_eq!(DropWorkflowBehavior::Manual as i32, 2);
|
||||
assert_eq!(DropWorkflowBehavior::Disable as i32, 3);
|
||||
}
|
||||
|
||||
/// Interlacing codes must match `include/common/videoparams.h`.
|
||||
#[test]
|
||||
fn interlacing_discriminants() {
|
||||
assert_eq!(Interlacing::None as i32, 0);
|
||||
assert_eq!(Interlacing::TopFirst as i32, 1);
|
||||
assert_eq!(Interlacing::BottomFirst as i32, 2);
|
||||
}
|
||||
|
||||
/// Video-type codes must match `include/common/videoparams.h`.
|
||||
#[test]
|
||||
fn video_type_discriminants() {
|
||||
assert_eq!(VideoType::Video as i32, 0);
|
||||
assert_eq!(VideoType::Still as i32, 1);
|
||||
assert_eq!(VideoType::ImageSequence as i32, 2);
|
||||
}
|
||||
|
||||
/// Color-range codes must match `include/common/videoparams.h`.
|
||||
#[test]
|
||||
fn color_range_discriminants() {
|
||||
assert_eq!(ColorRange::Limited as i32, 0);
|
||||
assert_eq!(ColorRange::Full as i32, 1);
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Real-library smoke tests for the OCIO/image-backed utilities.
|
||||
//!
|
||||
//! These exercise the real OpenColorIO library through `ocio-rs`/`ocio-sys`
|
||||
//! (compiled with `OCIO_RS_ENABLE_REAL=1`, see `.cargo/config.toml`). Each
|
||||
//! OCIO test loads the project's own config at
|
||||
//! `engine/render/ocioconf/config.ocio` and sets the `OCIO` environment
|
||||
//! variable first (the library reads it at config-load time). The image tests
|
||||
//! round-trip a small float TIFF through a temp file via the `image` crate.
|
||||
|
||||
use oak_common::error::Error;
|
||||
use oak_common::ocioutils::OcioConfig;
|
||||
use oak_common::oiioutils::{read_image_f32, write_image_f32};
|
||||
|
||||
/// Path to the project's OCIO config, relative to this crate's manifest dir.
|
||||
fn config_path() -> String {
|
||||
let manifest = env!("CARGO_MANIFEST_DIR");
|
||||
// crates/oakcommon -> repository root
|
||||
format!("{}/../../engine/render/ocioconf/config.ocio", manifest)
|
||||
}
|
||||
|
||||
/// The `OCIO` env var is consumed by the library at config-load time; setting
|
||||
/// it here keeps the test hermetic regardless of the host environment.
|
||||
fn set_ocio_env() -> String {
|
||||
let path = config_path();
|
||||
std::env::set_var("OCIO", &path);
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
|
||||
fn ocio_load_and_list_colorspaces() {
|
||||
let path = set_ocio_env();
|
||||
let config = OcioConfig::from_file(&path).expect("config.ocio should load");
|
||||
let count = config.colorspace_count().expect("count should work");
|
||||
assert!(count > 0, "config should define at least one color space");
|
||||
|
||||
let colorspaces = config.colorspaces().expect("listing should work");
|
||||
assert_eq!(colorspaces.len(), count as usize);
|
||||
assert!(colorspaces.contains(&"Linear".to_string()));
|
||||
assert!(colorspaces.contains(&"sRGB OETF".to_string()));
|
||||
|
||||
eprintln!("color spaces ({}): {:?}", colorspaces.len(), colorspaces);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
|
||||
fn ocio_roles_and_canonical_names() {
|
||||
let path = set_ocio_env();
|
||||
let config = OcioConfig::from_file(&path).unwrap();
|
||||
|
||||
let roles = config.roles().unwrap();
|
||||
assert!(!roles.is_empty(), "config should define roles");
|
||||
eprintln!("roles: {:?}", roles);
|
||||
|
||||
assert!(
|
||||
config.has_role("scene_linear").unwrap(),
|
||||
"scene_linear role should exist"
|
||||
);
|
||||
assert!(config.has_role("default").unwrap());
|
||||
assert!(!config.has_role("no_such_role").unwrap());
|
||||
|
||||
// scene_linear maps to the Linear color space.
|
||||
assert_eq!(config.canonical_name("scene_linear").unwrap(), "Linear");
|
||||
// Role/name equivalence: role name resolves to its canonical color space.
|
||||
assert_eq!(config.canonical_name("Linear").unwrap(), "Linear");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
|
||||
fn ocio_displays_and_views() {
|
||||
let path = set_ocio_env();
|
||||
let config = OcioConfig::from_file(&path).unwrap();
|
||||
|
||||
let display = config.default_display().unwrap();
|
||||
assert_eq!(display, "sRGB");
|
||||
let view = config.default_view(&display).unwrap();
|
||||
assert_eq!(view, "sRGB OETF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
|
||||
fn ocio_processor_apply_rgba() {
|
||||
let path = set_ocio_env();
|
||||
let config = OcioConfig::from_file(&path).unwrap();
|
||||
|
||||
// Linear -> sRGB OETF: a mid-gray 0.18 (a common linear display-referred
|
||||
// midpoint) should map well above itself and stay finite.
|
||||
let processor = config.processor("Linear", "sRGB OETF").unwrap();
|
||||
let mut px = [0.18f32, 0.18f32, 0.18f32, 1.0f32];
|
||||
processor.apply_rgba(&mut px).unwrap();
|
||||
assert!(
|
||||
px[0] > 0.18f32,
|
||||
"sRGB OETF should lift 0.18 linear, got {}",
|
||||
px[0]
|
||||
);
|
||||
assert!(
|
||||
px[0] < 1.0f32 + 1e-6,
|
||||
"sRGB OETF output should be <= 1.0, got {}",
|
||||
px[0]
|
||||
);
|
||||
assert!(px.iter().all(|v| v.is_finite()));
|
||||
|
||||
// Display-referred path: scene_linear -> default sRGB view.
|
||||
let processor = config
|
||||
.display_processor("scene_linear", "sRGB", "sRGB OETF")
|
||||
.unwrap();
|
||||
let mut px = [0.18f32, 0.18f32, 0.18f32, 1.0f32];
|
||||
processor.apply_rgba(&mut px).unwrap();
|
||||
assert!(px.iter().all(|v| v.is_finite()));
|
||||
assert!(
|
||||
px[0] > 0.18f32,
|
||||
"display processor should also lift 0.18, got {}",
|
||||
px[0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
|
||||
fn ocio_error_paths() {
|
||||
let path = set_ocio_env();
|
||||
|
||||
// Nonexistent config file.
|
||||
let err = OcioConfig::from_file("/nonexistent/oakcommon-real-ocio.ocio").unwrap_err();
|
||||
eprintln!("nonexistent config error: {err:?}");
|
||||
assert!(matches!(err, Error::Failed(_)));
|
||||
|
||||
let config = OcioConfig::from_file(&path).unwrap();
|
||||
|
||||
// Unknown destination color space.
|
||||
let err = config
|
||||
.processor("Linear", "No Such Color Space")
|
||||
.unwrap_err();
|
||||
eprintln!("unknown colorspace error: {err:?}");
|
||||
assert!(matches!(err, Error::Failed(_)));
|
||||
|
||||
// Unknown display/view.
|
||||
let err = config
|
||||
.display_processor("Linear", "No Such Display", "No View")
|
||||
.unwrap_err();
|
||||
eprintln!("unknown display error: {err:?}");
|
||||
assert!(matches!(err, Error::Failed(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_f32_round_trip() {
|
||||
let dir = std::env::temp_dir().join("oakcommon-real-ocio");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("roundtrip.tif");
|
||||
let path_str = path.to_str().unwrap().to_string();
|
||||
|
||||
// 2x2 RGBA float image.
|
||||
let w = 2;
|
||||
let h = 2;
|
||||
let c = 4;
|
||||
let pixels: Vec<f32> = vec![
|
||||
0.0, 0.25, 0.5, 1.0, 0.75, 0.5, 0.25, 1.0, 1.0, 0.0, 0.5, 0.0, 0.125, 0.625, 0.875, 1.0,
|
||||
];
|
||||
|
||||
write_image_f32(&path_str, w, h, c, &pixels).expect("write should succeed");
|
||||
|
||||
let img = read_image_f32(&path_str).expect("read should succeed");
|
||||
assert_eq!(img.width, w);
|
||||
assert_eq!(img.height, h);
|
||||
assert_eq!(img.channels, c);
|
||||
assert_eq!(img.pixels.len(), (w * h * c) as usize);
|
||||
|
||||
for (i, (a, b)) in img.pixels.iter().zip(pixels.iter()).enumerate() {
|
||||
let diff = (a - b).abs();
|
||||
assert!(diff < 1e-6, "pixel {i}: wrote {b}, read back {a}");
|
||||
}
|
||||
|
||||
std::fs::remove_file(&path).ok();
|
||||
}
|
||||
@@ -9,3 +9,17 @@ license = "GPL-3.0-or-later"
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "2.0.20"
|
||||
ocio-rs = { version = "0.2", features = ["bundled"] }
|
||||
# wgpu: portable GPU backend — same major as oak-render's shaderfx/naga
|
||||
# generation (25); the moved backend/color/texture/frame code is written
|
||||
# against this API.
|
||||
wgpu = "25"
|
||||
log = "0.4.34"
|
||||
# TOML persistence for the application config (configstore.rs).
|
||||
toml = "0.8"
|
||||
# XML helpers (xmlutils.rs).
|
||||
quick-xml = "0.41.0"
|
||||
# Pure-Rust image I/O for oiioutils.rs (TIFF only — the only format current
|
||||
# callers need).
|
||||
image = { version = "0.25", default-features = false, features = ["tiff"] }
|
||||
|
||||
@@ -37,17 +37,17 @@ use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use oak_core::PixelFormat;
|
||||
use crate::PixelFormat;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::frame::VideoParamsPod;
|
||||
use crate::texture::{Frame, Texture};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
/// Backend selection preference (mapped onto wgpu backends).
|
||||
///
|
||||
/// The choice is user-visible: the settings panel exposes a renderer
|
||||
/// dropdown (Auto/Metal/Vulkan/OpenGL/CPU) persisted through the
|
||||
/// oakcommon config C ABI under the "GraphicsBackend" key
|
||||
/// oak_core config C ABI under the "GraphicsBackend" key
|
||||
/// (C++ parity: `RenderManager::backend_from_string` config round-trip).
|
||||
/// "auto" resolves Metal → Vulkan → GL → CPU at runtime.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -91,7 +91,7 @@ impl BackendKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the user's persisted choice through the oakcommon config C ABI
|
||||
/// Read the user's persisted choice through the oak_core config C ABI
|
||||
/// ("GraphicsBackend"). `OAK_RENDER_BACKEND` overrides the config
|
||||
/// (tests / headless environments).
|
||||
pub fn from_user_config() -> BackendKind {
|
||||
@@ -179,7 +179,7 @@ impl DisplayBitDepth {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the user's persisted choice through the oakcommon config C ABI
|
||||
/// Read the user's persisted choice through the oak_core config C ABI
|
||||
/// ("DisplayBitDepth").
|
||||
pub fn from_user_config() -> DisplayBitDepth {
|
||||
let configured =
|
||||
@@ -238,10 +238,10 @@ pub trait GpuContextLike: Send + Sync {
|
||||
fn download(&self, token: u64) -> Result<Frame>;
|
||||
/// Blit texture → texture (plain copy; color-managed deferred).
|
||||
fn blit(
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -531,10 +531,10 @@ impl GpuContext {
|
||||
/// of this pass (see README §4), so a `Some` processor returns
|
||||
/// `Error::Failed` and the plain-copy WGSL pipeline is used for `None`.
|
||||
pub fn blit(
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()> {
|
||||
if processor.is_some() {
|
||||
return Err(Error::Failed(
|
||||
@@ -830,7 +830,7 @@ impl GpuContext {
|
||||
|
||||
/// Run one effect pass: fragment-shade `dst` from `textures[0]` (the
|
||||
/// main input) plus any extra input textures, with `uniforms` as the
|
||||
/// packed std140 block (see [`crate::shaderfx::pack_uniforms`]).
|
||||
/// packed std140 block (see `oak_render::shaderfx::pack_uniforms`).
|
||||
pub fn run_shader_pass(
|
||||
&self,
|
||||
program: &ShaderProgram,
|
||||
@@ -1000,10 +1000,10 @@ impl GpuContextLike for GpuContext {
|
||||
}
|
||||
|
||||
fn blit(
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()> {
|
||||
self.blit(src, dst, processor)
|
||||
}
|
||||
@@ -1049,11 +1049,11 @@ fn pollster_block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
// Minimal futures executor (wgpu brings futures-core transitively; a tiny
|
||||
// block_on is enough for the immediately-ready adapter/device futures).
|
||||
mod futures_executor {
|
||||
use std::future::Future;
|
||||
use std::pin::pin;
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
use std::future::Future;
|
||||
use std::pin::pin;
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
fn noop_raw_waker() -> RawWaker {
|
||||
fn noop_raw_waker() -> RawWaker {
|
||||
fn no_op(_: *const ()) {}
|
||||
fn clone(_: *const ()) -> RawWaker {
|
||||
noop_raw_waker()
|
||||
@@ -1250,10 +1250,10 @@ impl DisplayRenderer {
|
||||
/// GPU path: plain-copy WGSL blit; a color processor on the GPU path is
|
||||
/// deferred (`Error::Failed`, see [`GpuContext::blit`]).
|
||||
pub fn blit_color_managed(
|
||||
&self,
|
||||
src: Option<&Texture>,
|
||||
dst: &mut Texture,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: Option<&Texture>,
|
||||
dst: &mut Texture,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()> {
|
||||
match (src, dst) {
|
||||
(
|
||||
@@ -1342,9 +1342,9 @@ pub fn frame_from_pixels_for_upload(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn backend_string_roundtrip() {
|
||||
for (s, kind) in [
|
||||
("auto", BackendKind::Auto),
|
||||
@@ -1496,78 +1496,6 @@ mod tests {
|
||||
ctx.destroy_texture(dst);
|
||||
}
|
||||
|
||||
/// End-to-end effect pass: a translated node shader (gain multiply)
|
||||
/// runs through `compile_shader_pass`/`run_shader_pass` and the
|
||||
/// readback matches the expected pixels exactly.
|
||||
#[test]
|
||||
fn gpu_effect_pass_runs_translated_shader() {
|
||||
let Some(ctx) = any_gpu() else {
|
||||
eprintln!("no adapter; skipping effect pass");
|
||||
return;
|
||||
};
|
||||
let glsl = r#"
|
||||
uniform sampler2D tex_in;
|
||||
uniform float gain_in;
|
||||
|
||||
in vec2 ove_texcoord;
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
frag_color = texture(tex_in, ove_texcoord) * gain_in;
|
||||
}
|
||||
"#;
|
||||
let translated = crate::shaderfx::translate(glsl).unwrap();
|
||||
let program = ctx
|
||||
.compile_shader_pass(
|
||||
"test-gain",
|
||||
&translated.wgsl,
|
||||
translated.textures.len() as u32,
|
||||
!translated.uniforms.is_empty(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut row = oak_node::value::NodeValueRow::new();
|
||||
row.insert("gain_in".into(), oak_node::value::NodeValue::Float(0.5));
|
||||
let uniforms = crate::shaderfx::pack_uniforms(&translated, &row);
|
||||
|
||||
let w = 4;
|
||||
let h = 2;
|
||||
let src = ctx.create_texture(w, h).unwrap();
|
||||
let dst = ctx.create_texture(w, h).unwrap();
|
||||
let mut frame = Frame::new();
|
||||
let mut pod = VideoParamsPod::default();
|
||||
pod.width = w;
|
||||
pod.height = h;
|
||||
frame.set_video_params(pod);
|
||||
frame.allocate();
|
||||
// Distinct values per pixel (F32 RGBA): 0.2/0.4/0.6/1.0 shifted
|
||||
// per pixel, so a UV mixup would be visible.
|
||||
for px in 0..(w * h) as usize {
|
||||
for c in 0..4 {
|
||||
let v = 0.2 + 0.1 * (px + c) as f32;
|
||||
frame.data[(px * 4 + c) * 4..(px * 4 + c) * 4 + 4]
|
||||
.copy_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
}
|
||||
ctx.upload(src, &frame).unwrap();
|
||||
ctx.run_shader_pass(&program, &uniforms, &[src], dst).unwrap();
|
||||
let out = ctx.download(dst).unwrap();
|
||||
for px in 0..(w * h) as usize {
|
||||
for c in 0..4 {
|
||||
let at = (px * 4 + c) * 4;
|
||||
let got = f32::from_le_bytes(out.data[at..at + 4].try_into().unwrap());
|
||||
let want = (0.2 + 0.1 * (px + c) as f32) * 0.5;
|
||||
assert!(
|
||||
(got - want).abs() < 1e-6,
|
||||
"px {px} ch {c}: got {got}, want {want}"
|
||||
);
|
||||
}
|
||||
}
|
||||
ctx.destroy_texture(src);
|
||||
ctx.destroy_texture(dst);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gpu_missing_texture_errors() {
|
||||
let Some(ctx) = any_gpu() else {
|
||||
@@ -1669,9 +1597,9 @@ void main() {
|
||||
assert_eq!(df.data[4], 0x22);
|
||||
// Pass-through processor is a no-op.
|
||||
r.blit_color_managed(
|
||||
Some(&src),
|
||||
&mut dst,
|
||||
Some(&crate::color::ColorProcessor::pass_through()),
|
||||
Some(&src),
|
||||
&mut dst,
|
||||
Some(&crate::color::ColorProcessor::pass_through()),
|
||||
)
|
||||
.unwrap();
|
||||
// Size mismatch rejected.
|
||||
@@ -24,7 +24,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use oak_core::PixelFormat;
|
||||
use crate::PixelFormat;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::texture::Frame;
|
||||
@@ -199,7 +199,7 @@ impl ColorProcessor {
|
||||
/// starts from an OCIO named space (sRGB and friends); P3/BT.2020
|
||||
/// targets have no named space in the builtin configs, so the caller
|
||||
/// linearizes and gamut-maps to XYZ itself
|
||||
/// (`oak_common::colormath::output_spec_to_xyz_d65`) and this chain only
|
||||
/// (`crate::colormath::output_spec_to_xyz_d65`) and this chain only
|
||||
/// needs the ICC half: it runs the existing builder with the config's
|
||||
/// `cie_xyz_d65_interchange` role as the source space (XYZ → linear
|
||||
/// Rec.709, whose inverse the builder's leg 2 immediately undoes — a
|
||||
@@ -427,27 +427,27 @@ fn bytemuck_f32_slice(data: &mut [u8]) -> Option<&mut [f32]> {
|
||||
/// project-properties commit). Render and export paths read it — a single
|
||||
/// project is open at a time, so a process global is the same shape as the
|
||||
/// OCIO default config above.
|
||||
static PIPELINE_COLOR: LazyLock<Mutex<(oak_common::colormath::WorkingColorSpace, oak_common::colormath::OutputColorSpec)>> =
|
||||
static PIPELINE_COLOR: LazyLock<Mutex<(crate::colormath::WorkingColorSpace, crate::colormath::OutputColorSpec)>> =
|
||||
LazyLock::new(|| Mutex::new((
|
||||
oak_common::colormath::WorkingColorSpace::default(),
|
||||
oak_common::colormath::OutputColorSpec::default(),
|
||||
crate::colormath::WorkingColorSpace::default(),
|
||||
crate::colormath::OutputColorSpec::default(),
|
||||
)));
|
||||
|
||||
/// Set the pipeline color settings (working space + output spec).
|
||||
pub fn set_pipeline_color_settings(
|
||||
working: oak_common::colormath::WorkingColorSpace,
|
||||
output: oak_common::colormath::OutputColorSpec,
|
||||
working: crate::colormath::WorkingColorSpace,
|
||||
output: crate::colormath::OutputColorSpec,
|
||||
) {
|
||||
*PIPELINE_COLOR.lock().unwrap_or_else(|e| e.into_inner()) = (working, output);
|
||||
}
|
||||
|
||||
/// The pipeline working colorspace.
|
||||
pub fn pipeline_working_space() -> oak_common::colormath::WorkingColorSpace {
|
||||
pub fn pipeline_working_space() -> crate::colormath::WorkingColorSpace {
|
||||
PIPELINE_COLOR.lock().unwrap_or_else(|e| e.into_inner()).0
|
||||
}
|
||||
|
||||
/// The pipeline output/delivery spec.
|
||||
pub fn pipeline_output_spec() -> oak_common::colormath::OutputColorSpec {
|
||||
pub fn pipeline_output_spec() -> crate::colormath::OutputColorSpec {
|
||||
PIPELINE_COLOR.lock().unwrap_or_else(|e| e.into_inner()).1
|
||||
}
|
||||
|
||||
@@ -457,8 +457,8 @@ pub fn pipeline_output_spec() -> oak_common::colormath::OutputColorSpec {
|
||||
/// sRGB in the legacy pass-through mode).
|
||||
pub fn pipeline_working_ofx_name() -> &'static str {
|
||||
match pipeline_working_space() {
|
||||
oak_common::colormath::WorkingColorSpace::AcesCg => "ACEScg",
|
||||
oak_common::colormath::WorkingColorSpace::SrgbLegacy => "sRGB",
|
||||
crate::colormath::WorkingColorSpace::AcesCg => "ACEScg",
|
||||
crate::colormath::WorkingColorSpace::SrgbLegacy => "sRGB",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -804,7 +804,7 @@ mod tests {
|
||||
f.format = PixelFormat::U8;
|
||||
assert_eq!(
|
||||
valid.convert_frame(&mut f).unwrap_err().code(),
|
||||
crate::error::OAKRENDER_E_INVALID
|
||||
crate::error::OAKCORE_E_INVALID
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -967,7 +967,7 @@ mod tests {
|
||||
|
||||
/// The non-sRGB project output gamut display path: content converted to
|
||||
/// CIE XYZ (D65, unit luminance) by
|
||||
/// `oak_common::colormath::output_spec_to_xyz_d65` must flow through the
|
||||
/// `crate::colormath::output_spec_to_xyz_d65` must flow through the
|
||||
/// display ICC. Builds by running the classic builder with the config's
|
||||
/// `cie_xyz_d65_interchange` role as the source space — the feasibility
|
||||
/// question this test answers is whether OCIO accepts the role name as a
|
||||
@@ -1005,10 +1005,10 @@ mod tests {
|
||||
// the same encoded values — legs 1+2 (XYZ→lin709→XYZ) are the inverse
|
||||
// round trip of the classic chain's lin709→XYZ leg, so both must land
|
||||
// on the same device values.
|
||||
let spec = oak_common::colormath::OutputColorSpec::default();
|
||||
let spec = crate::colormath::OutputColorSpec::default();
|
||||
let encoded = [0.5f32, 0.5, 0.5, 1.0];
|
||||
let mut xyz_in = encoded;
|
||||
oak_common::colormath::output_spec_to_xyz_d65(&mut xyz_in, spec);
|
||||
crate::colormath::output_spec_to_xyz_d65(&mut xyz_in, spec);
|
||||
let mut via_xyz = xyz_in;
|
||||
let _ = p.convert_f32_rgba(&mut via_xyz, 1);
|
||||
let srgb = ColorProcessor::create_display_icc("sRGB Encoded Rec.709 (sRGB)", icc)
|
||||
@@ -1034,17 +1034,19 @@ mod tests {
|
||||
/// The exact chain the viewers use (BGRA8, display-class ICC from
|
||||
/// `OAK_DISPLAY_ICC`): a mid-grey frame must NOT collapse to black —
|
||||
/// the viewer-black-screen regression guard. Skipped without the env
|
||||
/// var (point it at the display profile under investigation).
|
||||
/// var (point it at the display profile under investigation); an empty
|
||||
/// value is treated as unset, same as `displayicc::env_override_icc`.
|
||||
#[test]
|
||||
fn display_icc_bgra8_never_outputs_black() {
|
||||
let _lock = config_lock();
|
||||
if set_up_default_config().is_err() {
|
||||
return;
|
||||
}
|
||||
let Ok(icc) = std::env::var("OAK_DISPLAY_ICC") else {
|
||||
eprintln!("OAK_DISPLAY_ICC unset; skipping");
|
||||
let icc = std::env::var("OAK_DISPLAY_ICC").unwrap_or_default();
|
||||
if icc.is_empty() {
|
||||
eprintln!("OAK_DISPLAY_ICC unset or empty; skipping");
|
||||
return;
|
||||
};
|
||||
}
|
||||
let p = ColorProcessor::create_display_icc_bgra8("sRGB Encoded Rec.709 (sRGB)", &icc)
|
||||
.expect("handle always returned");
|
||||
assert!(p.is_valid(), "BGRA8 ICC processor builds from {icc}");
|
||||
@@ -22,7 +22,7 @@
|
||||
//! as a refcounted handle ([`OakColorTransform`] in `crate::ffi`); this
|
||||
//! module owns the plain-data description behind the handle.
|
||||
//!
|
||||
//! The C++-only functions `oakcommon_colortransform_init_from_native` /
|
||||
//! The C++-only functions `oak_core_colortransform_init_from_native` /
|
||||
//! `get_native` take or return `olive::ColorTransform` and cannot be
|
||||
//! expressed from Rust; they are served by the C++ adapter layer, not here.
|
||||
|
||||
@@ -14,32 +14,31 @@
|
||||
// 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 helpers (config, file functions) — direct Rust calls into
|
||||
//! the oakcommon crate (single-lib unification; the former
|
||||
//! `bridge/common.rs`). The configuration-location and disk-cache
|
||||
//! helpers delegate to oakcommon's own implementation.
|
||||
//! Config / file-function helpers — thin wrappers over this crate's
|
||||
//! `configstore` and `filefunctions` (the former `bridge/common.rs`,
|
||||
//! moved here from oak-render with the backend/color merge).
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// Read a config string via the domain store
|
||||
/// (`ConfigStore::get(group, key)`); `None` when missing or empty.
|
||||
pub fn config_get_string(group: Option<&str>, key: &str) -> Option<String> {
|
||||
oak_common::configstore::ConfigStore::instance()
|
||||
crate::configstore::ConfigStore::instance()
|
||||
.get(group, key)
|
||||
.ok()
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// `oakcommon_config_get_int(group, key, default)`.
|
||||
/// `oak_core_config_get_int(group, key, default)`.
|
||||
pub fn config_get_int(group: Option<&str>, key: &str, default: i32) -> i32 {
|
||||
oak_common::configstore::ConfigStore::instance().get_int(group, key, default)
|
||||
crate::configstore::ConfigStore::instance().get_int(group, key, default)
|
||||
}
|
||||
|
||||
/// The configuration directory — oakcommon's implementation
|
||||
/// The configuration directory — `filefunctions`' implementation
|
||||
/// (`FileFunctions::get_configuration_location`, honoring `OAK_CONFIG_DIR`
|
||||
/// and the platform fallbacks).
|
||||
pub fn configuration_location() -> String {
|
||||
oak_common::filefunctions::FileFunctions::new()
|
||||
crate::filefunctions::FileFunctions::new()
|
||||
.get_configuration_location()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -51,7 +50,7 @@ pub static ENV_TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||
/// The default disk cache directory (C++ `DiskManager::
|
||||
/// get_default_disk_cache_path`): `<configuration_location>/mediacache`.
|
||||
pub fn default_disk_cache_path() -> String {
|
||||
oak_common::filefunctions::default_disk_cache_path()
|
||||
crate::filefunctions::default_disk_cache_path()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -33,11 +33,11 @@ use std::sync::{Mutex, OnceLock};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
/// Error-handler callback for user-visible config errors
|
||||
/// (`OakCommonConfigErrorHandler`). Called with title, message, and the
|
||||
/// (`oak_coreConfigErrorHandler`). Called with title, message, and the
|
||||
/// registered userdata.
|
||||
pub type ErrorHandler = Option<unsafe extern "C" fn(*const c_char, *const c_char, *mut c_void)>;
|
||||
|
||||
/// Entry types (`OakCommonConfigEntryType`).
|
||||
/// Entry types (`oak_coreConfigEntryType`).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum EntryType {
|
||||
/// No entry / null type.
|
||||
@@ -377,7 +377,7 @@ impl ConfigStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set a string entry. Mirrors `oakcommon_config_set` (`config.cpp:102`):
|
||||
/// Set a string entry. Mirrors `oak_core_config_set` (`config.cpp:102`):
|
||||
/// a new key is created as a string; setting an existing typed entry
|
||||
/// parses the string into its declared type, and an unparseable value
|
||||
/// leaves the entry unchanged.
|
||||
@@ -400,7 +400,7 @@ impl ConfigStore {
|
||||
}
|
||||
|
||||
/// Read an entry as a string (two-stage getter semantics: formatted
|
||||
/// for numeric/bool entries). Mirrors `oakcommon_config_get`
|
||||
/// for numeric/bool entries). Mirrors `oak_core_config_get`
|
||||
/// (`config.cpp:134`).
|
||||
pub fn get(&self, group: Option<&str>, key: &str) -> Result<String> {
|
||||
if key.is_empty() {
|
||||
@@ -492,7 +492,7 @@ impl ConfigStore {
|
||||
self.set_entry(join_key(group, key), ConfigValue::Double(v));
|
||||
}
|
||||
|
||||
/// Entry type of a key, or `NotFound`. Mirrors `oakcommon_config_entry_type`
|
||||
/// Entry type of a key, or `NotFound`. Mirrors `oak_core_config_entry_type`
|
||||
/// (`config.cpp:270`).
|
||||
pub fn entry_type(&self, group: Option<&str>, key: &str) -> Result<EntryType> {
|
||||
if key.is_empty() {
|
||||
@@ -505,7 +505,7 @@ impl ConfigStore {
|
||||
}
|
||||
|
||||
/// Register (or clear, with a null handler) the error handler. Mirrors
|
||||
/// the domain half of `oakcommon_config_set_error_handler` (`config.cpp:288`).
|
||||
/// the domain half of `oak_core_config_set_error_handler` (`config.cpp:288`).
|
||||
pub fn set_error_handler(&self, handler: ErrorHandler, userdata: *mut c_void) -> Result<()> {
|
||||
let ptr = handler.map_or(std::ptr::null_mut(), |h| h as *mut c_void);
|
||||
self.error_handler.store(ptr, Ordering::Release);
|
||||
@@ -887,7 +887,7 @@ mod tests {
|
||||
fn with_temp_config<T>(f: impl FnOnce(&Path) -> T) -> T {
|
||||
let _guard = test_lock().lock().unwrap();
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("oakcommon_configstore_test_{}", std::process::id()));
|
||||
std::env::temp_dir().join(format!("oak_core_configstore_test_{}", std::process::id()));
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
std::env::set_var("OAK_CONFIG_DIR", &dir);
|
||||
let result = f(&dir);
|
||||
@@ -1363,7 +1363,7 @@ CustomKey=hello
|
||||
let res = s.load();
|
||||
s.set_error_handler(None, std::ptr::null_mut()).unwrap();
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
|
||||
|
||||
let reported = REPORTED.lock().unwrap().clone();
|
||||
assert_eq!(reported.len(), 1);
|
||||
@@ -1716,7 +1716,7 @@ FlatAfterEmptySection=ok
|
||||
let res = s.load();
|
||||
s.set_error_handler(None, std::ptr::null_mut()).unwrap();
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
|
||||
assert_eq!(REPORTED.lock().unwrap().len(), 1);
|
||||
});
|
||||
}
|
||||
@@ -1735,7 +1735,7 @@ FlatAfterEmptySection=ok
|
||||
let res = s.load();
|
||||
s.set_error_handler(None, std::ptr::null_mut()).unwrap();
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
|
||||
let reported = REPORTED.lock().unwrap().clone();
|
||||
assert_eq!(reported.len(), 1);
|
||||
assert_eq!(reported[0].0, "Error loading settings");
|
||||
@@ -1751,7 +1751,7 @@ FlatAfterEmptySection=ok
|
||||
fn test_save_failure_reports_error() {
|
||||
let _g = test_lock().lock().unwrap();
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("oakcommon_configstore_test_{}", std::process::id()));
|
||||
std::env::temp_dir().join(format!("oak_core_configstore_test_{}", std::process::id()));
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
// Point OAK_CONFIG_DIR at a regular FILE so writing
|
||||
// "<dir>/config.toml.tmp" fails (create_dir_all on it is a silent
|
||||
@@ -1772,7 +1772,7 @@ FlatAfterEmptySection=ok
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
|
||||
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
|
||||
let reported = REPORTED.lock().unwrap().clone();
|
||||
assert_eq!(reported.len(), 1);
|
||||
assert_eq!(reported[0].0, "Error saving settings");
|
||||
@@ -21,7 +21,7 @@
|
||||
//! Built on the `log` facade crate (crates.io `log`, MIT/Apache-2.0):
|
||||
//! filtering is `log::set_max_level` and the stderr sink is a `log::Log`
|
||||
//! implementation installed on first use. No hand-rolled filter state.
|
||||
//! The printf-style C ABI (`oakcommon_log`) is implemented in
|
||||
//! The printf-style C ABI (`oak_core_log`) is implemented in
|
||||
//! `crate::ffi` over this module's [`log`] helper.
|
||||
|
||||
use std::io::Write;
|
||||
@@ -0,0 +1,138 @@
|
||||
// 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/>.
|
||||
|
||||
//! Error codes, mirroring `include/render/error.h` verbatim; project-wide
|
||||
//! -MMCCCC scheme (module registry in include/common/error.h), pass-through untranslated.
|
||||
|
||||
/// Success.
|
||||
pub const OAKCORE_OK: i32 = 0;
|
||||
/// Null handle or invalid argument.
|
||||
pub const OAKCORE_E_INVALID: i32 = -10001;
|
||||
/// Call not valid in the current state.
|
||||
pub const OAKCORE_E_STATE: i32 = -10002;
|
||||
/// The underlying operation failed.
|
||||
pub const OAKCORE_E_FAILED: i32 = -10003;
|
||||
/// Index out of range / entry not found.
|
||||
pub const OAKCORE_E_NOT_FOUND: i32 = -10004;
|
||||
/// Allocation failed.
|
||||
pub const OAKCORE_E_NOMEM: i32 = -10005;
|
||||
|
||||
/// Crate-internal result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Crate-internal error.
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Null handle or invalid argument.
|
||||
#[error("render: invalid argument")]
|
||||
Invalid,
|
||||
/// Wrong state.
|
||||
#[error("render: invalid state")]
|
||||
State,
|
||||
/// Operation failed (context string is log-only).
|
||||
#[error("render: operation failed: {0}")]
|
||||
Failed(String),
|
||||
/// Not found.
|
||||
#[error("render: not found")]
|
||||
NotFound,
|
||||
/// Out of memory.
|
||||
#[error("render: out of memory")]
|
||||
NoMem,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Map to the public error code.
|
||||
pub fn code(&self) -> i32 {
|
||||
match self {
|
||||
Error::Invalid => OAKCORE_E_INVALID,
|
||||
Error::State => OAKCORE_E_STATE,
|
||||
Error::Failed(_) => OAKCORE_E_FAILED,
|
||||
Error::NotFound => OAKCORE_E_NOT_FOUND,
|
||||
Error::NoMem => OAKCORE_E_NOMEM,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Error::Failed`] carrying a context message (log-only).
|
||||
///
|
||||
/// Convenience constructor used by the OCIO/`image` wrappers in
|
||||
/// `ocioutils.rs` / `oiioutils.rs` when an underlying library call fails.
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Error::Failed(message.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ocio_rs::OcioError> for Error {
|
||||
fn from(e: ocio_rs::OcioError) -> Self {
|
||||
// The Display impl of `OcioError` always produces a non-empty message
|
||||
// (every variant carries text or a fixed phrase); it becomes the
|
||||
// log-only context of `Error::Failed`.
|
||||
Error::Failed(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Every variant must produce a non-empty `Display` message; the
|
||||
/// `Failed` variant must surface its context string.
|
||||
#[test]
|
||||
fn display_is_non_empty() {
|
||||
for msg in [
|
||||
Error::Invalid.to_string(),
|
||||
Error::State.to_string(),
|
||||
Error::Failed("context".into()).to_string(),
|
||||
Error::NotFound.to_string(),
|
||||
Error::NoMem.to_string(),
|
||||
] {
|
||||
assert!(!msg.trim().is_empty(), "empty Display message");
|
||||
}
|
||||
assert!(
|
||||
Error::Failed("context".into())
|
||||
.to_string()
|
||||
.contains("context")
|
||||
);
|
||||
}
|
||||
|
||||
/// `Error` must be usable behind a trait object.
|
||||
#[test]
|
||||
fn error_is_object_safe() {
|
||||
let errs: Vec<Box<dyn std::error::Error>> = vec![
|
||||
Box::new(Error::Invalid),
|
||||
Box::new(Error::State),
|
||||
Box::new(Error::Failed("context".into())),
|
||||
Box::new(Error::NotFound),
|
||||
Box::new(Error::NoMem),
|
||||
];
|
||||
assert_eq!(errs.len(), 5);
|
||||
}
|
||||
|
||||
/// No variant wraps a downstream error, so `source()` stays `None`.
|
||||
#[test]
|
||||
fn source_is_none() {
|
||||
assert!(std::error::Error::source(&Error::Failed("context".into())).is_none());
|
||||
}
|
||||
|
||||
/// The `code()` mapping must be unchanged by the `Error` trait impl.
|
||||
#[test]
|
||||
fn code_mapping_unchanged() {
|
||||
assert_eq!(Error::Invalid.code(), OAKCORE_E_INVALID);
|
||||
assert_eq!(Error::State.code(), OAKCORE_E_STATE);
|
||||
assert_eq!(Error::Failed("context".into()).code(), OAKCORE_E_FAILED);
|
||||
assert_eq!(Error::NotFound.code(), OAKCORE_E_NOT_FOUND);
|
||||
assert_eq!(Error::NoMem.code(), OAKCORE_E_NOMEM);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@
|
||||
//! The C ABI frame functions (`oakrender_codec_frame_*`) marshal this
|
||||
//! type; the FFI layer stores [`Frame`] values in `OakCodecFrame` handles.
|
||||
|
||||
use oak_core::{PixelFormat, Rational};
|
||||
use crate::PixelFormat;
|
||||
use crate::Rational;
|
||||
|
||||
/// Mirror of the `oakrender_video_params` POD (include/render/renderer.h,
|
||||
/// field order and semantics verbatim). Stored inside [`crate::texture::Frame`]
|
||||
@@ -75,7 +76,7 @@ impl Default for VideoParamsPod {
|
||||
|
||||
impl VideoParamsPod {
|
||||
/// The default render size used when a ticket carries no force size and
|
||||
/// the output node's video params cannot be queried (oakcommon bridge
|
||||
/// the output node's video params cannot be queried (oak_core bridge
|
||||
/// pending).
|
||||
pub const DEFAULT_WIDTH: i32 = 1920;
|
||||
/// See [`VideoParamsPod::DEFAULT_WIDTH`].
|
||||
@@ -116,9 +117,10 @@ impl VideoParamsPod {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::PixelFormat;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn pod_defaults() {
|
||||
let p = VideoParamsPod::default();
|
||||
assert_eq!(p.format, PixelFormat::F32 as i32);
|
||||
@@ -21,12 +21,58 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod cancelatom;
|
||||
pub mod colormath;
|
||||
pub mod colortransform;
|
||||
pub mod commandlineparser;
|
||||
pub mod commonutil;
|
||||
pub mod configstore;
|
||||
pub mod debug;
|
||||
pub mod displayicc;
|
||||
pub mod error;
|
||||
pub mod ffmpegutils;
|
||||
pub mod filefunctions;
|
||||
pub mod miscutils;
|
||||
pub mod ocioutils;
|
||||
pub mod oiioutils;
|
||||
pub mod qtutils;
|
||||
pub mod subtitleparams;
|
||||
pub mod videoparams;
|
||||
pub mod xmlutils;
|
||||
|
||||
/// Test-only helpers shared across unit-test modules.
|
||||
///
|
||||
/// Several domain test modules (e.g. `configstore`, `filefunctions`) mutate
|
||||
/// process-global state — notably the `OAK_CONFIG_DIR` environment variable
|
||||
/// and shared temp paths — while exercising configuration-location logic.
|
||||
/// Rust runs tests in parallel, so all such tests must serialize on a single
|
||||
/// process-wide lock to avoid racing each other across module boundaries.
|
||||
#[cfg(test)]
|
||||
#[doc(hidden)]
|
||||
pub mod test_support {
|
||||
use std::sync::Mutex;
|
||||
|
||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Process-wide lock guarding tests that mutate global config/env state.
|
||||
///
|
||||
/// Hold this for the duration of any test (or helper) that sets/removes
|
||||
/// `OAK_CONFIG_DIR` or touches the shared configuration temp path.
|
||||
pub fn env_lock() -> &'static Mutex<()> {
|
||||
&ENV_LOCK
|
||||
}
|
||||
}
|
||||
|
||||
mod rational;
|
||||
mod samplefmt;
|
||||
mod timerange;
|
||||
|
||||
/// Shared ABI value-handle type (see [`handle::CHandle`]).
|
||||
pub mod handle;
|
||||
pub mod color;
|
||||
pub mod texture;
|
||||
pub mod frame;
|
||||
pub mod backend;
|
||||
|
||||
pub use handle::CHandle;
|
||||
pub use rational::Rational;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ocioutils::PixelFormat;
|
||||
use image::{ExtendedColorType, ImageBuffer, Rgb, Rgba};
|
||||
use oak_core::Rational;
|
||||
use crate::Rational;
|
||||
|
||||
/// OIIO base type codes, matching `OIIO::TypeDesc::BASETYPE`.
|
||||
///
|
||||
@@ -104,7 +104,7 @@ impl OIIOUtils {
|
||||
/// known-but-unmappable types (INT8/INT16/INT32/UINT32/INT64/UINT64/
|
||||
/// STRING/PTR/DOUBLE/LASTBASE) print to stderr in C++ and return
|
||||
/// `invalid`; here they all fall to `Ok(PixelFormat::Invalid)`. The
|
||||
/// `base_type < 0` error mirrors the `oakcommon_oiioutils_get_format_from_oiio_basetype`
|
||||
/// `base_type < 0` error mirrors the `oak_core_oiioutils_get_format_from_oiio_basetype`
|
||||
/// c_api guard; the `>= LASTBASE` upper-bound guard is likewise a c_api
|
||||
/// concern and is not replicated in the domain function.
|
||||
pub fn get_format_from_oiio_basetype(&self, base_type: i32) -> Result<PixelFormat> {
|
||||
@@ -514,7 +514,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn temp_tiff_path(name: &str) -> (std::path::PathBuf, String) {
|
||||
let dir = std::env::temp_dir().join("oakcommon-oiioutils");
|
||||
let dir = std::env::temp_dir().join("oak_core-oiioutils");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join(name);
|
||||
let path_str = path.to_str().unwrap().to_string();
|
||||
@@ -598,7 +598,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn image_f32_read_missing_file_errors() {
|
||||
let err = read_image_f32("/nonexistent/oakcommon-oiioutils.tif").unwrap_err();
|
||||
let err = read_image_f32("/nonexistent/oak_core-oiioutils.tif").unwrap_err();
|
||||
assert!(matches!(err, Error::Failed(_)));
|
||||
}
|
||||
}
|
||||
@@ -211,7 +211,7 @@ mod tests {
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"oakcommon_qtutils_{}_{}_{}.tmp",
|
||||
"oak_core_qtutils_{}_{}_{}.tmp",
|
||||
std::process::id(),
|
||||
tag,
|
||||
n
|
||||
@@ -1155,14 +1155,14 @@ mod tests {
|
||||
(7, 3),
|
||||
(i32::MAX, 1),
|
||||
] {
|
||||
let r = oak_core::Rational::new(n as i64, d as i64);
|
||||
let r = crate::Rational::new(n as i64, d as i64);
|
||||
assert_eq!(
|
||||
rational_reduce(n, d),
|
||||
(r.numerator() as i32, r.denominator() as i32)
|
||||
);
|
||||
}
|
||||
for s in ["1/2", "7", "4/2", "junk", "a/b", "1/2/3", ""] {
|
||||
let r = oak_core::Rational::from_string(s);
|
||||
let r = crate::Rational::from_string(s);
|
||||
assert_eq!(
|
||||
rational_from_string(s),
|
||||
(r.numerator() as i32, r.denominator() as i32),
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
//! Textures and CPU frames.
|
||||
|
||||
use crate::Rational;
|
||||
use crate::PixelFormat;
|
||||
use std::sync::Arc;
|
||||
|
||||
use oak_core::{PixelFormat, Rational};
|
||||
|
||||
use crate::backend::{BackendKind, GpuContextLike};
|
||||
use crate::error::Result;
|
||||
use crate::frame::VideoParamsPod;
|
||||
@@ -545,7 +545,7 @@ impl VideoParams {
|
||||
|
||||
/// Load parameters from an XML fragment.
|
||||
pub fn load_xml(&mut self, xml: &str) -> Result<()> {
|
||||
// Mirrors oakcommon_videoparams_load_xml + VideoParams::load():
|
||||
// Mirrors oak_core_videoparams_load_xml + VideoParams::load():
|
||||
// parse, fail on error, position on the root element, then consume
|
||||
// its children.
|
||||
let events = parse_xml(xml).ok_or_else(|| Error::Failed("XML parse error".to_string()))?;
|
||||
@@ -613,7 +613,7 @@ impl VideoParams {
|
||||
|
||||
/// Save parameters to an XML fragment.
|
||||
pub fn save_xml(&self) -> Result<String> {
|
||||
// Mirrors oakcommon_videoparams_save_xml: a `<videoparams>` root with
|
||||
// Mirrors oak_core_videoparams_save_xml: a `<videoparams>` root with
|
||||
// the exact child order of VideoParams::save(). The writer emits no
|
||||
// whitespace; each child is `<name>text</name>`.
|
||||
let mut out = String::new();
|
||||
@@ -2289,14 +2289,14 @@ mod tests {
|
||||
(7, 3),
|
||||
(100, 10),
|
||||
] {
|
||||
let r = oak_core::Rational::new(n as i64, d as i64);
|
||||
let r = crate::Rational::new(n as i64, d as i64);
|
||||
assert_eq!(
|
||||
make_rational(n, d),
|
||||
(r.numerator() as i32, r.denominator() as i32)
|
||||
);
|
||||
}
|
||||
for s in ["1/2", "7", "4/2", "junk", "a/b", "1/2/3", "-6/3"] {
|
||||
let r = oak_core::Rational::from_string(s);
|
||||
let r = crate::Rational::from_string(s);
|
||||
assert_eq!(
|
||||
rational_from_string(s),
|
||||
(r.numerator() as i32, r.denominator() as i32),
|
||||
@@ -2320,9 +2320,9 @@ mod tests {
|
||||
1,
|
||||
);
|
||||
vp.set_start_time(11);
|
||||
let tb = oak_core::Rational::new(1001, 30000);
|
||||
let tb = crate::Rational::new(1001, 30000);
|
||||
for (n, d) in [(1i32, 1i32), (1, 2), (24000, 1001), (-3, 1), (0, 1)] {
|
||||
let expected = tb.time_to_timestamp(oak_core::Rational::new(n as i64, d as i64)) + 11;
|
||||
let expected = tb.time_to_timestamp(crate::Rational::new(n as i64, d as i64)) + 11;
|
||||
assert_eq!(vp.time_in_timebase_units(n, d), Some(expected));
|
||||
}
|
||||
}
|
||||
Generated
+2
-2
@@ -309,7 +309,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oakcommon"
|
||||
name = "oak_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
@@ -328,7 +328,7 @@ name = "oaknode"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oakcodec",
|
||||
"oakcommon",
|
||||
"oak_core",
|
||||
"oakcore-rs",
|
||||
"oakundo",
|
||||
]
|
||||
|
||||
@@ -10,7 +10,6 @@ crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
oak-core = { path = "../oak-core" }
|
||||
oak-common = { path = "../oak-common" }
|
||||
oak-undo = { path = "../oak-undo" }
|
||||
oak-codec = { path = "../oak-codec" }
|
||||
thiserror = "2"
|
||||
|
||||
@@ -44,8 +44,8 @@ frozen, implemented verbatim by `src/ffi.rs`.
|
||||
4. **Undo.** Commands are created through the oakundo C ABI
|
||||
(`bridge::undo`); the C++ `UndoCommand` subclass hierarchy becomes
|
||||
vtable commands whose userdata is a Rust closure.
|
||||
5. **Serialization.** XML read/write goes through the oakcommon C ABI
|
||||
(`bridge::common`) until oakcommon itself is rewritten.
|
||||
5. **Serialization.** XML read/write goes through the oak_core C ABI
|
||||
(`bridge::common`) until oak_core itself is rewritten.
|
||||
6. **Threading.** The C++ code relied on Qt's event thread +
|
||||
`called_on_owner_thread()` assertions. Rust replaces this with
|
||||
`Mutex<Graph>` interior mutability plus explicit
|
||||
|
||||
@@ -772,15 +772,15 @@ mod tests {
|
||||
use oak_codec::footagedescription::{FootageDescription, StreamEntry};
|
||||
|
||||
fn video_entry(stream_index: i32, duration: i64) -> StreamEntry {
|
||||
let mut vp = oak_common::videoparams::VideoParams::new_basic(
|
||||
1920,
|
||||
1080,
|
||||
oak_common::ocioutils::PixelFormat::F32,
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
let mut vp = oak_core::videoparams::VideoParams::new_basic(
|
||||
1920,
|
||||
1080,
|
||||
oak_core::ocioutils::PixelFormat::F32,
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
vp.set_stream_index(stream_index);
|
||||
vp.set_frame_rate(30000, 1001);
|
||||
@@ -826,7 +826,7 @@ mod tests {
|
||||
assert_eq!(v.width, 1920);
|
||||
assert_eq!(v.height, 1080);
|
||||
assert_eq!(v.frame_rate, oak_core::Rational::new(30000, 1001));
|
||||
assert_eq!(v.pixel_format, oak_common::ocioutils::PixelFormat::F32.code());
|
||||
assert_eq!(v.pixel_format, oak_core::ocioutils::PixelFormat::F32.code());
|
||||
// 300000 ticks at 1/30000 = 10 seconds.
|
||||
assert_eq!(video.duration, oak_core::Rational::new(10, 1));
|
||||
}
|
||||
@@ -835,7 +835,7 @@ mod tests {
|
||||
fn conversion_skips_subtitles_and_unusable_durations() {
|
||||
let mut desc = FootageDescription::new("ffmpeg");
|
||||
desc.push_stream(video_entry(0, i64::MIN)); // AV_NOPTS_VALUE
|
||||
desc.push_stream(StreamEntry::Subtitle(oak_common::subtitleparams::SubtitleParams::new()));
|
||||
desc.push_stream(StreamEntry::Subtitle(oak_core::subtitleparams::SubtitleParams::new()));
|
||||
|
||||
let streams = streams_from_description(&desc);
|
||||
assert_eq!(streams.len(), 1, "the subtitle stream is skipped");
|
||||
|
||||
@@ -110,7 +110,7 @@ impl DisplayTransformNode {
|
||||
fn generate_processor(&mut self, core: &mut NodeCore) {
|
||||
let _ = core;
|
||||
// The C++ wraps the color manager, builds a display transform
|
||||
// (`oakcommon_colortransform_init_display`) for the selected
|
||||
// (`oak_core_colortransform_init_display`) for the selected
|
||||
// display/view, resolves the reference color space and creates
|
||||
// the processor via `oakrender_color_processor_create_transform`,
|
||||
// storing it with OcioBase::set_processor. Without a manager (the
|
||||
|
||||
@@ -20,11 +20,21 @@
|
||||
//! ([`crate::traverser::RenderHooks::resolve`]).
|
||||
//! `// CPP-PARITY: app/render/job/footagejob.h, shaderjob.h`.
|
||||
|
||||
use oak_core::color::ColorProcessor;
|
||||
use oak_core::Rational;
|
||||
|
||||
use crate::id::NodeId;
|
||||
use crate::nodes::plugin::PluginJobPayload;
|
||||
use crate::value::NodeValueRow;
|
||||
|
||||
/// Job types
|
||||
|
||||
pub enum Job{
|
||||
FootageJob(FootageJobPayload),
|
||||
ShaderJob(ShaderJobPayload),
|
||||
PluginJob(PluginJobPayload),
|
||||
ColorTransformJob
|
||||
}
|
||||
|
||||
/// C++ `FootageJob` payload: the decode request a footage node emits at
|
||||
/// its output instead of a texture. The render hooks decode it at the
|
||||
/// request time and replace it with the resulting frame.
|
||||
@@ -67,6 +77,11 @@ pub struct ShaderJobPayload {
|
||||
pub iterative_input: String,
|
||||
}
|
||||
|
||||
pub struct ColorTransformJobPayload{
|
||||
pub color_processor: ColorProcessor,
|
||||
|
||||
}
|
||||
|
||||
impl Default for FootageJobPayload {
|
||||
fn default() -> Self {
|
||||
FootageJobPayload {
|
||||
|
||||
@@ -140,29 +140,29 @@ impl Project {
|
||||
|
||||
/// The pipeline working colorspace (project property; ACEScg when the
|
||||
/// setting is absent).
|
||||
pub fn working_color_space(&self) -> oak_common::colormath::WorkingColorSpace {
|
||||
oak_common::colormath::WorkingColorSpace::from_setting(
|
||||
pub fn working_color_space(&self) -> oak_core::colormath::WorkingColorSpace {
|
||||
oak_core::colormath::WorkingColorSpace::from_setting(
|
||||
self.settings.get(SETTING_WORKING_COLOR_SPACE).map(String::as_str).unwrap_or(""),
|
||||
)
|
||||
}
|
||||
|
||||
/// The output/delivery colorspace (project property; sRGB when the
|
||||
/// settings are absent).
|
||||
pub fn output_color_spec(&self) -> oak_common::colormath::OutputColorSpec {
|
||||
oak_common::colormath::OutputColorSpec::from_settings(
|
||||
pub fn output_color_spec(&self) -> oak_core::colormath::OutputColorSpec {
|
||||
oak_core::colormath::OutputColorSpec::from_settings(
|
||||
self.settings.get(SETTING_OUTPUT_GAMUT).map(String::as_str).unwrap_or(""),
|
||||
self.settings.get(SETTING_OUTPUT_TRANSFER).map(String::as_str).unwrap_or(""),
|
||||
)
|
||||
}
|
||||
|
||||
/// Set the pipeline working colorspace property.
|
||||
pub fn set_working_color_space(&mut self, space: oak_common::colormath::WorkingColorSpace) {
|
||||
pub fn set_working_color_space(&mut self, space: oak_core::colormath::WorkingColorSpace) {
|
||||
self.settings
|
||||
.insert(SETTING_WORKING_COLOR_SPACE.to_string(), space.as_setting().to_string());
|
||||
}
|
||||
|
||||
/// Set the output/delivery colorspace properties.
|
||||
pub fn set_output_color_spec(&mut self, spec: oak_common::colormath::OutputColorSpec) {
|
||||
pub fn set_output_color_spec(&mut self, spec: oak_core::colormath::OutputColorSpec) {
|
||||
self.settings
|
||||
.insert(SETTING_OUTPUT_GAMUT.to_string(), spec.gamut.as_setting().to_string());
|
||||
self.settings
|
||||
@@ -357,8 +357,8 @@ impl Project {
|
||||
_ => {}
|
||||
}
|
||||
// Default location: the shared disk-cache directory (single-lib:
|
||||
// lives in oakcommon, used by oaknode and oakrender alike).
|
||||
oak_common::filefunctions::default_disk_cache_path()
|
||||
// lives in oak_core, used by oaknode and oakrender alike).
|
||||
oak_core::filefunctions::default_disk_cache_path()
|
||||
}
|
||||
|
||||
/// Copy all settings from `src` into `self` (C++
|
||||
|
||||
@@ -123,9 +123,9 @@ impl SequenceBehavior {
|
||||
|
||||
/// Apply the default video/audio parameters (C++
|
||||
/// `ViewerOutput::set_default_parameters()`; the config lookups read
|
||||
/// the oakcommon config store directly).
|
||||
/// the oak_core config store directly).
|
||||
pub fn set_default_parameters(&mut self) {
|
||||
let config = oak_common::configstore::ConfigStore::instance();
|
||||
let config = oak_core::configstore::ConfigStore::instance();
|
||||
let width = config.get_int(None, "DefaultSequenceWidth", 1920);
|
||||
let height = config.get_int(None, "DefaultSequenceHeight", 1080);
|
||||
let sample_rate = config.get_int(None, "DefaultSequenceAudioFrequency", 48000);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
//! Project (de)serialization: the C++ `ProjectSerializer` family.
|
||||
//!
|
||||
//! XML I/O goes through oakcommon's [`XmlReader`]/[`XmlWriter`] (direct
|
||||
//! XML I/O goes through oak_core's [`XmlReader`]/[`XmlWriter`] (direct
|
||||
//! Rust calls, single-lib unification). The XML shape mirrors
|
||||
//! the C++ `Node::save`/`Project::save` writers (`// CPP-PARITY:
|
||||
//! src/node/src/node.cpp:node::save`, `// CPP-PARITY:
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use oak_common::xmlutils::{XmlReader, XmlWriter};
|
||||
use oak_core::xmlutils::{XmlReader, XmlWriter};
|
||||
use oak_core::Rational;
|
||||
|
||||
use crate::graph::Graph;
|
||||
@@ -43,7 +43,7 @@ use crate::project::{NodeRef, Project};
|
||||
use crate::value::{NodeValue, ValueType};
|
||||
|
||||
/// Minimal XML reader surface the serializer needs (implemented over
|
||||
/// oakcommon's `xmlutils`).
|
||||
/// oak_core's `xmlutils`).
|
||||
pub trait XmlRead {
|
||||
/// Advance to the next start element; false at end/close.
|
||||
fn next_start_element(&mut self) -> bool;
|
||||
@@ -73,17 +73,17 @@ pub trait XmlWrite {
|
||||
fn characters(&mut self, _text: &str) {}
|
||||
}
|
||||
|
||||
/// Reader over oakcommon's [`XmlReader`].
|
||||
/// Reader over oak_core's [`XmlReader`].
|
||||
pub struct XmlReaderBridge {
|
||||
/// The oakcommon reader.
|
||||
/// The oak_core reader.
|
||||
reader: XmlReader,
|
||||
/// Current element name (cached).
|
||||
name: String,
|
||||
}
|
||||
|
||||
/// Writer over oakcommon's [`XmlWriter`].
|
||||
/// Writer over oak_core's [`XmlWriter`].
|
||||
pub struct XmlWriterBridge {
|
||||
/// The oakcommon writer.
|
||||
/// The oak_core writer.
|
||||
writer: XmlWriter,
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ pub fn parse_node_ref(text: &str) -> Option<NodeId> {
|
||||
pub fn save(project: &Project) -> crate::error::Result<String> {
|
||||
use crate::error::Error;
|
||||
let mut writer = XmlWriterBridge::new().ok_or(Error::Failed(
|
||||
"oakcommon XML writer unavailable".to_string(),
|
||||
"oak_core XML writer unavailable".to_string(),
|
||||
))?;
|
||||
|
||||
writer.start_element("project");
|
||||
@@ -510,7 +510,7 @@ pub fn load_with_id_map(
|
||||
) -> crate::error::Result<(Arc<Mutex<Project>>, std::collections::HashMap<u64, NodeId>)> {
|
||||
use crate::error::Error;
|
||||
let mut reader = XmlReaderBridge::new(xml).ok_or(Error::Failed(
|
||||
"oakcommon XML reader unavailable".to_string(),
|
||||
"oak_core XML reader unavailable".to_string(),
|
||||
))?;
|
||||
|
||||
// Detect the root element.
|
||||
|
||||
@@ -570,7 +570,7 @@ fn lerp_arr4(a: &[f64; 4], b: &[f64; 4], t: f64) -> [f64; 4] {
|
||||
]
|
||||
}
|
||||
|
||||
/// Video parameters (plain data; mirrors oakcommon `VideoParams` C++
|
||||
/// Video parameters (plain data; mirrors oak_core `VideoParams` C++
|
||||
/// fields — the C ABI marshals field-by-field).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct VideoParams {
|
||||
|
||||
@@ -117,7 +117,7 @@ Runtime dependencies (crates.io):
|
||||
order.
|
||||
- `quick-xml` 0.41 — streaming XML codec for the FCPXML layer
|
||||
(`src/fcpxml.rs`). Same major version the other Rust modules use
|
||||
(`crates/oakcommon/Cargo.toml`).
|
||||
(`crates/oak_core/Cargo.toml`).
|
||||
- `oakcore-rs` (path: `../../oakcore-rs`) — shared `Rational` value type
|
||||
(used by the `Rational::from_double` port and for exact FCPXML
|
||||
rational-time conversion); same path dependency the other bindings use.
|
||||
|
||||
Generated
+3
-3
@@ -756,7 +756,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oakcommon"
|
||||
name = "oak_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
@@ -775,7 +775,7 @@ name = "oaknode"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oakcodec",
|
||||
"oakcommon",
|
||||
"oak_core",
|
||||
"oakcore-rs",
|
||||
"oakundo",
|
||||
]
|
||||
@@ -795,7 +795,7 @@ dependencies = [
|
||||
name = "oakrender"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oakcommon",
|
||||
"oak_core",
|
||||
"oakcore-rs",
|
||||
"ocio-rs",
|
||||
"wgpu",
|
||||
|
||||
@@ -16,7 +16,7 @@ crate-type = ["staticlib", "rlib"]
|
||||
# 模块桥改直接 Rust 调用(单库化,见 docs/zh/plans/riir/single-lib.md);
|
||||
# 只允许零依赖起步。确需引入的 crate 必须在 README 登记理由。
|
||||
oak-core = { path = "../oak-core" }
|
||||
oak-undo = { path = "../oak-undo" }
|
||||
oak-undo = { path = "../oak-undo" }
|
||||
oak-node = { path = "../oak-node" }
|
||||
oak-render = { path = "../oak-render" }
|
||||
# Error derive(Display + std::error::Error,见 src/error.rs);理由已登记 README。
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
//! clip handle 即 `&props`)。
|
||||
//!
|
||||
//! 单库化后 oakrender 的 ffi 已删除:帧访问走
|
||||
//! [`oak_render::texture::Texture::to_frame`] 值路径(GPU 纹理经后端
|
||||
//! [`oak_core::texture::Texture::to_frame`] 值路径(GPU 纹理经后端
|
||||
//! 下载、CPU 纹理克隆),帧释放随值 drop 自动发生(原
|
||||
//! `texture_get_frame`/`frame_free` 句柄调用面随桩删除)。
|
||||
|
||||
@@ -89,7 +89,7 @@ impl ClipInstance {
|
||||
props.set_one(
|
||||
crate::host::PROP_CLIP_COLOURSPACE,
|
||||
crate::property::Value::String(
|
||||
std::ffi::CString::new(oak_render::color::pipeline_working_ofx_name()).unwrap(),
|
||||
std::ffi::CString::new(oak_core::color::pipeline_working_ofx_name()).unwrap(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -192,8 +192,8 @@ impl ClipInstance {
|
||||
scale: RenderScale,
|
||||
region: Option<OfxRectD>,
|
||||
) -> crate::error::Result<crate::image::Image> {
|
||||
use crate::render::PIXEL_FORMAT_F32;
|
||||
use crate::error::Error;
|
||||
use crate::render::PIXEL_FORMAT_F32;
|
||||
|
||||
let _ = (time, scale);
|
||||
if region.is_some() {
|
||||
@@ -333,7 +333,7 @@ impl ClipInstance {
|
||||
/// 输出纹理由 oakrender 侧创建并经 [`Self::set_output_texture`]
|
||||
/// 挂入——本函数取该纹理的 CPU 帧(GPU 纹理经后端下载,写回后
|
||||
/// 对 `Texture::Gpu` 再经
|
||||
/// [`oak_render::backend::GpuContextLike::upload`] 上传),按帧
|
||||
/// [`oak_core::backend::GpuContextLike::upload`] 上传),按帧
|
||||
/// 参数校验 F32 与尺寸后整帧拷贝图像像素(全链路 F32;C++
|
||||
/// pluginrenderer 的 `readback/wrap` 路径第 1 期以 CPU 拷贝表达,
|
||||
/// GL 走 [`crate::render`] 的 `// [P2]`)。未挂输出纹理
|
||||
@@ -343,8 +343,8 @@ impl ClipInstance {
|
||||
&self,
|
||||
image: &crate::image::Image,
|
||||
) -> crate::error::Result<crate::render::Texture> {
|
||||
use crate::render::{texture_get_frame, PIXEL_FORMAT_F32};
|
||||
use crate::error::Error;
|
||||
use crate::render::{texture_get_frame, PIXEL_FORMAT_F32};
|
||||
|
||||
let texture = self
|
||||
.output_texture
|
||||
@@ -396,7 +396,7 @@ impl ClipInstance {
|
||||
/// 本 clip 的时间域(clipGetFrameRange)。
|
||||
///
|
||||
/// `// TODO(value-model)`:输入范围经 oakrender 帧的时间基推导
|
||||
/// (time_base)——随 clip 迁移到 `oak_render::texture::Texture`
|
||||
/// (time_base)——随 clip 迁移到 `oak_core::texture::Texture`
|
||||
/// 值模型落地。
|
||||
pub fn frame_range(&self) -> crate::error::Result<OfxRangeD> {
|
||||
let _ = OfxRangeD::default();
|
||||
|
||||
@@ -48,7 +48,7 @@ use oak_node::factory::{DynNodeConstructor, DynamicNodeMeta};
|
||||
use oak_node::input::{flags as input_flags, Input};
|
||||
use oak_node::node::{Category, NodeBehavior, NodeCore};
|
||||
use oak_node::nodes::plugin::{
|
||||
PluginInstanceHandle, PluginNode, SOURCE_CLIP, TEXTURE_INPUT,
|
||||
PluginInstanceHandle, PluginNode, SOURCE_CLIP, TEXTURE_INPUT,
|
||||
};
|
||||
use oak_node::value::{NodeValue, ValueType};
|
||||
|
||||
@@ -832,10 +832,10 @@ fn set_text_param(inst: &Instance, key: &str, text: &str) {
|
||||
/// executor 槽实现:JobSpec::Plugin → render_driver::render_frame。
|
||||
fn execute_plugin_job(
|
||||
req: &oak_render::eval::PluginJobRequest<'_>,
|
||||
) -> oak_render::error::Result<oak_render::texture::Texture> {
|
||||
use oak_render::error::Error;
|
||||
) -> oak_render::error::Result<oak_core::texture::Texture> {
|
||||
use oak_render::error::Error;
|
||||
|
||||
let oak_render::eval::JobSpec::Plugin {
|
||||
let oak_render::eval::JobSpec::Plugin {
|
||||
instance,
|
||||
time,
|
||||
effect_input_id,
|
||||
@@ -880,7 +880,7 @@ fn execute_plugin_job(
|
||||
)?;
|
||||
let job = crate::render_driver::RenderJob {
|
||||
time: *time,
|
||||
dst: oak_render::texture::Texture::wrap_frame(dst_frame),
|
||||
dst: oak_core::texture::Texture::wrap_frame(dst_frame),
|
||||
src: Some(req.src.clone()),
|
||||
effect_input_id: effect_input_id.clone(),
|
||||
inputs: inputs.clone(),
|
||||
@@ -962,9 +962,9 @@ fn shared_plugin_instance(identifier: &str) -> Option<u64> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
/// Records what the mock plugin entry saw, so the push-button test can
|
||||
/// Records what the mock plugin entry saw, so the push-button test can
|
||||
/// assert the kOfxActionInstanceChanged routing and its inArgs.
|
||||
static PUSH_ENTRY_CALLS: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
|
||||
|
||||
@@ -1174,10 +1174,10 @@ mod tests {
|
||||
};
|
||||
let out = execute_plugin_job(&oak_render::eval::PluginJobRequest {
|
||||
spec: &spec,
|
||||
src: oak_render::texture::Texture::wrap_frame(frame),
|
||||
src: oak_core::texture::Texture::wrap_frame(frame),
|
||||
})
|
||||
.expect("the GL-capable render succeeds (no MissingHostFeature purple)");
|
||||
let oak_render::texture::Texture::Cpu(out_frame) = &out else {
|
||||
let oak_core::texture::Texture::Cpu(out_frame) = &out else {
|
||||
panic!("a CPU frame comes back");
|
||||
};
|
||||
let mut px = [0f32; 4];
|
||||
@@ -1191,14 +1191,14 @@ mod tests {
|
||||
|
||||
/// 构造一个只含 push-button 参数的最小实例(直接登记进注册表)。
|
||||
fn instance_with_push_button() -> u64 {
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use crate::descriptor::EffectDescriptor;
|
||||
use crate::handle::RefBox;
|
||||
use crate::host::Plugin;
|
||||
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
|
||||
use crate::descriptor::EffectDescriptor;
|
||||
use crate::handle::RefBox;
|
||||
use crate::host::Plugin;
|
||||
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
unsafe extern "C" fn dummy_entry(
|
||||
unsafe extern "C" fn dummy_entry(
|
||||
action: *const c_char,
|
||||
_: *const c_void,
|
||||
in_args: *mut c_void,
|
||||
@@ -1289,14 +1289,14 @@ mod tests {
|
||||
/// 构造一个带 parametric 参数(维度 2、自定义 range、双维 UI 颜色
|
||||
/// 已配置)与一个 String 参数的实例(直接登记进注册表)。
|
||||
fn instance_with_parametric() -> u64 {
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use crate::descriptor::EffectDescriptor;
|
||||
use crate::handle::RefBox;
|
||||
use crate::host::Plugin;
|
||||
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
|
||||
use crate::descriptor::EffectDescriptor;
|
||||
use crate::handle::RefBox;
|
||||
use crate::host::Plugin;
|
||||
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
unsafe extern "C" fn dummy_entry(
|
||||
unsafe extern "C" fn dummy_entry(
|
||||
_: *const c_char,
|
||||
_: *const c_void,
|
||||
_: *mut c_void,
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
//! oakrender 桥(single-lib unification):纹理/帧值类型与渲染调用面。
|
||||
//!
|
||||
//! oakrender 的 C ABI 已删除(单库化):纹理是
|
||||
//! [`oak_render::texture::Texture`](value enum,无句柄),CPU 帧是
|
||||
//! [`oak_render::texture::Frame`]。本 crate 的 render 驱动与 GL suite
|
||||
//! [`oak_core::texture::Texture`](value enum,无句柄),CPU 帧是
|
||||
//! [`oak_core::texture::Frame`]。本 crate 的 render 驱动与 GL suite
|
||||
//! 直接持值类型:
|
||||
//!
|
||||
//! - [`Texture`] = [`oak_render::texture::Texture`](值别名;
|
||||
//! - [`Texture`] = [`oak_core::texture::Texture`](值别名;
|
||||
//! clone 即引用语义,drop 自动释放后端 token——原 `texture_free`/
|
||||
//! `frame_free` 调用面随值模型删除);
|
||||
//! - [`Frame`] = [`oak_render::texture::Frame`](值别名);
|
||||
//! - [`Renderer`] = `Arc<dyn oak_render::backend::GpuContextLike>`
|
||||
//! - [`Frame`] = [`oak_core::texture::Frame`](值别名);
|
||||
//! - [`Renderer`] = `Arc<dyn oak_core::backend::GpuContextLike>`
|
||||
//! (渲染器即 oakrender 后端上下文,facade 经
|
||||
//! [`oak_render::backend::GpuContext::create`] 创建);
|
||||
//! [`oak_core::backend::GpuContext::create`] 创建);
|
||||
//! - [`VideoParams`] 直接别名 oakrender 的
|
||||
//! [`oak_render::frame::VideoParamsPod`](同布局 POD);
|
||||
//! [`oak_core::frame::VideoParamsPod`](同布局 POD);
|
||||
//! - 像素格式常量直接别名 [`oak_core::PixelFormat`]。
|
||||
//!
|
||||
//! 保留桩(GPU 相关、wgpu 模型无直接 Rust 等价物):
|
||||
@@ -42,7 +42,7 @@
|
||||
/// `oakrender_video_params` POD — single-lib unification: aliases the
|
||||
/// oakrender crate's struct (identical layout;
|
||||
/// include/render/renderer.h:78).
|
||||
pub type VideoParams = oak_render::frame::VideoParamsPod;
|
||||
pub type VideoParams = oak_core::frame::VideoParamsPod;
|
||||
|
||||
/// olive::PixelFormat::Format 的 f32 值。
|
||||
pub const PIXEL_FORMAT_F32: i32 = oak_core::PixelFormat::F32 as i32;
|
||||
@@ -51,13 +51,13 @@ pub const PIXEL_FORMAT_U8: i32 = oak_core::PixelFormat::U8 as i32;
|
||||
|
||||
/// oakrender 渲染器(后端上下文;Arc 共享,GPU 纹理据此 upload/
|
||||
/// download/blit——无需独立渲染器句柄)。
|
||||
pub type Renderer = std::sync::Arc<dyn oak_render::backend::GpuContextLike>;
|
||||
pub type Renderer = std::sync::Arc<dyn oak_core::backend::GpuContextLike>;
|
||||
|
||||
/// oakrender 纹理(值型;GPU 或 CPU 包装)。
|
||||
pub type Texture = oak_render::texture::Texture;
|
||||
pub type Texture = oak_core::texture::Texture;
|
||||
|
||||
/// oakrender CPU 帧(值型)。
|
||||
pub type Frame = oak_render::texture::Frame;
|
||||
pub type Frame = oak_core::texture::Frame;
|
||||
|
||||
// ---- 桥调用面(值型实现;原 CHandle 桩随单库化重写)----------------------
|
||||
|
||||
@@ -138,10 +138,10 @@ pub fn texture_id(_texture: &Texture) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
/// 渲染器是否为 OpenGL 后端([`oak_render::backend::BackendKind::Gl`];
|
||||
/// 渲染器是否为 OpenGL 后端([`oak_core::backend::BackendKind::Gl`];
|
||||
/// 原 `renderer_is_open_gl` 的句柄形态改为后端上下文 kind 查询)。
|
||||
pub fn renderer_is_open_gl(renderer: &Renderer) -> bool {
|
||||
renderer.kind() == oak_render::backend::BackendKind::Gl
|
||||
renderer.kind() == oak_core::backend::BackendKind::Gl
|
||||
}
|
||||
|
||||
/// A GL-kind marker context for [`RenderJob::renderer`]. The field's only
|
||||
@@ -154,22 +154,22 @@ pub fn renderer_is_open_gl(renderer: &Renderer) -> bool {
|
||||
/// oak-worker, where gl_bridge creates its own offscreen context.
|
||||
pub struct GlKindMarker;
|
||||
|
||||
impl oak_render::backend::GpuContextLike for GlKindMarker {
|
||||
fn kind(&self) -> oak_render::backend::BackendKind {
|
||||
oak_render::backend::BackendKind::Gl
|
||||
impl oak_core::backend::GpuContextLike for GlKindMarker {
|
||||
fn kind(&self) -> oak_core::backend::BackendKind {
|
||||
oak_core::backend::BackendKind::Gl
|
||||
}
|
||||
fn destroy_texture(&self, _token: u64) {}
|
||||
fn upload(&self, _token: u64, _frame: &oak_render::texture::Frame) -> oak_render::error::Result<()> {
|
||||
fn upload(&self, _token: u64, _frame: &oak_core::texture::Frame) -> oak_render::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn download(&self, _token: u64) -> oak_render::error::Result<oak_render::texture::Frame> {
|
||||
Ok(oak_render::texture::Frame::new())
|
||||
fn download(&self, _token: u64) -> oak_render::error::Result<oak_core::texture::Frame> {
|
||||
Ok(oak_core::texture::Frame::new())
|
||||
}
|
||||
fn blit(
|
||||
&self,
|
||||
_src: u64,
|
||||
_dst: u64,
|
||||
_processor: Option<&oak_render::color::ColorProcessor>,
|
||||
&self,
|
||||
_src: u64,
|
||||
_dst: u64,
|
||||
_processor: Option<&oak_core::color::ColorProcessor>,
|
||||
) -> oak_render::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -181,9 +181,9 @@ mod tests {
|
||||
|
||||
/// 测试渲染器:最小 GpuContextLike 假实现(无 GPU 适配器需求)。
|
||||
struct FakeGpu;
|
||||
impl oak_render::backend::GpuContextLike for FakeGpu {
|
||||
fn kind(&self) -> oak_render::backend::BackendKind {
|
||||
oak_render::backend::BackendKind::Cpu
|
||||
impl oak_core::backend::GpuContextLike for FakeGpu {
|
||||
fn kind(&self) -> oak_core::backend::BackendKind {
|
||||
oak_core::backend::BackendKind::Cpu
|
||||
}
|
||||
fn destroy_texture(&self, _token: u64) {}
|
||||
fn upload(&self, _token: u64, _frame: &Frame) -> oak_render::error::Result<()> {
|
||||
@@ -193,10 +193,10 @@ mod tests {
|
||||
Ok(Frame::new())
|
||||
}
|
||||
fn blit(
|
||||
&self,
|
||||
_src: u64,
|
||||
_dst: u64,
|
||||
_processor: Option<&oak_render::color::ColorProcessor>,
|
||||
&self,
|
||||
_src: u64,
|
||||
_dst: u64,
|
||||
_processor: Option<&oak_core::color::ColorProcessor>,
|
||||
) -> oak_render::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ unsafe extern "C" fn clip_get_property_set(clip: *mut c_void, out: *mut *mut c_v
|
||||
/// (HS:2003-2049;`getImage` 失败 → Failed)。
|
||||
///
|
||||
/// `// TODO(clip)`:fetch_image 待 clip 迁移到
|
||||
/// `oak_render::texture::Texture` 值模型(当前帧访问为本地桩)。
|
||||
/// `oak_core::texture::Texture` 值模型(当前帧访问为本地桩)。
|
||||
unsafe extern "C" fn clip_get_image(
|
||||
clip: *mut c_void,
|
||||
time: c_double,
|
||||
|
||||
@@ -334,26 +334,26 @@ mod tests {
|
||||
assert!(gl_ctx().is_none());
|
||||
// 最小 GpuContextLike 假实现(无 GPU 适配器需求)。
|
||||
struct FakeGpu;
|
||||
impl oak_render::backend::GpuContextLike for FakeGpu {
|
||||
fn kind(&self) -> oak_render::backend::BackendKind {
|
||||
oak_render::backend::BackendKind::Cpu
|
||||
impl oak_core::backend::GpuContextLike for FakeGpu {
|
||||
fn kind(&self) -> oak_core::backend::BackendKind {
|
||||
oak_core::backend::BackendKind::Cpu
|
||||
}
|
||||
fn destroy_texture(&self, _token: u64) {}
|
||||
fn upload(
|
||||
&self,
|
||||
_token: u64,
|
||||
_frame: &oak_render::texture::Frame,
|
||||
_frame: &oak_core::texture::Frame,
|
||||
) -> oak_render::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn download(&self, _token: u64) -> oak_render::error::Result<oak_render::texture::Frame> {
|
||||
Ok(oak_render::texture::Frame::new())
|
||||
fn download(&self, _token: u64) -> oak_render::error::Result<oak_core::texture::Frame> {
|
||||
Ok(oak_core::texture::Frame::new())
|
||||
}
|
||||
fn blit(
|
||||
&self,
|
||||
_src: u64,
|
||||
_dst: u64,
|
||||
_processor: Option<&oak_render::color::ColorProcessor>,
|
||||
&self,
|
||||
_src: u64,
|
||||
_dst: u64,
|
||||
_processor: Option<&oak_core::color::ColorProcessor>,
|
||||
) -> oak_render::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -369,7 +369,7 @@ mod tests {
|
||||
let got = gl_ctx().unwrap();
|
||||
assert_eq!(
|
||||
got.renderer.kind(),
|
||||
oak_render::backend::BackendKind::Cpu
|
||||
oak_core::backend::BackendKind::Cpu
|
||||
);
|
||||
assert!(got.output_texture.is_dummy());
|
||||
assert_eq!(got.gl_pixel_depth, "OfxBitDepthFloat");
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
//! 运行时装配成 oak-test-plugin.ofx.bundle):filter 上下文、
|
||||
//! Double 参数 gain、双 clip(Source/Output)。插件未构建时相关
|
||||
//! 用例经 [`skip`] 提前返回。
|
||||
//! 单库化后像素路径经 oakrender 值模型(`oak_render::texture::Texture`)
|
||||
//! 单库化后像素路径经 oakrender 值模型(`oak_core::texture::Texture`)
|
||||
//! 驱动;渲染 goldens 待该迁移落地。
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -42,8 +42,8 @@ use std::sync::Arc;
|
||||
use oak_core::{PixelFormat, Rational};
|
||||
use oak_plugin::host::Host;
|
||||
use oak_plugin::render::{Renderer, Texture};
|
||||
use oak_render::backend::{BackendKind, GpuContextLike};
|
||||
use oak_render::texture::Frame;
|
||||
use oak_core::backend::{BackendKind, GpuContextLike};
|
||||
use oak_core::texture::Frame;
|
||||
|
||||
const GL_PLUGIN_ID: &str = "org.oak.test-plugin.gl";
|
||||
|
||||
@@ -67,7 +67,7 @@ impl GpuContextLike for FakeGlRenderer {
|
||||
&self,
|
||||
_src: u64,
|
||||
_dst: u64,
|
||||
_processor: Option<&oak_render::color::ColorProcessor>,
|
||||
_processor: Option<&oak_core::color::ColorProcessor>,
|
||||
) -> oak_render::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ use oak_node::node::{NodeBehavior, NodeCore};
|
||||
use oak_node::traverser::{EvalRequest, Traverser};
|
||||
use oak_node::value::{NodeValue, ValueType};
|
||||
use oak_plugin::host::Host;
|
||||
use oak_render::texture::Texture;
|
||||
use oak_core::texture::Texture;
|
||||
|
||||
const PLUGIN_ID: &str = "org.oak.test-plugin";
|
||||
const IDENTITY_ID: &str = "org.oak.test-plugin.identity";
|
||||
|
||||
Generated
+2
-2
@@ -623,7 +623,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oakcommon"
|
||||
name = "oak_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
@@ -641,7 +641,7 @@ version = "0.1.0"
|
||||
name = "oakrender"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oakcommon",
|
||||
"oak_core",
|
||||
"oakcore-rs",
|
||||
"ocio-rs",
|
||||
"wgpu",
|
||||
|
||||
@@ -10,7 +10,6 @@ crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
oak-core = { path = "../oak-core" }
|
||||
oak-common = { path = "../oak-common" }
|
||||
# Direct Rust calls (single-lib unification): the decode bridge calls
|
||||
# oakcodec's ffi, the node bridge calls oaknode's ffi. Both directions
|
||||
# are acyclic (oakcodec → oakcore-rs/oak-ffmpeg-link; oaknode → oakcodec).
|
||||
|
||||
+15
-11
@@ -62,12 +62,10 @@ frozen, implemented verbatim by `src/ffi.rs`.
|
||||
```
|
||||
src/
|
||||
lib.rs crate doc + module map
|
||||
error.rs error codes (mirrors include/render/error.h)
|
||||
error.rs re-exports oak_core::error (the OAKRENDER_* codes stay as
|
||||
the public-code contract)
|
||||
handle.rs refcounted-handle scaffolding (facade entry points only)
|
||||
texture.rs Texture value type (wraps backend textures / CPU frames)
|
||||
frame.rs VideoParamsPod + Frame helpers
|
||||
cache.rs PlaybackCache / FrameHashCache family + C++-parity disk state
|
||||
color.rs ColorProcessor over ocio-rs + default config + LUT library
|
||||
manager.rs RenderManager singleton + lifecycle + disk cache
|
||||
ticket.rs Ticket arena, params, exactly-once completion delivery
|
||||
worker.rs JobDispatch seam + thread-free InlineDispatcher (audio
|
||||
@@ -84,22 +82,28 @@ src/
|
||||
grow-on-demand segment geometry (S3)
|
||||
autocacher.rs PreviewAutoCacher
|
||||
eval.rs RenderHooks impl: the CPU evaluation seam
|
||||
backend.rs wgpu device/queue/texture management + DisplayRenderer
|
||||
shaderfx.rs effect GLSL→WGSL translation (naga) + std140 uniform
|
||||
packing + the effect runner
|
||||
copier.rs Render-side project copy client (bridge::node)
|
||||
cancelatom.rs the cancellation primitive
|
||||
bridge/ C ABI imports: node.rs, common.rs, codec.rs (dlsym-resolved)
|
||||
ffi.rs include/render/*.h export layer
|
||||
tests/ contract + golden tests (common/ has shared helpers)
|
||||
```
|
||||
|
||||
The value/GPU types — `backend.rs` (wgpu device/queue/texture management
|
||||
+ DisplayRenderer), `color.rs` (ColorProcessor over ocio-rs + default
|
||||
config + LUT library), `texture.rs`, `frame.rs` and the `commonutil.rs`
|
||||
config helpers — moved to `oak-core` in the oak-common/oak-core merge;
|
||||
this crate uses them as `oak_core::*`.
|
||||
|
||||
## Hard rules
|
||||
|
||||
1. `CHandle` only appears at the facade boundary: the crate's internal
|
||||
calls pass Rust types directly; `handle::make_owned`/`get`/`get_mut`
|
||||
are the facade entry points the oakengine stubs call.
|
||||
2. No `unsafe` outside `backend.rs` (GPU FFI), `bridge/`, and the M15
|
||||
process-isolation transport (`ipc.rs` / `procpool.rs`: POSIX shm +
|
||||
SPSC rings; every block carries its own SAFETY comment).
|
||||
2. No `unsafe` outside `handle.rs`, the evaluation seam (`eval.rs`), and
|
||||
the M15 process-isolation transport (`ipc.rs` / `procpool.rs`: POSIX
|
||||
shm + SPSC rings; every block carries its own SAFETY comment). GPU
|
||||
unsafe lives in `oak-core`'s `backend.rs`.
|
||||
3. F32 + ACEScg pipeline invariants are asserted in tests, not in
|
||||
comments (see tests/pipeline_test.rs).
|
||||
|
||||
@@ -118,7 +122,7 @@ tests/ contract + golden tests (common/ has shared helpers)
|
||||
`Error::Failed` for a processor; the CPU path applies the processor
|
||||
in float. `oakrender_color_processor_create_transform` resolves the
|
||||
destination transform against the default config's reference role
|
||||
until the oakcommon color-transform bridge lands.
|
||||
until the oak_core color-transform bridge lands.
|
||||
- **Worker process isolation** — landed in M15: `procpool.rs`
|
||||
(`ProcessDispatcher`) + `scheduler.rs` + `ipc.rs` drive real
|
||||
oak-worker processes (spawn, handshake, batched renders into
|
||||
|
||||
@@ -297,9 +297,9 @@ mod tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use crate::frame::VideoParamsPod;
|
||||
use crate::texture::{Frame, Texture};
|
||||
use crate::worker::{InlineDispatcher, JobDispatch};
|
||||
use oak_core::frame::VideoParamsPod;
|
||||
use oak_core::texture::{Frame, Texture};
|
||||
|
||||
fn frame_producer() -> crate::ticket::Producer {
|
||||
Arc::new(|_, _| {
|
||||
|
||||
@@ -261,7 +261,7 @@ pub struct PlaybackCache {
|
||||
impl PlaybackCache {
|
||||
/// New cache for `owner` (C++ `PlaybackCache(parent)`).
|
||||
pub fn new(kind: CacheKind, owner: OwnerIdentity) -> Self {
|
||||
let disk_dir = crate::commonutil::default_disk_cache_path();
|
||||
let disk_dir = oak_core::commonutil::default_disk_cache_path();
|
||||
Self {
|
||||
kind,
|
||||
owner,
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
//! flag shared between a render/encode caller and its worker.
|
||||
//!
|
||||
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): the
|
||||
//! implementation moved to oakcommon; this module re-exports it so the
|
||||
//! implementation moved to oak_core; this module re-exports it so the
|
||||
//! render ffi's `oakrender_cancelatom_*` exports (and their C-ABI
|
||||
//! consumers, e.g. oakcodec) keep working unchanged.
|
||||
|
||||
/// Shared cancellation atom (oakcommon).
|
||||
pub use oak_common::cancelatom::CancelAtom;
|
||||
/// Shared cancellation atom (oak_core).
|
||||
pub use oak_core::cancelatom::CancelAtom;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user