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

# Zig

타입캐스트 [API](https://studio.typecast.ai/developers/api)를 위한 공식 Zig 라이브러리입니다. AI 음성을 사용하여 텍스트를 자연스러운 음성으로 변환합니다.

순수 Zig 구현 - C 의존성 없음. Zig 표준 라이브러리의 `std.http.Client`와 `std.json`만 사용합니다.

<CardGroup cols={2}>
<Card title="소스 코드" icon="github" href="https://github.com/neosapience/typecast-sdk/tree/main/typecast-zig">
타입캐스트 Zig SDK 소스 코드
</Card>

<Card title="패키지" icon="cube" href="https://github.com/neosapience/typecast-sdk/tree/main/typecast-zig">
Zig 패키지 (zig fetch)
</Card>
</CardGroup>

## 설치

`zig fetch`로 의존성을 추가합니다:

```bash
zig fetch --save "https://github.com/neosapience/typecast-sdk/archive/refs/tags/typecast-zig/v0.2.12.tar.gz"
```

<Note>최신 등록 버전은 SDK Git 태그 기준 **typecast-zig/v0.2.12**입니다.</Note>

그런 다음 `build.zig`에 import를 추가합니다:

```zig
const typecast_dep = b.dependency("typecast_zig", .{
    .target = target,
    .optimize = optimize,
});
exe.root_module.addImport("typecast", typecast_dep.module("typecast"));
```

## 빠른 시작

```zig
const std = @import("std");
const typecast = @import("typecast");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // 클라이언트 초기화 (환경변수에서 TYPECAST_API_KEY 읽기)
    var client = typecast.Client.init(allocator, .{
        .api_key = std.posix.getenv("TYPECAST_API_KEY") orelse return error.MissingApiKey,
    });
    defer client.deinit();

    // 텍스트를 음성으로 변환
    const response = try client.textToSpeech(.{
        .voice_id = "tc_672c5f5ce59fac2a48faeaee",
        .text = "안녕하세요! 타입캐스트 Zig SDK입니다.",
        .model = .ssfm_v30,
    });
    defer allocator.free(response.audio_data);

    // 오디오 파일 저장
    const file = try std.fs.cwd().createFile("output.wav", .{});
    defer file.close();
    try file.writeAll(response.audio_data);

    std.debug.print("{d} 바이트 저장, 재생 시간: {d:.1}초\n", .{
        response.audio_data.len, response.duration,
    });
}
```

## 기능

- **다중 음성 모델**: `ssfm-v30` (최신) 및 `ssfm-v21` AI 음성 모델 지원
- **다국어 지원**: 영어, 한국어, 스페인어, 일본어, 중국어 등 35+개 언어
- **감정 제어**: 프리셋 감정 (normal, happy, sad, angry, whisper, toneup, tonedown) 또는 스마트 문맥 인식 추론
- **오디오 커스터마이징**: 음량 (LUFS -70 to 0), 피치 (-12 to +12 세미톤), 템포 (0.5x to 2.0x), 포맷 (WAV/MP3) 제어
- **보이스 탐색**: V2 Voices API로 모델, 성별, 나이, 용도별 필터링
- **순수 Zig**: 외부 의존성 없이 표준 라이브러리만 사용
- **타임스탬프 TTS**: 자막, 가라오케, 립싱크를 위한 단어·문자 단위 정렬 데이터
- **스트리밍**: 저지연 재생을 위한 실시간 청크 오디오 전송 (콜백 기반)
- **명시적 메모리 관리**: 호출자 제공 allocator로 명확한 소유권 관리

## 보이스 추천

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

```zig
const voices = try client.recommendVoices(
    "warm female voice for a product tutorial",
    3,
);
defer {
    for (voices) |voice| {
        allocator.free(voice.voice_id);
        allocator.free(voice.voice_name);
    }
    allocator.free(voices);
}

for (voices) |voice| {
    std.debug.print("{s} {s} {d:.3}\n", .{
        voice.voice_id,
        voice.voice_name,
        voice.score,
    });
}
```

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

## 설정

환경변수 또는 직접 API 키를 전달할 수 있습니다:

```zig
const typecast = @import("typecast");

// 환경변수 사용 (권장)
// export TYPECAST_API_KEY="your-api-key-here"
var client = typecast.Client.init(allocator, .{
    .api_key = std.posix.getenv("TYPECAST_API_KEY") orelse return error.MissingApiKey,
});
defer client.deinit();
```

```zig
// 직접 전달
var client = typecast.Client.init(allocator, .{
    .api_key = "your-api-key-here",
});
defer client.deinit();
```

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

```zig API 키 없는 프록시
var client = typecast.Client.init(allocator, .{
    .base_url = "https://your-proxy.example.com",
});
defer client.deinit();
```

## 고급 사용법

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

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

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

    ```zig
    const response = try client.textToSpeech(.{
        .voice_id = "tc_672c5f5ce59fac2a48faeaee",
        .text = "모든 것이 잘 될 거예요.",
        .model = .ssfm_v30,
        .prompt = .{ .smart = .{
            .previous_text = "방금 최고의 소식을 들었어요!",
            .next_text = "축하하고 싶어요!",
        } },
    });
    defer allocator.free(response.audio_data);
    ```
  </Tab>

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

    ```zig
    const response = try client.textToSpeech(.{
        .voice_id = "tc_672c5f5ce59fac2a48faeaee",
        .text = "이 기능들을 보여드리게 되어 정말 기쁩니다!",
        .model = .ssfm_v30,
        .prompt = .{ .preset = .{
            .emotion_preset = .happy,
            .emotion_intensity = 1.5,
        } },
    });
    defer allocator.free(response.audio_data);
    ```
  </Tab>
</Tabs>

### 오디오 커스터마이징

음량, 피치, 템포, 출력 포맷을 제어합니다:

```zig
const response = try client.textToSpeech(.{
    .voice_id = "tc_672c5f5ce59fac2a48faeaee",
    .text = "커스터마이징된 오디오 출력!",
    .model = .ssfm_v30,
    .output = .{
        .target_lufs = -14.0,
        .audio_pitch = 2,
        .audio_tempo = 1.2,
        .audio_format = .mp3,
    },
});
defer allocator.free(response.audio_data);
```

### 파일로 바로 생성하기

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

```zig
const response = try client.generateToFile("output.mp3", .{
    .text = "안녕하세요, 타입캐스트입니다.",
    .voice_id = "tc_672c5f5ce59fac2a48faeaee", // voice_id는 https://studio.typecast.ai/developers/api/voices 에서 확인하세요.
});
defer allocator.free(response.audio_data);
```

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

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

```zig
var composer = client.composeSpeech();
try composer.defaults(.{ .voice_id = "tc_672c5f5ce59fac2a48faeaee", .model = .ssfm_v30 });
try composer.say("안녕하세요<|5s|>반갑습니다<|1s|>오늘<|2s|>날씨는 어떤 것 같으세요?", .{});

const audio = try composer.generate(allocator);
defer allocator.free(audio.audio_data);
```

### 다중 화자 합성

한 파일 안에서 서로 다른 voice나 구간별 pitch, tempo, prompt 같은 옵션을 조합해야 할 때 사용합니다. composer는 세그먼트를 `POST /v1/text-to-speech/compose`로 보내며 WAV 또는 MP3를 직접 반환합니다. 무음 제거는 TTS 세그먼트에 명시적으로 설정하고, 명시적인 쉼은 유지됩니다.

```zig
var composer = client.composeSpeech();
defer composer.deinit();

try composer.defaults(.{
    .voice_id = "tc_672c5f5ce59fac2a48faeaee",
    .model = .ssfm_v30,
});
try composer.say("Hello there", .{});
try composer.pause(5);
try composer.say("Nice to meet you", .{
    .voice_id = "tc_60e5426de8b95f1d3000d7b5",
    .output = .{ .audio_pitch = 2 },
});
try composer.pause(2);
try composer.say("How does the weather feel?", .{});

const audio = try composer.generate(.wav);
defer audio.deinit(allocator);
try std.fs.cwd().writeFile(.{ .sub_path = "conversation.wav", .data = audio.audio_data });
```

### 보이스 탐색 (V2 API)

향상된 메타데이터와 함께 사용 가능한 보이스를 조회합니다:

```zig
// 모든 보이스 조회
const voices = try client.getVoicesV2(null);
defer allocator.free(voices);

// 모델별 필터링
const filtered = try client.getVoicesV2(.{ .model = .ssfm_v30 });
defer allocator.free(filtered);

for (voices) |voice| {
    std.debug.print("ID: {s}, 이름: {s}\n", .{ voice.voice_id, voice.voice_name });
}
```

### 스트리밍

콜백을 통해 실시간으로 오디오 청크를 스트리밍합니다:

```zig
try client.textToSpeechStream(.{
    .voice_id = "tc_672c5f5ce59fac2a48faeaee",
    .text = "이 텍스트를 실시간으로 오디오로 스트리밍합니다.",
    .model = .ssfm_v30,
}, struct {
    var first = true;
    fn onChunk(chunk: []const u8) anyerror!void {
        var data = chunk;
        if (first) {
            data = chunk[44..]; // 44바이트 WAV 헤더 건너뛰기
            first = false;
        }
        // data는 32000 Hz 16비트 모노 원시 PCM
        // 오디오 출력으로 전달
    }
}.onChunk);
```

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

## 타임스탬프 TTS

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

### 기본 사용법

```zig
const typecast = @import("typecast");
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const client = try typecast.TypecastClient.init(allocator, "YOUR_API_KEY");
    defer client.deinit();

    const result = try client.textToSpeechWithTimestamps(.{
        .voice_id = "tc_60e5426de8b95f1d3000d7b5",
        .text = "Hello. How are you?",
        .model = "ssfm-v30",
    });
    defer result.deinit();

    try std.fs.cwd().writeFile("output.wav", result.audioBytes());
    std.debug.print("재생 시간: {d:.3}초\n", .{result.audio_duration});

    for (result.words) |word| {
        std.debug.print("  [{d:.3}s – {d:.3}s] {s}\n",
            .{word.start_time, word.end_time, word.text});
    }
}
```

### 정밀도(Granularity) 설정

`.granularity = .word`(기본값) 또는 `.granularity = .char`를 설정해 정렬 단위를 제어합니다.

```zig
// 문자 단위 정렬 - 일본어·중국어에 필수
const result = try client.textToSpeechWithTimestamps(.{
    .voice_id    = "tc_60e5426de8b95f1d3000d7b5",
    .text        = "Hello. How are you?",
    .model       = "ssfm-v30",
    .granularity = .char,
});
```

### 자막 내보내기

```zig
const srt = try result.toSrt(allocator);
defer allocator.free(srt);
try std.fs.cwd().writeFile("output.srt", srt);

const vtt = try result.toVtt(allocator);
defer allocator.free(vtt);
try std.fs.cwd().writeFile("output.vtt", vtt);
```

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

## 지원 언어

35+개 언어를 지원하며 자동 언어 감지 기능을 제공합니다:

| 코드 | 언어 | 코드 | 언어 | 코드 | 언어 |
|------|------|------|------|------|------|
| `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 에러 처리를 위해 Zig의 error union을 사용합니다:

```zig
const response = client.textToSpeech(.{
    .voice_id = "tc_672c5f5ce59fac2a48faeaee",
    .text = "안녕하세요",
    .model = .ssfm_v30,
}) catch |err| switch (err) {
    error.Unauthorized => {
        std.debug.print("유효하지 않은 API 키\n", .{});
        return err;
    },
    error.PaymentRequired => {
        std.debug.print("크레딧 부족\n", .{});
        return err;
    },
    error.RateLimited => {
        std.debug.print("요청 한도 초과 - 잠시 후 재시도\n", .{});
        return err;
    },
    else => return err,
};
defer allocator.free(response.audio_data);
```

### 에러 유형

| 에러 | 상태 코드 | 설명 |
|------|-----------|------|
| `error.BadRequest` | 400 | 잘못된 요청 파라미터 |
| `error.Unauthorized` | 401 | 유효하지 않거나 누락된 API 키 |
| `error.PaymentRequired` | 402 | 크레딧 부족 |
| `error.NotFound` | 404 | 리소스를 찾을 수 없음 |
| `error.UnprocessableEntity` | 422 | 유효성 검사 오류 |
| `error.RateLimited` | 429 | 요청 한도 초과 |
| `error.InternalServerError` | 500 | 서버 오류 |
| `error.JsonParseError` | - | JSON 파싱 오류 |

## API 레퍼런스

### 클라이언트 메서드

| 메서드 | 설명 |
|--------|------|
| `init(allocator, config)` | 설정으로 클라이언트 생성 |
| `deinit()` | 클라이언트 리소스 정리 |
| `textToSpeech(request)` | 텍스트를 음성 오디오로 변환 |
| `generateToFile(path, request)` | 음성을 생성하고 로컬 파일로 바로 저장 |
| `textToSpeechStream(request, callback)` | 콜백을 통한 오디오 청크 스트리밍 |
| `getMySubscription()` | 구독 정보 조회 |
| `getVoices(model)` | 사용 가능한 보이스 조회 (V1) |
| `getVoicesV2(filter)` | 메타데이터와 함께 보이스 조회 (V2) |
| `getVoiceV2(voice_id, model)` | 특정 보이스 조회 |

## 무음 길이 조절

이 기능은 **0.2.12 이상**에서 지원합니다.

`remove_silence_ms`는 제거할 시간이 아니라 **남길 무음 길이**를 지정합니다. `0`부터 `1000`ms까지의 정수를 사용하세요. `0`은 검출된 무음을 제거하며, 생략하거나 `null`을 지정하면 지정 길이에 따른 무음 제거를 적용하지 않습니다.

일반·스트리밍·타임스탬프 TTS는 `output.remove_silence_ms`, Compose는 각 `tts` 세그먼트의 `segments[].output.remove_silence_ms`로 전달합니다. 반환 타임스탬프는 처리 후 오디오를 기준으로 하며, 명시적인 `pause` 세그먼트는 유지됩니다.

스트리밍의 기본 앞부분 무음 트림은 별개입니다. 특히 `0`처럼 작은 값에서는 재생 가능한 청크 수신에 간격이 생길 수 있으므로 충분한 재생 버퍼를 확보하고 실제 콘텐츠로 확인하세요.

아래 출력 설정을 해당 요청의 `output`에 전달하세요. 스트리밍은 스트리밍 전용 출력 타입을 사용합니다.

```zig
const output = typecast.models.Output{ .remove_silence_ms = 300 };
const stream_output = typecast.models.OutputStream{ .remove_silence_ms = 300 };
```
