02. Text-to-Speech 직접 호출 v2
이 노트북은 client.audio.speech.with_streaming_response.create(...)를 직접 사용해 음성 파일을 만듭니다. voice, speed, response_format 같은 매개변수가 어떤 차이를 만드는지 단계별로 확인합니다.
# Colab 또는 로컬 노트북 실행 환경을 구분하기 위해 sys를 가져옵니다.
import sys
# 패키지 설치 명령을 현재 Python 커널에서 실행하기 위해 subprocess를 가져옵니다.
import subprocess
# 패키지 설치 여부를 확인하기 위해 importlib.util을 가져옵니다.
import importlib.util
# 환경 변수에서 API 키를 읽고 설정하기 위해 os를 가져옵니다.
import os
# API 키를 화면에 노출하지 않고 입력받기 위해 getpass를 가져옵니다.
import getpass
# .env 파일 위치를 다루기 위해 pathlib의 Path를 가져옵니다.
from pathlib import Path
# google.colab 모듈이 있으면 현재 런타임이 Google Colab이라고 판단합니다.
IN_COLAB = "google.colab" in sys.modules
# 노트북에서 사용하는 import 이름과 pip 패키지 이름을 짝지어 둡니다.
REQUIRED_PACKAGES = {
"openai": "openai>=2.26.0",
"dotenv": "python-dotenv>=1.2.2",
"pydantic": "pydantic>=2.11.0",
"PIL": "pillow>=12.1.1",
"requests": "requests>=2.32.5",
"numpy": "numpy>=2.3.3",
"websockets": "websockets>=15.0.1",
"websocket": "websocket-client>=1.8.0",
"nest_asyncio": "nest-asyncio>=1.6.0",
}
def ensure_package(import_name: str, package_name: str) -> None:
if importlib.util.find_spec(import_name) is not None:
return
subprocess.check_call(
[sys.executable, "-m", "pip", "install", "-q", package_name]
)
if IN_COLAB:
for import_name, package_name in REQUIRED_PACKAGES.items():
ensure_package(import_name, package_name)
from dotenv import load_dotenv
load_dotenv(Path.cwd() / ".env")
if not os.getenv("OPENAI_API_KEY"):
secret_key = None
try:
from google.colab import userdata
secret_key = userdata.get("OPENAI_API_KEY")
except Exception:
secret_key = None
if secret_key:
os.environ["OPENAI_API_KEY"] = secret_key
else:
entered_key = getpass.getpass("OPENAI_API_KEY를 입력하세요: ").strip()
if entered_key:
os.environ["OPENAI_API_KEY"] = entered_key
if not os.getenv("OPENAI_API_KEY"):
raise RuntimeError(
"OPENAI_API_KEY가 없습니다. Colab Secrets, 수동 입력, 또는 .env 파일로 설정해 주세요."
)
print(f"개발환경: {'Colab' if IN_COLAB else '로컬'}")
print("OPENAI_API_KEY 준비 완료")
개발환경: 로컬
OPENAI_API_KEY 준비 완료
실습 전에 보는 핵심 개념과 API
핵심 개념
- TTS (Text-to-Speech, 텍스트-음성 변환)는 텍스트를 자연스러운 음성으로 합성하는 작업입니다.
- 같은 텍스트라도 voice, speed, instructions에 따라 전달 느낌이 크게 달라집니다.
- 일반 응답 방식은 결과 전체를 받은 뒤 저장하고, 스트리밍 방식은 생성되는 오디오를 바로 파일로 흘려보낼 수 있습니다.
API 소개
- client.audio.speech.create(...)는 텍스트를 음성으로 변환하는 기본 TTS API입니다.
- client.audio.speech.with_streaming_response.create(...)는 스트리밍 응답을 받아 바로 파일로 저장할 때 유용합니다.
- 이 노트북은 기본 합성, 목소리 비교, 스트리밍 저장, 출력 포맷 비교를 다룹니다.
API 호출부와 주요 매개변수
speech = client.audio.speech.create(
model="gpt-4o-mini-tts",
voice="alloy",
input=text,
speed=1.0,
instructions="차분하고 또렷한 안내 방송 톤으로 읽어 주세요.",
response_format="mp3",
)
with client.audio.speech.with_streaming_response.create(
model="gpt-4o-mini-tts",
voice="nova",
input=text,
response_format="wav",
) as response:
response.stream_to_file(output_path)
- model
- 보통 gpt-4o-mini-tts 같은 음성 합성 모델을 넣습니다.
- 기본값: 실질적인 기본값이 없으므로 명시하는 것이 안전합니다.
- voice
- 최신 문서와 예시에서는 alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin, cedar 같은 내장 목소리를 볼 수 있습니다.
- 서비스 톤에 따라 밝은 안내형, 차분한 설명형, 프리미엄 내레이션형처럼 체감이 달라집니다.
- 기본값: 실질적인 기본값이 없으므로 명시하는 것이 일반적입니다.
- input
- 읽어 줄 텍스트입니다.
- 짧은 안내 문장부터 여러 문장짜리 설명 텍스트까지 넣을 수 있습니다.
- 기본값: 없습니다.
- speed
- 말하기 속도를 조절하는 숫자 배수입니다.
- 실습에서는 1.0을 기준 속도로 보고, 0.95처럼 조금 느리게 조절해 비교할 수 있습니다.
- 기본값: 문서와 예시 기준으로 1.0을 기준값으로 생각하면 됩니다.
- instructions
- 감정, 톤, 전달 스타일을 제어하는 추가 지시입니다.
- 예를 들어 친절한 상담원 톤, 차분한 교육 톤, 빠른 뉴스 톤 같은 스타일을 줄 수 있습니다.
- 기본값: 없습니다. 생략하면 모델 기본 스타일로 읽습니다.
- response_format
- 자주 쓰는 값은 mp3, wav이며, 엔드포인트에 따라 opus, flac, pcm 같은 형식도 고려할 수 있습니다.
- 일반 배포 파일은 mp3, 후처리나 품질 확인은 wav가 읽기 쉽습니다.
- 기본값: 실습에서는 항상 명시하는 편이 안전합니다. 문서와 예시에서는 mp3를 기본 예시 형식으로 자주 사용합니다.
요청/응답 필드에서 볼 것
- TTS 실습에서는 요청에서 voice, speed, instructions, response_format을 먼저 읽고, 지금 음성이 어떤 톤과 형식으로 생성될지 미리 예상해 보는 것이 좋습니다.
- 일반 응답 방식은 먼저 파일로 저장해 실제 재생이 되는지 확인하고, 저장된 파일 크기와 형식을 메타데이터에 같이 적어 두면 비교가 쉬워집니다.
- 스트리밍 응답을 쓸 때는 stream_to_file()이 정말 바로 저장되는지 확인하고, 같은 텍스트를 일반 응답 방식과 비교해 차이가 있는지도 들어보면 학습 효과가 큽니다.
- 결과 비교에서는 음색 차이만 보지 말고 speed, instructions, response_format을 함께 적어 두어 어떤 설정이 어떤 인상을 만들었는지 연결해 보세요.
- 실습 로그에는 입력 텍스트, 모델, 목소리, 출력 파일 경로, 파일 크기, 선택한 형식을 함께 남겨 두면 나중에 샘플 음성 라이브러리처럼 재활용하기 좋습니다.
1단계. 실행 환경 준비
TTS는 텍스트를 파일로 바꾸는 작업이므로 출력 경로와 모델명을 먼저 확인합니다.
# 운영체제 환경 변수를 읽기 위해 os 모듈을 가져옵니다.
import os
# JSON 데이터를 저장하고 보기 좋게 출력하기 위해 json 모듈을 가져옵니다.
import json
# 오디오 base64 데이터를 다루기 위해 base64 모듈을 가져옵니다.
import base64
# 실행 시간을 측정하기 위해 time 모듈을 가져옵니다.
import time
# 비동기 Realtime 예제에서 이벤트 루프를 사용하기 위해 asyncio를 가져옵니다.
import asyncio
# 파일 경로를 안전하게 다루기 위해 Path를 가져옵니다.
from pathlib import Path
# .env 파일의 환경 변수를 읽기 위해 load_dotenv를 가져옵니다.
from dotenv import load_dotenv
# OpenAI API를 직접 호출하기 위해 OpenAI 클라이언트를 가져옵니다.
from openai import OpenAI
# 현재 실행 위치를 저장합니다.
CURRENT_DIR = Path.cwd()
# 루트에서 실행하는 경우 audio 트랙 폴더를 찾습니다.
if (CURRENT_DIR / "06.audio").exists():
# 워크스페이스 루트에서 실행 중이면 06.audio를 실습 루트로 사용합니다.
AUDIO_ROOT = CURRENT_DIR / "06.audio"
# 하위 폴더에서 실행하는 경우 경로 조각에서 06.audio를 찾습니다.
elif "06.audio" in [part for part in CURRENT_DIR.parts]:
# 06.audio까지의 경로를 복원합니다.
AUDIO_ROOT = Path(*CURRENT_DIR.parts[: CURRENT_DIR.parts.index("06.audio") + 1])
# 그 밖의 경우 상대 경로를 사용합니다.
else:
# 현재 위치 기준 06.audio를 실습 루트로 사용합니다.
AUDIO_ROOT = CURRENT_DIR / "06.audio"
# 워크스페이스 루트는 audio 트랙의 상위 폴더입니다.
WORKSPACE_ROOT = AUDIO_ROOT.parent
# v2 결과 저장 폴더를 정합니다.
OUTPUT_DIR = AUDIO_ROOT / "output" / "v2"
# 결과 저장 폴더를 생성합니다.
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# 워크스페이스 루트의 .env 파일을 읽습니다.
load_dotenv(WORKSPACE_ROOT / ".env")
# API 키가 없으면 명확한 오류로 중단합니다.
if not os.getenv("OPENAI_API_KEY"):
# 교육생이 바로 확인할 수 있도록 환경 변수 이름을 알려 줍니다.
raise RuntimeError("OPENAI_API_KEY가 없습니다. 워크스페이스 루트의 .env 파일을 확인해 주세요.")
# OpenAI API 클라이언트를 생성합니다.
client = OpenAI()
# 음성 인식 모델명을 환경 변수에서 읽습니다.
TRANSCRIBE_MODEL = os.getenv("OPENAI_TRANSCRIBE_MODEL", "gpt-4o-mini-transcribe")
# 음성 합성 모델명을 환경 변수에서 읽습니다.
TTS_MODEL = os.getenv("OPENAI_TTS_MODEL", "gpt-4o-mini-tts")
# 오디오 입출력 LLM 모델명을 환경 변수에서 읽습니다.
AUDIO_LLM_MODEL = os.getenv("OPENAI_AUDIO_LLM_MODEL", "gpt-audio-mini")
# Realtime API 모델명을 환경 변수에서 읽습니다.
REALTIME_MODEL = os.getenv("OPENAI_REALTIME_MODEL", "gpt-realtime-mini")
# 워크스페이스 기준 상대 경로를 반환하는 함수를 정의합니다.
def workspace_path(path: Path) -> str:
# 절대 경로를 워크스페이스 기준 / 구분 문자열로 변환합니다.
return path.resolve().relative_to(WORKSPACE_ROOT.resolve()).as_posix()
# 환경 정보를 출력합니다.
print(f"오디오 실습 폴더: {AUDIO_ROOT}")
# 결과 저장 폴더를 출력합니다.
print(f"결과 저장 폴더: {OUTPUT_DIR}")
# 사용할 음성 인식 모델을 출력합니다.
print(f"STT 모델: {TRANSCRIBE_MODEL}")
# 사용할 음성 합성 모델을 출력합니다.
print(f"TTS 모델: {TTS_MODEL}")
오디오 실습 폴더: c:\Users\USER\Desktop\AS.1.1\d02\06.audio
결과 저장 폴더: c:\Users\USER\Desktop\AS.1.1\d02\06.audio\output\v2
STT 모델: gpt-4o-mini-transcribe
TTS 모델: gpt-4o-mini-tts
2단계. 기본 음성 합성
가장 단순한 TTS 호출은 model, voice, input 세 가지가 핵심입니다. 스트리밍 응답을 파일로 저장하는 공식 SDK 패턴을 직접 사용합니다.
# 기본 TTS 결과 파일 경로를 정합니다.
basic_path = OUTPUT_DIR / "02-02_tts_basic.mp3"
# 합성할 안내 문장을 준비합니다.
text = "OpenAI 음성 합성 API는 텍스트를 자연스러운 음성 파일로 변환합니다."
# TTS 스트리밍 응답 컨텍스트를 엽니다.
with client.audio.speech.with_streaming_response.create(
# 음성 합성 모델을 지정합니다.
model=TTS_MODEL,
# 기본 실습 목소리를 지정합니다.
voice="alloy",
# 합성할 텍스트를 전달합니다.
input=text,
# MP3 형식으로 저장합니다.
response_format="mp3",
) as response:
# 스트리밍 응답을 파일로 저장합니다.
response.stream_to_file(basic_path)
# 저장 위치를 출력합니다.
print(f"기본 TTS 저장: {workspace_path(basic_path)}")
기본 TTS 저장: 06.audio/output/v2/02-02_tts_basic.mp3
3단계. voice와 streaming 비교
같은 문장을 다른 목소리로 합성하면 서비스 톤이 달라집니다. 반복문 안에서도 API 호출 매개변수가 그대로 드러나도록 작성합니다.
# 비교할 목소리 목록을 준비합니다.
voices = ["alloy", "nova"]
# 목소리별 결과 파일을 저장할 리스트를 준비합니다.
voice_outputs = []
# 목소리 목록을 하나씩 순회합니다.
for voice in voices:
# 현재 목소리의 출력 파일 경로를 정합니다.
output_path = OUTPUT_DIR / f"02-03_tts_voice_{voice}.mp3"
# 현재 목소리로 TTS API를 직접 호출합니다.
with client.audio.speech.with_streaming_response.create(
# 음성 합성 모델을 지정합니다.
model=TTS_MODEL,
# 반복 중인 목소리를 지정합니다.
voice=voice,
# 같은 문장을 사용해 목소리 차이만 비교합니다.
input="같은 문장도 목소리에 따라 학습자의 인상이 달라집니다.",
# MP3 형식으로 결과를 받습니다.
response_format="mp3",
) as response:
# 스트리밍 응답을 파일로 저장합니다.
response.stream_to_file(output_path)
# 결과 기록에 현재 파일 정보를 추가합니다.
voice_outputs.append(
{
"voice": voice,
"path": workspace_path(output_path),
"bytes": output_path.stat().st_size,
}
)
# 목소리 비교 결과를 JSON으로 저장합니다.
(OUTPUT_DIR / "02-03_tts_voice_comparison.json").write_text(
json.dumps(voice_outputs, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
# 비교 결과를 출력합니다.
print(json.dumps(voice_outputs, ensure_ascii=False, indent=2))
[
{
"voice": "alloy",
"path": "06.audio/output/v2/02-03_tts_voice_alloy.mp3",
"bytes": 82560
},
{
"voice": "nova",
"path": "06.audio/output/v2/02-03_tts_voice_nova.mp3",
"bytes": 78720
}
]
4단계. 속도와 출력 형식 조절
TTS 응답은 MP3뿐 아니라 WAV 같은 형식으로도 받을 수 있습니다. 속도와 형식을 직접 지정해 실무에서 필요한 파일 조건을 맞추는 방법을 확인합니다.
# 고급 TTS 결과 파일 경로를 정합니다.
advanced_path = OUTPUT_DIR / "02-04_tts_advanced.wav"
# 고급 TTS 호출을 시작합니다.
with client.audio.speech.with_streaming_response.create(
# 음성 합성 모델을 지정합니다.
model=TTS_MODEL,
# 또렷한 설명에 어울리는 목소리를 지정합니다.
voice="shimmer",
# 합성할 교육용 문장을 전달합니다.
input="이번 단계에서는 말하기 속도와 파일 형식을 직접 제어합니다.",
# WAV 형식으로 결과를 받습니다.
response_format="wav",
# 기본값보다 약간 빠른 속도로 합성합니다.
speed=1.15,
) as response:
# 스트리밍 응답을 WAV 파일로 저장합니다.
response.stream_to_file(advanced_path)
# 고급 합성 메타데이터를 구성합니다.
metadata = {
"model": TTS_MODEL,
"voice": "shimmer",
"response_format": "wav",
"speed": 1.15,
"path": workspace_path(advanced_path),
"bytes": advanced_path.stat().st_size,
}
# 메타데이터를 JSON으로 저장합니다.
(OUTPUT_DIR / "02-04_tts_advanced_meta.json").write_text(
json.dumps(metadata, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
# 저장 위치를 출력합니다.
print(json.dumps(metadata, ensure_ascii=False, indent=2))
{
"model": "gpt-4o-mini-tts",
"voice": "shimmer",
"response_format": "wav",
"speed": 1.15,
"path": "06.audio/output/v2/02-04_tts_advanced.wav",
"bytes": 300462
}
speed 값 해설
이 셀의 speed 는 말하기 속도를 조절하는 배수입니다. 현재 설치된 OpenAI Python SDK 기준으로 0.25 부터 4.0 까지의 실수 값을 넣을 수 있고, 1.0 이 기본 속도입니다.
- 0.25 ~ 0.9: 더 천천히 읽습니다. 차분한 안내, 또박또박한 교육용 설명에 어울립니다.
- 1.0: 기본 속도입니다. 별도 조정이 없는 일반적인 기준 음성입니다.
- 1.05 ~ 1.2: 약간 더 빠르게 읽습니다. 짧은 안내나 템포가 필요한 설명에 자주 씁니다.
- 1.5 이상: 상당히 빠르게 들릴 수 있어서, 비교 실험이 아니면 자연스러움이 떨어질 수 있습니다.
이 예제는 speed=1.15 를 사용하므로 기본값보다 조금 빠른 속도로 읽는 예시입니다. 따라서 저장되는 메타데이터의 speed 값도 같은 1.15 로 남겨 두어야, 나중에 결과 파일과 설정을 다시 비교할 때 혼동이 없습니다.
'AI System > OpenAI API와 바이브 코딩으로 배우는 AI 서비스 개발' 카테고리의 다른 글
| d02 - 06. Audio - 04. voice agent - README.md (0) | 2026.06.02 |
|---|---|
| d02 - 06. Audio - 03. audio in llm v2 (0) | 2026.06.02 |
| d02 - 06. Audio - 01. stt (0) | 2026.06.02 |
| d02 - 05. Image - 04. workflow (0) | 2026.06.02 |
| d02 - 05. Image - 03. analysis (0) | 2026.06.02 |