use std::path::Path;
use base64::Engine;
use super::project::mod_root;
use crate::serve::ServiceError;
const KINDS: [(&[u8], &str); 5] = [
(b"OggS", "audio/ogg"),
(b"RIFF", "audio/wav"),
(b"fLaC", "audio/flac"),
(b"ID3", "audio/mpeg"),
(b"\xFF\xFB", "audio/mpeg"),
];
const ANY_AUDIO: &str = "audio/*";
#[derive(yog_data::Schema)]
pub(crate) struct SoundData {
pub(crate) data: String,
pub(crate) mime: String,
}
#[derive(yog_data::Schema)]
pub(crate) struct SoundList {
pub(crate) own: Vec<String>,
pub(crate) game: Vec<String>,
}
pub(crate) fn sound_list(file: &Path) -> Result<SoundList, ServiceError> {
let root = mod_root(file).ok_or("not in a mod")?;
Ok(SoundList {
own: crate::media::sounds(&root),
game: crate::media::game_sounds(),
})
}
pub(crate) fn sound_data(file: &Path, sound: &str) -> Result<SoundData, ServiceError> {
let bytes = match sound.strip_prefix(yog_ids::sep::TYPED) {
Some(res) => own(file, res)?,
None => game(sound)?,
};
let mime = KINDS
.iter()
.find(|(magic, _)| bytes.starts_with(magic))
.map_or(ANY_AUDIO, |(_, m)| m);
Ok(SoundData {
data: base64::engine::general_purpose::STANDARD.encode(&bytes),
mime: mime.to_owned(),
})
}
fn own(file: &Path, res: &str) -> Result<Vec<u8>, ServiceError> {
let (set, name) = res
.split_once(yog_ids::sep::DOT)
.ok_or_else(|| ServiceError::from(format!("`{res}`: a sound is `@Set.name`")))?;
let root = mod_root(file).ok_or("not in a mod")?;
crate::media::sound(&root, set, name)
.ok_or_else(|| ServiceError::not_found(format!("no sound `@{res}` in the mod's .yres")))
}
fn game(id: &str) -> Result<Vec<u8>, ServiceError> {
crate::media::game_sound(id).ok_or_else(|| {
ServiceError::not_found(format!(
"the game's sound {id}: no assets found on this machine"
))
})
}
#[cfg(test)]
mod tests {
use base64::Engine;
#[test]
fn the_mods_sound_is_its_yres_bytes() {
let dir = std::env::temp_dir().join(format!("yog-sound-data-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("resources")).unwrap();
std::fs::write(dir.join("yog.toml"), "[mod]\nid = \"mymod\"\n").unwrap();
let ogg = base64::engine::general_purpose::STANDARD.encode(b"OggS....");
std::fs::write(
dir.join("resources/Sounds.yres"),
format!(r#"{{ "click": {{ "type": "sound", "value": "@bytes:{ogg}" }} }}"#),
)
.unwrap();
let file = dir.join("resources/click.ysound");
let s = super::sound_data(&file, "@Sounds.click").unwrap();
assert_eq!(
(s.mime.as_str(), s.data.as_str()),
("audio/ogg", ogg.as_str())
);
assert!(super::sound_data(&file, "@Sounds.nope").is_err());
assert_eq!(super::sound_list(&file).unwrap().own, vec!["@Sounds.click".to_owned()]);
let _ = std::fs::remove_dir_all(&dir);
}
}