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

> 공식 Rust SDK로 타입캐스트 API에 액세스하세요.

[타입캐스트 API](https://typecast.ai)를 위한 공식 Rust 라이브러리입니다. AI 기반 음성을 사용하여 텍스트를 생동감 있는 음성으로 변환하세요.

Tokio 런타임을 사용한 async/await 지원으로 구축되었습니다. Cargo 패키지 관리자와 함께 작동합니다.

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

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

## 설치

`Cargo.toml`에 다음을 추가하세요:

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

또는 Cargo를 사용하여 의존성을 추가하세요:

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

<Warning>
  최신 등록 버전은 crates.io 기준 **0.3.7**입니다. **버전 0.3.7 이상**이 설치되어 있는지 확인하세요. 업데이트가 필요하면 `Cargo.toml`을 확인하세요.
</Warning>

## 빠른 시작

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 클라이언트 초기화 (환경변수에서 TYPECAST_API_KEY 읽기)
    let client = TypecastClient::from_env()?;

    // 텍스트를 음성으로 변환
    let request = TTSRequest::new(
        "tc_672c5f5ce59fac2a48faeaee",
        "안녕하세요! 저는 텍스트 음성 변환 에이전트입니다.",
        TTSModel::SsfmV30,
    );

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

    // 오디오 파일 저장
    fs::write("output.wav", &response.audio_data)?;

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

    Ok(())
}
```

## 기능

Typecast Rust SDK는 텍스트 음성 변환을 위한 강력한 기능을 제공합니다:

* **다중 음성 모델**: `ssfm-v30`(최신) 및 `ssfm-v21` AI 음성 모델 지원
* **다국어 지원**: 영어, 한국어, 스페인어, 일본어, 중국어 등 37개 언어 지원
* **감정 제어**: 이모션 프리셋(normal, happy, sad, angry, whisper, toneup, tonedown) 또는 스마트 문맥 인식 추론
* **오디오 사용자 정의**: 라우드니스 LUFS(-70 to 0), 피치(-12 to +12 반음), 템포(0.5x to 2.0x), 형식(WAV/MP3) 제어
* **음성 탐색**: 모델, 성별, 나이, 사용 사례별 필터링이 가능한 V2 Voices API
* **빌더 패턴**: 쉬운 요청 구성을 위한 메서드 체이닝 Fluent API
* **Async/Await**: 효율적인 비동기 작업을 위해 Tokio 기반으로 구축
* **타임스탬프 TTS**: 자막, 가라오케, 립싱크를 위한 단어·문자 단위 정렬 데이터
* **스트리밍**: 저지연 재생을 위한 실시간 청크 오디오 전송
* **포괄적인 오류 처리**: 패턴 매칭을 지원하는 타입화된 에러 enum

## 보이스 추천

원하는 스타일은 알지만 정확한 `voice_id`를 모를 때 `recommend_voices`를 사용합니다.

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

추천 결과에는 `voice_id`, `voice_name`, `score`만 포함됩니다. 지원 모델, 감정, 성별, 연령대, 사용 사례 같은 상세 메타데이터가 필요하면 `get_voice_v2` 또는 `get_voices_v2`로 추가 조회하세요.

## 구성

환경 변수 또는 생성자를 통해 API 키를 설정하세요:

```rust theme={null}
use typecast_rust::{TypecastClient, ClientConfig};
use std::time::Duration;

// 환경 변수 사용 (권장)
// export TYPECAST_API_KEY="your-api-key-here"
let client = TypecastClient::from_env()?;

// 또는 직접 전달
let client = TypecastClient::with_api_key("your-api-key-here")?;

// 또는 사용자 정의 구성과 함께
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>
  자체 프록시를 통해 요청하는 경우 `base_url`을 프록시 엔드포인트로 설정하고 API 키를 생략할 수 있습니다. API 키가 비어 있거나 없으면 SDK는 `X-API-KEY` 헤더를 보내지 않습니다. 기본 Typecast 호스트로 요청할 때는 API 키가 계속 필요합니다.
</Info>

```rust API 키 없는 프록시 theme={null}
let config = ClientConfig::new("")
    .base_url("https://your-proxy.example.com");

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

### 환경 파일

프로젝트 루트에 `.env` 파일을 만드세요:

```bash theme={null}
TYPECAST_API_KEY=your-api-key-here
```

<Info>
  `.env` 파일에서 환경 변수를 로드하려면 `dotenvy` 크레이트를 사용하세요.
</Info>

## 고급 사용법

### 감정 제어 (ssfm-v30)

ssfm-v30은 두 가지 감정 제어 모드를 제공합니다: **프리셋** 및 **스마트**.

<Tabs>
  <Tab title="스마트 모드">
    AI가 문맥에서 감정을 추론하도록 합니다:

    ```rust theme={null}
    use typecast_rust::{TTSRequest, TTSModel, SmartPrompt};

    let request = TTSRequest::new(
        "tc_672c5f5ce59fac2a48faeaee",
        "모든 것이 잘 될 거예요.",
        TTSModel::SsfmV30,
    )
    .prompt(
        SmartPrompt::new()
            .previous_text("방금 최고의 소식을 들었어요!")  // 선택적 문맥
            .next_text("축하할 수 있어서 너무 기다려져요!")     // 선택적 문맥
    );

    let response = client.text_to_speech(&request).await?;
    ```
  </Tab>

  <Tab title="프리셋 모드">
    프리셋 값으로 감정을 명시적으로 설정합니다:

    ```rust theme={null}
    use typecast_rust::{TTSRequest, TTSModel, PresetPrompt, EmotionPreset};

    let request = TTSRequest::new(
        "tc_672c5f5ce59fac2a48faeaee",
        "이 기능들을 보여드리게 되어 정말 기대됩니다!",
        TTSModel::SsfmV30,
    )
    .prompt(
        PresetPrompt::new()
            .emotion_preset(EmotionPreset::Happy)  // Normal, Happy, Sad, Angry, Whisper, ToneUp, ToneDown
            .emotion_intensity(1.5)                 // 범위: 0.0 ~ 2.0
    );

    let response = client.text_to_speech(&request).await?;
    ```
  </Tab>
</Tabs>

### 오디오 사용자 정의

라우드니스, 피치, 템포 및 출력 형식을 제어합니다:

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

let request = TTSRequest::new(
    "tc_672c5f5ce59fac2a48faeaee",
    "사용자 정의 오디오 출력!",
    TTSModel::SsfmV30,
)
.output(
    Output::new()
        .target_lufs(-14.0)                 // 범위: -70 ~ 0 (LUFS)
        .audio_pitch(2)                   // 범위: -12 to +12 반음
        .audio_tempo(1.2)                 // 범위: 0.5x to 2.0x
        .audio_format(AudioFormat::Mp3)   // 옵션: Wav, Mp3
)
.seed(42);  // 부호 없는 정수 시드 (재현 가능한 결과)

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_to_file`은 음성 합성과 파일 저장을 한 번에 처리합니다. `model`은 기본값으로 `ssfm-v30`을 사용하고, `.mp3` 또는 `.wav` 확장자로 출력 형식을 결정합니다.

```rust theme={null}
client.generate_to_file(
    "output.mp3",
    GenerateToFileRequest::new("tc_672c5f5ce59fac2a48faeaee", "안녕하세요, 타입캐스트입니다."), // voice_id는 https://typecast.ai/developers/api/voices 에서 확인하세요.
).await?;
```

### 텍스트만으로 쉼 표현

한 voice로 읽는 문장 안에 쉼만 넣고 싶다면 텍스트에 pause markup을 직접 작성합니다. `<|5s|>`, `<|1s|>`, `<|0.3s|>`, `<|0.34413s|>`처럼 쓰며 값은 초 단위이고 반드시 `s`로 끝납니다. 별도 pause 함수를 호출하지 않아도 텍스트만 보고 쉼 위치를 확인할 수 있습니다.

```rust theme={null}
let audio = client
    .compose_speech()
    .defaults(ComposerSettings::new().voice_id("tc_672c5f5ce59fac2a48faeaee").model(TtsModel::SsfmV30))
    .say("안녕하세요<|5s|>반갑습니다<|1s|>오늘<|2s|>날씨는 어떤 것 같으세요?")
    .generate()
    .await?;
```

### 다중 화자 합성

한 파일 안에서 서로 다른 voice나 구간별 pitch, tempo, prompt, seed 같은 옵션을 조합해야 할 때 사용합니다. composer는 각 구간을 WAV로 생성하고 앞뒤 무음 PCM 샘플을 trim한 뒤 합성합니다. MP3가 필요하면 먼저 WAV를 생성한 다음 앱 또는 서버 파이프라인에서 변환하세요.

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

### 음성 탐색 (V2 API)

향상된 메타데이터로 사용 가능한 음성을 나열하고 필터링합니다:

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

// 모든 음성 가져오기
let voices = client.get_voices_v2(None).await?;

// 기준으로 필터링
let filter = VoicesV2Filter::new()
    .model(TTSModel::SsfmV30)
    .gender(Gender::Female)
    .age(Age::YoungAdult);

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

// 음성 정보 표시
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(", "));
    }
}

// ID로 특정 음성 가져오기
let voice = client.get_voice_v2("tc_672c5f5ce59fac2a48faeaee").await?;
println!("Voice: {} ({:?})", voice.voice_name, voice.gender);
```

### 다국어 콘텐츠

SDK는 자동 언어 감지와 함께 37개 언어를 지원합니다:

```rust theme={null}
// 언어 자동 감지 (권장)
let request = TTSRequest::new(
    "tc_672c5f5ce59fac2a48faeaee",
    "こんにちは。お元気ですか。",
    TTSModel::SsfmV30,
);

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

// 또는 명시적으로 언어 지정
let korean_request = TTSRequest::new(
    "tc_672c5f5ce59fac2a48faeaee",
    "안녕하세요. 반갑습니다.",
    TTSModel::SsfmV30,
)
.language("kor");  // ISO 639-3 언어 코드

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

### 스트리밍

저지연 재생을 위한 실시간 오디오 청크 스트리밍:

```rust theme={null}
// 원시 PCM 추출 (44바이트 WAV 헤더 건너뛰기)
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..]  // WAV 헤더 건너뛰기
    } else {
        &bytes
    };
    // pcm은 32000 Hz 16비트 모노 원시 PCM
    // 오디오 출력으로 전달 (예: rodio, cpal)
}
```

<Note>
  **WAV 스트리밍 형식:** 32000 Hz, 16비트, 모노 PCM. 첫 번째 청크에 44바이트 WAV 헤더(size = `0xFFFFFFFF`)가 포함되며, 이후 청크는 원시 PCM 데이터만 포함합니다. MP3 형식: 320 kbps, 44100 Hz, 각 청크는 독립적으로 디코딩 가능합니다. `StreamExt`를 사용하려면 `futures-util`이 필요합니다.
</Note>

## 타임스탬프 TTS

`text_to_speech_with_timestamps()`는 `POST /v1/text-to-speech/with-timestamps`를 래핑하며, 오디오와 함께 단어·문자 단위 정렬 데이터를 반환합니다. 가라오케 하이라이트, 자막 생성, 립싱크 애플리케이션에 활용할 수 있습니다.

### 기본 사용법

```rust theme={null}
use typecast::{TypecastClient, models::TTSRequestWithTimestamps};
use std::fs;

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

    let result = client.text_to_speech_with_timestamps(TTSRequestWithTimestamps {
        voice_id: "tc_60e5426de8b95f1d3000d7b5".to_string(),
        text: "Hello. How are you?".to_string(),
        model: "ssfm-v30".to_string(),
        ..Default::default()
    }).await?;

    fs::write("output.wav", result.audio_bytes())?;
    println!("재생 시간: {:.3}초", result.audio_duration);

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

### 정밀도(Granularity) 설정

`Granularity::Word`(기본값) 또는 `Granularity::Char`를 설정해 정렬 단위를 제어합니다.

```rust theme={null}
use typecast::models::Granularity;

// 문자 단위 정렬 — 일본어·중국어에 필수
let result = client.text_to_speech_with_timestamps(TTSRequestWithTimestamps {
    voice_id: "tc_60e5426de8b95f1d3000d7b5".to_string(),
    text: "Hello. How are you?".to_string(),
    model: "ssfm-v30".to_string(),
    granularity: Some(Granularity::Char),
    ..Default::default()
}).await?;
```

### 자막 내보내기

```rust theme={null}
let srt = result.to_srt()?;
fs::write("output.srt", srt)?;

let vtt = result.to_vtt()?;
fs::write("output.vtt", vtt)?;
```

<Note>
  **일본어·중국어:** 공백 구분자가 없는 언어(jpn, zho)는 단어 단위 세그먼트가 의미를 갖지 않습니다. 해당 언어에는 `Granularity::Char`를 사용해 문자 단위 정렬 데이터를 얻으세요.
</Note>

## 지원 언어

SDK는 자동 언어 감지와 함께 37개 언어를 지원합니다:

| 코드    | 언어    | 코드    | 언어     | 코드    | 언어     |
| ----- | ----- | ----- | ------ | ----- | ------ |
| `eng` | 영어    | `jpn` | 일본어    | `ukr` | 우크라이나어 |
| `kor` | 한국어   | `ell` | 그리스어   | `ind` | 인도네시아어 |
| `spa` | 스페인어  | `tam` | 타밀어    | `dan` | 덴마크어   |
| `deu` | 독일어   | `tgl` | 타갈로그어  | `swe` | 스웨덴어   |
| `fra` | 프랑스어  | `fin` | 핀란드어   | `msa` | 말레이어   |
| `ita` | 이탈리아어 | `zho` | 중국어    | `ces` | 체코어    |
| `pol` | 폴란드어  | `slk` | 슬로바키아어 | `por` | 포르투갈어  |
| `nld` | 네덜란드어 | `ara` | 아랍어    | `bul` | 불가리아어  |
| `rus` | 러시아어  | `hrv` | 크로아티아어 | `ron` | 루마니아어  |
| `ben` | 벵골어   | `hin` | 힌디어    | `hun` | 헝가리어   |
| `nan` | 민난어   | `nor` | 노르웨이어  | `pan` | 펀자브어   |
| `tha` | 태국어   | `tur` | 터키어    | `vie` | 베트남어   |
| `yue` | 광둥어   |       |        |       |        |

<Info>
  지정하지 않으면 입력 텍스트에서 언어가 자동으로 감지됩니다.
</Info>

## 오류 처리

SDK는 패턴 매칭으로 API 오류를 처리할 수 있는 타입화된 에러 enum을 제공합니다:

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

let request = TTSRequest::new("voice_id", "안녕하세요", TTSModel::SsfmV30);

match client.text_to_speech(&request).await {
    Ok(response) => {
        println!("Success! Duration: {:.2}s", response.duration);
    }
    Err(TypecastError::Unauthorized { detail }) => {
        // 401: 잘못된 API 키
        eprintln!("Invalid API key: {}", detail);
    }
    Err(TypecastError::PaymentRequired { detail }) => {
        // 402: 크레딧 부족
        eprintln!("Insufficient credits: {}", detail);
    }
    Err(TypecastError::NotFound { detail }) => {
        // 404: 리소스를 찾을 수 없음
        eprintln!("Voice not found: {}", detail);
    }
    Err(TypecastError::RateLimited { detail }) => {
        // 429: 요청 한도 초과
        eprintln!("Rate limit exceeded - please try again later: {}", detail);
    }
    Err(TypecastError::ServerError { detail }) => {
        // 500: 서버 오류
        eprintln!("Server error: {}", detail);
    }
    Err(e) => {
        eprintln!("Error: {}", e);
    }
}
```

### 오류 유형

| 오류 변형             | 상태 코드 | 설명              |
| ----------------- | ----- | --------------- |
| `BadRequest`      | 400   | 잘못된 요청 매개변수     |
| `Unauthorized`    | 401   | 잘못되거나 누락된 API 키 |
| `PaymentRequired` | 402   | 크레딧 부족          |
| `Forbidden`       | 403   | 접근 거부           |
| `NotFound`        | 404   | 리소스를 찾을 수 없음    |
| `ValidationError` | 422   | 유효성 검사 오류       |
| `RateLimited`     | 429   | 요청 한도 초과        |
| `ServerError`     | 500   | 서버 오류           |
| `HttpError`       | -     | HTTP 클라이언트 오류   |
| `JsonError`       | -     | JSON 직렬화 오류     |

### 헬퍼 메서드

```rust theme={null}
if let Err(e) = result {
    if e.is_unauthorized() {
        println!("Please check your API key");
    } else if e.is_rate_limited() {
        println!("Please try again later");
    } else if e.is_server_error() {
        println!("Server issue, please try again later");
    }

    if let Some(code) = e.status_code() {
        println!("HTTP status: {}", code);
    }
}
```

## API 레퍼런스

### TypecastClient 메서드

| 메서드                               | 설명                     |
| --------------------------------- | ---------------------- |
| `from_env()`                      | 환경 변수에서 클라이언트 생성       |
| `with_api_key(key)`               | API 키로 클라이언트 생성        |
| `new(config)`                     | 사용자 정의 구성으로 클라이언트 생성   |
| `text_to_speech(&request)`        | 텍스트를 음성 오디오로 변환        |
| `generate_to_file(path, request)` | 음성을 생성하고 로컬 파일로 바로 저장  |
| `get_voices_v2(filter)`           | 선택적 필터로 사용 가능한 음성 가져오기 |
| `get_voice_v2(voice_id)`          | ID로 특정 음성 가져오기         |

### TTSRequest 필드

| 필드         | 타입                  | 필수 | 설명                                      |
| ---------- | ------------------- | -- | --------------------------------------- |
| `voice_id` | `String`            | ✓  | 음성 ID (형식: `tc_*` 또는 `uc_*`)            |
| `text`     | `String`            | ✓  | 합성할 텍스트 (최대 2000자)                      |
| `model`    | `TTSModel`          | ✓  | TTS 모델 (`SsfmV21` 또는 `SsfmV30`)         |
| `language` | `Option<String>`    |    | ISO 639-3 코드 (생략 시 자동 감지)               |
| `prompt`   | `Option<TTSPrompt>` |    | 감정 설정 (Prompt/PresetPrompt/SmartPrompt) |
| `output`   | `Option<Output>`    |    | 오디오 출력 설정                               |
| `seed`     | `Option<u32>`       |    | 재현성을 위한 부호 없는 정수 시드 (≥ 0)               |

### TTSResponse 필드

| 필드           | 타입            | 설명                      |
| ------------ | ------------- | ----------------------- |
| `audio_data` | `Vec<u8>`     | 생성된 오디오 데이터             |
| `duration`   | `f64`         | 오디오 길이 (초)              |
| `format`     | `AudioFormat` | 오디오 형식 (`Wav` 또는 `Mp3`) |

## 완전한 예제

```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>> {
    // 클라이언트 초기화
    let client = TypecastClient::from_env()?;
    
    // 음성 탐색
    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());
    
    // 첫 번째 음성 사용
    if let Some(voice) = voices.first() {
        let request = TTSRequest::new(
            &voice.voice_id,
            "Typecast에 오신 것을 환영합니다! 텍스트 음성 변환 API의 데모입니다.",
            TTSModel::SsfmV30,
        )
        .language("kor")
        .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!("welcome.mp3 saved ({:.2}s)", response.duration);
    }
    
    Ok(())
}
```
