From 2630ca154f2016f8eabe531235531c4ca685af51 Mon Sep 17 00:00:00 2001 From: legop3 Date: Sun, 14 Jun 2026 05:14:28 -0400 Subject: [PATCH] better tts a little --- README.md | 7 ++-- sipcord-bridge/src/call/mod.rs | 58 +++++++++++++++++++++++++++++++--- todo | 5 +++ 3 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 todo diff --git a/README.md b/README.md index ca3197d..33fe29c 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,10 @@ timeout_seconds = 10 max_attempts = 3 ``` -The menu uses `espeak-ng` for local text-to-speech. Press `#` to repeat the -current menu page, `9` for the next page when available, and `*` for the -previous page when available. +The menu uses `espeak-ng` for local text-to-speech with a female English voice. +Emoji and common Discord channel separators are skipped in spoken names. Press +`#` to repeat the current menu page, `9` for the next page when available, and +`*` for the previous page when available. ### 4a. Run with Docker (recommended) diff --git a/sipcord-bridge/src/call/mod.rs b/sipcord-bridge/src/call/mod.rs index 0e505b1..d2e7dd3 100644 --- a/sipcord-bridge/src/call/mod.rs +++ b/sipcord-bridge/src/call/mod.rs @@ -1594,7 +1594,7 @@ async fn select_guild_from_menu( let prompt = build_option_prompt( "Select a Discord server.", page_items, - |guild| guild.name.as_str(), + |guild| clean_tts_label(&guild.name), page, guilds.len(), ); @@ -1651,11 +1651,11 @@ async fn select_channel_from_menu( let mut attempts = 0u8; loop { let page_items = page_slice(channels, page); - let intro = format!("Select a voice channel in {}.", guild.name); + let intro = format!("Select a voice channel in {}.", clean_tts_label(&guild.name)); let prompt = build_option_prompt( &intro, page_items, - |channel| channel.name.as_str(), + |channel| clean_tts_label(&channel.name), page, channels.len(), ); @@ -1716,7 +1716,7 @@ fn has_next_page(total: usize, page: usize) -> bool { fn build_option_prompt( intro: &str, items: &[T], - label: impl Fn(&T) -> &str, + label: impl Fn(&T) -> String, page: usize, total: usize, ) -> String { @@ -1734,6 +1734,54 @@ fn build_option_prompt( prompt } +fn clean_tts_label(label: &str) -> String { + let mut out = String::new(); + let mut last_was_space = false; + + for ch in label.chars() { + let replacement = if is_tts_skipped_symbol(ch) || ch.is_control() { + Some(' ') + } else { + match ch { + '_' | '-' | '|' | '/' | '\\' | ':' | ';' | ',' | '.' | '#' | '[' | ']' | '(' + | ')' | '{' | '}' => Some(' '), + _ => Some(ch), + } + }; + + if let Some(ch) = replacement { + if ch.is_whitespace() { + if !last_was_space { + out.push(' '); + last_was_space = true; + } + } else { + out.push(ch); + last_was_space = false; + } + } + } + + let cleaned = out.trim(); + if cleaned.is_empty() { + "unnamed".to_string() + } else { + cleaned.to_string() + } +} + +fn is_tts_skipped_symbol(ch: char) -> bool { + matches!( + ch as u32, + 0x200D + | 0x20E3 + | 0xFE00..=0xFE0F + | 0x2500..=0x257F + | 0x2600..=0x27BF + | 0x1F000..=0x1FAFF + ) +} + async fn wait_for_menu_digit( call_id: CallId, menu: &MenuRoute, @@ -1846,6 +1894,8 @@ async fn synthesize_tts_samples( let out_path = std::env::temp_dir().join(format!("sipcord-tts-{}-{}.wav", call_id, stamp)); let espeak_status = Command::new("espeak-ng") + .arg("-v") + .arg("en+f3") .arg("-w") .arg(&raw_path) .arg(text) diff --git a/todo b/todo new file mode 100644 index 0000000..361d01f --- /dev/null +++ b/todo @@ -0,0 +1,5 @@ +call status feedback in discord + +simple directory in toml config with exts to call / labels +OR +build something into the IVR that uses your CID and extension to register your phone into the directory? \ No newline at end of file