타입캐스트 API를 위한 공식 Rust 라이브러리입니다. AI 기반 음성을 사용하여 텍스트를 생동감 있는 음성으로 변환하세요.
Tokio 런타임을 사용한 async/await 지원으로 구축되었습니다. Cargo 패키지 관리자와 함께 작동합니다.
설치
Cargo.toml에 다음을 추가하세요:
[dependencies]
typecast-rust = "0.3.10"
tokio = { version = "1", features = ["full"] }
또는 Cargo를 사용하여 의존성을 추가하세요:
cargo add typecast-rust tokio --features tokio/full
빠른 시작
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-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
- 빌더 패턴: 쉬운 요청 구성을 위한 메서드 체이닝 Fluent API
- Async/Await: 효율적인 비동기 작업을 위해 Tokio 기반으로 구축
- 타임스탬프 TTS: 자막, 가라오케, 립싱크를 위한 단어·문자 단위 정렬 데이터
- 스트리밍: 저지연 재생을 위한 실시간 청크 오디오 전송
- 포괄적인 오류 처리: 패턴 매칭을 지원하는 타입화된 에러 enum
보이스 추천
원하는 스타일은 알지만 정확한 voice_id를 모를 때 recommend_voices를 사용합니다.
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 키를 설정하세요:
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)?;
let config = ClientConfig::new("")
.base_url("https://your-proxy.example.com");
let client = TypecastClient::new(config)?;
환경 파일
프로젝트 루트에 .env 파일을 만드세요:
TYPECAST_API_KEY=your-api-key-here
고급 사용법
감정 제어 (ssfm-v30)
ssfm-v30은 두 가지 감정 제어 모드를 제공합니다: 프리셋 및 스마트.
AI가 문맥에서 감정을 추론하도록 합니다:
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?;
오디오 사용자 정의
라우드니스, 피치, 템포 및 출력 형식을 제어합니다:
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 확장자로 출력 형식을 결정합니다.
client.generate_to_file(
"output.mp3",
GenerateToFileRequest::new("tc_672c5f5ce59fac2a48faeaee", "안녕하세요, 타입캐스트입니다."), // voice_id는 https://studio.typecast.ai/developers/api/voices 에서 확인하세요.
).await?;
텍스트만으로 쉼 표현
한 voice로 읽는 문장 안에 쉼만 넣고 싶다면 텍스트에 pause markup을 직접 작성합니다. <|5s|>, <|1s|>, <|0.3s|>, <|0.34413s|>처럼 쓰며 값은 초 단위이고 반드시 s로 끝납니다. 별도 pause 함수를 호출하지 않아도 텍스트만 보고 쉼 위치를 확인할 수 있습니다.
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를 생성한 다음 앱 또는 서버 파이프라인에서 변환하세요.
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)
향상된 메타데이터로 사용 가능한 음성을 나열하고 필터링합니다:
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개 언어를 지원합니다:
// 언어 자동 감지 (권장)
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?;
스트리밍
저지연 재생을 위한 실시간 오디오 청크 스트리밍:
// 원시 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)
}
타임스탬프 TTS
text_to_speech_with_timestamps()는 POST /v1/text-to-speech/with-timestamps를 래핑하며, 오디오와 함께 단어·문자 단위 정렬 데이터를 반환합니다. 가라오케 하이라이트, 자막 생성, 립싱크 애플리케이션에 활용할 수 있습니다.
기본 사용법
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를 설정해 정렬 단위를 제어합니다.
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?;
자막 내보내기
let srt = result.to_srt()?;
fs::write("output.srt", srt)?;
let vtt = result.to_vtt()?;
fs::write("output.vtt", vtt)?;
지원 언어
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 | 광둥어 |
오류 처리
SDK는 패턴 매칭으로 API 오류를 처리할 수 있는 타입화된 에러 enum을 제공합니다:
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 직렬화 오류 |
헬퍼 메서드
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) |
완전한 예제
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(())
}