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

# Swift

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

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

Swift 5.9 이상과 호환되며 모든 Apple 플랫폼을 지원합니다: iOS, macOS, tvOS, watchOS, visionOS.

<CardGroup cols={2}>
  <Card title="Swift Package" icon="cube" href="https://github.com/neosapience/typecast-sdk/tree/main/typecast-swift">
    Typecast Swift SDK
  </Card>

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

## 요구 사항

| 플랫폼      | 최소 버전  |
| -------- | ------ |
| iOS      | 13.0+  |
| macOS    | 10.15+ |
| tvOS     | 13.0+  |
| watchOS  | 6.0+   |
| visionOS | 1.0+   |
| Swift    | 5.9+   |

## 설치

<Tabs>
  <Tab title="Swift Package Manager">
    `Package.swift`에 다음을 추가하세요:

    ```swift theme={null}
    dependencies: [
        .package(url: "https://github.com/neosapience/typecast-sdk.git", from: "0.3.8")
    ]
    ```

    그런 다음 타겟 의존성에 `Typecast`를 추가하세요:

    ```swift theme={null}
    targets: [
        .target(
            name: "YourTarget",
            dependencies: ["Typecast"]
        )
    ]
    ```
  </Tab>

  <Tab title="Xcode">
    1. Xcode에서 프로젝트를 엽니다
    2. **File** → **Add Package Dependencies...** 로 이동합니다
    3. 저장소 URL을 입력합니다: `https://github.com/neosapience/typecast-sdk.git`
    4. 버전 규칙을 선택하고 **Add Package**를 클릭합니다
    5. `Typecast` 라이브러리를 선택하고 타겟에 추가합니다
  </Tab>
</Tabs>

<Warning>
  최신 등록 버전은 SDK Git 태그 기준 **typecast-swift/v0.3.8**입니다. **Swift 5.9 이상**이 설치되어 있는지 확인하세요. SDK는 이 최소 버전이 필요한 Swift Concurrency(async/await)를 사용합니다.
</Warning>

## 빠른 시작

```swift theme={null}
import AVFoundation
import Typecast

let client = TypecastClient(apiKey: "YOUR_API_KEY")
var audioPlayer: AVAudioPlayer?

// 편의 메서드로 간단하게 사용
let audio = try await client.speak(
    "안녕하세요! 저는 텍스트 음성 변환 에이전트입니다.",
    voiceId: "tc_672c5f5ce59fac2a48faeaee"
)

// 데이터에서 직접 오디오 재생
audioPlayer = try AVAudioPlayer(data: audio.audioData)
audioPlayer?.play()
print("Duration: \(audio.duration)s, Format: \(audio.format.rawValue)")
```

## 기능

Typecast Swift 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
* **Swift Concurrency**: 현대적인 Swift 개발을 위한 완전한 async/await 지원
* **스레드 안전**: 안전한 동시 사용을 위해 모든 타입이 `Sendable`을 준수
* **타임스탬프 TTS**: 자막, 가라오케, 립싱크를 위한 단어·문자 단위 정렬 데이터
* **스트리밍**: 저지연 재생을 위한 실시간 청크 오디오 전송
* **크로스 플랫폼**: iOS, macOS, tvOS, watchOS, visionOS에서 작동

## 보이스 추천

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

```swift theme={null}
let voices = try await client.recommendVoices(
    query: "warm female voice for a product tutorial",
    count: 3
)

for voice in voices {
    print("\(voice.voiceId) \(voice.voiceName) \(voice.score)")
}
```

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

## 구성

API 키로 클라이언트를 초기화하세요:

```swift theme={null}
import Typecast

// 직접 초기화
let client = TypecastClient(apiKey: "your-api-key")

// 사용자 정의 base URL과 함께
let client = TypecastClient(
    apiKey: "your-api-key",
    baseURL: "https://api.typecast.ai"
)

// 구성 구조체 사용
let config = TypecastConfiguration(apiKey: "your-api-key")
let client = TypecastClient(configuration: config)
```

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

```swift API 키 없는 프록시 theme={null}
let client = TypecastClient(
    baseURL: "https://your-proxy.example.com"
)
```

## 고급 사용법

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

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

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

    ```swift theme={null}
    let request = TTSRequest(
        voiceId: "tc_672c5f5ce59fac2a48faeaee",
        text: "모든 것이 잘 될 거예요.",
        model: .ssfmV30,
        prompt: .smart(SmartPrompt(
            previousText: "방금 최고의 소식을 들었어요!",  // 선택적 문맥
            nextText: "축하할 수 있어서 너무 기다려져요!"     // 선택적 문맥
        ))
    )

    let response = try await client.textToSpeech(request)
    audioPlayer = try AVAudioPlayer(data: response.audioData)
    audioPlayer?.play()
    ```
  </Tab>

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

    ```swift theme={null}
    let request = TTSRequest(
        voiceId: "tc_672c5f5ce59fac2a48faeaee",
        text: "이 기능들을 보여드리게 되어 정말 기대됩니다!",
        model: .ssfmV30,
        prompt: .preset(PresetPrompt(
            emotionPreset: .happy,  // normal, happy, sad, angry, whisper, toneup, tonedown
            emotionIntensity: 1.5   // 범위: 0.0 ~ 2.0
        ))
    )

    let response = try await client.textToSpeech(request)
    audioPlayer = try AVAudioPlayer(data: response.audioData)
    audioPlayer?.play()
    ```
  </Tab>

  <Tab title="편의 메서드">
    빠른 감정 제어를 위한 편의 메서드를 사용하세요:

    ```swift theme={null}
    let audio = try await client.speak(
        "정말 기대돼요!",
        voiceId: "tc_672c5f5ce59fac2a48faeaee",
        emotion: .happy,
        intensity: 1.5
    )

    audioPlayer = try AVAudioPlayer(data: audio.audioData)
    audioPlayer?.play()
    ```
  </Tab>
</Tabs>

### 오디오 사용자 정의

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

```swift theme={null}
let request = TTSRequest(
    voiceId: "tc_672c5f5ce59fac2a48faeaee",
    text: "사용자 정의 오디오 출력!",
    model: .ssfmV30,
    output: OutputSettings(
        targetLufs: -14.0,     // 범위: -70 ~ 0 (LUFS)
        audioPitch: 2,        // 범위: -12 to +12 반음
        audioTempo: 1.2,      // 범위: 0.5x to 2.0x
        audioFormat: .mp3     // 옵션: .wav, .mp3
    ),
    seed: 42  // 부호 없는 정수 시드 (재현 가능한 결과)
)

let response = try await client.textToSpeech(request)

audioPlayer = try AVAudioPlayer(data: response.audioData)
audioPlayer?.play()
print("Duration: \(response.duration)s, Format: \(response.format.rawValue)")
```

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

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

```swift theme={null}
try await client.generateToFile(
    "output.mp3",
    request: GenerateToFileRequest(
        voiceId: "tc_672c5f5ce59fac2a48faeaee", // voice_id는 https://typecast.ai/developers/api/voices 에서 확인하세요.
        text: "안녕하세요, 타입캐스트입니다."
    )
)
```

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

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

```swift theme={null}
let audio = try await client.composeSpeech()
    .defaults(ComposerSettings(voiceId: "tc_672c5f5ce59fac2a48faeaee", model: .ssfmV30))
    .say("안녕하세요<|5s|>반갑습니다<|1s|>오늘<|2s|>날씨는 어떤 것 같으세요?")
    .generate()
```

### 다중 화자 합성

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

```swift theme={null}
let audio = try await client.composeSpeech()
    .defaults(ComposerSettings(voiceId: "tc_672c5f5ce59fac2a48faeaee", model: .ssfmV30))
    .say("Hello there")
    .pause(5)
    .say("Nice to meet you", overrides: ComposerSettings(
        voiceId: "tc_60e5426de8b95f1d3000d7b5",
        output: OutputSettings(audioPitch: 2)
    ))
    .say("Today")
    .pause(2)
    .say("How does the weather feel?")
    .generate()

try audio.audioData.write(to: URL(fileURLWithPath: "conversation.wav"))
```

### 음성 탐색 (V2 API)

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

```swift theme={null}
// 모든 음성 가져오기
let voices = try await client.getVoices()

// 기준으로 필터링
let filteredVoices = try await client.getVoices(filter: VoicesV2Filter(
    model: .ssfmV30,
    gender: .female,
    age: .youngAdult
))

// 특정 음성 가져오기
let voice = try await client.getVoice(voiceId: "tc_672c5f5ce59fac2a48faeaee")

// 음성 정보 표시
print("ID: \(voice.voiceId), Name: \(voice.voiceName)")
print("Gender: \(voice.gender?.rawValue ?? "N/A"), Age: \(voice.age?.rawValue ?? "N/A")")

for model in voice.models {
    print("Model: \(model.version.rawValue), Emotions: \(model.emotions.joined(separator: ", "))")
}

if let useCases = voice.useCases {
    print("Use cases: \(useCases.joined(separator: ", "))")
}
```

### 다국어 콘텐츠

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

```swift theme={null}
// 자동 언어 감지 (권장)
let request = TTSRequest(
    voiceId: "tc_672c5f5ce59fac2a48faeaee",
    text: "こんにちは。お元気ですか。",
    model: .ssfmV30
)

let response = try await client.textToSpeech(request)

// 또는 언어를 명시적으로 지정
let koreanRequest = TTSRequest(
    voiceId: "tc_672c5f5ce59fac2a48faeaee",
    text: "안녕하세요. 반갑습니다.",
    model: .ssfmV30,
    language: .korean  // 명시적 언어 코드
)

let koreanResponse = try await client.textToSpeech(koreanRequest)

audioPlayer = try AVAudioPlayer(data: koreanResponse.audioData)
audioPlayer?.play()
```

### 스트리밍

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

```swift theme={null}
import AVFoundation
import Typecast

let engine = AVAudioEngine()
let playerNode = AVAudioPlayerNode()
let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 32000, channels: 1, interleaved: true)!

engine.attach(playerNode)
engine.connect(playerNode, to: engine.mainMixerNode, format: format)
try engine.start()
playerNode.play()

let stream = try await client.textToSpeechStream(request)
var first = true

for try await chunk in stream {
    var pcmData = chunk
    if first {
        pcmData = chunk.dropFirst(44)  // 44바이트 WAV 헤더 건너뛰기
        first = false
    }
    let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(pcmData.count / 2))!
    buffer.frameLength = buffer.frameCapacity
    pcmData.withUnsafeBytes { ptr in
        buffer.int16ChannelData!.pointee.update(from: ptr.bindMemory(to: Int16.self).baseAddress!, count: Int(buffer.frameLength))
    }
    playerNode.scheduleBuffer(buffer)
}
```

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

## 타임스탬프 TTS

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

### 기본 사용법

```swift theme={null}
import Typecast

let client = TypecastClient(apiKey: "YOUR_API_KEY")

let result = try await client.textToSpeechWithTimestamps(TTSRequestWithTimestamps(
    voiceId: "tc_60e5426de8b95f1d3000d7b5",
    text: "Hello. How are you?",
    model: "ssfm-v30"
))

audioPlayer = try AVAudioPlayer(data: result.audioBytes())
audioPlayer?.play()
print(String(format: "재생 시간: %.3f초", result.audioDuration))

for word in result.words {
    print(String(format: "  [%.3fs – %.3fs] %@", word.startTime, word.endTime, word.text))
}
```

### 정밀도(Granularity) 설정

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

```swift theme={null}
// 문자 단위 정렬 — 일본어·중국어에 필수
let result = try await client.textToSpeechWithTimestamps(TTSRequestWithTimestamps(
    voiceId: "tc_60e5426de8b95f1d3000d7b5",
    text: "Hello. How are you?",
    model: "ssfm-v30",
    granularity: .char
))
```

### 자막 내보내기

```swift theme={null}
let srt = try result.toSrt()
print(srt)

let vtt = try result.toVtt()
print(vtt)
```

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

## 지원 언어

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

| 코드           | 언어    | 코드           | 언어     | 코드            | 언어     |
| ------------ | ----- | ------------ | ------ | ------------- | ------ |
| `.english`   | 영어    | `.japanese`  | 일본어    | `.ukrainian`  | 우크라이나어 |
| `.korean`    | 한국어   | `.greek`     | 그리스어   | `.indonesian` | 인도네시아어 |
| `.spanish`   | 스페인어  | `.tamil`     | 타밀어    | `.danish`     | 덴마크어   |
| `.german`    | 독일어   | `.tagalog`   | 타갈로그어  | `.swedish`    | 스웨덴어   |
| `.french`    | 프랑스어  | `.finnish`   | 핀란드어   | `.malay`      | 말레이어   |
| `.italian`   | 이탈리아어 | `.chinese`   | 중국어    | `.czech`      | 체코어    |
| `.polish`    | 폴란드어  | `.slovak`    | 슬로바키아어 | `.portuguese` | 포르투갈어  |
| `.dutch`     | 네덜란드어 | `.arabic`    | 아랍어    | `.bulgarian`  | 불가리아어  |
| `.russian`   | 러시아어  | `.croatian`  | 크로아티아어 | `.romanian`   | 루마니아어  |
| `.bengali`   | 벵골어   | `.hindi`     | 힌디어    | `.hungarian`  | 헝가리어   |
| `.minNan`    | 민난어   | `.norwegian` | 노르웨이어  | `.punjabi`    | 펀자브어   |
| `.thai`      | 태국어   | `.turkish`   | 터키어    | `.vietnamese` | 베트남어   |
| `.cantonese` | 광둥어   |              |        |               |        |

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

## 오류 처리

SDK는 API 오류 처리를 위한 포괄적인 `TypecastError` 열거형을 제공합니다:

```swift theme={null}
import Typecast

do {
    let response = try await client.textToSpeech(request)
} catch let error as TypecastError {
    switch error {
    case .unauthorized(let message):
        // 401: 잘못된 API 키
        print("Invalid API key: \(message)")
    case .paymentRequired(let message):
        // 402: 크레딧 부족
        print("Insufficient credits: \(message)")
    case .notFound(let message):
        // 404: 리소스를 찾을 수 없음
        print("Voice not found: \(message)")
    case .validationError(let message):
        // 422: 유효성 검사 오류
        print("Validation error: \(message)")
    case .rateLimitExceeded(let message):
        // 429: 요청 한도 초과
        print("Rate limit exceeded: \(message)")
    case .serverError(let message):
        // 500: 서버 오류
        print("Server error: \(message)")
    case .networkError(let underlyingError):
        // 네트워크 연결 문제
        print("Network error: \(underlyingError.localizedDescription)")
    case .invalidResponse(let message):
        // 서버의 잘못된 응답
        print("Invalid response: \(message)")
    default:
        print("Error: \(error.localizedDescription)")
    }
    
    // 사용 가능한 경우 상태 코드에 액세스
    if let statusCode = error.statusCode {
        print("HTTP status: \(statusCode)")
    }
}
```

### 오류 타입

| 오류                   | 상태 코드 | 설명              |
| -------------------- | ----- | --------------- |
| `.badRequest`        | 400   | 잘못된 요청 매개변수     |
| `.unauthorized`      | 401   | 잘못되거나 누락된 API 키 |
| `.paymentRequired`   | 402   | 크레딧 부족          |
| `.notFound`          | 404   | 리소스를 찾을 수 없음    |
| `.validationError`   | 422   | 유효성 검사 오류       |
| `.rateLimitExceeded` | 429   | 요청 한도 초과        |
| `.serverError`       | 500   | 서버 오류           |
| `.networkError`      | -     | 네트워크 연결 문제      |
| `.invalidResponse`   | -     | 서버의 잘못된 응답      |

## 플랫폼별 사용법

### iOS

```swift theme={null}
import Typecast
import AVFoundation

class TTSManager {
    private let client = TypecastClient(apiKey: "YOUR_API_KEY")
    private var audioPlayer: AVAudioPlayer?
    
    func speak(_ text: String) async throws {
        let audio = try await client.speak(text, voiceId: "tc_672c5f5ce59fac2a48faeaee")
        
        // 데이터에서 직접 오디오 재생
        audioPlayer = try AVAudioPlayer(data: audio.audioData)
        audioPlayer?.play()
    }
}
```

### macOS

```swift theme={null}
import Typecast
import AppKit
import AVFoundation

class MacTTSManager {
    private let client = TypecastClient(apiKey: "YOUR_API_KEY")
    private var audioPlayer: AVAudioPlayer?
    
    func speak(_ text: String) async throws {
        let audio = try await client.speak(text, voiceId: "tc_672c5f5ce59fac2a48faeaee")
        
        audioPlayer = try AVAudioPlayer(data: audio.audioData)
        audioPlayer?.play()
    }
    
}
```

### watchOS

```swift theme={null}
import Typecast
import AVFoundation

class WatchTTSManager {
    private let client = TypecastClient(apiKey: "YOUR_API_KEY")
    private var audioPlayer: AVAudioPlayer?
    
    func speak(_ text: String) async throws {
        let audio = try await client.speak(text, voiceId: "tc_672c5f5ce59fac2a48faeaee")
        
        audioPlayer = try AVAudioPlayer(data: audio.audioData)
        audioPlayer?.play()
    }
}
```

## API 레퍼런스

### TypecastClient 메서드

| 메서드                                         | 설명                     |
| ------------------------------------------- | ---------------------- |
| `textToSpeech(_:)`                          | 텍스트를 음성 오디오로 변환        |
| `generateToFile(_:request:)`                | 음성을 생성하고 로컬 파일로 바로 저장  |
| `speak(_:voiceId:model:)`                   | 최소 매개변수로 간단한 TTS       |
| `speak(_:voiceId:model:emotion:intensity:)` | 감정 프리셋으로 TTS           |
| `getVoices(filter:)`                        | 선택적 필터로 사용 가능한 음성 가져오기 |
| `getVoice(voiceId:)`                        | ID로 특정 음성 가져오기         |

### TTSRequest 필드

| 필드         | 타입               | 필수 | 설명                                       |
| ---------- | ---------------- | -- | ---------------------------------------- |
| `voiceId`  | `String`         | ✓  | 음성 ID (형식: `tc_*`)                       |
| `text`     | `String`         | ✓  | 합성할 텍스트 (최대 2000자)                       |
| `model`    | `TTSModel`       | ✓  | TTS 모델 (`.ssfmV21` 또는 `.ssfmV30`)        |
| `language` | `LanguageCode`   |    | 언어 코드 (생략 시 자동 감지)                       |
| `prompt`   | `TTSPrompt`      |    | 감정 설정 (`.basic`, `.preset`, 또는 `.smart`) |
| `output`   | `OutputSettings` |    | 오디오 출력 설정                                |
| `seed`     | `UInt32`         |    | 재현성을 위한 부호 없는 정수 시드 (≥ 0)                |

### TTSResponse 필드

| 필드          | 타입             | 설명                        |
| ----------- | -------------- | ------------------------- |
| `audioData` | `Data`         | 생성된 오디오 데이터               |
| `duration`  | `TimeInterval` | 오디오 길이 (초)                |
| `format`    | `AudioFormat`  | 오디오 형식 (`.wav` 또는 `.mp3`) |
