타입캐스트 API를 위한 공식 Swift 라이브러리입니다. AI 기반 음성을 사용하여 텍스트를 생동감 있는 음성으로 변환하세요.
Swift 5.9 이상과 호환되며 모든 Apple 플랫폼을 지원합니다: iOS, macOS, tvOS, watchOS, visionOS.
요구 사항
| 플랫폼 | 최소 버전 |
|---|---|
| iOS | 13.0+ |
| macOS | 10.15+ |
| tvOS | 13.0+ |
| watchOS | 6.0+ |
| visionOS | 1.0+ |
| Swift | 5.9+ |
설치
태그가 지정된 릴리스를 클론한 후 Swift 패키지 디렉터리를 로컬 의존성으로 참조하세요:
git clone --branch typecast-swift/v0.3.10 --depth 1 https://github.com/neosapience/typecast-sdk.git
dependencies: [
.package(path: "typecast-sdk/typecast-swift")
],
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "Typecast", package: "typecast-swift")
]
)
]
Xcode에서는 File → Add Package Dependencies... → **Add Local...**을 선택한 후 클론한 typecast-swift 디렉터리를 지정하세요.
빠른 시작
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-v21AI 음성 모델 지원 - 다국어 지원: 영어, 한국어, 스페인어, 일본어, 중국어 등 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를 사용합니다.
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 키로 클라이언트를 초기화하세요:
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)
let client = TypecastClient(
baseURL: "https://your-proxy.example.com"
)
고급 사용법
감정 제어 (ssfm-v30)
ssfm-v30은 두 가지 감정 제어 모드를 제공합니다: 프리셋 및 스마트.
AI가 문맥에서 감정을 추론하도록 합니다:
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()
오디오 사용자 정의
라우드니스, 피치, 템포 및 출력 형식을 제어합니다:
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 확장자로 출력 형식을 결정합니다.
try await client.generateToFile(
"output.mp3",
request: GenerateToFileRequest(
voiceId: "tc_672c5f5ce59fac2a48faeaee", // voice_id는 https://studio.typecast.ai/developers/api/voices 에서 확인하세요.
text: "안녕하세요, 타입캐스트입니다."
)
)
텍스트만으로 쉼 표현
한 voice로 읽는 문장 안에 쉼만 넣고 싶다면 텍스트에 pause markup을 직접 작성합니다. <|5s|>, <|1s|>, <|0.3s|>, <|0.34413s|>처럼 쓰며 값은 초 단위이고 반드시 s로 끝납니다. 별도 pause 함수를 호출하지 않아도 텍스트만 보고 쉼 위치를 확인할 수 있습니다.
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를 생성한 다음 앱 또는 서버 파이프라인에서 변환하세요.
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)
향상된 메타데이터로 사용 가능한 음성을 나열하고 필터링합니다:
// 모든 음성 가져오기
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개 언어를 지원합니다:
// 자동 언어 감지 (권장)
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()
스트리밍
저지연 재생을 위한 실시간 오디오 청크 스트리밍:
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)
}
타임스탬프 TTS
textToSpeechWithTimestamps()는 POST /v1/text-to-speech/with-timestamps를 래핑하며, 오디오와 함께 단어·문자 단위 정렬 데이터를 반환합니다. 가라오케 하이라이트, 자막 생성, 립싱크 애플리케이션에 활용할 수 있습니다.
기본 사용법
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를 설정해 정렬 단위를 제어합니다.
// 문자 단위 정렬 - 일본어·중국어에 필수
let result = try await client.textToSpeechWithTimestamps(TTSRequestWithTimestamps(
voiceId: "tc_60e5426de8b95f1d3000d7b5",
text: "Hello. How are you?",
model: "ssfm-v30",
granularity: .char
))
자막 내보내기
let srt = try result.toSrt()
print(srt)
let vtt = try result.toVtt()
print(vtt)
지원 언어
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 | 광둥어 |
오류 처리
SDK는 API 오류 처리를 위한 포괄적인 TypecastError 열거형을 제공합니다:
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
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
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
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) |