Voice Studio API · v1
감정까지 연기하는
한국어 음성 API
OpenAI SDK를 그대로 쓰고 base_url만 바꾸면 됩니다. 대본에 [화남 0.9] 같은 태그를 붙이면 그 구간만 화난 목소리로 읽습니다.
- 첫 소리까지
- 0.1초
- 한국어 글자 오류율
- 0.9%
- 감정
- 8가지
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://voicestudio.gensapps.com/v1",
apiKey: process.env.VOICE_STUDIO_KEY,
});
const audio = await client.audio.speech.create({
model: "voice-studio-1",
voice: "minji",
input: "[차분]괜찮아.[/차분] [놀람 0.7]잠깐, 저 소리 들었어?[/놀람] [화남 0.9]누가 문을 열어 둔 거야![/화남]",
instructions: "긴장한 밤 장면. 낮은 목소리로.",
response_format: "mp3",
markup: "emotion-v1", // 감정 표기 켜기
});
fs.writeFileSync("scene.mp3", Buffer.from(await audio.arrayBuffer()));import os
from openai import OpenAI
client = OpenAI(
base_url="https://voicestudio.gensapps.com/v1",
api_key=os.environ["VOICE_STUDIO_KEY"],
)
with client.audio.speech.with_streaming_response.create(
model="voice-studio-1",
voice="minji",
input="[차분]괜찮아.[/차분] [놀람 0.7]잠깐, 저 소리 들었어?[/놀람] [화남 0.9]누가 문을 열어 둔 거야![/화남]",
instructions="긴장한 밤 장면. 낮은 목소리로.",
response_format="mp3",
extra_body={"markup": "emotion-v1"}, # 감정 표기 켜기
) as audio:
audio.stream_to_file("scene.mp3")
목차
빠른 시작
- API 키 받기가입한 뒤 조직 설정에서 키를 만듭니다. 키는 만들 때 한 번만 보입니다.
- SDK 설치OpenAI 공식 SDK를 그대로 씁니다.
- 첫 음성 만들기아래 예제를 그대로 실행합니다. 키는 환경 변수
VOICE_STUDIO_KEY에서 읽습니다.
npm install openai
export VOICE_STUDIO_KEY="vs-..."pip install openai
export VOICE_STUDIO_KEY="vs-..."
인증
모든 요청에 Authorization: Bearer <API 키> 헤더를 붙입니다. 기본 주소는 https://voicestudio.gensapps.com/v1입니다.
키는 서버에만 두고 브라우저 코드에 넣지 않습니다.
POST
/v1/audio/speech음성 만들기
대본 하나를 한 목소리로 읽습니다. 응답 본문은 음성 파일입니다.
| 필드 | 형식 | 설명 |
|---|---|---|
model | 문자열 | voice-studio-1 |
voice | 문자열 | 목소리 id. 목소리 목록에서 찾습니다. 감정 강화(베타)는 minji@preview처럼 id 뒤에 @preview를 붙입니다 |
input | 문자열 | 읽을 대본(2,000자까지). input과 script 중 하나를 보냅니다 |
script | 객체 · 선택 | 줄마다 목소리를 정하는 JSON 대본. JSON 대본에서 설명합니다 |
markup | 문자열 · 선택 | emotion-v1 · emotion-v2이면 대괄호 태그를 감정으로 읽습니다. 없으면 태그도 글자 그대로 읽습니다 |
instructions | 문자열 · 선택 | 전체 말투(500자까지). 구간 태그가 있으면 그 구간은 태그를 따릅니다 |
response_format | 문자열 · 선택 | mp3 · wav(기본 mp3) |
생성한 음성에는 들리지 않는 워터마크가 들어갑니다. AI 기본법의 생성물 표시 의무를 따릅니다.
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://voicestudio.gensapps.com/v1",
apiKey: process.env.VOICE_STUDIO_KEY,
});
const audio = await client.audio.speech.create({
model: "voice-studio-1",
voice: "minji",
input: "[차분]괜찮아.[/차분] [놀람 0.7]잠깐, 저 소리 들었어?[/놀람] [화남 0.9]누가 문을 열어 둔 거야![/화남]",
instructions: "긴장한 밤 장면. 낮은 목소리로.",
response_format: "mp3",
markup: "emotion-v1", // 감정 표기 켜기
});
fs.writeFileSync("scene.mp3", Buffer.from(await audio.arrayBuffer()));import os
from openai import OpenAI
client = OpenAI(
base_url="https://voicestudio.gensapps.com/v1",
api_key=os.environ["VOICE_STUDIO_KEY"],
)
with client.audio.speech.with_streaming_response.create(
model="voice-studio-1",
voice="minji",
input="[차분]괜찮아.[/차분] [놀람 0.7]잠깐, 저 소리 들었어?[/놀람] [화남 0.9]누가 문을 열어 둔 거야![/화남]",
instructions="긴장한 밤 장면. 낮은 목소리로.",
response_format="mp3",
extra_body={"markup": "emotion-v1"}, # 감정 표기 켜기
) as audio:
audio.stream_to_file("scene.mp3")
감정 표기 emotion-v1
태그가 없으면 글 그대로 읽습니다. 구간을 대괄호 태그로 감싸면 그 구간만 그 감정으로 읽고, 한 문장 안에서도 감정을 여러 번 바꿀 수 있습니다.
| 형식 | 의미 | 예시 |
|---|---|---|
[감정]…[/감정] | 구간 감정 | [기쁨]드디어 왔어요![/기쁨] |
[감정 세기]…[/감정] | 세기는 0~1, 생략하면 0.6. 지금은 세기 값에 따른 소리 차이를 보장하지 않습니다 | [화남 0.9]나가![/화남] |
감정 이름
차분
calm기쁨 joy슬픔 sad화남 angry놀람 surprised속삭임 whisper두려움 fear진지 serious
한국어와 영어 이름을 모두 받습니다. 구간 안에 다른 감정 구간을 넣을 수 없습니다. 지원하지 않는 감정이나 닫히지 않은 태그는 무시하지 않고 오류로 돌려줍니다.
curl https://voicestudio.gensapps.com/v1/audio/speech \
-H "Authorization: Bearer $VOICE_STUDIO_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "voice-studio-1", "voice": "minji", "markup": "emotion-v1",
"input": "[기쁨]드디어 도착했어요![/기쁨] 오늘 정말 길었네요."}' \
-o hello.mp3
GET
/v1/voices목소리 목록
쓸 수 있는 목소리의 id · 이름 · 성별 · 나이대 · 음색을 돌려줍니다.
const res = await fetch("https://voicestudio.gensapps.com/v1/voices", {
headers: { Authorization: `Bearer ${process.env.VOICE_STUDIO_KEY}` },
});
for (const v of (await res.json()).data) console.log(v.id, v.name, v.tone);import os, httpx
res = httpx.get(
"https://voicestudio.gensapps.com/v1/voices",
headers={"Authorization": f"Bearer {os.environ['VOICE_STUDIO_KEY']}"},
)
for v in res.json()["data"]:
print(v["id"], v["name"], v["tone"])
오류
오류는 {"error": {"type", "message"}} 형식의 JSON으로 돌려줍니다.
| 코드 | 의미 | 조치 |
|---|---|---|
400 invalid_markup | 지원하지 않는 감정 · 닫히지 않은 태그. 몇 번째 글자인지 position으로 알려 줍니다 | 표기를 고쳐 다시 보냅니다 |
| 401 | API 키가 없거나 맞지 않습니다 | 키를 확인합니다 |
| 402 | 크레딧이 부족합니다 | 조직관리자에게 충전을 요청합니다 |
| 429 | 한도를 넘었습니다 | Retry-After 초만큼 기다린 뒤 다시 보냅니다 |
| 503 | 처리 용량이 잠시 부족합니다 | Retry-After 뒤 다시 보냅니다 |
API는 준비 중입니다. 위 형식은 설계안이며 공개 전에 바뀔 수 있습니다.