1use std::path::{Path, PathBuf};
4use std::sync::LazyLock;
5
6use regex::Regex;
7use sha2::{Digest, Sha256};
8
9const MAX_SLUG_LEN: usize = 180;
11
12static NON_SLUG: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[^a-z0-9_]+").unwrap());
13static MULTI_DASH: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"-{2,}").unwrap());
14
15pub fn slugify_effect_key(text: &str) -> String {
21 if text.is_empty() {
22 return String::new();
23 }
24 let slug = text.to_lowercase().replace(": ", "_");
25 let slug = NON_SLUG.replace_all(&slug, "-");
26 let slug = MULTI_DASH.replace_all(&slug, "-");
27 let slug = slug.trim_matches('-').to_string();
28 if slug.chars().count() > MAX_SLUG_LEN {
29 let h = format!("{:x}", Sha256::digest(slug.as_bytes()));
30 let head: String = slug.chars().take(MAX_SLUG_LEN).collect();
31 return format!("{}_{}", head.trim_end_matches('-'), &h[..8]);
32 }
33 slug
34}
35
36pub fn shared_sfx_path(sfx_dir: &Path, effect_key: &str, backend: &str) -> PathBuf {
39 let slug = slugify_effect_key(effect_key);
40 let suffix = if backend == "elevenlabs" {
41 String::new()
42 } else {
43 format!(".{backend}")
44 };
45 sfx_dir.join(format!("{slug}{suffix}.mp3"))
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn slugify_examples_from_python_docstring() {
54 assert_eq!(slugify_effect_key("BEAT"), "beat");
55 assert_eq!(
56 slugify_effect_key("SFX: DOOR OPENS, BELL CHIMES"),
57 "sfx_door-opens-bell-chimes"
58 );
59 assert_eq!(
60 slugify_effect_key("AMBIENCE: Café — night"),
61 "ambience_caf-night"
62 );
63 assert_eq!(slugify_effect_key(""), "");
64 assert_eq!(slugify_effect_key("--x--"), "x");
65 }
66
67 #[test]
68 fn long_keys_get_hash_suffix() {
69 let long = "a".repeat(200);
70 let s = slugify_effect_key(&long);
71 assert_eq!(s.len(), 180 + 1 + 8);
72 assert!(s.starts_with(&"a".repeat(180)));
73 assert!(s[181..].chars().all(|c| c.is_ascii_hexdigit()));
74 }
75
76 #[test]
77 fn shared_path_backend_infix() {
78 assert_eq!(
79 shared_sfx_path(Path::new("SFX"), "BEAT", "elevenlabs"),
80 Path::new("SFX/beat.mp3")
81 );
82 assert_eq!(
83 shared_sfx_path(Path::new("SFX"), "SFX: X", "audioldm2"),
84 Path::new("SFX/sfx_x.audioldm2.mp3")
85 );
86 }
87}