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

# Python

> Access the Typecast API with our official Python SDK.

<CardGroup cols={2}>
  <Card title="Package" icon="python" href="https://pypi.org/project/typecast-python/">
    Typecast Python SDK
  </Card>

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

## Installation

Install the Typecast Python SDK using pip:

```bash theme={null}
pip install --upgrade typecast-python
```

<Note>The package is installed as `typecast-python`, but imported as `typecast`.</Note>

<Warning>
  Latest registered version: **0.3.8** on PyPI. Make sure you have **version 0.3.8 or higher** installed. You can check your version with `pip show typecast-python`. If you have an older version, run `pip install --upgrade typecast-python` to update.
</Warning>

## Quick Start

Here's a simple example to convert text to speech:

```python theme={null}
from typecast import Typecast
from typecast.models import TTSRequest

# Initialize client
client = Typecast(api_key="YOUR_API_KEY")

# Convert text to speech
response = client.text_to_speech(TTSRequest(
    text="Hello there! I'm your friendly text-to-speech agent.",
    model="ssfm-v30",
    voice_id="tc_672c5f5ce59fac2a48faeaee"
))

# Save audio file
with open('output.wav', 'wb') as f:
    f.write(response.audio_data)

print(f"Duration: {response.duration}s, Format: {response.format}")
```

## Features

The Typecast Python 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)
* **Async Support**: Built-in async client for high-performance applications
* **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
* **Type Hints**: Full type annotations with Pydantic models
* **Timestamp TTS**: Word- and character-level alignment data for subtitles, karaoke, and lip-sync
* **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`.

```python theme={null}
recommendations = client.recommend_voices(
    "warm female voice for a product tutorial",
    count=3,
)

for voice in recommendations:
    print(voice.voice_id, voice.voice_name, voice.score)
```

Recommendation results contain only `voice_id`, `voice_name`, and `score`. Use `voice_v2(voice_id)` or `voices_v2()` when you need detailed metadata such as supported models, emotions, gender, age, or use cases.

## Configuration

You can configure the API key using environment variables or pass it directly to the client:

<CodeGroup>
  ```bash Environment Variable theme={null}
  export TYPECAST_API_KEY="your-api-key-here"
  ```

  ```python From Environment theme={null}
  from typecast import Typecast

  # From environment variable
  client = Typecast()
  ```

  ```python Direct Configuration theme={null}
  from typecast import Typecast

  # Or pass directly
  client = Typecast(api_key="your-api-key-here")
  ```
</CodeGroup>

<Info>
  When requests go through your own proxy, set `TYPECAST_API_HOST` or pass `api_host` and omit `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>

```python Proxy without API key theme={null}
from typecast import Typecast

client = Typecast(api_host="https://your-proxy.example.com")
```

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

    ```python theme={null}
    from typecast import Typecast
    from typecast.models import TTSRequest, SmartPrompt

    client = Typecast()

    response = client.text_to_speech(TTSRequest(
        text="Everything is going to be okay.",
        model="ssfm-v30",
        voice_id="tc_672c5f5ce59fac2a48faeaee",
        prompt=SmartPrompt(
            emotion_type="smart",
            previous_text="I just got the best news!",  # Optional context
            next_text="I can't wait to celebrate!"      # Optional context
        )
    ))
    ```
  </Tab>

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

    ```python theme={null}
    from typecast import Typecast
    from typecast.models import TTSRequest, PresetPrompt

    client = Typecast()

    response = client.text_to_speech(TTSRequest(
        text="I am so excited to show you these features!",
        model="ssfm-v30",
        voice_id="tc_672c5f5ce59fac2a48faeaee",
        prompt=PresetPrompt(
            emotion_type="preset",
            emotion_preset="happy",    # normal, happy, sad, angry, whisper, toneup, tonedown
            emotion_intensity=1.5      # Range: 0.0 to 2.0
        )
    ))
    ```
  </Tab>
</Tabs>

### Audio Customization

Control loudness, pitch, tempo, and output format:

```python theme={null}
from typecast import Typecast
from typecast.models import TTSRequest, Output

client = Typecast()

response = client.text_to_speech(TTSRequest(
    text="Customized audio output!",
    model="ssfm-v30",
    voice_id="tc_672c5f5ce59fac2a48faeaee",
    output=Output(
        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="mp3"   # Options: wav, mp3
    ),
    seed=42                  # Unsigned seed for reproducible results
))
```

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

```python theme={null}
client.generate_to_file(
    'output.mp3',
    text='Hello from Typecast.',
    voice_id='tc_672c5f5ce59fac2a48faeaee'  # Find voice IDs at https://typecast.ai/developers/api/voices
)

# Async client
await async_client.generate_to_file(
    'output.mp3',
    text='Hello from Typecast.',
    voice_id='tc_672c5f5ce59fac2a48faeaee'  # Find voice IDs at https://typecast.ai/developers/api/voices
)
```

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

```python theme={null}
response = (
    client.compose_speech()
    .defaults(voice_id="tc_672c5f5ce59fac2a48faeaee", model="ssfm-v30")
    .say("Hello<|5s|>Nice to meet you<|1s|>Today<|2s|>how does the weather feel?")
    .generate()
)
```

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

```python theme={null}
from typecast import Typecast
from typecast.models import Output

client = Typecast(api_key="YOUR_API_KEY")

response = (
    client.compose_speech()
    .defaults(voice_id="tc_672c5f5ce59fac2a48faeaee", model="ssfm-v30")
    .say("Hello there")
    .pause(5)
    .say("Nice to meet you", voice_id="tc_60e5426de8b95f1d3000d7b5", output=Output(audio_pitch=2))
    .say("Today")
    .pause(2)
    .say("How does the weather feel?")
    .generate()
)

with open("conversation.wav", "wb") as f:
    f.write(response.audio_data)
```

### Voice Discovery (V2 API)

List and filter available voices with enhanced metadata:

```python theme={null}
from typecast import Typecast
from typecast.models import VoicesV2Filter, TTSModel, GenderEnum, AgeEnum

client = Typecast()

# Get all voices
voices = client.voices_v2()

# Filter by criteria
filtered = client.voices_v2(VoicesV2Filter(
    model=TTSModel.SSFM_V30,
    gender=GenderEnum.FEMALE,
    age=AgeEnum.YOUNG_ADULT
))

# Display voice info
for voice in voices:
    print(f"ID: {voice.voice_id}, Name: {voice.voice_name}")
    print(f"Gender: {voice.gender}, Age: {voice.age}")
    print(f"Models: {', '.join(m.version.value for m in voice.models)}")
    print(f"Use cases: {voice.use_cases}")
```

### Async Client

For high-performance applications, use the async client:

```python theme={null}
import asyncio
from typecast import AsyncTypecast
from typecast.models import TTSRequest

async def main():
    async with AsyncTypecast() as client:
        response = await client.text_to_speech(TTSRequest(
            text="Hello from async!",
            model="ssfm-v30",
            voice_id="tc_672c5f5ce59fac2a48faeaee"
        ))

        with open('async_output.wav', 'wb') as f:
            f.write(response.audio_data)

asyncio.run(main())
```

### Streaming

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

```python theme={null}
# pip install requests sounddevice
import sounddevice as sd
from typecast import Typecast
from typecast.models import TTSRequestStream, OutputStream

client = Typecast()

request = TTSRequestStream(
    text="Stream this text as audio in real time.",
    model="ssfm-v30",
    voice_id="tc_672c5f5ce59fac2a48faeaee",
    output=OutputStream(audio_format="wav")
)

with sd.RawOutputStream(samplerate=32000, channels=1, dtype="int16") as player:
    buf, first = bytearray(), True
    for chunk in client.text_to_speech_stream(request):
        if first:
            chunk = chunk[44:]  # Skip 44-byte WAV header
            first = False
        buf.extend(chunk)
        n = len(buf) - (len(buf) % 2)  # int16 alignment
        if n:
            player.write(bytes(buf[:n]))
            del buf[:n]
```

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

```python theme={null}
from typecast import Typecast
from typecast.models import TTSRequestWithTimestamps

client = Typecast(api_key="YOUR_API_KEY")

response = client.text_to_speech_with_timestamps(TTSRequestWithTimestamps(
    text="Hello. How are you?",
    model="ssfm-v30",
    voice_id="tc_60e5426de8b95f1d3000d7b5",
))

# Save audio
with open("output.wav", "wb") as f:
    f.write(response.audio_bytes())

print(f"Duration: {response.audio_duration}s")
for word in response.words:
    print(f"  [{word.start_time:.3f}s – {word.end_time:.3f}s] {word.text}")
```

### Granularity

Pass `granularity="word"` (default) or `granularity="char"` to control the alignment unit.

```python theme={null}
# Character-level alignment — required for Japanese / Chinese
response = client.text_to_speech_with_timestamps(TTSRequestWithTimestamps(
    text="Hello. How are you?",
    model="ssfm-v30",
    voice_id="tc_60e5426de8b95f1d3000d7b5",
    granularity="char",
))

for char in response.characters:
    print(f"  [{char.start_time:.3f}s – {char.end_time:.3f}s] {char.text}")
```

### Subtitle Export

The response object includes helpers that convert alignment data to SRT or WebVTT captions. Captions are split on sentence terminators (`. ? ! 。 ？ ！`) and capped at 7 seconds / 42 characters per cue (BBC/Netflix subtitle guidelines).

```python theme={null}
# Export SRT captions
srt_text = response.to_srt()
with open("output.srt", "w", encoding="utf-8") as f:
    f.write(srt_text)

# Export WebVTT captions
vtt_text = response.to_vtt()
with open("output.vtt", "w", encoding="utf-8") as f:
    f.write(vtt_text)
```

### Save Audio Helper

```python theme={null}
# Equivalent to writing audio_bytes() to a file
response.save_audio("output.wav")
```

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

```python theme={null}
from typecast import Typecast
from typecast.models import TTSRequest

client = Typecast(api_key="YOUR_API_KEY")

voice = client.clone_voice(
    audio="sample.wav",
    name="my-voice",
    model="ssfm-v30",
)

response = client.text_to_speech(TTSRequest(
    text="Hello from my cloned voice!",
    voice_id=voice.voice_id,
    model="ssfm-v30",
))

with open("output.wav", "wb") as f:
    f.write(response.audio_data)

client.delete_voice(voice.voice_id)
```

<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

**Recommended**: Use the `LanguageCode` enum for type-safe language selection. You can also pass the ISO 639-3 code as a string (e.g., `"eng"`).

The SDK supports 37 languages with ISO 639-3 codes:

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

Use the `LanguageCode` enum for type-safe language selection:

```python theme={null}
from typecast.models import TTSRequest, LanguageCode

response = client.text_to_speech(TTSRequest(
    text="Hello",
    model="ssfm-v30",
    voice_id="tc_672c5f5ce59fac2a48faeaee",
    language=LanguageCode.ENG
))
```

## Error Handling

The SDK provides specific exceptions for different HTTP status codes:

```python theme={null}
from typecast import (
    Typecast,
    TypecastError,
    BadRequestError,
    UnauthorizedError,
    PaymentRequiredError,
    NotFoundError,
    UnprocessableEntityError,
    RateLimitError,
    InternalServerError,
)

try:
    response = client.text_to_speech(request)
except UnauthorizedError:
    print("Invalid API key")
except PaymentRequiredError:
    print("Insufficient credits")
except RateLimitError:
    print("Rate limit exceeded - please retry later")
except TypecastError as e:
    print(f"Error {e.status_code}: {e.message}")
```

| Exception                  | Status Code | Description                |
| -------------------------- | ----------- | -------------------------- |
| `BadRequestError`          | 400         | Invalid request parameters |
| `UnauthorizedError`        | 401         | Invalid or missing API key |
| `PaymentRequiredError`     | 402         | Insufficient credits       |
| `NotFoundError`            | 404         | Resource not found         |
| `UnprocessableEntityError` | 422         | Validation error           |
| `RateLimitError`           | 429         | Rate limit exceeded        |
| `InternalServerError`      | 500         | Server error               |
