Files
oak-editor/crates/oakrender/src/frame.rs
T
Mike-Solar 013a175707 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)
2026-08-10 20:24:25 +08:00

147 lines
4.4 KiB
Rust

// 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/>.
//! CPU frame payloads and the [`VideoParamsPod`] value (the Rust mirror of
//! the `oakrender_video_params` POD in `include/render/renderer.h`).
//!
//! The C ABI frame functions (`oakrender_codec_frame_*`) marshal this
//! type; the FFI layer stores [`Frame`] values in `OakCodecFrame` handles.
use oakcore_rs::{PixelFormat, Rational};
/// Mirror of the `oakrender_video_params` POD (include/render/renderer.h,
/// field order and semantics verbatim). Stored inside [`crate::texture::Frame`]
/// so the ffi `*_get_params` exports can report the full metadata.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VideoParamsPod {
/// Frame width (full resolution).
pub width: i32,
/// Frame height (full resolution).
pub height: i32,
/// Frame duration numerator (e.g. 1001/30000 s).
pub time_base_num: i32,
/// Frame duration denominator.
pub time_base_den: i32,
/// `olive::PixelFormat::Format` as int.
pub format: i32,
/// Pixel aspect numerator.
pub pixel_aspect_num: i32,
/// Pixel aspect denominator.
pub pixel_aspect_den: i32,
/// `olive::VideoParams::Interlacing` as int.
pub interlacing: i32,
/// `olive::VideoParams::ColorRange` as int.
pub color_range: i32,
/// Preview resolution divider (1 = full).
pub divider: i32,
/// `olive::VideoParams::Type` (0 = video).
pub video_type: i32,
/// 0/1.
pub premultiplied_alpha: i32,
}
impl Default for VideoParamsPod {
fn default() -> Self {
Self {
width: 0,
height: 0,
time_base_num: 1,
time_base_den: 1,
format: PixelFormat::F32 as i32,
pixel_aspect_num: 1,
pixel_aspect_den: 1,
interlacing: 0,
color_range: 0,
divider: 1,
video_type: 0,
premultiplied_alpha: 0,
}
}
}
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
/// pending).
pub const DEFAULT_WIDTH: i32 = 1920;
/// See [`VideoParamsPod::DEFAULT_WIDTH`].
pub const DEFAULT_HEIGHT: i32 = 1080;
/// Frame rate as a rational (time base flipped; null for a null time
/// base).
pub fn frame_rate(&self) -> Rational {
if self.time_base_num <= 0 || self.time_base_den <= 0 {
return Rational::NULL;
}
Rational::new(self.time_base_den as i64, self.time_base_num as i64)
}
/// The time base (frame duration).
pub fn time_base(&self) -> Rational {
if self.time_base_num <= 0 || self.time_base_den <= 0 {
return Rational::NULL;
}
Rational::new(self.time_base_num as i64, self.time_base_den as i64)
}
/// The internal (fixed) channel count — the C++ engine uses 4 for the
/// video pipeline; not exposed in the POD.
pub const INTERNAL_CHANNEL_COUNT: i32 = 4;
/// Pixel dimensions of the data buffer honoring the preview divider
/// (C++ `VideoParams::effective_width/height` with a divider).
pub fn effective_width(&self) -> i32 {
(self.width / self.divider.max(1)).max(0)
}
/// See [`VideoParamsPod::effective_width`].
pub fn effective_height(&self) -> i32 {
(self.height / self.divider.max(1)).max(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pod_defaults() {
let p = VideoParamsPod::default();
assert_eq!(p.format, PixelFormat::F32 as i32);
assert_eq!(p.time_base(), Rational::new(1, 1));
}
#[test]
fn effective_size_honors_divider() {
let mut p = VideoParamsPod::default();
p.width = 3840;
p.height = 2160;
p.divider = 2;
assert_eq!(p.effective_width(), 1920);
assert_eq!(p.effective_height(), 1080);
}
#[test]
fn frame_rate_is_flipped_time_base() {
let mut p = VideoParamsPod::default();
p.time_base_num = 1001;
p.time_base_den = 30000;
assert_eq!(p.frame_rate(), Rational::new(30000, 1001));
assert_eq!(p.time_base(), Rational::new(1001, 30000));
}
}