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

# Go

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

Go 1.21 이상 버전과 호환됩니다. 외부 의존성 없이 Go 표준 라이브러리만 사용합니다.

<CardGroup cols={2}>
<Card title="Go Reference" icon="cube" href="https://pkg.go.dev/github.com/neosapience/typecast-sdk/typecast-go">
Typecast Go SDK
</Card>

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

## 설치

```bash
go get github.com/neosapience/typecast-sdk/typecast-go
```

<Warning>
최신 등록 버전은 Go modules 기준 **typecast-go/v0.3.14**입니다. **Go 1.21 이상**이 설치되어 있는지 확인하세요. `go version` 명령으로 버전을 확인할 수 있습니다.
</Warning>

## 빠른 시작

```go
package main

import (
    "context"
    "os"

    typecast "github.com/neosapience/typecast-sdk/typecast-go"
)

func main() {
    // 클라이언트 초기화
    client := typecast.NewClient(&typecast.ClientConfig{
        APIKey: "YOUR_API_KEY",
    })

    ctx := context.Background()

    // 텍스트를 음성으로 변환
    response, err := client.TextToSpeech(ctx, &typecast.TTSRequest{
        VoiceID: "tc_672c5f5ce59fac2a48faeaee",
        Text:    "안녕하세요! 저는 텍스트 음성 변환 에이전트입니다.",
        Model:   typecast.ModelSSFMV30,
    })
    if err != nil {
        panic(err)
    }

    // 오디오 파일 저장
    os.WriteFile("output.wav", response.AudioData, 0644)
    
    println("Audio saved! Format:", string(response.Format))
}
```

## 기능

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

- **다중 음성 모델**: `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
- **Context 지원**: 취소 및 타임아웃을 위한 `context.Context` 완전 지원
- **타임스탬프 TTS**: 자막, 가라오케, 립싱크를 위한 단어·문자 단위 정렬 데이터
- **스트리밍**: 저지연 재생을 위한 실시간 청크 오디오 전송
- **의존성 없음**: Go 표준 라이브러리만 사용

## 보이스 추천

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

```go
voices, err := client.RecommendVoices(
    context.Background(),
    "warm female voice for a product tutorial",
    3,
)
if err != nil {
    panic(err)
}

for _, voice := range voices {
    fmt.Println(voice.VoiceID, voice.VoiceName, voice.Score)
}
```

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

## 설정

환경 변수 또는 직접 전달로 API 키를 설정하세요:

```go
import typecast "github.com/neosapience/typecast-sdk/typecast-go"

// 환경 변수 사용 (권장)
// export TYPECAST_API_KEY="your-api-key-here"
client := typecast.NewClient(nil)

// 또는 직접 전달
client := typecast.NewClient(&typecast.ClientConfig{
    APIKey: "your-api-key-here",
})

// 사용자 정의 설정
client := typecast.NewClient(&typecast.ClientConfig{
    APIKey:  "your-api-key-here",
    BaseURL: "https://api.typecast.ai",  // 선택사항
    Timeout: 60 * time.Second,           // 선택사항
})
```

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

```go API 키 없는 프록시
client := typecast.NewClient(&typecast.ClientConfig{
    BaseURL: "https://your-proxy.example.com",
})
```

### 환경 변수

| 변수 | 설명 |
|------|------|
| `TYPECAST_API_KEY` | 타입캐스트 API 키 |
| `TYPECAST_API_HOST` | 사용자 정의 API 기본 URL (선택사항) |

## 고급 사용법

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

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

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

    ```go
    response, err := client.TextToSpeech(ctx, &typecast.TTSRequest{
        VoiceID: "tc_672c5f5ce59fac2a48faeaee",
        Text:    "모든 것이 잘 될 거예요.",
        Model:   typecast.ModelSSFMV30,
        Prompt: &typecast.SmartPrompt{
            EmotionType:  "smart",
            PreviousText: "방금 최고의 소식을 들었어요!",  // 선택적 문맥
            NextText:     "축하하고 싶어요!",             // 선택적 문맥
        },
    })
    ```
  </Tab>

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

    ```go
    intensity := 1.5
    
    response, err := client.TextToSpeech(ctx, &typecast.TTSRequest{
        VoiceID: "tc_672c5f5ce59fac2a48faeaee",
        Text:    "이 기능들을 보여드리게 되어 정말 신나요!",
        Model:   typecast.ModelSSFMV30,
        Prompt: &typecast.PresetPrompt{
            EmotionType:      "preset",
            EmotionPreset:    typecast.EmotionHappy,  // normal, happy, sad, angry, whisper, toneup, tonedown
            EmotionIntensity: &intensity,             // 범위: 0.0 ~ 2.0
        },
    })
    ```
  </Tab>
</Tabs>

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

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

```go
lufs := -14.0
pitch := 2
tempo := 1.2

response, err := client.TextToSpeech(ctx, &typecast.TTSRequest{
    VoiceID: "tc_672c5f5ce59fac2a48faeaee",
    Text:    "커스터마이즈된 오디오 출력!",
    Model:   typecast.ModelSSFMV30,
    Output: &typecast.Output{
        TargetLUFS:  &lufs,                     // 범위: -70 ~ 0 (LUFS)
        AudioPitch:  &pitch,                    // 범위: -12 to +12 반음
        AudioTempo:  &tempo,                    // 범위: 0.5x to 2.0x
        AudioFormat: typecast.AudioFormatMP3,  // 옵션: WAV, MP3
    },
})

os.WriteFile("output.mp3", response.AudioData, 0644)
fmt.Printf("Format: %s\n", response.Format)
```

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

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

```go
_, err := client.GenerateToFile(ctx, "output.mp3", typecast.GenerateToFileRequest{
    Text:    "안녕하세요, 타입캐스트입니다.",
    VoiceID: "tc_672c5f5ce59fac2a48faeaee", // voice_id는 https://studio.typecast.ai/developers/api/voices 에서 확인하세요.
})
if err != nil {
    panic(err)
}
```

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

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

```go
audio, err := client.ComposeSpeech().
    Defaults(typecast.ComposerSettings{VoiceID: "tc_672c5f5ce59fac2a48faeaee", Model: typecast.TTSModelSSFMV30}).
    Say("안녕하세요<|5s|>반갑습니다<|1s|>오늘<|2s|>날씨는 어떤 것 같으세요?").
    Generate(ctx)
```

### 다중 화자 합성

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

```go
package main

import (
    "context"
    "os"

    typecast "github.com/neosapience/typecast-sdk/typecast-go"
)

func main() {
    client := typecast.NewClient("YOUR_API_KEY")
    response, err := client.ComposeSpeech().
        Defaults(typecast.ComposerSettings{VoiceID: "tc_672c5f5ce59fac2a48faeaee", Model: typecast.ModelSSFMV30}).
        Say("Hello there").
        Pause(5).
        SayWith("Nice to meet you", typecast.ComposerSettings{VoiceID: "tc_60e5426de8b95f1d3000d7b5", Output: &typecast.Output{AudioPitch: 2}}).
        Say("Today").
        Pause(2).
        Say("How does the weather feel?").
        Generate(context.Background())
    if err != nil {
        panic(err)
    }
    _ = os.WriteFile("conversation.wav", response.AudioData, 0644)
}
```

### 음성 검색 (V2 API)

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

```go
// 모든 음성 가져오기
voices, err := client.GetVoicesV2(ctx, nil)

// 조건별 필터링
voices, err := client.GetVoicesV2(ctx, &typecast.VoicesV2Filter{
    Model:  typecast.ModelSSFMV30,
    Gender: typecast.GenderFemale,
    Age:    typecast.AgeYoungAdult,
})

// 음성 정보 표시
for _, voice := range voices {
    fmt.Printf("ID: %s, Name: %s\n", voice.VoiceID, voice.VoiceName)

    if voice.Gender != nil {
        fmt.Printf("Gender: %s\n", *voice.Gender)
    }
    if voice.Age != nil {
        fmt.Printf("Age: %s\n", *voice.Age)
    }

    for _, model := range voice.Models {
        fmt.Printf("Model: %s, Emotions: %v\n", model.Version, model.Emotions)
    }
}

// 특정 음성 상세 정보 가져오기
voice, err := client.GetVoiceV2(ctx, "tc_672c5f5ce59fac2a48faeaee")
```

### 다국어 콘텐츠

SDK는 자동 언어 감지를 통해 35+개 언어를 지원합니다:

```go
// 언어 자동 감지 (권장)
response, err := client.TextToSpeech(ctx, &typecast.TTSRequest{
    VoiceID: "tc_672c5f5ce59fac2a48faeaee",
    Text:    "こんにちは。お元気ですか。",
    Model:   typecast.ModelSSFMV30,
})

// 또는 언어 명시적으로 지정
response, err := client.TextToSpeech(ctx, &typecast.TTSRequest{
    VoiceID:  "tc_672c5f5ce59fac2a48faeaee",
    Text:     "안녕하세요. 반갑습니다.",
    Model:    typecast.ModelSSFMV30,
    Language: "kor",  // ISO 639-3 언어 코드
})
```

### 스트리밍

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

```go
// 원시 PCM 추출 (44바이트 WAV 헤더 건너뛰기)
reader, _ := client.TextToSpeechStream(context.Background(), request)
defer reader.Close()

buf := make([]byte, 4096)
first := true
for {
    n, err := reader.Read(buf)
    if n > 0 {
        data := buf[:n]
        if first {
            data = data[44:]  // WAV 헤더 건너뛰기
            first = false
        }
        // data는 32000 Hz 16비트 모노 원시 PCM
        // 오디오 출력으로 전달 (예: oto, portaudio)
        _ = data
    }
    if err != nil {
        break
    }
}
```

<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`를 래핑하며, 오디오와 함께 단어·문자 단위 정렬 데이터를 반환합니다. 가라오케 하이라이트, 자막 생성, 립싱크 애플리케이션에 활용할 수 있습니다.

### 기본 사용법

```go
package main

import (
    "context"
    "fmt"
    "os"

    typecast "github.com/neosapience/typecast-sdk/typecast-go"
)

func main() {
    client := typecast.NewClient(&typecast.ClientConfig{APIKey: "YOUR_API_KEY"})
    ctx := context.Background()

    result, err := client.TextToSpeechWithTimestamps(ctx, &typecast.TTSRequestWithTimestamps{
        VoiceID: "tc_60e5426de8b95f1d3000d7b5",
        Text:    "Hello. How are you?",
        Model:   typecast.ModelSSFMV30,
    })
    if err != nil {
        panic(err)
    }

    os.WriteFile("output.wav", result.AudioBytes(), 0644)
    fmt.Printf("재생 시간: %.3f초\n", result.AudioDuration)

    for _, w := range result.Words {
        fmt.Printf("  [%.3fs – %.3fs] %s\n", w.StartTime, w.EndTime, w.Text)
    }
}
```

### 정밀도(Granularity) 설정

`Granularity: typecast.GranularityWord`(기본값) 또는 `Granularity: typecast.GranularityChar`를 전달해 정렬 단위를 제어합니다.

```go
// 문자 단위 정렬 - 일본어·중국어에 필수
result, err := client.TextToSpeechWithTimestamps(ctx, &typecast.TTSRequestWithTimestamps{
    VoiceID:     "tc_60e5426de8b95f1d3000d7b5",
    Text:        "Hello. How are you?",
    Model:       typecast.ModelSSFMV30,
    Granularity: typecast.GranularityChar,
})
```

### 자막 내보내기

```go
srt, _ := result.ToSrt()
os.WriteFile("output.srt", []byte(srt), 0644)

vtt, _ := result.ToVtt()
os.WriteFile("output.vtt", []byte(vtt), 0644)
```

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

## 지원 언어

SDK는 자동 언어 감지를 통해 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는 특정 오류를 처리하기 위한 헬퍼 메서드가 있는 `APIError` 타입을 제공합니다:

```go
import typecast "github.com/neosapience/typecast-sdk/typecast-go"

response, err := client.TextToSpeech(ctx, request)
if err != nil {
    if apiErr, ok := err.(*typecast.APIError); ok {
        fmt.Printf("Error %d: %s\n", apiErr.StatusCode, apiErr.Message)
        
        // 특정 오류 처리
        switch {
        case apiErr.IsUnauthorized():
            // 401: 잘못된 API 키
        case apiErr.IsForbidden():
            // 403: 접근 거부
        case apiErr.IsPaymentRequired():
            // 402: 크레딧 부족
        case apiErr.IsNotFound():
            // 404: 리소스를 찾을 수 없음
        case apiErr.IsValidationError():
            // 422: 유효성 검사 오류
        case apiErr.IsRateLimited():
            // 429: 요청 제한 초과
        case apiErr.IsServerError():
            // 5xx: 서버 오류
        case apiErr.IsBadRequest():
            // 400: 잘못된 요청
        }
    }
}
```

### 오류 유형

| 메서드 | 상태 코드 | 설명 |
|--------|-----------|------|
| `IsBadRequest()` | 400 | 잘못된 요청 매개변수 |
| `IsUnauthorized()` | 401 | 잘못되거나 누락된 API 키 |
| `IsPaymentRequired()` | 402 | 크레딧 부족 |
| `IsForbidden()` | 403 | 접근 거부 |
| `IsNotFound()` | 404 | 리소스를 찾을 수 없음 |
| `IsValidationError()` | 422 | 유효성 검사 오류 |
| `IsRateLimited()` | 429 | 요청 제한 초과 |
| `IsServerError()` | 5xx | 서버 오류 |

## Context와 타임아웃

SDK는 취소 및 타임아웃을 위한 Go의 `context.Context`를 완전히 지원합니다:

```go
// 타임아웃 사용
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

response, err := client.TextToSpeech(ctx, request)
if err != nil {
    if ctx.Err() == context.DeadlineExceeded {
        fmt.Println("Request timed out")
    }
}

// 취소 사용
ctx, cancel := context.WithCancel(context.Background())
go func() {
    time.Sleep(5 * time.Second)
    cancel()  // 5초 후 취소
}()

response, err := client.TextToSpeech(ctx, request)
```

## API 레퍼런스

### 클라이언트 메서드

| 메서드 | 설명 |
|--------|------|
| `TextToSpeech(ctx, request)` | 텍스트를 음성 오디오로 변환 |
| `GenerateToFile(ctx, path, request)` | 음성을 생성하고 로컬 파일로 바로 저장 |
| `GetVoicesV2(ctx, filter)` | 필터링으로 사용 가능한 음성 가져오기 |
| `GetVoiceV2(ctx, voiceID)` | ID로 특정 음성 가져오기 |
| `GetVoices(ctx, model)` | 음성 가져오기 (V1 API, 권장하지 않음) |
| `GetVoice(ctx, voiceID, model)` | 음성 가져오기 (V1 API, 권장하지 않음) |

### TTSRequest 필드

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `VoiceID` | `string` | ✓ | 음성 ID (형식: `tc_*` 또는 `uc_*`) |
| `Text` | `string` | ✓ | 합성할 텍스트 (최대 2000자) |
| `Model` | `TTSModel` | ✓ | TTS 모델 (`ModelSSFMV21` 또는 `ModelSSFMV30`) |
| `Language` | `string` | | ISO 639-3 코드 (생략 시 자동 감지) |
| `Prompt` | `*Prompt` / `*PresetPrompt` / `*SmartPrompt` | | 감정 설정 |
| `Output` | `*Output` | | 오디오 출력 설정 |

### TTSResponse 필드

| 필드 | 타입 | 설명 |
|------|------|------|
| `AudioData` | `[]byte` | 생성된 오디오 데이터 |
| `Duration` | `float64` | 오디오 길이 (초) |
| `Format` | `AudioFormat` | 오디오 형식 (`wav` 또는 `mp3`) |

### 상수

#### 모델

| 상수 | 값 | 설명 |
|------|-----|------|
| `ModelSSFMV30` | `ssfm-v30` | 향상된 운율의 최신 모델 |
| `ModelSSFMV21` | `ssfm-v21` | 안정적인 프로덕션 모델 |

#### 감정 프리셋

| 상수 | ssfm-v21 | ssfm-v30 |
|------|----------|----------|
| `EmotionNormal` | ✓ | ✓ |
| `EmotionHappy` | ✓ | ✓ |
| `EmotionSad` | ✓ | ✓ |
| `EmotionAngry` | ✓ | ✓ |
| `EmotionWhisper` | ✗ | ✓ |
| `EmotionToneUp` | ✗ | ✓ |
| `EmotionToneDown` | ✗ | ✓ |

#### 오디오 형식

| 상수 | 값 | 설명 |
|------|-----|------|
| `AudioFormatWAV` | `wav` | 비압축 PCM 오디오 |
| `AudioFormatMP3` | `mp3` | 압축된 MP3 오디오 |

## 무음 길이 조절

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

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

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

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

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

```go
remainingSilence := 300
output := &typecast.Output{RemoveSilenceMS: &remainingSilence}
streamOutput := &typecast.OutputStream{RemoveSilenceMS: &remainingSilence}
```

`RemoveSilenceMS`는 포인터입니다. `nil`과 `0`을 구분하려면 위처럼 정수의 주소를 전달하세요.
