use std::io::Cursor;
use kira::sound::static_sound::StaticSoundData;
pub(crate) fn compile_audio_clip_payload(args: &serde_json::Value) -> Result<Vec<u8>, String> {
let source = args
.get("source")
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.ok_or_else(|| "AudioClip: missing 'source'".to_string())?;
let bytes =
std::fs::read(source).map_err(|e| format!("AudioClip: failed to read '{source}': {e}"))?;
StaticSoundData::from_cursor(Cursor::new(bytes.clone()))
.map_err(|e| format!("AudioClip: '{source}' is not a decodable audio file: {e}"))?;
Ok(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn tiny_wav() -> Vec<u8> {
let sample_rate: u32 = 8000;
let samples: u32 = sample_rate / 10;
let data_len = samples * 2; let mut wav = Vec::new();
wav.extend_from_slice(b"RIFF");
wav.extend_from_slice(&(36 + data_len).to_le_bytes());
wav.extend_from_slice(b"WAVE");
wav.extend_from_slice(b"fmt ");
wav.extend_from_slice(&16u32.to_le_bytes());
wav.extend_from_slice(&1u16.to_le_bytes()); wav.extend_from_slice(&1u16.to_le_bytes()); wav.extend_from_slice(&sample_rate.to_le_bytes());
wav.extend_from_slice(&(sample_rate * 2).to_le_bytes()); wav.extend_from_slice(&2u16.to_le_bytes()); wav.extend_from_slice(&16u16.to_le_bytes()); wav.extend_from_slice(b"data");
wav.extend_from_slice(&data_len.to_le_bytes());
wav.extend(std::iter::repeat_n(0u8, data_len as usize));
wav
}
#[test]
fn missing_source_errors() {
assert!(compile_audio_clip_payload(&serde_json::json!({})).is_err());
assert!(compile_audio_clip_payload(&serde_json::json!({ "source": "" })).is_err());
}
#[test]
fn nonexistent_file_errors() {
let args = serde_json::json!({ "source": "definitely_not_here_9182.ogg" });
assert!(compile_audio_clip_payload(&args).is_err());
}
#[test]
fn non_audio_bytes_are_rejected() {
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(b"this is plainly not an audio file")
.unwrap();
let args = serde_json::json!({ "source": file.path().to_str().unwrap() });
assert!(compile_audio_clip_payload(&args).is_err());
}
#[test]
fn valid_wav_compiles_to_its_own_bytes() {
let wav = tiny_wav();
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(&wav).unwrap();
let args = serde_json::json!({ "source": file.path().to_str().unwrap() });
let payload = compile_audio_clip_payload(&args).expect("valid WAV should compile");
assert_eq!(payload, wav, "payload should be the source bytes verbatim");
}
}