convert and bounce: refuse output extensions the encoder cannot write instead of saving WAV data (#47)
This commit is contained in:
parent
eac0edd896
commit
43f1fb5681
|
|
@ -115,10 +115,8 @@ fn convert(args: &[String]) -> ExitCode {
|
|||
soundcraft_dsp::offline::normalize(&mut buf.channels, -0.1, false);
|
||||
}
|
||||
let out_ext = std::path::Path::new(output).extension().and_then(|x| x.to_str()).unwrap_or("wav").to_ascii_lowercase();
|
||||
let format = match out_ext.as_str() {
|
||||
"aif" | "aiff" => soundcraft_audio_io::FileFormat::Aiff,
|
||||
"flac" => soundcraft_audio_io::FileFormat::Flac,
|
||||
_ => soundcraft_audio_io::FileFormat::Wav,
|
||||
let Some(format) = soundcraft_audio_io::encode_format_for(&out_ext) else {
|
||||
return fail(format!("{output}: cannot write `.{out_ext}` files (supported: {})", soundcraft_audio_io::ENCODE_EXTENSIONS));
|
||||
};
|
||||
let bit_depth = match arg_value(args, "--bit-depth").as_deref() {
|
||||
Some("16") => soundcraft_audio_io::BitDepth::Int16,
|
||||
|
|
|
|||
|
|
@ -257,6 +257,19 @@ pub fn encode(buf: &AudioBuffer, opts: &EncodeOptions) -> Result<Vec<u8>> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Formats [`encode`] can write, as the extensions that name them.
|
||||
pub const ENCODE_EXTENSIONS: &str = "wav, aif, aiff, flac";
|
||||
|
||||
/// The format [`encode`] writes for a file extension or format name (`wav`, `aiff`, `flac`, case-insensitive), or `None` when it cannot write it.
|
||||
pub fn encode_format_for(name: &str) -> Option<FileFormat> {
|
||||
match name.trim_start_matches('.').to_ascii_lowercase().as_str() {
|
||||
"wav" | "wave" | "bwf" => Some(FileFormat::Wav),
|
||||
"aif" | "aiff" => Some(FileFormat::Aiff),
|
||||
"flac" => Some(FileFormat::Flac),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a WAV file in the RF64 layout regardless of size (normally used only above 4 GiB).
|
||||
pub fn encode_wav_rf64(buf: &AudioBuffer, opts: &EncodeOptions) -> Result<Vec<u8>> {
|
||||
wav::encode(buf, opts.bit_depth, opts.dither, opts.bwf.as_ref(), true, None)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
use soundcraft_audio_io::{FileFormat, encode_format_for};
|
||||
|
||||
#[test]
|
||||
fn only_formats_the_encoder_writes_are_named() {
|
||||
assert_eq!(encode_format_for("wav"), Some(FileFormat::Wav));
|
||||
assert_eq!(encode_format_for("WAV"), Some(FileFormat::Wav));
|
||||
assert_eq!(encode_format_for(".aif"), Some(FileFormat::Aiff));
|
||||
assert_eq!(encode_format_for("aiff"), Some(FileFormat::Aiff));
|
||||
assert_eq!(encode_format_for("flac"), Some(FileFormat::Flac));
|
||||
for other in ["mp3", "ogg", "m4a", "xyz", ""] {
|
||||
assert_eq!(encode_format_for(other), None, "{other}");
|
||||
}
|
||||
}
|
||||
|
|
@ -233,11 +233,10 @@ fn bounce(e: &mut Engine, p: &Value) -> Result<Value> {
|
|||
let r = range_param(e, "file.bounce_mix", p)?;
|
||||
let r = if r.is_empty() { soundcraft_time::Range::new(0, e.session().content_end().max(1)) } else { r };
|
||||
let ext = std::path::Path::new(&path).extension().and_then(|x| x.to_str()).unwrap_or("wav").to_ascii_lowercase();
|
||||
let format = match str_param(p, "format").unwrap_or(ext.as_str()) {
|
||||
"aif" | "aiff" => soundcraft_audio_io::FileFormat::Aiff,
|
||||
"flac" => soundcraft_audio_io::FileFormat::Flac,
|
||||
_ => soundcraft_audio_io::FileFormat::Wav,
|
||||
};
|
||||
let format_name = str_param(p, "format").unwrap_or(ext.as_str());
|
||||
let format = soundcraft_audio_io::encode_format_for(format_name).ok_or_else(|| {
|
||||
bad("file.bounce_mix", format!("cannot write `{format_name}` files (supported: {})", soundcraft_audio_io::ENCODE_EXTENSIONS))
|
||||
})?;
|
||||
let bit_depth = match p.get("bit_depth").map(|b| b.to_string().trim_matches('"').to_string()).as_deref() {
|
||||
Some("16") => soundcraft_audio_io::BitDepth::Int16,
|
||||
Some("32") | Some("32f") => soundcraft_audio_io::BitDepth::Float32,
|
||||
|
|
@ -270,3 +269,24 @@ 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 tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bounce_refuses_formats_it_cannot_write() {
|
||||
let mut e = Engine::default();
|
||||
let dir = std::env::temp_dir().join(format!("sc-bounce-ext-{}", std::process::id()));
|
||||
assert!(std::fs::create_dir_all(&dir).is_ok());
|
||||
for name in ["mix.mp3", "mix.ogg", "mix.xyz"] {
|
||||
let path = dir.join(name);
|
||||
let err = e.execute("file.bounce_mix", &json!({"path": path.to_string_lossy(), "start": 0, "end": 480})).map(|_| ()).unwrap_err();
|
||||
assert!(err.to_string().contains("supported: wav"), "{name}: {err}");
|
||||
assert!(!path.exists(), "{name} must not be written");
|
||||
}
|
||||
let ok = dir.join("mix.flac");
|
||||
assert!(e.execute("file.bounce_mix", &json!({"path": ok.to_string_lossy(), "start": 0, "end": 480})).is_ok());
|
||||
assert!(std::fs::remove_dir_all(&dir).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue