Skip to main content

Crate kothok_edge_tts

Crate kothok_edge_tts 

Source
Expand description

§kothok-edge-tts

crates.io documentation license

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:

RepoRole
KoThok (EReader)E-reader app built on kobo-core
kobo-coreKobo 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:

  1. Builds a DRM token (Microsoft requires it)
  2. Opens a secure WebSocket to speech.platform.bing.com
  3. Sends your text as SSML
  4. 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.

FunctionNetworkNotes
synthesize()RequiredEach call opens a fresh WebSocket to speech.platform.bing.com
list_voices() / spawn_voice_fetch()RequiredFetches the voice catalogue from the Edge server
load_voice_cache() / save_voice_cache()Not neededReads/writes a local JSON file - works offline
voices_for_lang() / voice_label()Not neededPure 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

cargo add kothok-edge-tts

§Quick start

use kothok_edge_tts::{init_tls, EdgeTts, Engine, TtsEvent};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    init_tls(); // once, before the first connect

    let events = EdgeTts
        .synthesize("Hello world.", "en-US-EmmaMultilingualNeural", "+0%", "en-US")
        .await?;

    let mut audio = Vec::new();
    for ev in events {
        match ev {
            TtsEvent::Audio(bytes) => audio.extend_from_slice(&bytes),
            TtsEvent::WordBoundary { offset, text, .. } => {
                println!("word \"{text}\" at {offset} ticks");
            }
            TtsEvent::TurnEnd => {}
        }
    }
    // audio == raw MP3 (audio-24khz-48kbitrate-mono-mp3)
    Ok(())
}

§Multi-language

Any Microsoft neural voice works. Just pass the voice short-name and BCP-47 lang tag:

EdgeTts.synthesize("Hello", "en-US-EmmaMultilingualNeural", "+0%", "en-US").await?;
EdgeTts.synthesize("নমস্কার", "bn-BD-NabanitaNeural", "+0%", "bn-BD").await?;
EdgeTts.synthesize("مرحبا", "ar-SA-ZariyahNeural", "+0%", "ar-SA").await?;

Full voice list: Microsoft Speech language support

§synthesize parameters

ParameterDescriptionExample
textOne utterance (max ~4 KB; chunk longer text)"Hello world."
voiceVoice short-name"en-US-EmmaMultilingualNeural"
rateSSML prosody rate"+0%", "+25%", "-10%"
langBCP-47 language tag"en-US", "bn-BD"

§Swappable backend

EdgeTts implements the Engine trait. Implement it yourself to mock or swap backends:

struct MockTts;
impl Engine for MockTts {
    fn synthesize(&self, _: &str, _: &str, _: &str, _: &str)
        -> impl Future<Output = Result<Vec<TtsEvent>, TtsError>> + Send
    { async { Ok(vec![TtsEvent::Audio(vec![0xFF; 100]), TtsEvent::TurnEnd]) } }
}

§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.

NameKindDescription
DEFAULT_VOICE_BNconstDefault Bangla voice: "bn-BD-NabanitaNeural"
DEFAULT_VOICE_ENconstDefault English voice: "en-US-EmmaMultilingualNeural"
EdgeTtsstructReference Engine impl. Stateless: each call opens a fresh WebSocket
EnginetraitSwappable TTS-backend contract: synthesize(text, voice, rate, lang)
init_tls()fnInstall 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)fnLoad cached voices from a JSON file on disk
normalize_lang(lang)fnMap "bn" to "bn-BD", "ar" to "ar-SA", etc. Falls back to "en-US"
save_voice_cache(path, &vec)fnSave voices to a JSON file on disk
set_dynamic_voices(vec)fnStore fetched voices for voices_for_lang lookups
spawn_voice_fetch()fnSpawn background thread that fetches voices, returns Receiver<Vec<VoiceInfo>>
TtsErrorenumCrate error type. Variants: Connect, Ws, Io, NoAudio
TtsEventenumStream events. Variants: Audio(Vec<u8>), WordBoundary{offset, duration, text}, TurnEnd
voice_label(voice_id)fnHuman-readable label for a voice ID: "Nabanita (bn-BD)"
VoiceEntrystructUI-facing voice with id() and label() accessors
VoiceInfostructServer voice entry with short_name(), locale(), gender(), friendly_name() accessors
voices_for_lang(lang)fnGet VoiceEntry list for a language (dynamic if fetched, fallback otherwise)

§License

MIT (LICENSE).

Structs§

EdgeTts
A stateless Edge-TTS engine.
VoiceEntry
A voice with a short ID and a human-readable label for UI display.
VoiceInfo
One voice entry from the Edge voice-list JSON response.

Enums§

TtsError
All ways a synthesis request can fail.
TtsEvent
One item in the event stream produced by crate::Engine::synthesize.

Constants§

DEFAULT_VOICE_BN
Default Bengali voice short-name.
DEFAULT_VOICE_EN
Default English voice short-name.
RATE_BASELINE_OFFSET
Baseline offset (percentage points) applied to every prosody rate.

Traits§

Engine
A text-to-speech backend.

Functions§

init_tls
Install rustls’s ring crypto provider.
list_voices
Fetch the complete Edge-TTS voice catalogue.
load_voice_cache
Load voice data from a JSON cache file. Returns an empty vec on any read/parse failure.
normalize_lang
Map a language tag (full BCP-47 or just a prefix like "bn") to its default locale. Unknown prefixes fall back to "en-US".
rate_percent
Map a 0..100 slider value to an Edge-TTS SSML prosody rate percentage.
rate_string
Format a 0..100 slider value as an Edge-TTS rate string, e.g. “-10%”.
save_voice_cache
Write voice data to a JSON cache file.
set_dynamic_voices
Store the dynamic voice catalogue. No-op if already set.
spawn_voice_fetch
Spawn a blocking thread that fetches the voice list and returns it via a channel. The caller receives a Receiver that yields the voices once the fetch completes, or drops on failure.
voice_label
Look up a human-readable label for a voice by its short-name.
voices_for_lang
Return voices matching lang from the dynamic catalogue, or from the hardcoded fallback tables if no dynamic voices are loaded.