Gemini API 음성 생성

Gemini API 음성 생성 (Speech Generation, Interactions API)

Gemini API는 Gemini 텍스트 음성 변환(TTS) 생성 기능으로 텍스트 입력을 단일 화자 또는 다중 화자 오디오로 변환할 수 있어요. 텍스트 음성 변환은 [제어 가능(controllable)] 하며, 구조화된 턴 메타데이터(speech_metadata)와 인라인 발성 태그를 결합해 오디오의 스타일, 억양, 속도, 어조를 이끌 수 있습니다.

출처: 문서

본문

TTS 기능은 대화형·비정형 오디오와 멀티모달 입력·출력을 위해 설계된 Live API를 통한 음성 생성과는 달라요. Live API는 역동적인 대화형 컨텍스트에서 뛰어나지만, Gemini API의 TTS는 팟캐스트·오디오북 생성처럼 스타일과 사운드를 세밀하게 제어하며 텍스트를 정확히 낭독해야 하는 시나리오에 맞춰져 있어요.

이 가이드는 Gemini 3.8 Flash TTS(gemini-3.8-flash-tts)와 Gemini 3.8 Flash-Lite TTS(gemini-3.8-flash-lite-tts)로 텍스트에서 단일·다중 화자 오디오를 생성하는 방법을 보여줘요.

시작하기 전에

지원 모델 섹션에 나열된 Gemini TTS 모델을 사용해야 해요. 최적의 결과를 위해 언제 어떤 모델을 쓸까를 검토해 워크로드에 맞는 최선의 모델을 선택하세요.

빌드를 시작하기 전에 AI Studio에서 Gemini TTS 모델을 테스트해 보면 유용해요.

참고: TTS 모델은 텍스트 전용 입력을 받고 오디오 전용 출력을 만들어요. TTS 모델에 특화된 제한 사항 전체는 Limitations 섹션을 참고하세요.

단일 화자 TTS

Gemini 3.8 TTS 모델로 텍스트를 단일 화자 오디오로 변환하려면 input에 원문(verbatim) 전사본을 전달하고 speech_metadata 어노테이션으로 턴 수준 스타일링을 붙이며 generation_config.speech_config에서 목소리를 구성하세요. 사전 제작 음성 옵션, 확장 음성 라이브러리(GET /v1beta/voices), 커스텀 Voice design ID(voice_...), 또는 Voice replication ID(voice_..., 선택적 무상태 voicekey_...)에서 음성을 고를 수 있어요.

이 예시는 모델의 기본 WAV 출력 오디오(audio/wav)를 파일에 직접 저장해요.

import base64
from google import genai

client = genai.Client()

interaction = client.interactions.create(
    model="gemini-3.8-flash-tts",
    input=[{
        "type": "user_input",
        "content": [{
            "type": "text",
            "text": "Have a wonderful day!",
            "annotations": [{
                "type": "speech_metadata",
                "style": "cheerful and friendly",
            }],
        }],
    }],
    response_format={"type": "audio"},
    generation_config={
        "speech_config": [
            {"voice": "Kore"},
        ]
    },
)

with open("out.wav", "wb") as f:
    f.write(base64.b64decode(interaction.output_audio.data))
import * as fs from 'node:fs';
import {GoogleGenAI} from '@google/genai';

async function main() {
   const client = new GoogleGenAI({});

   const interaction = await client.interactions.create({
      model: 'gemini-3.8-flash-tts',
      input: [{
         type: 'user_input',
         content: [{
            type: 'text',
            text: 'Have a wonderful day!',
            annotations: [{
               type: 'speech_metadata',
               style: 'cheerful and friendly',
            }],
         }],
      }],
      response_format: { type: 'audio' },
      generation_config: {
         speech_config: [
            { voice: 'Kore' },
         ],
      },
   });

   const audioBuffer = Buffer.from(interaction.output_audio.data, 'base64');
   fs.writeFileSync('out.wav', audioBuffer);
}
await main();
package main

import (
    "context"
    "encoding/base64"
    "encoding/binary"
    "log"
    "os"

    "google.golang.org/genai"
    "google.golang.org/genai/interactions/models/interactions"
    "google.golang.org/genai/interactions/models/operations"
)

func saveWaveFile(filename string, pcmData []byte) error {
    f, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer f.Close()

    sampleRate := uint32(24000)
    numChannels := uint16(1)
    bitsPerSample := uint16(16)
    byteRate := sampleRate * uint32(numChannels) * uint32(bitsPerSample/8)
    blockAlign := numChannels * (bitsPerSample / 8)
    dataSize := uint32(len(pcmData))

    f.WriteString("RIFF")
    binary.Write(f, binary.LittleEndian, uint32(36+dataSize))
    f.WriteString("WAVEfmt ")
    binary.Write(f, binary.LittleEndian, uint32(16))
    binary.Write(f, binary.LittleEndian, uint16(1))
    binary.Write(f, binary.LittleEndian, numChannels)
    binary.Write(f, binary.LittleEndian, sampleRate)
    binary.Write(f, binary.LittleEndian, byteRate)
    binary.Write(f, binary.LittleEndian, blockAlign)
    binary.Write(f, binary.LittleEndian, bitsPerSample)
    f.WriteString("data")
    binary.Write(f, binary.LittleEndian, dataSize)
    _, err = f.Write(pcmData)
    return err
}

func main() {
    ctx := context.Background()
    client, err := genai.NewClient(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    generationConfig := &interactions.GenerationConfig{
        SpeechConfig: genai.Ptr(interactions.NewSpeechConfigUnion([]interactions.SpeechConfig{
            {Voice: genai.Ptr("Kore")},
        })),
    }

    res, err := client.Interactions.Create(ctx, operations.CreateInteractionRequest{
        Body: operations.NewCreateInteractionRequestBody(interactions.CreateModelInteraction{
            Model: interactions.Model("gemini-3.1-flash-tts-preview"),
            Input: interactions.NewInteractionsInput("Say cheerfully: Have a wonderful day!"),
            ResponseFormat: genai.Ptr(interactions.NewCreateModelInteractionResponseFormat(
                interactions.NewResponseFormat(interactions.AudioResponseFormat{}),
            )),
            GenerationConfig: generationConfig,
        }),
    })
    if err != nil {
        log.Fatal(err)
    }

    if res.Interaction.OutputAudio != nil && res.Interaction.OutputAudio.Data != nil {
        pcmBytes, err := base64.StdEncoding.DecodeString(*res.Interaction.OutputAudio.Data)
        if err != nil {
            log.Fatal(err)
        }
        if err := saveWaveFile("out.wav", pcmBytes); err != nil {
            log.Fatal(err)
        }
    }
}
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.8-flash-tts",
    "input": [{
      "type": "user_input",
      "content": [{
        "type": "text",
        "text": "Have a wonderful day!",
        "annotations": [{
          "type": "speech_metadata",
          "style": "cheerful and friendly"
        }]
      }]
    }],
    "response_format": {
      "type": "audio"
    },
    "generation_config": {
      "speech_config": [
        { "voice": "Kore" }
      ]
    }
  }' | jq -r '[.steps[] | select(.type=="model_output") | .content[] | select(.type=="audio")] | last | .data' | base64 --decode > out.wav

Python·JavaScript SDK에서는 interaction.output_audio 편의 속성으로 생성된 오디오 데이터를 가져올 수 있어요. 이 속성은 마지막으로 생성된 오디오 블록을 반환합니다(원시 REST JSON 응답에서는 base64 인코딩 오디오가 steps[].content[].data에 저장). 편의 속성에 대한 자세한 내용은 Interactions overview를 참고하세요.

다중 화자 TTS

다중 화자 대화의 경우 speech_config.speakers에서 두 화자를 구성하고, 각 턴을 speaker와 선택적 턴 수준 style을 지정하는 speech_metadata 어노테이션이 있는 별도의 텍스트 항목으로 전달해요. 자연스러운 턴 진행 박자를 위해 "mode": "conversational"을 사용하세요.

import base64
from google import genai

client = genai.Client()

interaction = client.interactions.create(
    model="gemini-3.8-flash-tts",
    input=[{
        "type": "user_input",
        "content": [
            {
                "type": "text",
                "text": "How's it going today Jane?",
                "annotations": [{
                    "type": "speech_metadata",
                    "speaker": "Joe",
                    "style": "cheerful and friendly",
                }],
            },
            {
                "type": "text",
                "text": "Not too bad, how about you? Ready to test these new voices?",
                "annotations": [{
                    "type": "speech_metadata",
                    "speaker": "Jane",
                    "style": "calm and relaxed",
                }],
            },
        ],
    }],
    response_format={"type": "audio"},
    generation_config={
        "speech_config": {
            "mode": "conversational",
            "speakers": [
                {"speaker": "Joe", "voice": "Puck"},
                {"speaker": "Jane", "voice": "Kore"},
            ],
        }
    },
)

with open("out.wav", "wb") as f:
    f.write(base64.b64decode(interaction.output_audio.data))
import * as fs from 'node:fs';
import {GoogleGenAI} from '@google/genai';

async function main() {
   const client = new GoogleGenAI({});

   const interaction = await client.interactions.create({
      model: 'gemini-3.8-flash-tts',
      input: [{
         type: 'user_input',
         content: [
            {
               type: 'text',
               text: "How's it going today Jane?",
               annotations: [{
                  type: 'speech_metadata',
                  speaker: 'Joe',
                  style: 'cheerful and friendly',
               }],
            },
            {
               type: 'text',
               text: 'Not too bad, how about you? Ready to test these new voices?',
               annotations: [{
                  type: 'speech_metadata',
                  speaker: 'Jane',
                  style: 'calm and relaxed',
               }],
            },
         ],
      }],
      response_format: { type: 'audio' },
      generation_config: {
         speech_config: {
            mode: 'conversational',
            speakers: [
               { speaker: 'Joe', voice: 'Puck' },
               { speaker: 'Jane', voice: 'Kore' },
            ],
         },
      },
   });

   const audioBuffer = Buffer.from(interaction.output_audio.data, 'base64');
   fs.writeFileSync('out.wav', audioBuffer);
}

await main();
package main

import (
    "context"
    "encoding/base64"
    "encoding/binary"
    "log"
    "os"

    "google.golang.org/genai"
    "google.golang.org/genai/interactions/models/interactions"
    "google.golang.org/genai/interactions/models/operations"
)

func saveWaveFile(filename string, pcmData []byte) error {
    f, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer f.Close()

    sampleRate := uint32(24000)
    numChannels := uint16(1)
    bitsPerSample := uint16(16)
    byteRate := sampleRate * uint32(numChannels) * uint32(bitsPerSample/8)
    blockAlign := numChannels * (bitsPerSample / 8)
    dataSize := uint32(len(pcmData))

    f.WriteString("RIFF")
    binary.Write(f, binary.LittleEndian, uint32(36+dataSize))
    f.WriteString("WAVEfmt ")
    binary.Write(f, binary.LittleEndian, uint32(16))
    binary.Write(f, binary.LittleEndian, uint16(1))
    binary.Write(f, binary.LittleEndian, numChannels)
    binary.Write(f, binary.LittleEndian, sampleRate)
    binary.Write(f, binary.LittleEndian, byteRate)
    binary.Write(f, binary.LittleEndian, blockAlign)
    binary.Write(f, binary.LittleEndian, bitsPerSample)
    f.WriteString("data")
    binary.Write(f, binary.LittleEndian, dataSize)
    _, err = f.Write(pcmData)
    return err
}

func main() {
    ctx := context.Background()
    client, err := genai.NewClient(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    prompt := "TTS the following conversation between Joe and Jane:\n" +
        "Joe: How's it going today Jane?\n" +
        "Jane: Not too bad, how about you?"

    generationConfig := &interactions.GenerationConfig{
        SpeechConfig: genai.Ptr(interactions.NewSpeechConfigUnion([]interactions.SpeechConfig{
            {Speaker: genai.Ptr("Joe"), Voice: genai.Ptr("Kore")},
            {Speaker: genai.Ptr("Jane"), Voice: genai.Ptr("Puck")},
        })),
    }

    res, err := client.Interactions.Create(ctx, operations.CreateInteractionRequest{
        Body: operations.NewCreateInteractionRequestBody(interactions.CreateModelInteraction{
            Model: interactions.Model("gemini-3.1-flash-tts-preview"),
            Input: interactions.NewInteractionsInput(prompt),
            ResponseFormat: genai.Ptr(interactions.NewCreateModelInteractionResponseFormat(
                interactions.NewResponseFormat(interactions.AudioResponseFormat{}),
            )),
            GenerationConfig: generationConfig,
        }),
    })
    if err != nil {
        log.Fatal(err)
    }

    if res.Interaction.OutputAudio != nil && res.Interaction.OutputAudio.Data != nil {
        pcmBytes, err := base64.StdEncoding.DecodeString(*res.Interaction.OutputAudio.Data)
        if err != nil {
            log.Fatal(err)
        }
        if err := saveWaveFile("out.wav", pcmBytes); err != nil {
            log.Fatal(err)
        }
    }
}
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.8-flash-tts",
    "input": [{
      "type": "user_input",
      "content": [
        {
          "type": "text",
          "text": "How'\''s it going today Jane?",
          "annotations": [{
            "type": "speech_metadata",
            "speaker": "Joe",
            "style": "cheerful and friendly"
          }]
        },
        {
          "type": "text",
          "text": "Not too bad, how about you? Ready to test these new voices?",
          "annotations": [{
            "type": "speech_metadata",
            "speaker": "Jane",
            "style": "calm and relaxed"
          }]
        }
      ]
    }],
    "response_format": {
      "type": "audio"
    },
    "generation_config": {
      "speech_config": {
        "mode": "conversational",
        "speakers": [
          { "speaker": "Joe", "voice": "Puck" },
          { "speaker": "Jane", "voice": "Kore" }
        ]
      }
    }
  }'

메타데이터와 태그로 음성 스타일 제어

Gemini 3.8 TTS는 text 필드를 엄격히 원문(verbatim) 전사본으로 취급해요. 무대 지시가 소리 내어 읽히지 않게 전달을 제어하려면 범위별로 지시를 나누세요.

  • 지속적 턴 수준 전달(speech_metadata.style): 전체 턴에 적용되는 감정·전달 스타일·운율·속도·볼륨을 style 필드에 두세요(예: "style": "whispered urgently", "style": "out of breath", "style": "warm and enthusiastic").
  • 시점 이벤트(인라인 태그): 순간적 비발화 발성 폭발이나 일시정지를 앵글 브래킷으로 전사본 안에 직접 두세요(예: "Wait... <short pause> did you hear that? <sigh>" 또는 "Excuse me <cough> as I was saying...").

종합 모범 사례는 Prompting 가이드를 참고하세요.

스트리밍 음성 생성

stream: true로 설정해 합성되는 대로 생성된 오디오를 스트리밍할 수 있어요. 유너리 요청(RIFF 헤더가 있는 완전한 WAV 파일 반환)과 달리, 스트리밍 요청은 기본적으로 무헤더 raw 16비트 부호 있는 리틀엔디언 선형 PCM(audio/l16, 24 kHz, mono) 청크를 반환하므로 오디오 청크를 컨테이너 헤더 없이 연속적으로 재생·연결할 수 있어요.

import base64
from google import genai

client = genai.Client()

stream = client.interactions.create(
    model="gemini-3.8-flash-tts",
    input=[{
        "type": "user_input",
        "content": [{
            "type": "text",
            "text": "Have a wonderful day!",
            "annotations": [{
                "type": "speech_metadata",
                "style": "cheerful and friendly",
            }],
        }],
    }],
    response_format={"type": "audio"},
    generation_config={
        "speech_config": [
            {"voice": "Kore"},
        ]
    },
    stream=True,
)

for event in stream:
    if event.event_type == "step.delta":
        if event.delta.type == "audio":
            audio_data = base64.b64decode(event.delta.data)
            # Process the audio chunk (e.g. play it or write to a file)
import {GoogleGenAI} from '@google/genai';

async function main() {
   const client = new GoogleGenAI({});

   const stream = await client.interactions.create({
      model: 'gemini-3.8-flash-tts',
      input: [{
         type: 'user_input',
         content: [{
            type: 'text',
            text: 'Have a wonderful day!',
            annotations: [{
               type: 'speech_metadata',
               style: 'cheerful and friendly',
            }],
         }],
      }],
      response_format: { type: 'audio' },
      generation_config: {
         speech_config: [
            { voice: 'Kore' },
         ],
      },
      stream: true,
   });

   for await (const event of stream) {
      if (event.event_type === 'step.delta') {
         if (event.delta.type === 'audio') {
            const audioBuffer = Buffer.from(event.delta.data, 'base64');
            // Process the audio buffer
         }
      }
   }
}
await main();
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  --no-buffer \
  -d '{
    "model": "gemini-3.8-flash-tts",
    "input": [{
      "type": "user_input",
      "content": [{
        "type": "text",
        "text": "Have a wonderful day!",
        "annotations": [{
          "type": "speech_metadata",
          "style": "cheerful and friendly"
        }]
      }]
    }],
    "response_format": {
      "type": "audio"
    },
    "generation_config": {
      "speech_config": [
        { "voice": "Kore" }
      ]
    },
    "stream": true
  }'

오디오 출력 형식

Gemini 3.8 TTS 모델은 요청이 유너리인지 스트리밍인지에 따라 다른 기본 오디오 형식을 사용해요.

  • 유너리 요청(stream=False): 표준 RIFF 헤더가 있는 완전한 WAV(audio/wav) 오디오(24 kHz, mono, 16-bit 부호 있는 리틀엔디언 PCM) 반환. 수동으로 WAV 헤더를 앞에 붙이지 않고 디코딩된 오디오 바이트를 .wav 파일에 직접 저장할 수 있어요.
  • 스트리밍 요청(stream=True): 기본적으로 무헤더 raw Linear PCM(audio/l16) 청크(24 kHz, mono, 16-bit 부호 있는 리틀엔디언 PCM) 반환.

다른 오디오 인코딩이나 샘플 레이트를 요청하려면 response_format 안에서 mime_type과 선택적 sample_rate를 구성하세요.

형식 mime_type 값 설명
WAV (유너리 기본) "audio/wav" RIFF 헤더가 있는 무압축 WAV 파일(16-bit 부호 있는 리틀엔디언 PCM, mono, 기본 24 kHz). 유너리 요청의 기본값.
Raw PCM (L16) (스트리밍 기본) "audio/l16" 무압축, 무헤더 16-bit 부호 있는 리틀엔디언 선형 PCM 오디오(24 kHz, mono). 스트리밍 요청의 기본값.
Mu-law "audio/mulaw" 8-bit G.711 mu-law 인코딩 오디오(북미·일본 전화/IVR 시스템에서 흔히 사용).
A-law "audio/alaw" 8-bit G.711 A-law 인코딩 오디오(유럽·국제 전화 시스템에서 흔히 사용).

헤르츠 단위의 sample_rate(예: 24000, 16000, 8000)도 지정할 수 있어요.

import base64
from google import genai

client = genai.Client()

interaction = client.interactions.create(
    model="gemini-3.8-flash-tts",
    input=[{
        "type": "user_input",
        "content": [{
            "type": "text",
            "text": "Have a wonderful day!",
            "annotations": [{
                "type": "speech_metadata",
                "style": "cheerful and friendly",
            }],
        }],
    }],
    response_format={
        "type": "audio",
        "mime_type": "audio/l16",  # "audio/wav" (default), "audio/l16", "audio/mulaw", or "audio/alaw"
        "sample_rate": 24000,
    },
    generation_config={
        "speech_config": [
            {"voice": "Kore"},
        ]
    },
)

with open("out.pcm", "wb") as f:
    f.write(base64.b64decode(interaction.output_audio.data))
import * as fs from 'node:fs';
import {GoogleGenAI} from '@google/genai';

async function main() {
   const client = new GoogleGenAI({});

   const interaction = await client.interactions.create({
      model: 'gemini-3.8-flash-tts',
      input: [{
         type: 'user_input',
         content: [{
            type: 'text',
            text: 'Have a wonderful day!',
            annotations: [{
               type: 'speech_metadata',
               style: 'cheerful and friendly',
            }],
         }],
      }],
      response_format: {
         type: 'audio',
         mime_type: 'audio/l16', // 'audio/wav' (default), 'audio/l16', 'audio/mulaw', or 'audio/alaw'
         sample_rate: 24000,
      },
      generation_config: {
         speech_config: [
            { voice: 'Kore' },
         ],
      },
   });

   const audioBuffer = Buffer.from(interaction.output_audio.data, 'base64');
   fs.writeFileSync('out.pcm', audioBuffer);
}
await main();
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.8-flash-tts",
    "input": [{
      "type": "user_input",
      "content": [{
        "type": "text",
        "text": "Have a wonderful day!",
        "annotations": [{
          "type": "speech_metadata",
          "style": "cheerful and friendly"
        }]
      }]
    }],
    "response_format": {
      "type": "audio",
      "mime_type": "audio/l16",
      "sample_rate": 24000
    },
    "generation_config": {
      "speech_config": [
        { "voice": "Kore" }
      ]
    }
  }'

음성 옵션

Gemini 3.8 TTS는 음성을 선택하거나 만드는 네 가지 방법을 지원해요.

  1. 사전 제작 스튜디오 음성: 아래 표에 나열된 30개의 큐레이트 음성.
  2. 확장 음성 라이브러리: client.voices.list()(GET /v1beta/voices)로 접근할 수 있는, 언어·억양·캐릭터 아키타입에 걸친 수백 개의 추가 음성.
  3. Voice design: Google AI Studio나 POST /v1beta/voices(type="prompted")를 사용해 자연어 설명에서 커스텀 발성 페르소나 생성. 영구 voice_... ID와 CreateVoice·GetVoice의 sample_audio WAV 미리보기를 반환.
  4. Voice replication: Google AI Studio나 POST /v1beta/voices(type="replicated", 기본 영구 store=True 또는 선택적 무상태 store=False)를 사용해 참조·동의 오디오에서 화자 음성 복제.

커스텀 음성 한도와 TTL

음성 유형 저장 모드 할당량/한도 보존 기간(TTL)
상태 유지 음성 (Stateful voices) (voice_..., prompted 또는 replicated) store=True 프로젝트당 200개 음성 (prompted·replicated 음성 공유) 1년
무상태 음성 키 (Stateless voice keys) (voicekey_..., replicated) store=False 클라이언트 관리 7일

사전 제작 음성

Zephyr -- Bright Puck -- Upbeat Charon -- Informative
Kore -- Firm Fenrir -- Excitable Leda -- Youthful
Orus -- Firm Aoede -- Breezy Callirrhoe -- Easy-going
Autonoe -- Bright Enceladus -- Breathy Iapetus -- Clear
Umbriel -- Easy-going Algieba -- Smooth Despina -- Smooth
Erinome -- Clear Algenib -- Gravelly Rasalgethi -- Informative
Laomedeia -- Upbeat Achernar -- Soft Alnilam -- Firm
Schedar -- Even Gacrux -- Mature Pulcherrima -- Forward
Achird -- Friendly Zubenelgenubi -- Casual Vindemiatrix -- Gentle
Sadachbia -- Lively Sadaltager -- Knowledgeable Sulafat -- Warm

확장 음성 라이브러리와 필터링

앞 표의 30개 스튜디오 음성 외에 확장 음성 라이브러리는 언어, 지역 억양, 캐릭터 페르소나, 도메인에 걸친 수백 개의 추가 음성을 제공해요. Google AI Studio에서 전체 음성 라이브러리를 대화형으로 탐색·필터·미리듣거나 client.voices.list()(GET /v1beta/voices, google-genai 2.25.0+ / @google/genai 2.24.0+ 사용)로 프로그래매틱하게 조회할 수 있어요.

ListVoices는 커스텀 저장 음성(최신순)을 반환한 뒤 필터 기준에 맞는 사전 제작 카탈로그 음성을 반환해요. 목록 필터에 여러 값을 전달하면 그 필터의 어느 값과 일치하는 음성이 반환되고(OR), 서로 다른 필터 파라미터는 AND로 결합돼요.

파라미터 유형 설명
language_code list[str] BCP-47 언어 태그(예: ["en-US", "en-GB"]). 대소문자 무시 정확 일치.
region_code list[str] ISO 3166-1 alpha-2 또는 UN M.49 지역 코드(예: ["US", "GB"]).
accent list[str] 지역 억양 설명자(예: ["American", "British"]).
gender list[str] 지각된 성별 표현("female", "male", "neutral").
pitch list[str] 발성 피치 분류("low", "medium", "high").
persona list[str] 발성 페르소나 또는 캐릭터 아키타입(예: ["Warm, Friendly"], ["Narrator"]).
contexts (REST에서는 context) list[str] 최적 사용 도메인(예: ["Audiobook", "Conversational", "News"]).
type (Python에서는 type_) list[str] 음성 출처로 필터: "prebuilt", "prompted"(Voice design), 또는 "replicated"(Voice replication).
search str display_name과 description에 대해 대소문자 무시로 일치시키는 자유 텍스트 부분 문자열 검색.
page_size int 페이지당 최대 반환 음성 수(기본 50, 최대 1000).
page_token str response.next_page_token에서 온 토큰으로 다음 결과 페이지를 가져옴.
from google import genai

client = genai.Client()

# Filter the Voice Library by language, gender, pitch, domain context, and keyword
response = client.voices.list(
    language_code=["en-US", "en-GB"],
    gender=["female"],
    pitch=["medium", "low"],
    contexts=["Audiobook", "Conversational"],
    type_=["prebuilt"],
    search="warm",
    page_size=50,
)

for voice in response.voices or []:
    print(
        f"{voice.id} | {voice.display_name} ({voice.language_code},"
        f" {voice.accent}, {voice.gender}, pitch={voice.pitch}):"
        f" {voice.description}"
    )
import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI();

// Filter the Voice Library by language, gender, pitch, domain context, and keyword
const response = await ai.voices.list({
  language_code: ["en-US", "en-GB"],
  gender: ["female"],
  pitch: ["medium", "low"],
  contexts: ["Audiobook", "Conversational"],
  type: ["prebuilt"],
  search: "warm",
  page_size: 50,
});

for (const voice of response.voices ?? []) {
  console.log(
    `${voice.id} | ${voice.display_name} (${voice.language_code}, ${voice.accent}, ${voice.gender}, pitch=${voice.pitch}): ${voice.description}`
  );
}
curl -G "https://generativelanguage.googleapis.com/v1beta/voices" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  --data-urlencode "language_code=en-US" \
  --data-urlencode "language_code=en-GB" \
  --data-urlencode "gender=female" \
  --data-urlencode "pitch=medium" \
  --data-urlencode "context=Audiobook" \
  --data-urlencode "type=prebuilt" \
  --data-urlencode "search=warm" \
  --data-urlencode "page_size=50"

지원 언어

TTS 모델은 입력 언어를 자동 감지해요. Gemini 3.8 Flash TTS(gemini-3.8-flash-tts)는 130개 이상의 언어를, Gemini 3.8 Flash-Lite TTS(gemini-3.8-flash-lite-tts)는 100개 이상의 언어를 지원해요:

언어 Gemini 3.8 Flash TTS Gemini 3.8 Flash-Lite TTS
Acehnese (Arab script) ✔️ ✔️
Afrikaans ✔️ ✔️
Akan ✔️ ✔️
Amharic ✔️ ✔️
Armenian ✔️ ✔️
Assamese ✔️ ✔️
Awadhi ✔️ ✔️
Balinese ✔️ ✔️
Bangla ✔️ ✔️
Banjar (Arab script) ✔️ —
Banjar (Latn script) ✔️ ✔️
Bashkir ✔️ —
Basque ✔️ ✔️
Belarusian ✔️ ✔️
Bemba ✔️ —
Bhojpuri ✔️ ✔️
Bosnian ✔️ ✔️
Buginese ✔️ ✔️
Bulgarian ✔️ ✔️
Burmese ✔️ —
Cantonese ✔️ ✔️
Catalan ✔️ ✔️
Cebuano ✔️ ✔️
Central Kurdish ✔️ ✔️
Chhattisgarhi ✔️ ✔️
Chinese (Hans script) ✔️ ✔️
Chinese (Hant script) ✔️ ✔️
Crimean Tatar ✔️ —
Croatian ✔️ ✔️
Czech ✔️ ✔️
Danish ✔️ ✔️
Dutch ✔️ ✔️
Dyula ✔️ —
Dzongkha ✔️ —
Egyptian Arabic ✔️ ✔️
English ✔️ ✔️
Estonian ✔️ ✔️
Filipino ✔️ ✔️
Finnish ✔️ —
French ✔️ ✔️
Galician ✔️ ✔️
Ganda ✔️ ✔️
Georgian ✔️ ✔️
German ✔️ ✔️
Greek ✔️ ✔️
Guarani ✔️ —
Gujarati ✔️ ✔️
Haitian Creole ✔️ ✔️
Halh Mongolian ✔️ ✔️
Hausa ✔️ ✔️
Hebrew ✔️ ✔️
Hindi ✔️ ✔️
Hungarian ✔️ ✔️
Icelandic ✔️ ✔️
Igbo ✔️ —
Iloko ✔️ ✔️
Indonesian ✔️ ✔️
Iranian Persian ✔️ ✔️
Italian ✔️ ✔️
Japanese ✔️ ✔️
Javanese ✔️ ✔️
Kabyle ✔️ —
Kamba ✔️ ✔️
Kannada ✔️ ✔️
Kashmiri (Arab script) ✔️ ✔️
Kashmiri (Deva script) ✔️ ✔️
Kazakh ✔️ ✔️
Khmer ✔️ ✔️
Kikuyu ✔️ ✔️
Kinyarwanda ✔️ ✔️
Kongo ✔️ ✔️
Korean ✔️ ✔️
Kyrgyz ✔️ ✔️
Lao ✔️ ✔️
Latgalian ✔️ —
Lingala ✔️ ✔️
Lithuanian ✔️ —
Luxembourgish ✔️ —
Macedonian ✔️ ✔️
Magahi ✔️ ✔️
Maithili ✔️ ✔️
Malayalam ✔️ ✔️
Maltese ✔️ ✔️
Manipuri ✔️ ✔️
Marathi ✔️ ✔️
Minangkabau (Arab script) ✔️ ✔️
Minangkabau (Latn script) ✔️ —
Mizo ✔️ ✔️
Nepali (individual language) ✔️ ✔️
Nigerian Fulfulde ✔️ ✔️
North Azerbaijani ✔️ ✔️
Northern Sotho ✔️ ✔️
Northern Uzbek ✔️ ✔️
Norwegian Bokmål ✔️ ✔️
Norwegian Nynorsk ✔️ ✔️
Nyanja ✔️ ✔️
Occitan ✔️ —
Odia (individual language) ✔️ ✔️
Pangasinan ✔️ —
Persian (Afghanistan) ✔️ ✔️
Polish ✔️ ✔️
Portuguese ✔️ ✔️
Punjabi ✔️ ✔️
Romanian ✔️ ✔️
Russian ✔️ ✔️
Santali ✔️ ✔️
Serbian ✔️ ✔️
Sindhi ✔️ —
Sinhala ✔️ ✔️
Slovak ✔️ ✔️
Slovenian ✔️ —
Somali ✔️ —
South Azerbaijani ✔️ ✔️
Southern Pashto ✔️ ✔️
Southern Sotho ✔️ —
Spanish ✔️ ✔️
Standard Arabic (Arab script) ✔️ ✔️
Standard Arabic (Latn script) ✔️ ✔️
Standard Latvian ✔️ ✔️
Standard Malay ✔️ ✔️
Swahili (individual language) ✔️ —
Swati ✔️ —
Swedish ✔️ —
Tajik ✔️ —
Tamil ✔️ ✔️
Telugu ✔️ ✔️
Thai ✔️ —
Tigrinya ✔️ —
Tosk Albanian ✔️ —
Turkish ✔️ ✔️
Uyghur ✔️ —
Vietnamese ✔️ ✔️

지원 모델

모델 단일 화자 다중 화자 Voice design Voice replication
Gemini 3.8 Flash TTS (gemini-3.8-flash-tts) ✔️ ✔️ ✔️ ✔️
Gemini 3.8 Flash-Lite TTS (gemini-3.8-flash-lite-tts) ✔️ ✔️ ✔️ ✔️
Gemini 3.1 Flash TTS Preview ✔️ ✔️ — —
Gemini 2.5 Pro Preview TTS ✔️ ✔️ — —

언제 어떤 모델을 쓸까

두 Gemini 3.8 TTS 모델은 정확히 같은 API 스키마와 프롬프팅 형식을 공유하므로 파라미터 하나만 바꿔 전환할 수 있어요.

  • 최대 음향 충실도, 뉘앙스 있는 연기, 표현 제어가 최우선일 때 Gemini 3.8 Flash TTS(gemini-3.8-flash-tts)를 사용하세요. 스튜디오급 창작 작업, 복잡한 다중 화자 대화, 무거운 발성 폭발 태그, 어려운 발음, 지역·소수 방언, 그리고 견고한 음성·룸톤 안정성이 요구되는 장편 내레이션에 이상적이에요.
  • 빠르고 비용 효율적인 일꾼으로 gemini-3.1-flash-tts-preview를 대체할 때 Gemini 3.8 Flash-Lite TTS(gemini-3.8-flash-lite-tts)를 사용하세요. 대량 프로덕션, 대화형 음성 에이전트 파이프라인, 읽어주기 기능, 신뢰할 만한 음성 복제, 주요 언어의 일상 단일 화자 발성에 최적화돼 있어요.

마이그레이션 가이드

gemini-3.1-flash-tts-preview 또는 이전 Gemini TTS 모델에서 Gemini 3.8 TTS로 마이그레이션한다면:

  1. 턴 수준 지시를 speech_metadata로 옮기기: Gemini 3.8 TTS는 입력 텍스트를 엄격히 원문 전사본으로 취급해요. 지속적인 전달 지시(style — "whispering", "out of breath", "speaking slowly" 같은)와 화자 라벨(speaker)을 전사 텍스트에 무대 지시를 넣는 대신 구조화된 speech_metadata 어노테이션으로 옮기세요.
  2. 시점 발성 이벤트에만 앵글 브래킷 인라인 태그 사용: 순간적 비발화 발성과 일시정지를 앵글 브래킷(<laugh>, <sigh>, <cough>, <breath>, <short pause>)으로 전사본 안에 인라인으로 유지하세요. 효과음 태그(박수·쿵)는 피하고 전달 스타일은 speech_metadata.style에 두세요.
  3. 다중 화자 요청의 모든 턴에 speaker 지정: 다중 화자 요청의 모든 턴은 설정된 화자 중 하나와 일치하는 speaker를 speech_metadata에 명시적으로 포함해야 해요.
  4. Voice design으로 페르소나를 미리 설계: 여러 문단의 "Audio Profile" 또는 "Director's Notes" 블록을 Voice design으로 만든 커스텀 음성으로 대체하고, 그 voice_... ID를 최소 또는 빈 style 문자열과 함께 TTS 요청에 전달하세요.
  5. 유너리 요청의 기본 WAV(audio/wav) 출력 감안: gemini-3.1-flash-tts-preview와 이전 TTS 모델(기본적으로 무헤더 raw PCM audio/l16 반환)과 달리, Gemini 3.8 TTS는 유너리 요청에서 기본적으로 표준 RIFF 헤더가 있는 WAV 오디오(audio/wav)를 반환해요.
    • 코드가 예전에 raw PCM 바이트를 WAV 헤더로 감쌌다면(예: Python의 wave 모듈이나 ffmpeg), 수동 헤더 래퍼를 제거하고 반환된 바이트를 .wav 파일에 직접 쓰세요.
    • 파이프라인에 무헤더 raw PCM, mu-law, A-law 오디오가 필요하다면 response_format을 "audio/l16", "audio/mulaw", 또는 "audio/alaw"로 명시적으로 설정하세요. Audio output formats 참고.

프롬프팅 가이드

Gemini 3.8 TTS 모델은 입력 텍스트를 엄격히 원문(verbatim) 전사본으로 취급해요. 무대 지시가 평문에 삽입되던 이전 프리뷰 모델과 달리, Gemini 3.8 TTS는 지속적 턴 수준 지시(speech_metadata)와 시점 인라인 발성 태그를 분리해요.

스타일 필드와 인라인 태그

범위별로 연기 지시를 나누세요.

  • 턴 수준 전달(speech_metadata.style): 지속적인 전달 속성(감정·운율·전반 속도·전달 스타일, 예: "whispering", "out of breath", "muttering", "sarcastic")을 speech_metadata의 style 필드에 두세요. 턴 전반에 걸쳐 안정적인 캐릭터와 연기를 만들려면 Voice design에서 페르소나를 미리 설계하고 style은 선택적 턴 수준 조정에만 사용하세요.
  • 시점 이벤트(인라인 태그): 순간적 비발화 발성 폭발·숨·일시정지를 앵글 브래킷(<cough>, <breath>, <sigh>, <short pause>)으로 전사본 안에 인라인으로 두세요. 최고 오디오 품질을 위해 앵글 브래킷(<...>)을 사용하고, 비발화 효과음보다 인간 발성에 충실하세요.
범위 놓을 위치 예시
턴 수준 (턴 전반에 지속) speech_metadata.style "angry tone", "speaking rapidly", "out of breath", "whispers", "sarcastic"
시점 (특정 단어에서 발생) text에 인라인(<...>) "<cough> Thank you all for coming tonight! <throat-clearing> As I was saying..."

속도와 일시정지

리듬과 침묵을 세 가지 세분성 수준으로 제어할 수 있어요.

  • 구두점과 생략부호: 자연스러운 대화형 망설임에 쉼표, 대시(--), 생략부호(...)를 사용하세요.
  • 인라인 일시정지 태그: 화자가 멈춰야 하는 정확한 지점에 <short pause> 또는 <long pause>를 삽입하세요.
  • 턴 수준 속도: speech_metadata에서 "style": "speaking rapidly" 또는 "style": "speaking slowly"를 설정해 전체 턴의 발화 속도를 제어하세요.

운율과 피치

speech_metadata.style 로 턴 전반의 운율·피치·억양을 제어하세요(예: "style": "high pitch, cheerful and excited inflection" 또는 "style": "monotone and flat"). 감정이나 운율이 대화 중간에 바뀌면 대본을 각 턴마다 다른 style 값을 가진 별도 턴으로 나누세요.

강조

전사본에서 특정 단어를 대문자화하고 구두점·인라인 발성 태그와 결합해 핵심 단어에 자연스러운 발성 강세를 두세요.

This is a VERY important point!
It was a VERY long day <sigh> ... nobody listens anymore.

발성 폭발과 비언어 소리

비언어 인간 발성을 앵글 브래킷(<...>)으로 소리가 발생해야 하는 정확한 지점에 인라인으로 두세요. 권장 발성 태그:

<argh> <breath> <heavy breath> <exhales>
<cackle> <cheer> <chuckle> / <chuckles> <cough>
<cry> <gasp> <giggle> <groan>
<growl> <grunt> <grr> <hiss>
<laugh> / <laughter> <moan> <pant> <pff> / <phew>
<scream> <shout> <shriek> <sigh> / <sighs>
<sneeze> <snicker> <snort> <sob>
<throat-clearing> <tsk> <whimper> <whispers> / <whispering>
<yawn> <short pause> <long pause>

참고: 전사본이 비영어 언어라면 최상의 결과를 위해 계속 영어 인라인 태그를 사용하세요.

백채널과 겹치는 발성

다중 화자 대화에서 리스너 반응을 화자 턴 안의 파이프 문자(|reaction|)로 감싸면, 반응마다 별도 턴으로 쪼개지 않고 자연스러운 백채널이나 겹치는 발성을 만들 수 있어요.

  • 짧은 백채널 교환: 활동 화자의 턴 안에 짧은 리스너 반응(|oh hmm|, |oh really?|, |absolutely|)을 겹쳐 넣으세요.
    • 턴 1 (화자 A): "So the launch is Thursday |oh hmm| Are we actually ready?"
    • 턴 2 (화자 B): "Ready enough |oh really?| The last blocker cleared this morning."
    • 턴 3 (화자 A): "Then let's ship it |absolutely| and watch the dashboards."
  • 겹치고 인터리브된 발성: 여러 파이프 세그먼트로 두 화자 사이의 동시 또는 인터리브 발성을 시뮬레이션하세요(gemini-3.8-flash-tts에서 가장 잘 동작).
    • 동시 카운트다운/코러스: "Let's surprise him on three |ok| ready?" 다음에 "one. two. three. |happy| happy |birthday| birthday!"
    • 완전 화자 겹침: "Hello |oh| there |my| it |goodness| must |gracious| be |would| almost |you| time |look| for |at that| dinner"

세대 간 일관성과 피해야 할 것

턴 전반에서 발성 정체성을 안정적으로 유지하려면 다음 지침을 따르세요.

  • 긴 스타일 블록 대신 Voice design에서 페르소나를 미리 설계: 이전 모델에서 이어온 장문 "Audio Profile" 문단과 다중 글머리 "Director's Notes"는 음성 드리프트의 가장 흔한 원인이에요. Voice design에서 영구 커스텀 voice_... 페르소나를 만들고 그 음성 ID를 TTS 호출에 전달하세요.
  • 안정성을 위해 음성 참조에 의존(메타 지시 생략): Gemini 3.8 TTS 모델은 오디오 참조를 먼저 고정하도록 학습됐어요. 모델에게 음성을 고정하라고 지시하는 내용("do not switch speaker identity", "maintain identical timbre" 같은)은 포함하지 마세요 — 추가 프롬프트 텍스트가 드리프트를 늘립니다. 불필요한 스타일 지시를 버리고 음성 참조가 제공하는 안정점 주변에서 모델이 자연스럽게 변하게 두세요.
  • style에서 불변의 화자 특성을 바꾸려 하지 마세요: speech_metadata.style에 나이·성별·이름·영구 억양 변경을 두지 마세요. 대신 확장 음성 라이브러리에서 지역 음성을 고르거나 Voice design으로 만들어요.

권장 워크플로

  1. 캐릭터를 한 번 구축: Voice design에서 캐릭터를 만들거나, 확장 음성 라이브러리에서 대상 언어·페르소나에 맞는 지역 음성을 선택하세요.
  2. 비유창성이 있는 자연스러운 구어 전사본 작성: 최대 자연스러움을 위해 text를 실제 구어 전사본으로 쓰세요 — 자연스러운 대화 비유창성과 망설임 포함(예: "Oh uh yeah I think... hm, so that's interesting").
  3. 먼저 평문 TTS 테스트: 빈 style 필드로 전사본을 먼저 합성하세요 — 대부분 요청은 style 지시가 전혀 필요 없어요.
  4. 조정용으로만 짧은 style 프롬프트 추가: 특정 전달 조정이 필요한 턴에만 간결한 style 문자열(예: "casual, friendly" 또는 "muttering, then reassuring")을 추가하고, 일관된 기준선이 필요하면 동일한 짧은 문자열을 턴에 걸쳐 재사용하세요.

다중 턴 대화와 음성 에이전트

실시간 대화형 음성 에이전트나 다중 턴 애플리케이션을 만들 때:

  • LLM 텍스트 청크가 도착할 때 턴당 TTS 호출을 한 번 하세요.
  • 턴 전반에 걸쳐 화자 정체성을 구성된 voice(사전 제작, 설계 voice_..., 또는 복제 voice_.../voicekey_...)가 전달하게 하세요 — 매 턴 긴 캐릭터 페르소나를 다시 보내지 마세요.
  • 턴별 style 필드는 비워 두거나 대화 전체에 하나의 짧은 상수 문자열(예: "casual, friendly")을 보내세요.
  • 더 강한 스타일 프롬프트를 사용하기보다 긴 에이전트 응답을 더 짧은 턴으로 나누세요.

제한 사항

  • TTS 모델은 텍스트 전용 입력을 받고 오디오 전용 출력을 생성해요.
  • 단일 요청 다중 화자 생성(speech_config.speakers)은 사전 제작 음성으로 최대 2명 화자를 지원해요. 다중 캐릭터 대화에서 커스텀 설계(voice_...) 또는 복제(voice_.../voicekey_...) 음성을 결합하려면 각 화자의 턴을 개별적으로 합성하세요. 유너리 요청이 기본적으로 44바이트 RIFF 헤더가 있는 audio/wav를 반환하므로, 24kHz PCM 오디오 프레임을 연결하기 전에 raw PCM({"type": "audio", "mime_type": "audio/l16"})을 요청하거나 각 턴에서 WAV 헤더를 제거하세요.
  • 커스텀 음성 저장 한도와 TTL:
    • 상태 유지 음성(store=True, prompted 또는 replicated): 프로젝트당 최대 200개 음성, 1년 TTL.
    • 무상태 음성 키(store=False, voicekey_...): 7일 TTL.
  • 지원 언어 섹션에서 언어 커버리지를 검토하세요.

다음 단계

더 알아보기 (Learn more)

이 페이지는 Interactions API(interactions.create, speech_config)를 사용한 버전이에요. GenerateContent API 버전은 generate-content/speech-generation 문서에 있어요. 자동 언어 감지와 턴 수준 speech_metadata + 인라인 발성 태그 조합이 Gemini 3.8 TTS의 핵심이에요. 커스텀 페르소나는 voice-design, 복제는 voice-replication, 실시간 양방향 오디오는 live API 문서를 이어서 보면 좋아요.