텍스트를 음성으로

텍스트를 음성으로 (Text to speech)

글을 소리로 바꾸고 싶을 때가 있죠. 블로그 포스트를 내레이션으로 만들거나, 여러 언어로 된 음성 오디오를 만들거나, 실시간으로 음성을 바로 재생하고 싶을 때요. Audio API의 speech 엔드포인트가 바로 그 역할을 해요. 이 엔드포인트는 GPT-4o mini TTS 모델을 기반으로 하며, 13개의 내장 음성을 제공해요.

이용 정책에 따라, 사용자가 듣는 TTS 음성이 AI가 생성한 것이지 사람 목소리가 아니라는 점을 최종 사용자에게 명확히 고지해야 해요. 잊지 말아야 할 중요한 부분입니다.

출처: 공식문서

빠른 시작 (Quickstart)

speech 엔드포인트는 세 가지 핵심 입력을 받아요.

  1. 사용할 모델
  2. 오디오로 바꿀 텍스트
  3. 결과를 말해 줄 음성

간단한 요청 예시를 볼게요.

입력 텍스트로 음성 오디오 생성하기

import fs from "fs";
import path from "path";
import OpenAI from "openai";

const openai = new OpenAI();
const speechFile = path.resolve("./speech.mp3");

const mp3 = await openai.audio.speech.create({
  model: "gpt-4o-mini-tts",
  voice: "coral",
  input: "Today is a wonderful day to build something people love!",
  instructions: "Speak in a cheerful and positive tone.",
});

const buffer = Buffer.from(await mp3.arrayBuffer());
await fs.promises.writeFile(speechFile, buffer);
from pathlib import Path
from openai import OpenAI

client = OpenAI()
speech_file_path = Path(__file__).parent / "speech.mp3"

with client.audio.speech.with_streaming_response.create(
    model="gpt-4o-mini-tts",
    voice="coral",
    input="Today is a wonderful day to build something people love!",
    instructions="Speak in a cheerful and positive tone.",
) as response:
    response.stream_to_file(speech_file_path)
package main

import (
	"context"
	"io"
	"os"

	"github.com/openai/openai-go/v3"
)

func main() {
	client := openai.NewClient()
	response, err := client.Audio.Speech.New(context.Background(), openai.AudioSpeechNewParams{
		Model:        openai.SpeechModelGPT4oMiniTTS,
		Voice:        openai.AudioSpeechNewParamsVoiceUnion{OfAudioSpeechNewsVoiceString2: openai.String("coral")},
		Input:        "Today is a wonderful day to build something people love!",
		Instructions: openai.String("Speak in a cheerful and positive tone."),
	})
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()

	file, err := os.Create("speech.mp3")
	if err != nil {
		panic(err)
	}
	if _, err := io.Copy(file, response.Body); err != nil {
		panic(err)
	}
	if err := file.Close(); err != nil {
		panic(err)
	}
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.http.HttpResponse;
import com.openai.models.audio.speech.SpeechCreateParams;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

try (HttpResponse audio =
    client
        .audio()
        .speech()
        .create(
            SpeechCreateParams.builder()
                .model("gpt-4o-mini-tts")
                .voice("coral")
                .input("Today is a wonderful day to build something people love!")
                .instructions("Speak in a cheerful and positive tone.")
                .build())) {
  Files.copy(audio.body(), Path.of("speech.mp3"), StandardCopyOption.REPLACE_EXISTING);
}
using OpenAI.Audio;
#pragma warning disable OPENAI001

string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
string model = "gpt-4o-mini-tts";
AudioClient client = new(model, key);

BinaryData audio = await client.GenerateSpeechAsync(
    "Today is a wonderful day to build something people love!",
    GeneratedSpeechVoice.Coral,
    new SpeechGenerationOptions
    {
        Instructions = "Speak in a cheerful and positive tone.",
    }
);

await File.WriteAllBytesAsync("speech.mp3", audio.ToArray());
require "openai"

client = OpenAI::Client.new
audio = client.audio.speech.create(
  model: "gpt-4o-mini-tts",
  voice: "coral",
  input: "Today is a wonderful day to build something people love!",
  instructions: "Speak in a cheerful and positive tone."
)
File.binwrite("speech.mp3", audio.read)
curl https://api.openai.com/v1/audio/speech \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o-mini-tts",
    "input": "Today is a wonderful day to build something people love!",
    "voice": "coral",
    "instructions": "Speak in a cheerful and positive tone."
  }' \
  --output speech.mp3
openai audio:speech create \
  --model gpt-4o-mini-tts \
  --voice coral \
  --instructions "Speak in a cheerful and positive tone." \
  --input "Today is a wonderful day to build something people love!" \
  --output speech.mp3

기본적으로 이 엔드포인트는 말한 내용의 MP3를 출력하지만, 지원되는 출력 형식 중 원하는 것으로 바꿀 수 있어요.

텍스트-음성 모델

지능적인 실시간 애플리케이션이라면 gpt-4o-mini-tts 모델을 써요. 가장 새롭고 가장 안정적인 텍스트-음성 모델이에요. 이 모델에 프롬프트를 줘서 다음 같은 발화 요소를 제어할 수 있어요.

  • 억양(Accent)
  • 감정 범위(Emotional range)
  • 억양 패턴(Intonation)
  • 흉내(Impressions)
  • 말 속도(Speed of speech)
  • 어조(Tone)
  • 속삭임(Whispering)

다른 텍스트-음성 모델로는 tts-1tts-1-hd가 있어요. tts-1은 지연 시간이 더 낮지만 tts-1-hd보다 품질이 낮아요.

음성 옵션

TTS 엔드포인트는 텍스트를 음성으로 렌더링하는 방식을 조절할 수 있는 내장 음성 13개를 제공해요. 이 음성들을 직접 듣고 실험해 보려면 OpenAI API 최신 텍스트-음성 모델을 체험할 수 있는 대화형 데모인 OpenAI.fm에서 확인할 수 있어요. 음성은 현재 영어에 최적화되어 있어요.

  • alloy
  • ash
  • ballad
  • coral
  • echo
  • fable
  • nova
  • onyx
  • sage
  • shimmer
  • verse
  • marin
  • cedar

최상의 품질을 원한다면 marin 또는 cedar를 추천해요.

음성 가용성은 모델에 따라 달라요. tts-1tts-1-hd 모델은 더 작은 집합인 alloy, ash, coral, echo, fable, onyx, nova, sage, shimmer만 지원해요.

Realtime API를 쓰고 있다면 사용 가능한 음성 집합이 조금 다를 수 있다는 점을 기억하세요. 현재 실시간 음성은 실시간 대화 가이드를 참고하세요.

실시간 오디오 스트리밍

Speech API는 청크 전송 인코딩을 통한 실시간 오디오 스트리밍을 지원해요. 즉 전체 파일이 생성되어 접근 가능해지기 전에도 오디오를 재생할 수 있다는 뜻이에요.

입력 텍스트의 음성을 스피커로 바로 스트리밍하기

import OpenAI from "openai";
import { playAudio } from "openai/helpers/audio";

const openai = new OpenAI();

const response = await openai.audio.speech.create({
  model: "gpt-4o-mini-tts",
  voice: "coral",
  input: "Today is a wonderful day to build something people love!",
  instructions: "Speak in a cheerful and positive tone.",
  response_format: "wav",
});

await playAudio(response);
import asyncio

from openai import AsyncOpenAI
from openai.helpers import LocalAudioPlayer

openai = AsyncOpenAI()


async def main() -> None:
    async with openai.audio.speech.with_streaming_response.create(
        model="gpt-4o-mini-tts",
        voice="coral",
        input="Today is a wonderful day to build something people love!",
        instructions="Speak in a cheerful and positive tone.",
        response_format="pcm",
    ) as response:
        await LocalAudioPlayer().play(response)


if __name__ == "__main__":
    asyncio.run(main())
package main

import (
	"context"
	"io"
	"os"

	"github.com/openai/openai-go/v3"
)

func main() {
	client := openai.NewClient()
	response, err := client.Audio.Speech.New(context.Background(), openai.AudioSpeechNewParams{
		Model:          openai.SpeechModelGPT4oMiniTTS,
		Voice:          openai.AudioSpeechNewParamsVoiceUnion{OfAudioSpeechNewsVoiceString2: openai.String("coral")},
		Input:          "Today is a wonderful day to build something people love!",
		Instructions:   openai.String("Speak in a cheerful and positive tone."),
		ResponseFormat: openai.AudioSpeechNewParamsResponseFormatWAV,
	})
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	if _, err := io.Copy(os.Stdout, response.Body); err != nil {
		panic(err)
	}
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.http.HttpResponse;
import com.openai.models.audio.speech.SpeechCreateParams;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

try (HttpResponse audio =
    client
        .audio()
        .speech()
        .create(
            SpeechCreateParams.builder()
                .model("gpt-4o-mini-tts")
                .voice("coral")
                .input("Today is a wonderful day to build something people love!")
                .instructions("Speak in a cheerful and positive tone.")
                .responseFormat(SpeechCreateParams.ResponseFormat.PCM)
                .streamFormat(SpeechCreateParams.StreamFormat.AUDIO)
                .build())) {
  AudioFormat format = new AudioFormat(24_000, 16, 1, true, false);
  String outputPath = System.getenv("OPENAI_EXAMPLE_AUDIO_OUTPUT_PATH");
  if (outputPath == null || outputPath.isBlank()) {
    try (SourceDataLine speakers = AudioSystem.getSourceDataLine(format)) {
      speakers.open(format);
      speakers.start();
      byte[] chunk = new byte[1024];
      int bytesRead;
      while ((bytesRead = audio.body().read(chunk)) != -1) {
        speakers.write(chunk, 0, bytesRead);
      }
      speakers.drain();
    }
  } else {
    try (OutputStream output = Files.newOutputStream(Path.of(outputPath))) {
      long bytes = audio.body().transferTo(output);
      System.out.println(bytes + " audio bytes");
    }
  }
}
require "openai"

client = OpenAI::Client.new
audio = client.audio.speech.create(
  model: "gpt-4o-mini-tts",
  voice: "alloy",
  input: "Welcome to the OpenAI API.",
  response_format: :pcm,
  stream_format: :audio
)
while (chunk = audio.read(1_024))
  puts(chunk.bytesize)
end
curl https://api.openai.com/v1/audio/speech \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o-mini-tts",
    "input": "Today is a wonderful day to build something people love!",
    "voice": "coral",
    "instructions": "Speak in a cheerful and positive tone.",
    "response_format": "wav"
  }' | ffplay -i -

가장 빠른 응답 시간을 원한다면 응답 형식으로 wav 또는 pcm을 추천해요.

지원되는 출력 형식

기본 응답 형식은 mp3이고, 그 외에 opus, wav 같은 형식도 사용할 수 있어요.

  • MP3: 일반적인 사용 사례의 기본 응답 형식이에요.
  • Opus: 인터넷 스트리밍과 통신용으로, 지연 시간이 낮아요.
  • AAC: 디지털 오디오 압축용으로, YouTube, Android, iOS에서 선호돼요.
  • FLAC: 무손실 오디오 압축용으로, 오디오 애호가들이 아카이빙에 선호해요.
  • WAV: 압축되지 않은 WAV 오디오로, 디코딩 오버헤드를 피하고 싶은 저지연 애플리케이션에 적합해요.
  • PCM: WAV와 비슷하지만 헤더 없이 24kHz(16비트 부호 있는, little-endian)의 원시 샘플을 담고 있어요.

지원되는 언어

TTS 모델의 언어 지원은 일반적으로 Whisper 모델을 따르고 있어요. Whisper는 여러 언어를 지원하고 좋은 성능을 보여주는데, 음성 자체가 영어에 최적화돼 있다는 점은 기억하세요. 지원 언어는 Afrikaans, Arabic, Armenian, Azerbaijani, Belarusian, Bosnian, Bulgarian, Catalan, Chinese, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, Galician, German, Greek, Hebrew, Hindi, Hungarian, Icelandic, Indonesian, Italian, Japanese, Kannada, Kazakh, Korean, Latvian, Lithuanian, Macedonian, Malay, Marathi, Maori, Nepali, Norwegian, Persian, Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovenian, Spanish, Swahili, Swedish, Tagalog, Tamil, Thai, Turkish, Ukrainian, Urdu, Vietnamese, Welsh예요.

원하는 언어로 입력 텍스트를 제공하면 해당 언어의 음성 오디오를 생성할 수 있어요.

커스텀 음성 (Custom voices)

화자의 동의 녹음과 매칭되는 오디오 샘플로 승인된 커스텀 음성을 만들 수 있어요. 자격 요건, 녹음 요건, 동의 문구, API 요청에 대한 자세한 내용은 Custom voices 문서를 참고하세요.

음성 만들기

사용자 지정 음성 만들기 문서를 따라 하세요.

음성 생성 시 음성 사용하기

음성 생성 시 만든 음성 ID를 전달해요. 자세한 내용은 음성 생성 예시를 참고하세요.

더 알아보기 (Learn more)