66 lines
2.6 KiB
Rust
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.as_chunks::<16>().0.iter().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);
|
|
}
|
|
}
|