kothok-edge-tts
A Microsoft Edge online text-to-speech client for Rust. Replicates the Edge browser "Read Aloud" WebSocket protocol. Returns the same 400+ neural voices across 140+ locales as streaming MP3 frames plus word-boundary metadata for highlighting.
Part of the KoThok e-reader ecosystem:
| Repo | Role |
|---|---|
| KoThok (EReader) | E-reader app built on kobo-core |
| kobo-core | Kobo device SDK (framebuffer, touch, audio, EPUB) |
| kothok-edge-tts (this) | Edge TTS WebSocket client |
Built on tokio, tokio-tungstenite, rustls (ring), and serde. No system
TLS or audio libraries required: everything compiles from source via cargo.
Architecture
Your text goes in, MP3 audio comes out:
flowchart LR
A["Your text"] --> B["EdgeTts"]
B --> C["Microsoft Edge<br/>TTS server"]
C --> D["MP3 audio<br/>+ word timing"]
D --> E["Your app"]
style A fill:#dcfce7,stroke:#16a34a
style B fill:#dbeafe,stroke:#2563eb
style C fill:#fef3c7,stroke:#d97706
style D fill:#dcfce7,stroke:#16a34a
style E fill:#dbeafe,stroke:#2563eb
Each synthesize() call does 4 things:
- Builds a DRM token (Microsoft requires it)
- Opens a secure WebSocket to
speech.platform.bing.com - Sends your text as SSML
- Streams back MP3 chunks + word timing until done
Internal modules (auth, connection, protocol, ssml) are crate-private.
Network dependency
This crate talks to Microsoft's Edge TTS service over the internet. There is no offline mode and no bundled voices.
| Function | Network | Notes |
|---|---|---|
synthesize() |
Required | Each call opens a fresh WebSocket to speech.platform.bing.com |
list_voices() / spawn_voice_fetch() |
Required | Fetches the voice catalogue from the Edge server |
load_voice_cache() / save_voice_cache() |
Not needed | Reads/writes a local JSON file - works offline |
voices_for_lang() / voice_label() |
Not needed | Pure lookups against the in-memory or cached voice list |
On a Kobo, pair this with kobo-core's wifi_toggle / wifi_status so the
host app can bring the radio up before the first synthesize() and check
connectivity before showing voice lists.
Install
Quick start
use ;
async
Multi-language
Any Microsoft neural voice works. Just pass the voice short-name and BCP-47 lang tag:
# use *;
# async
Full voice list: Microsoft Speech language support
synthesize parameters
| Parameter | Description | Example |
|---|---|---|
text |
One utterance (max ~4 KB; chunk longer text) | "Hello world." |
voice |
Voice short-name | "en-US-EmmaMultilingualNeural" |
rate |
SSML prosody rate | "+0%", "+25%", "-10%" |
lang |
BCP-47 language tag | "en-US", "bn-BD" |
Swappable backend
EdgeTts implements the Engine trait. Implement it yourself to mock or swap
backends:
# use *;
# use Future;
;
Token rotation
Microsoft rotates the SEC_MS_GEC_VERSION constant on Edge release cycles. A
daily CI job detects when it goes stale
(HTTP 403) and auto-creates a GitHub Issue with fix steps. Users should pin to
the latest published version.
Acknowledgements
Protocol logic ported from edge-tts by rany2: the reference Python implementation. All credit for reverse-engineering the Edge Read-Aloud protocol goes to that project.
API reference
Complete alphabetical list of everything exported from this crate.
| Name | Kind | Description |
|---|---|---|
DEFAULT_VOICE_BN |
const | Default Bangla voice: "bn-BD-NabanitaNeural" |
DEFAULT_VOICE_EN |
const | Default English voice: "en-US-EmmaMultilingualNeural" |
EdgeTts |
struct | Reference Engine impl. Stateless: each call opens a fresh WebSocket |
Engine |
trait | Swappable TTS-backend contract: synthesize(text, voice, rate, lang) |
init_tls() |
fn | Install rustls ring crypto provider. Call once at startup. Idempotent |
list_voices() |
fn (async) | Fetch full voice catalogue from Edge server. Returns Vec<VoiceInfo> |
load_voice_cache(path) |
fn | Load cached voices from a JSON file on disk |
normalize_lang(lang) |
fn | Map "bn" to "bn-BD", "ar" to "ar-SA", etc. Falls back to "en-US" |
save_voice_cache(path, &vec) |
fn | Save voices to a JSON file on disk |
set_dynamic_voices(vec) |
fn | Store fetched voices for voices_for_lang lookups |
spawn_voice_fetch() |
fn | Spawn background thread that fetches voices, returns Receiver<Vec<VoiceInfo>> |
TtsError |
enum | Crate error type. Variants: Connect, Ws, Io, NoAudio |
TtsEvent |
enum | Stream events. Variants: Audio(Vec<u8>), WordBoundary{offset, duration, text}, TurnEnd |
voice_label(voice_id) |
fn | Human-readable label for a voice ID: "Nabanita (bn-BD)" |
VoiceEntry |
struct | UI-facing voice with id() and label() accessors |
VoiceInfo |
struct | Server voice entry with short_name(), locale(), gender(), friendly_name() accessors |
voices_for_lang(lang) |
fn | Get VoiceEntry list for a language (dynamic if fetched, fallback otherwise) |
License
MIT (LICENSE).