Files
soundcraft/crates/model/src/source.rs
T
aiongg 86b31e71d3 Sessions: follow stored source paths only on this computer or the session's share
Audio source and movie paths stored in a .scraft session go through
io::stored_path (craft-docpath) before they reach the file system: a
relative path is taken from the session's folder, and paths on other
computers, devices and URLs are refused. A refused source is reported
as missing with the reason; a refused movie is marked offline and the
Video track and Video window show why instead of opening it. Import
Session Data and crash recovery resolve the same way.
2026-10-16 17:32:35 +00:00

116 lines
3.6 KiB
Rust

//! Audio sources (files in the session's Audio Files folder) and their decoded data, and the
//! movies video clips reference.
use crate::SourceId;
use soundcraft_audio_io::{AudioBuffer, FileFormat, peaks::Peaks};
use std::collections::BTreeMap;
use std::sync::Arc;
/// Metadata for an audio file used by the session (serialised).
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Source {
pub id: SourceId,
pub name: String,
/// Path relative to the session folder (or absolute for referenced media).
pub path: String,
pub channels: u16,
pub frames: u64,
pub sample_rate: u32,
pub format: FileFormat,
/// Original BWF time stamp (samples), if any.
#[serde(default)]
pub time_reference: u64,
/// True when the file has not been written to disk yet (recorded/rendered this session).
#[serde(default)]
pub unsaved: bool,
}
/// A movie used by Video track clips (serialised). The pictures themselves are never stored in
/// the session: the UI opens the movie from `path` and decodes frames on demand.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct VideoSource {
pub id: SourceId,
pub name: String,
/// Absolute path of the movie (referenced, never copied into the session folder).
pub path: String,
#[serde(default)]
pub width: u32,
#[serde(default)]
pub height: u32,
/// Frames per second (0 = unknown).
#[serde(default)]
pub frame_rate: f64,
/// Picture length in seconds.
#[serde(default)]
pub duration: f64,
/// Codec id (`h264`, `prores`, `mjpeg`, `hevc`, …).
#[serde(default)]
pub codec: String,
/// Why the movie is not opened, when its stored path was refused on opening the session.
#[serde(skip)]
pub offline: Option<String>,
}
impl VideoSource {
/// Display aspect ratio (width / height), 16:9 when unknown.
pub fn aspect(&self) -> f64 {
if self.width == 0 || self.height == 0 { 16.0 / 9.0 } else { f64::from(self.width) / f64::from(self.height) }
}
}
/// Decoded audio plus waveform overviews (not serialised).
#[derive(Debug)]
pub struct SourceAudio {
pub buffer: AudioBuffer,
pub peaks: Vec<Peaks>,
}
impl SourceAudio {
pub fn new(buffer: AudioBuffer) -> Self {
let peaks = buffer.channels.iter().map(|c| Peaks::build(c)).collect();
SourceAudio { buffer, peaks }
}
pub fn frames(&self) -> usize {
self.buffer.frames()
}
pub fn channel(&self, ch: usize) -> Option<&[f32]> {
self.buffer.channels.get(ch).map(Vec::as_slice)
}
}
/// Shared decoded audio, keyed by source id.
#[derive(Debug, Clone, Default)]
pub struct SourcePool {
map: BTreeMap<SourceId, Arc<SourceAudio>>,
}
impl SourcePool {
pub fn insert(&mut self, id: SourceId, audio: Arc<SourceAudio>) {
self.map.insert(id, audio);
}
pub fn get(&self, id: SourceId) -> Option<&Arc<SourceAudio>> {
self.map.get(&id)
}
pub fn remove(&mut self, id: SourceId) -> Option<Arc<SourceAudio>> {
self.map.remove(&id)
}
pub fn contains(&self, id: SourceId) -> bool {
self.map.contains_key(&id)
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn ids(&self) -> impl Iterator<Item = SourceId> + '_ {
self.map.keys().copied()
}
}
impl PartialEq for SourcePool {
fn eq(&self, other: &Self) -> bool {
self.map.len() == other.map.len() && self.map.iter().zip(other.map.iter()).all(|((a, x), (b, y))| a == b && Arc::ptr_eq(x, y))
}
}