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

Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
This commit is contained in:
2026-08-10 20:24:25 +08:00
parent f8540e3892
commit 013a175707
4212 changed files with 8331 additions and 2274987 deletions
+341
View File
@@ -0,0 +1,341 @@
// 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/>.
//! `olive::ConformManager` — pcm waveform cache files for fast scrubbing.
//!
//! Mirrors `src/codec/src/conformmanager.h`. Stateless (NOTES.md): actual
//! conform work is delegated to the global task submit callback
//! ([`crate::task`]); with no registrar the state queries report
//! `Unavailable`. Deterministic per-channel filenames derive from the
//! source + target audio params.
use std::ffi::CString;
use std::path::Path;
/// Conform state of one audio stream.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum ConformState {
/// Conform files exist.
Exists = 0,
/// Conform is being generated.
Generating = 1,
/// No task registrar; conform unavailable.
Unavailable = 2,
}
/// `olive::ConformManager` — stateless conform query/produce manager.
pub struct ConformManager;
impl ConformManager {
/// The process-wide ConformManager singleton.
pub fn instance() -> &'static ConformManager {
static INSTANCE: ConformManager = ConformManager;
&INSTANCE
}
/// Query (and when possible start) the conform of one audio stream.
///
/// `wait != 0` treats a post-submit miss as `Unavailable`; `wait == 0`
/// reports it as `Generating`. Without a task registrar the result is
/// always `Unavailable`.
pub fn get_conform_state(
&self,
cache_path: &str,
source_filename: &str,
stream_index: i32,
sample_rate: i32,
channel_layout: u64,
sample_format: i32,
wait: bool,
) -> crate::error::Result<ConformState> {
let filenames = conform_filenames(
cache_path,
source_filename,
stream_index,
sample_rate,
sample_format,
channel_layout,
);
// Return existing conform if it exists.
if all_conforms_exist(&filenames) {
return Ok(ConformState::Exists);
}
// Interim state (pre-M8): no task system, conform cannot be generated.
if !crate::task::task_submit_is_registered() {
return Ok(ConformState::Unavailable);
}
// The task owns the ".working" temporary names and the rename to the
// final per-channel filenames on success; output_filename carries the
// first channel's final path and the task derives the siblings.
let req = crate::task::TaskRequest {
kind: crate::task::TaskKind::Conform,
input_filename: source_filename,
output_filename: filenames.first().map(String::as_str).unwrap_or(""),
stream_index,
sample_rate,
channel_layout,
sample_format,
proxy_width: 0,
proxy_height: 0,
};
// Interim simplification: submission is synchronous — we always wait
// for the submit to return, regardless of `wait`.
if crate::task::submit_task(&req).is_err() {
return Ok(ConformState::Unavailable);
}
if all_conforms_exist(&filenames) {
return Ok(ConformState::Exists);
}
if wait {
// Synchronous wait already happened and the conform still does not
// exist: report the wait as failed.
return Ok(ConformState::Unavailable);
}
Ok(ConformState::Generating)
}
/// Number of conform (pcm) files for the given stream/params — one per
/// channel; 0 on invalid arguments.
pub fn get_conform_filename_count(
&self,
_cache_path: &str,
_source_filename: &str,
_stream_index: i32,
_sample_rate: i32,
channel_layout: u64,
_sample_format: i32,
) -> usize {
channel_layout.count_ones() as usize
}
/// The `index`-th conform filename.
pub fn get_conform_filename(
&self,
cache_path: &str,
source_filename: &str,
stream_index: i32,
sample_rate: i32,
channel_layout: u64,
sample_format: i32,
index: usize,
) -> crate::error::Result<String> {
let filenames = conform_filenames(
cache_path,
source_filename,
stream_index,
sample_rate,
sample_format,
channel_layout,
);
filenames
.get(index)
.cloned()
.ok_or(crate::error::Error::NotFound)
}
}
/// Deterministic conform base name plus per-channel pcm filenames, mirroring
/// `ConformManager::get_conformed_filename`: one file per channel under
/// `cache_path`, named `<identifier>-<stream>.<rate>.<format>.<layout>.<i>.pcm`.
fn conform_filenames(
cache_path: &str,
source_filename: &str,
stream_index: i32,
sample_rate: i32,
sample_format: i32,
channel_layout: u64,
) -> Vec<String> {
let count = channel_layout.count_ones() as usize;
let base = format!(
"{}-{}.{}.{}.{}",
unique_file_identifier(source_filename),
stream_index,
sample_rate,
sample_format,
channel_layout,
);
let mut out = Vec::with_capacity(count);
for i in 0..count {
let p = Path::new(cache_path).join(format!("{}.{}.pcm", base, i));
out.push(p.to_string_lossy().into_owned());
}
out
}
/// `oakcommon_filefunctions_get_unique_file_identifier` wrapper (the bridge
/// returns a 64-bit id directly).
fn unique_file_identifier(filename: &str) -> String {
let c = match CString::new(filename) {
Ok(c) => c,
Err(_) => return String::new(),
};
// # Safety: `c` is a valid NUL-terminated C string alive for the call.
let id = unsafe {
crate::bridge::common::oakcommon_filefunctions_get_unique_file_identifier(c.as_ptr())
};
format!("{}", id)
}
/// True when every conform filename already exists on disk.
fn all_conforms_exist(filenames: &[String]) -> bool {
filenames.iter().all(|f| Path::new(f).exists())
}
/// Shared test support: serializes access to the global task-submit registry
/// (unit tests run in parallel and would otherwise clear each other's
/// registration) and provides a callback that accepts any task.
#[cfg(test)]
pub(crate) mod test_util {
use crate::error::OAKCODEC_OK;
use crate::task::OakCodecTaskRequest;
/// Serializes every test that mutates the task-submit registry.
pub static REG_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
/// A task-submit callback that accepts every request (no-op).
pub unsafe extern "C" fn accept_cb(
_req: *const OakCodecTaskRequest,
_ud: *mut std::ffi::c_void,
) -> i32 {
OAKCODEC_OK
}
}
#[cfg(test)]
mod tests {
use super::*;
fn temp_subdir(name: &str) -> String {
let dir = std::env::temp_dir().join(format!(
"oakcodec_conform_{}_{}",
name,
std::process::id()
));
let _ = std::fs::create_dir_all(&dir);
dir.to_string_lossy().into_owned()
}
fn fnv1a64(bytes: &[u8]) -> u64 {
let mut h: u64 = 14695981039346656037;
for &b in bytes {
h ^= b as u64;
h = h.wrapping_mul(1099511628211);
}
h
}
#[test]
fn unique_identifier_matches_bridge_hash() {
// The test stub computes an FNV-1a-64 of the path bytes.
let expected = format!("{}", fnv1a64(b"media.mp4") as i64);
assert_eq!(unique_file_identifier("media.mp4"), expected);
// Deterministic: same input, same id.
assert_eq!(
unique_file_identifier("media.mp4"),
unique_file_identifier("media.mp4")
);
// Different input, different id.
assert_ne!(
unique_file_identifier("media.mp4"),
unique_file_identifier("other.mp4")
);
}
#[test]
fn filename_count_from_channel_layout() {
let m = ConformManager::instance();
assert_eq!(m.get_conform_filename_count("c", "s", 0, 48000, 0x3, 0), 2); // stereo
assert_eq!(m.get_conform_filename_count("c", "s", 0, 48000, 0x4, 0), 1); // mono
assert_eq!(m.get_conform_filename_count("c", "s", 0, 48000, 0, 0), 0); // invalid
assert_eq!(
m.get_conform_filename_count("c", "s", 0, 48000, 0x60F, 0),
6
); // 5.1
}
#[test]
fn conform_filename_derivation_and_range() {
let m = ConformManager::instance();
let cache = temp_subdir("names");
let id = fnv1a64(b"media.mp4") as i64;
let base = format!("{}-0.48000.0.3", id);
let f0 = m
.get_conform_filename(&cache, "media.mp4", 0, 48000, 0x3, 0, 0)
.unwrap();
let f1 = m
.get_conform_filename(&cache, "media.mp4", 0, 48000, 0x3, 0, 1)
.unwrap();
assert_eq!(f0, format!("{}/{}.0.pcm", cache, base));
assert_eq!(f1, format!("{}/{}.1.pcm", cache, base));
// Out of range.
assert!(matches!(
m.get_conform_filename(&cache, "media.mp4", 0, 48000, 0x3, 0, 5),
Err(crate::error::Error::NotFound)
));
}
#[test]
fn get_conform_state_unavailable_without_registrar() {
let _g = super::test_util::REG_LOCK.lock().unwrap();
// Ensure no registrar is left over.
crate::task::set_task_submit_cb_extern(None, std::ptr::null_mut());
let cache = temp_subdir("unavail");
let s = ConformManager::instance()
.get_conform_state(&cache, "missing.mp4", 0, 48000, 0x3, 0, false)
.unwrap();
assert_eq!(s, ConformState::Unavailable);
}
#[test]
fn get_conform_state_exists_when_files_present() {
let cache = temp_subdir("exists");
let m = ConformManager::instance();
for i in 0..2 {
let f = m
.get_conform_filename(&cache, "media.mp4", 0, 48000, 0x3, 0, i)
.unwrap();
std::fs::write(&f, b"pcm").unwrap();
}
let s = m
.get_conform_state(&cache, "media.mp4", 0, 48000, 0x3, 0, false)
.unwrap();
assert_eq!(s, ConformState::Exists);
}
#[test]
fn get_conform_state_generating_when_registered() {
let _g = super::test_util::REG_LOCK.lock().unwrap();
crate::task::set_task_submit_cb_extern(
Some(super::test_util::accept_cb),
std::ptr::null_mut(),
);
let cache = temp_subdir("generating");
let s = ConformManager::instance()
.get_conform_state(&cache, "missing.mp4", 0, 48000, 0x3, 0, false)
.unwrap();
crate::task::set_task_submit_cb_extern(None, std::ptr::null_mut());
assert_eq!(s, ConformState::Generating);
}
}