> ## 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

> Access the Typecast API with our official Rust SDK.

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 theme={null}
[dependencies]
typecast-rust = "0.3.7"
tokio = { version = "1", features = ["full"] }
```

Or use Cargo to add the dependency:

```bash theme={null}
cargo add typecast-rust tokio --features tokio/full
```

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

## Quick Start

```rust theme={null}
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**: 37 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 theme={null}
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.

## Configuration

Set your API key via environment variable or constructor:

```rust theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
    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 theme={null}
    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 theme={null}
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
)
.seed(42);  // Unsigned seed for reproducible results

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

```rust theme={null}
client.generate_to_file(
    "output.mp3",
    GenerateToFileRequest::new("tc_672c5f5ce59fac2a48faeaee", "Hello from Typecast."), // Find voice IDs at https://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 theme={null}
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, 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.

```rust theme={null}
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(())
}
```

### Voice Discovery (V2 API)

List and filter available voices with enhanced metadata:

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

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

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

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

// Display voice info
for voice in &voices {
    println!("ID: {}, Name: {}", voice.voice_id, voice.voice_name);
    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_v2("tc_672c5f5ce59fac2a48faeaee").await?;
println!("Voice: {} ({:?})", voice.voice_name, voice.gender);
```

### Multilingual Content

The SDK supports 37 languages with automatic language detection:

```rust theme={null}
// 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 theme={null}
// 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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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 37 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 theme={null}
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 theme={null}
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_v2(filter)`                     | Get available voices with optional filter            |
| `get_voice_v2(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                              |
| `seed`     | `Option<u32>`       |          | Unsigned integer seed for reproducibility (≥ 0)    |

### 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 theme={null}
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_v2(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(())
}
```
