Files
effectcraft/crates/engine/src/media_cache.rs
T
Brandon ThomasandClaude Opus 5.5 f855a4b55b M14.5: wire the remaining settings; keyframe assistants and labels; AE menu entries
Settings: 56 of 59 not-yet-wired settings now change behaviour (nested-comp switches,
constant vertex count, related-item time sync, match-name pick whip, split-layer order,
Home on open, motion path span, internal wireframes, zoom quality, label-coloured tabs,
mask colour cycling, gradients, grid/guide styles, text engine, font preview/recent/native
names, missing frames, unlabeled alpha, drag import, default output folder, segmenting,
bit depth names, preview sample rate, disk/media/conformed-audio caches, RAM reserve and
low-memory reduction, Video Preview window, 3D reference axes/extended viewer/draft
shadows, script file gate + File.execute warning, expression editor options and error
banner). Three stay display-only and are documented.

Features: Convert Audio to Keyframes, RPF Camera Import (JSON/CSV camera data),
keyframe colour labels + Select Keyframe Label Group, Recent Animation Presets.
Menus: Edit > History, File > Import Recent Footage, Replace Footage > With Layered
Comp, Save a Copy As XML (.ecprojx, opens again), Composition > VR (Create VR
Environment, Extract Cubemap), Assign Shortcut to 3D view / workspace, Window viewer
labels + open viewers, Help > System Compatibility Report / Enable Logging / Reveal
Logging File. Dynamic menu nodes (@dynamic:…) shared by the egui and native menus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 02:45:44 -04:00

66 lines
2.6 KiB
Rust

//! The media cache (Settings ▸ Disk ▸ Database and Cache Folder): audio waveform summaries kept
//! on disk so a footage file is summarised once, not in every session.
//!
//! A summary is stored as `Peaks/<hash>_<bins>.ecpk` (raw little-endian `f32` quadruples
//! `[min L, max L, min R, max R]` per bin), named by a hash of the footage path, its size and
//! modification time, so an edited file is summarised again.
use std::path::{Path, PathBuf};
/// The peak file of footage `path` at `bins` bins per second in the media cache `folder`
/// (`None` when the file can't be inspected).
pub fn peaks_path(folder: &Path, path: &str, bins: u32) -> Option<PathBuf> {
use std::hash::{Hash, Hasher};
let meta = std::fs::metadata(path).ok()?;
let mut h = std::collections::hash_map::DefaultHasher::new();
(path, meta.len(), meta.modified().ok()).hash(&mut h);
Some(folder.join("Peaks").join(format!("{:016x}_{bins}.ecpk", h.finish())))
}
/// Read a stored summary.
pub fn load_peaks(file: &Path) -> Option<Vec<[f32; 4]>> {
let b = std::fs::read(file).ok()?;
if b.len() % 16 != 0 {
return None;
}
let f = |c: &[u8]| f32::from_le_bytes([c[0], c[1], c[2], c[3]]);
Some(b.chunks_exact(16).map(|c| [f(&c[0..4]), f(&c[4..8]), f(&c[8..12]), f(&c[12..16])]).collect())
}
/// Store a summary (atomically: written beside, then renamed).
pub fn store_peaks(file: &Path, peaks: &[[f32; 4]]) -> std::io::Result<()> {
if let Some(d) = file.parent() {
std::fs::create_dir_all(d)?;
}
let mut b = Vec::with_capacity(peaks.len() * 16);
for p in peaks {
for v in p {
b.extend_from_slice(&v.to_le_bytes());
}
}
crate::config::atomic_write(file, &b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn peaks_round_trip_and_follow_the_file() {
let d = std::env::temp_dir().join(format!("ec-media-cache-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
let src = d.join("a.wav");
std::fs::write(&src, b"one").unwrap();
let p = peaks_path(&d, src.to_str().unwrap(), 200).unwrap();
assert!(p.starts_with(d.join("Peaks")));
let peaks = vec![[-0.5, 0.5, -0.25, 0.25], [0.0, 0.1, -0.1, 0.0]];
store_peaks(&p, &peaks).unwrap();
assert_eq!(load_peaks(&p).unwrap(), peaks);
// A changed file gets another name.
std::fs::write(&src, b"longer").unwrap();
assert_ne!(peaks_path(&d, src.to_str().unwrap(), 200).unwrap(), p);
let _ = std::fs::remove_dir_all(&d);
}
}