> ## Documentation Index
> Fetch the complete documentation index at: https://typecast.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust

The official Rust library for the [Typecast API](https://typecast.ai). Convert text to lifelike speech using AI-powered voices.

Built with async/await support using Tokio runtime. Works with Cargo package manager.

<CardGroup cols={2}>
<Card title="Crates.io" icon="cube" href="https://crates.io/crates/typecast-rust">
Typecast Rust SDK
</Card>

<Card title="Source Code" icon="github" href="https://github.com/neosapience/typecast-sdk/tree/main/typecast-rust">
Typecast Rust SDK Source Code
</Card>
</CardGroup>

## Installation

Add the following to your `Cargo.toml`:

```toml
[dependencies]
typecast-rust = "0.3.15"
tokio = { version = "1", features = ["full"] }
```

Or use Cargo to add the dependency:

```bash
cargo add typecast-rust tokio --features tokio/full
```

<Warning>
Latest registered version: **0.3.15** on crates.io. Make sure you have **version 0.3.15 or higher** installed. Check your `Cargo.toml` if you need to update.
</Warning>

## Quick Start

```rust
use typecast_rust::{TypecastClient, TTSRequest, TTSModel};
use std::fs;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize client (reads TYPECAST_API_KEY from environment)
    let client = TypecastClient::from_env()?;

    // Convert text to speech
    let request = TTSRequest::new(
        "tc_672c5f5ce59fac2a48faeaee",
        "Hello there! I'm your friendly text-to-speech agent.",
        TTSModel::SsfmV30,
    );

    let response = client.text_to_speech(&request).await?;

    // Save audio file
    fs::write("output.wav", &response.audio_data)?;

    println!(
        "Audio saved! Duration: {:.2}s, Format: {:?}",
        response.duration, response.format
    );

    Ok(())
}
```

## Features

The Typecast Rust SDK provides powerful features for text-to-speech conversion:

- **Multiple Voice Models**: Support for `ssfm-v30` (latest) and `ssfm-v21` AI voice models
- **Multi-language Support**: 35+ languages including English, Korean, Spanish, Japanese, Chinese, and more
- **Emotion Control**: Preset emotions (normal, happy, sad, angry, whisper, toneup, tonedown) or smart context-aware inference
- **Audio Customization**: Control loudness (LUFS -70 to 0), pitch (-12 to +12 semitones), tempo (0.5x to 2.0x), and format (WAV/MP3)
- **Voice Discovery**: V2 Voices API with filtering by model, gender, age, and use cases
- **Instant Voice Cloning**: Upload a WAV/MP3 sample and create a custom voice ID
- **Builder Pattern**: Fluent API with method chaining for easy request construction
- **Async/Await**: Built on Tokio for efficient asynchronous operations
- **Timestamp TTS**: Word- and character-level alignment data for subtitles, karaoke, and lip-sync
- **Comprehensive Error Handling**: Typed error enum with pattern matching support
- **Streaming**: Real-time chunked audio delivery for low-latency playback

## Voice Recommendations

Use `recommend_voices` when you know the desired style but not the exact `voice_id`.

```rust
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_v3` or `get_voices_v3` when you need current voice metadata.

## Configuration

Set your API key via environment variable or constructor:

```rust
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 directly
let client = TypecastClient::with_api_key("your-api-key-here")?;

// Or with custom configuration
let config = ClientConfig::new("your-api-key-here")
    .base_url("https://api.typecast.ai")
    .timeout(Duration::from_secs(120));

let client = TypecastClient::new(config)?;
```

<Info>
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.
</Info>

```rust Proxy without API key
let config = ClientConfig::new("")
    .base_url("https://your-proxy.example.com");

let client = TypecastClient::new(config)?;
```

### Environment File

Create a `.env` file in your project root:

```bash
TYPECAST_API_KEY=your-api-key-here
```

<Info>
Use the `dotenvy` crate to load environment variables from `.env` files.
</Info>

## Advanced Usage

### Emotion Control (ssfm-v30)

ssfm-v30 offers two emotion control modes: **Preset** and **Smart**.

<Tabs>
  <Tab title="Smart Mode">
    Let the AI infer emotion from context:

    ```rust
    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?;
    ```
  </Tab>

  <Tab title="Preset Mode">
    Explicitly set emotion with preset values:

    ```rust
    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?;
    ```
  </Tab>
</Tabs>

### Audio Customization

Control loudness, pitch, tempo, and output format:

```rust
use typecast_rust::{TTSRequest, TTSModel, Output, AudioFormat};

let request = TTSRequest::new(
    "tc_672c5f5ce59fac2a48faeaee",
    "Customized audio output!",
    TTSModel::SsfmV30,
)
.output(
    Output::new()
        .target_lufs(-14.0)                 // Range: -70 to 0 (LUFS)
        .audio_pitch(2)                   // Range: -12 to +12 semitones
        .audio_tempo(1.2)                 // Range: 0.5x to 2.0x
        .audio_format(AudioFormat::Mp3)   // Options: Wav, Mp3
);

let response = client.text_to_speech(&request).await?;

fs::write("output.mp3", &response.audio_data)?;
println!("Duration: {:.2}s, Format: {:?}", response.duration, response.format);
```

### Generate audio to a file

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](https://studio.typecast.ai/developers/api/voices) page.

```rust
client.generate_to_file(
    "output.mp3",
    GenerateToFileRequest::new("tc_672c5f5ce59fac2a48faeaee", "Hello from Typecast."), // Find voice IDs at https://studio.typecast.ai/developers/api/voices
).await?;
```

### Text pauses

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.

```rust
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?;
```

### Multi-speaker composition

Use the composer chaining API when one output file needs different voices or per-segment options such as pitch, tempo, or prompt. The composer sends segments to `POST /v1/text-to-speech/compose`, which returns WAV or MP3 directly. Set silence removal explicitly on TTS segments; explicit pauses are preserved.

```rust
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(())
}
```

## Control silence duration

Requires **0.3.15 or later**.

`remove_silence_ms` specifies the **silence duration to retain**, not the amount to remove. Use an integer from `0` to `1000` ms. `0` removes detected silence; omission or `null` disables duration-based silence removal.

Standard, streaming, and timestamp TTS use `output.remove_silence_ms`; Compose uses `segments[].output.remove_silence_ms` on each `tts` segment. Returned timestamps align with the processed audio, and explicit `pause` segments are preserved.

Streaming's default leading-silence trimming is separate. Small values such as `0` can leave gaps between playable chunks; allow sufficient playback buffering and test with your content.

Pass these output settings to the corresponding request's `output`. For streaming, use the streaming output type.

```rust
use typecast_rust::{Output, OutputStream};

let output = Output::new().remove_silence_ms(300);
let stream_output = OutputStream::new().remove_silence_ms(300);
```

### Voice Discovery (V3 API)

List and filter available voices with enhanced metadata:

```rust
use typecast_rust::{TypecastClient, VoicesV2Filter, TTSModel, Gender, Age};

// Get all voices
let voices = client.get_voices_v3(None).await?;

// Filter by criteria
let filter = VoicesV2Filter::new()
    .model(TTSModel::SsfmV30)
    .gender(Gender::Female)
    .age(Age::YoungAdult);

let filtered = client.get_voices_v3(Some(filter)).await?;

// Display voice info
for voice in &voices {
    println!("ID: {}, Name: {}", voice.voice_id, voice.voice_name.eng);
    println!("Gender: {:?}, Age: {:?}", voice.gender, voice.age);
    
    for model in &voice.models {
        println!("Model: {:?}, Emotions: {:?}", model.version, model.emotions);
    }
    
    if let Some(use_cases) = &voice.use_cases {
        println!("Use cases: {}", use_cases.join(", "));
    }
}

// Get a specific voice by ID
let voice = client.get_voice_v3("tc_672c5f5ce59fac2a48faeaee").await?;
println!("Voice: {} ({:?})", voice.voice_name.eng, voice.gender);
```

### Multilingual Content

The SDK supports 35+ languages with automatic language detection:

```rust
// Auto-detect language (recommended)
let request = TTSRequest::new(
    "tc_672c5f5ce59fac2a48faeaee",
    "こんにちは。お元気ですか。",
    TTSModel::SsfmV30,
);

let response = client.text_to_speech(&request).await?;

// Or specify language explicitly
let korean_request = TTSRequest::new(
    "tc_672c5f5ce59fac2a48faeaee",
    "안녕하세요. 반갑습니다.",
    TTSModel::SsfmV30,
)
.language("kor");  // ISO 639-3 language code

let korean_response = client.text_to_speech(&korean_request).await?;
```

### Streaming

Stream audio chunks in real-time for low-latency playback:

```rust
// 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)
}
```

<Note>
**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`.
</Note>

## Timestamp TTS

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

### Basic Usage

```rust
use typecast_rust::{TypecastClient, TTSRequestWithTimestamps, TTSModel};
use std::fs;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = TypecastClient::from_env()?;

    let request = TTSRequestWithTimestamps::new(
        "tc_60e5426de8b95f1d3000d7b5",
        "Hello. How are you?",
        TTSModel::SsfmV30,
    );

    let result = client.text_to_speech_with_timestamps(&request).await?;

    fs::write("output.wav", result.audio_bytes())?;
    println!("Duration: {:.3}s", result.audio_duration);

    for word in &result.words {
        println!("  [{:.3}s – {:.3}s] {}", word.start_time, word.end_time, word.text);
    }

    Ok(())
}
```

### Granularity

Chain `.granularity(Granularity::Word)` (default) or `.granularity(Granularity::Char)` to control the alignment unit.

```rust
use typecast_rust::Granularity;

let request = TTSRequestWithTimestamps::new(
    "tc_60e5426de8b95f1d3000d7b5",
    "Hello. How are you?",
    TTSModel::SsfmV30,
)
.granularity(Granularity::Char);  // required for Japanese / Chinese
```

### Subtitle Export

```rust
fs::write("output.srt", result.to_srt())?;
fs::write("output.vtt", result.to_vtt())?;
```

<Note>
**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.
</Note>

## Instant Voice Cloning

Clone a custom voice from a short audio sample, then pass the returned `uc_` voice ID directly to TTS.

```rust
use typecast_rust::{TypecastClient, TTSModel, TTSRequest};

let client = TypecastClient::from_env()?;
let audio = std::fs::read("sample.wav")?;

let voice = client
    .clone_voice(audio, "sample.wav", "My Voice", "ssfm-v30")
    .await?;

let request = TTSRequest::new(
    &voice.voice_id,
    "Hello from my cloned voice!",
    TTSModel::SsfmV30,
);

let response = client.text_to_speech(&request).await?;
std::fs::write("output.wav", &response.audio_data)?;
client.delete_voice(&voice.voice_id).await?;
```

<Warning>
Voice cloning audio must be **25 MB or smaller**, the audio duration must be **5-150 seconds**, and the custom voice name must be **1-30 characters**.
</Warning>

## Supported Languages

The SDK supports 35+ languages with automatic language detection:

| Code | Language | Code | Language | Code | Language |
|------|----------|------|----------|------|----------|
| `eng` | English | `jpn` | Japanese | `ukr` | Ukrainian |
| `kor` | Korean | `ell` | Greek | `ind` | Indonesian |
| `spa` | Spanish | `tam` | Tamil | `dan` | Danish |
| `deu` | German | `tgl` | Tagalog | `swe` | Swedish |
| `fra` | French | `fin` | Finnish | `msa` | Malay |
| `ita` | Italian | `zho` | Chinese | `ces` | Czech |
| `pol` | Polish | `slk` | Slovak | `por` | Portuguese |
| `nld` | Dutch | `ara` | Arabic | `bul` | Bulgarian |
| `rus` | Russian | `hrv` | Croatian | `ron` | Romanian |
| `ben` | Bengali | `hin` | Hindi | `hun` | Hungarian |
| `nan` | Hokkien | `nor` | Norwegian | `pan` | Punjabi |
| `tha` | Thai | `tur` | Turkish | `vie` | Vietnamese |
| `yue` | Cantonese | | | | |

<Info>
If not specified, the language will be automatically detected from the input text.
</Info>

## Error Handling

The SDK provides a typed error enum for handling API errors with pattern matching:

```rust
use typecast_rust::{TypecastClient, TTSRequest, TTSModel, TypecastError};

let request = TTSRequest::new("voice_id", "Hello", TTSModel::SsfmV30);

match client.text_to_speech(&request).await {
    Ok(response) => {
        println!("Success! Duration: {:.2}s", response.duration);
    }
    Err(TypecastError::Unauthorized { detail }) => {
        // 401: Invalid API key
        eprintln!("Invalid API key: {}", detail);
    }
    Err(TypecastError::PaymentRequired { detail }) => {
        // 402: Insufficient credits
        eprintln!("Insufficient credits: {}", detail);
    }
    Err(TypecastError::NotFound { detail }) => {
        // 404: Resource not found
        eprintln!("Voice not found: {}", detail);
    }
    Err(TypecastError::RateLimited { detail }) => {
        // 429: Rate limit exceeded
        eprintln!("Rate limit exceeded - please retry later: {}", detail);
    }
    Err(TypecastError::ServerError { detail }) => {
        // 500: Server error
        eprintln!("Server error: {}", detail);
    }
    Err(e) => {
        eprintln!("Error: {}", e);
    }
}
```

### Error Types

| Error Variant | Status Code | Description |
|---------------|-------------|-------------|
| `BadRequest` | 400 | Invalid request parameters |
| `Unauthorized` | 401 | Invalid or missing API key |
| `PaymentRequired` | 402 | Insufficient credits |
| `Forbidden` | 403 | Access denied |
| `NotFound` | 404 | Resource not found |
| `ValidationError` | 422 | Validation error |
| `RateLimited` | 429 | Rate limit exceeded |
| `ServerError` | 500 | Server error |
| `HttpError` | - | HTTP client error |
| `JsonError` | - | JSON serialization error |

### Helper Methods

```rust
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);
    }
}
```

## API Reference

### TypecastClient Methods

| Method | Description |
|--------|-------------|
| `from_env()` | Create client from environment variables |
| `with_api_key(key)` | Create client with API key |
| `new(config)` | Create client with custom configuration |
| `text_to_speech(&request)` | Convert text to speech audio |
| `generate_to_file(path, request)` | Generate speech and save it directly to a local file |
| `clone_voice(audio, filename, name, model)` | Create a custom voice via instant cloning |
| `delete_voice(voice_id)` | Delete a custom cloned voice |
| `get_voices_v3(filter)` | Get available voices with optional filter |
| `get_voice_v3(voice_id)` | Get a specific voice by ID |

### TTSRequest Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `voice_id` | `String` | ✓ | Voice ID (format: `tc_*` or `uc_*`) |
| `text` | `String` | ✓ | Text to synthesize (max 2000 chars) |
| `model` | `TTSModel` | ✓ | TTS model (`SsfmV21` or `SsfmV30`) |
| `language` | `Option<String>` | | ISO 639-3 code (auto-detected if omitted) |
| `prompt` | `Option<TTSPrompt>` | | Emotion settings (Prompt/PresetPrompt/SmartPrompt) |
| `output` | `Option<Output>` | | Audio output settings |

### TTSResponse Fields

| Field | Type | Description |
|-------|------|-------------|
| `audio_data` | `Vec<u8>` | Generated audio data |
| `duration` | `f64` | Audio duration in seconds |
| `format` | `AudioFormat` | Audio format (`Wav` or `Mp3`) |

## Complete Example

```rust
use typecast_rust::{
    TypecastClient, TTSRequest, TTSModel, PresetPrompt, 
    EmotionPreset, Output, AudioFormat, VoicesV2Filter, Gender,
};
use std::fs;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // Initialize client
    let client = TypecastClient::from_env()?;
    
    // Discover voices
    let filter = VoicesV2Filter::new()
        .model(TTSModel::SsfmV30)
        .gender(Gender::Female);
    
    let voices = client.get_voices_v3(Some(filter)).await?;
    println!("Found {} female voices", voices.len());
    
    // Use first voice
    if let Some(voice) = voices.first() {
        let request = TTSRequest::new(
            &voice.voice_id,
            "Welcome to Typecast! This is a demonstration of our text-to-speech API.",
            TTSModel::SsfmV30,
        )
        .language("eng")
        .prompt(
            PresetPrompt::new()
                .emotion_preset(EmotionPreset::Happy)
                .emotion_intensity(1.2)
        )
        .output(
            Output::new()
                .target_lufs(-14.0)
                .audio_format(AudioFormat::Mp3)
        );
        
        let response = client.text_to_speech(&request).await?;
        
        fs::write("welcome.mp3", &response.audio_data)?;
        println!("Saved welcome.mp3 ({:.2}s)", response.duration);
    }
    
    Ok(())
}
```
