Access the Typecast API with our official Rust SDK.
The official Rust library for the Typecast API. Convert text to lifelike speech using AI-powered voices.Built with async/await support using Tokio runtime. Works with Cargo package manager.
Use recommend_voices when you know the desired style but not the exact voice_id.
let voices = client .recommend_voices("warm female voice for a product tutorial", Some(3)) .await?;for voice in voices { println!("{} {} {}", voice.voice_id, voice.voice_name, voice.score);}
Recommendation results contain only voice_id, voice_name, and score. Use get_voice_v2 or get_voices_v2 when you need detailed metadata such as supported models, emotions, gender, age, or use cases.
Set your API key via environment variable or constructor:
use typecast_rust::{TypecastClient, ClientConfig};use std::time::Duration;// Using environment variable (recommended)// export TYPECAST_API_KEY="your-api-key-here"let client = TypecastClient::from_env()?;// Or pass directlylet client = TypecastClient::with_api_key("your-api-key-here")?;// Or with custom configurationlet config = ClientConfig::new("your-api-key-here") .base_url("https://api.typecast.ai") .timeout(Duration::from_secs(120));let client = TypecastClient::new(config)?;
When requests go through your own proxy, set base_url to the proxy endpoint and omit the API key. The SDK will not send the X-API-KEY header for empty or missing keys. Requests to the default Typecast host still require an API key.
Proxy without API key
let config = ClientConfig::new("") .base_url("https://your-proxy.example.com");let client = TypecastClient::new(config)?;
ssfm-v30 offers two emotion control modes: Preset and Smart.
Smart Mode
Preset Mode
Let the AI infer emotion from context:
use typecast_rust::{TTSRequest, TTSModel, SmartPrompt};let request = TTSRequest::new( "tc_672c5f5ce59fac2a48faeaee", "Everything is going to be okay.", TTSModel::SsfmV30,).prompt( SmartPrompt::new() .previous_text("I just got the best news!") // Optional context .next_text("I can't wait to celebrate!") // Optional context);let response = client.text_to_speech(&request).await?;
Explicitly set emotion with preset values:
use typecast_rust::{TTSRequest, TTSModel, PresetPrompt, EmotionPreset};let request = TTSRequest::new( "tc_672c5f5ce59fac2a48faeaee", "I am so excited to show you these features!", TTSModel::SsfmV30,).prompt( PresetPrompt::new() .emotion_preset(EmotionPreset::Happy) // Normal, Happy, Sad, Angry, Whisper, ToneUp, ToneDown .emotion_intensity(1.5) // Range: 0.0 to 2.0);let response = client.text_to_speech(&request).await?;
Use generate_to_file when you want the SDK to synthesize speech and write the audio bytes directly to a local file. The model defaults to ssfm-v30, and .mp3 / .wav extensions infer the output format when no output format is set. Browse available voice IDs on the Voices page.
client.generate_to_file( "output.mp3", GenerateToFileRequest::new("tc_672c5f5ce59fac2a48faeaee", "Hello from Typecast."), // Find voice IDs at https://typecast.ai/developers/api/voices).await?;
Use text pause markup when you only need silent gaps inside one composed text segment. Put <|5s|>, <|1s|>, <|0.3s|>, or <|0.34413s|> directly in the text. The value is interpreted as seconds and must end with s. This keeps the pause expression visible in plain text without adding separate pause calls.
let audio = client .compose_speech() .defaults(ComposerSettings::new().voice_id("tc_672c5f5ce59fac2a48faeaee").model(TtsModel::SsfmV30)) .say("Hello<|5s|>Nice to meet you<|1s|>Today<|2s|>how does the weather feel?") .generate() .await?;
Use the composer chaining API when one output file needs different voices or per-segment options such as pitch, tempo, prompt, or seed. The composer generates each segment as WAV, trims leading/trailing silent PCM samples, and concatenates the result. If you need MP3, generate WAV first and convert it in your app or server pipeline.
use typecast_rust::{ComposerSettings, Output, TTSModel, TypecastClient};#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = TypecastClient::new("YOUR_API_KEY")?; let audio = client .compose_speech() .defaults(ComposerSettings::new().voice_id("tc_672c5f5ce59fac2a48faeaee").model(TTSModel::SSFM_V30)) .say("Hello there") .pause(5.0) .say_with( "Nice to meet you", ComposerSettings::new().voice_id("tc_60e5426de8b95f1d3000d7b5").output(Output { audio_pitch: Some(2), ..Default::default() }), ) .say("Today") .pause(2) .say("How does the weather feel?") .generate() .await?; std::fs::write("conversation.wav", audio.audio_data)?; Ok(())}
Stream audio chunks in real-time for low-latency playback:
// Stream and extract raw PCM (skip 44-byte WAV header)let mut stream = client.text_to_speech_stream(&request).await?;let mut first = true;while let Some(chunk) = stream.next().await { let bytes = chunk?; let pcm = if first { first = false; &bytes[44..] // Skip WAV header } else { &bytes }; // pcm is raw 16-bit mono PCM at 32000 Hz // Feed to your audio output (e.g. rodio, cpal)}
WAV streaming format: 32000 Hz, 16-bit, mono PCM. The first chunk includes a 44-byte WAV header (size = 0xFFFFFFFF); subsequent chunks are raw PCM only. For MP3: 320 kbps, 44100 Hz, each chunk is independently decodable. Requires futures-util for StreamExt.
text_to_speech_with_timestamps() wraps POST /v1/text-to-speech/with-timestamps and returns the audio together with per-word and per-character alignment data — useful for karaoke highlights, subtitle generation, and lip-sync applications.
Chain .granularity(Granularity::Word) (default) or .granularity(Granularity::Char) to control the alignment unit.
use typecast_rust::Granularity;let request = TTSRequestWithTimestamps::new( "tc_60e5426de8b95f1d3000d7b5", "Hello. How are you?", TTSModel::SsfmV30,).granularity(Granularity::Char); // required for Japanese / Chinese
Japanese / Chinese: Word-level segmentation is not meaningful for languages without whitespace delimiters (jpn, zho). Use Granularity::Char for these languages to get character-level alignment.
if let Err(e) = result { if e.is_unauthorized() { println!("Check your API key"); } else if e.is_rate_limited() { println!("Wait and retry"); } else if e.is_server_error() { println!("Server issue, try again later"); } if let Some(code) = e.status_code() { println!("HTTP status: {}", code); }}