Warn about missing media when opening a session (#57)

This commit is contained in:
MarekSpodBiedry 2026-10-09 20:28:46 +02:00
parent eac0edd896
commit f8a156d255
3 changed files with 62 additions and 6 deletions

View File

@ -76,8 +76,9 @@ fn info(args: &[String]) -> ExitCode {
let Some(path) = args.first() else { return fail("info FILE") };
if path.ends_with(".scraft") {
let mut e = Engine::default();
if let Err(err) = soundcraft_engine::io::open_session(&mut e, path) {
return fail(err);
match soundcraft_engine::io::open_session(&mut e, path) {
Ok(missing) => warn_missing(&missing),
Err(err) => return fail(err),
}
outln!("{}", soundcraft_engine::inspect::session_text(&e));
return ExitCode::SUCCESS;
@ -135,10 +136,22 @@ fn convert(args: &[String]) -> ExitCode {
}
}
/// One stderr line per media file a session refers to but could not load (the exit code is unchanged).
fn missing_warnings(missing: &[String]) -> Vec<String> {
missing.iter().map(|m| format!("warning: missing media: {m}")).collect()
}
fn warn_missing(missing: &[String]) {
for line in missing_warnings(missing) {
eprintln!("{line}");
}
}
fn load_engine(args: &[String]) -> Result<Engine, String> {
if let Some(p) = arg_value(args, "--in") {
let mut e = Engine::default();
soundcraft_engine::io::open_session(&mut e, &p).map_err(|e| e.to_string())?;
let missing = soundcraft_engine::io::open_session(&mut e, &p).map_err(|e| e.to_string())?;
warn_missing(&missing);
Ok(e)
} else if args.iter().any(|a| a == "--demo" || a == "--sample") {
Ok(soundcraft_engine::demo::demo_engine())
@ -336,3 +349,15 @@ fn parity(args: &[String]) -> ExitCode {
}
ExitCode::SUCCESS
}
#[cfg(test)]
mod tests {
use super::missing_warnings;
#[test]
fn missing_media_gets_one_warning_each() {
assert!(missing_warnings(&[]).is_empty());
let w = missing_warnings(&["a.wav".to_string(), "b.wav: decode failed".to_string()]);
assert_eq!(w, vec!["warning: missing media: a.wav", "warning: missing media: b.wav: decode failed"]);
}
}

View File

@ -11,8 +11,8 @@ pub fn specs() -> Vec<CommandSpec> {
cmd!(noundo "session.new", "New...", ["File"], Some("Cmd+N"), "{name?: 'Untitled', sample_rate?: 48000, bit_depth?: 24, template?: blank|demo}", always, new_session),
cmd!(noundo "session.open", "Open Session...", ["File"], Some("Cmd+O"), "{path}", always, |e, p| {
let path = str_param(p, "path").ok_or_else(|| bad("session.open", "`path` required"))?.to_string();
crate::io::open_session(e, &path)?;
Ok(json!({"name": e.session().name, "tracks": e.session().tracks.len()}))
let missing = crate::io::open_session(e, &path)?;
Ok(json!({"name": e.session().name, "tracks": e.session().tracks.len(), "missing": missing}))
}),
cmd!(noundo "session.close", "Close Session", ["File"], Some("Cmd+Shift+W"), "{}", always, |e, _| { e.replace_session(Session::default()); e.path = None; Ok(json!({})) }),
cmd!(noundo "session.save", "Save Session", ["File"], Some("Cmd+S"), "{path?}", always, |e, p| {
@ -270,3 +270,34 @@ fn write_score(e: &mut Engine, p: &Value, id: &str, svg: bool) -> Result<Value>
let measures = parts.iter().map(|p| p.measures.len()).max().unwrap_or(0);
Ok(json!({"path": path, "parts": parts.len(), "measures": measures}))
}
#[cfg(test)]
mod open_tests {
use super::*;
fn remove_media(dir: &std::path::Path) {
for entry in std::fs::read_dir(dir).into_iter().flatten().flatten() {
let p = entry.path();
if p.is_dir() {
remove_media(&p);
} else if p.extension().is_some_and(|x| x != "scraft") {
let _ = std::fs::remove_file(&p);
}
}
}
#[test]
fn session_open_reports_missing_media() {
let mut e = crate::demo::demo_engine();
let dir = std::env::temp_dir().join(format!("soundcraft-open-missing-{}", std::process::id()));
let path = dir.join("Gone.scraft").to_string_lossy().into_owned();
e.execute("session.save_as", &json!({"path": path})).unwrap();
let mut ok = Engine::default();
assert!(ok.execute("session.open", &json!({"path": path})).unwrap()["missing"].as_array().is_some_and(Vec::is_empty));
remove_media(&dir);
let mut r = Engine::default();
let res = r.execute("session.open", &json!({"path": path})).unwrap();
assert!(res["missing"].as_array().is_some_and(|m| !m.is_empty()), "{res}");
let _ = std::fs::remove_dir_all(&dir);
}
}

View File

@ -24,7 +24,7 @@ claude mcp add soundcraft -- soundcraft-cli mcp --demo
| `execute` | Run one command with JSON params. |
| `batch` | Run several commands in order (`keep_going` optional). |
| `inspect_session` | Verify work: tracks, clips, mixer, plugins, sends, automation, markers, selection. |
| `new_session`, `open_session`, `save_session` | Session files (`.scraft`, audio in `Audio Files/`). |
| `new_session`, `open_session`, `save_session` | Session files (`.scraft`, audio in `Audio Files/`). `open_session` returns `missing`: the media files that could not be loaded (empty when all are found). |
| `import_audio` | WAV, AIFF, FLAC, MP3, OGG, AAC/M4A, ALAC, CAF onto a new or existing track. |
| `bounce_mix` | Render to WAV/AIFF/FLAC; returns peak dBFS and integrated LUFS. |
| `parity` | Feature-parity report. |