음성 복제

음성 복제 (Voice replication)

짧은 오디오 샘플로 화자의 음성 특성을 복제할 수 있는 기능이에요. Gemini API의 Voices 엔드포인트(POST /v1beta/voices)를 사용해요. 복제된 음성을 만들고 generateContent로 음성을 합성하는 방법을 살펴볼게요.

출처: 원문

본문

음성 복제는 Gemini API Voices 엔드포인트(POST /v1beta/voices)를 사용해 짧은 오디오 샘플에서 화자의 음성 특성을 복제할 수 있게 해줘요. Gemini 3.8 Flash TTS(gemini-3.8-flash-tts)와 Gemini 3.8 Flash-Lite TTS(gemini-3.8-flash-lite-tts) 모두 음성 복제를 지원해요.

복제, 동의 확인, 복제된 음성 오디션을 가장 빠르게 하는 방법은 Google AI Studio의 대화형 Voice Replication 경험이에요. 브라우저에서 레퍼런스와 동의 클립을 직접 녹음하거나 업로드하고, 음성을 미리 보며, 결과 voice_... ID를 애플리케이션 코드에 바로 복사할 수 있어요.

Google AI Studio에서 사용해 볼 수 있어요.

음성 복제 워크플로

상태 저장 vs 무상태 저장 모드

음성 복제는 voices.create(POST /v1beta/voices) 호출 시 두 가지 저장 모드를 지원하며, 기본은 상태 저장이에요:

  • 상태 저장 (store=True, 권장 기본값): Google이 검증된 음성 프로필을 프로젝트에 저장하고, 가볍고 영구적인 voice_id(replicated_voice.id, voice_abc123... 같은)를 반환해요. 이 voice_id를 요청 간에 전달하고 voices.list(), voices.get(), voices.delete()로 관리할 수 있어요.
  • 무상태 클라이언트 관리 키 (store=False, 선택): 생체 음성 프로필의 서버 측 저장이 전혀 필요 없는 워크로드에는 store=False를 설정하세요. API가 암호화된 자체 포함 voice_key(replicated_voice.key, voicekey_...로 시작)를 반환하며, 애플리케이션이 로컬에 저장했다가 합성 요청에 직접 전달해요.
저장 모드 식별자 프로젝트 한도 보존 (TTL)
상태 저장 음성 ( store=True ) voice_... 프로젝트당 200개 음성 (프롬프트·복제 음성 공유) 1년
무상태 음성 키 ( store=False ) voicekey_... 클라이언트 관리 7일

오디오와 동의 요구사항

모든 CreateVoice 복제 요청에는 같은 성인 화자의 실제 인간 오디오 녹음 두 개가 필요해요 (24kHz 모노 16비트 WAV 권장):

  1. 레퍼런스 오디오 (source_audio): 복제하려는 화자의 깨끗하고 자연스러운 음성 10~30초 클립.
  2. 동의 오디오 (consent_audio): 같은 화자가 지원 언어 중 하나로 필수 동의 문구를 또렷이 낭독하는 녹음 (예: 영어):

"I am the owner of this voice and I consent to Google using this voice to create a synthetic voice model."

복제 음성 만들기 (상태 저장 기본)

Google GenAI SDK(google-genai 2.25.0+ / @google/genai 2.24.0+)나 REST API를 store=True로 사용해 프로젝트에 복제 음성 프로필을 만들고 저장하세요:

Python

import base64
from google import genai

client = genai.Client()

with open("reference_speaker.wav", "rb") as f:
    source_b64 = base64.b64encode(f.read()).decode("utf-8")

with open("speaker_consent.wav", "rb") as f:
    consent_b64 = base64.b64encode(f.read()).decode("utf-8")

# Create a persistent replicated voice (store=True)
replicated_voice = client.voices.create(
    store=True,
    voice={
        "model": "gemini-3.8-flash-tts",
        "type": "replicated",
        "display_name": "Custom Replicated Speaker",
        "replicated": {
            "source_audio": {
                "mime_type": "audio/wav",
                "data": source_b64,
            },
            "consent_audio": {
                "mime_type": "audio/wav",
                "data": consent_b64,
            },
        },
    },
)

print(f"Created voice ID: {replicated_voice.id}")

JavaScript

import * as fs from "node:fs";
import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI();

const sourceB64 = fs.readFileSync("reference_speaker.wav").toString("base64");
const consentB64 = fs.readFileSync("speaker_consent.wav").toString("base64");

// Create a persistent replicated voice (store: true)
const replicatedVoice = await ai.voices.create({
  store: true,
  voice: {
    model: "gemini-3.8-flash-tts",
    type: "replicated",
    display_name: "Custom Replicated Speaker",
    replicated: {
      source_audio: {
        mime_type: "audio/wav",
        data: sourceB64,
      },
      consent_audio: {
        mime_type: "audio/wav",
        data: consentB64,
      },
    },
  },
});

console.log(`Created voice ID: ${replicatedVoice.id}`);

REST

SOURCE_B64=$(base64 -w 0 reference_speaker.wav)
CONSENT_B64=$(base64 -w 0 speaker_consent.wav)

curl "https://generativelanguage.googleapis.com/v1beta/voices" \
  -H "x-goog-api-key: *** \
  -H "Content-Type: application/json" \
  -X POST \
  -d "{
    \"store\": true,
    \"voice\": {
      \"model\": \"gemini-3.8-flash-tts\",
      \"type\": \"replicated\",
      \"display_name\": \"Custom Replicated Speaker\",
      \"replicated\": {
        \"source_audio\": {
          \"mime_type\": \"audio/wav\",
          \"data\": \"$SOURCE_B64\"
        },
        \"consent_audio\": {
          \"mime_type\": \"audio/wav\",
          \"data\": \"$CONSENT_B64\"
        }
      }
    }
  }"

복제 음성으로 음성 합성하기

generateContent를 호출할 때 반환된 id(voice_...)를 voiceConfig.voice에 전달하세요:

Python

from google import genai

client = genai.Client()

response = client.models.generate_content(
    model="gemini-3.8-flash-tts",
    contents=[{
        "role": "user",
        "parts": [{
            "text": (
                "Hello! This audio was synthesized using a replicated"
                " speaker voice."
            ),
            "speech_metadata": {"style": "warm and conversational"},
        }],
    }],
    config={
        "response_modalities": ["AUDIO"],
        "speech_config": {
            "voice_config": {"voice": replicated_voice.id}
        },
    },
)

audio_bytes = response.candidates[0].content.parts[0].inline_data.data
with open("replicated_speech.wav", "wb") as f:
    f.write(audio_bytes)

JavaScript

import * as fs from "node:fs";
import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI();

const response = await ai.models.generateContent({
  model: "gemini-3.8-flash-tts",
  contents: [{
    role: "user",
    parts: [{
      text: "Hello! This audio was synthesized using a replicated speaker voice.",
      speechMetadata: { style: "warm and conversational" },
    }],
  }],
  config: {
    responseModalities: ["AUDIO"],
    speechConfig: {
      voiceConfig: { voice: replicatedVoice.id },
    },
  },
});

const data = response.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
if (data) {
  fs.writeFileSync("replicated_speech.wav", Buffer.from(data, "base64"));
}

REST

curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash-tts:generateContent" \
  -H "x-goog-api-key: *** \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{
    "contents": [{
      "role": "user",
      "parts": [{
        "text": "Hello! This audio was synthesized using a replicated speaker voice.",
        "speech_metadata": {
          "style": "warm and conversational"
        }
      }]
    }],
    "generationConfig": {
      "responseModalities": ["AUDIO"],
      "speechConfig": {
        "voiceConfig": {
          "voice": "voice_YOUR_REPLICATED_VOICE_ID"
        }
      }
    }
  }'

저장된 복제 음성 관리

store=True로 생성하면 복제 음성을 Voices API에서 나열·필터링·검사·삭제할 수 있어요 (모든 필터 파라미터는 Extended Voice Library와 필터링 참고):

Python

from google import genai

client = genai.Client()

# List stored replicated voices in your project
response = client.voices.list(type_=["replicated"])
for voice in response.voices or []:
    print(voice.id, voice.display_name, voice.type)

# Retrieve a specific voice by ID
voice_details = client.voices.get(id=replicated_voice.id)

# Delete a stored replicated voice
client.voices.delete(id=replicated_voice.id)

JavaScript

import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI();

// List stored replicated voices in your project
const response = await ai.voices.list({ type: ["replicated"] });
for (const voice of response.voices ?? []) {
  console.log(voice.id, voice.display_name, voice.type);
}

// Retrieve a specific voice by ID
const voiceDetails = await ai.voices.get(replicatedVoice.id);

// Delete a stored replicated voice
await ai.voices.delete(replicatedVoice.id);

REST

# List stored replicated voices in your project
curl -G "https://generativelanguage.googleapis.com/v1beta/voices" \
  -H "x-goog-api-key: *** \
  --data-urlencode "type=replicated"

# Retrieve a specific voice by ID
curl "https://generativelanguage.googleapis.com/v1beta/voices/voice_YOUR_REPLICATED_VOICE_ID" \
  -H "x-goog-api-key: ***

# Delete a stored replicated voice
curl -X DELETE "https://generativelanguage.googleapis.com/v1beta/voices/voice_YOUR_REPLICATED_VOICE_ID" \
  -H "x-goog-api-key: ***

옵션: 무상태 클라이언트 관리 음성 키 ( store=False )

애플리케이션이 음성 프로필의 서버 측 저장을 전혀 원하지 않으면 복제 음성 생성 시 store=False를 설정하세요. API가 암호화된 voice_key(replicated_voice.key, voicekey_...로 시작)를 반환하며, 클라이언트 측에 저장하고 voiceConfig.voice에 직접 전달해요:

Python

import base64
from google import genai

client = genai.Client()

with open("reference_speaker.wav", "rb") as f:
    source_b64 = base64.b64encode(f.read()).decode("utf-8")

with open("speaker_consent.wav", "rb") as f:
    consent_b64 = base64.b64encode(f.read()).decode("utf-8")

# Create a stateless client-managed voice key (store=False)
replicated_voice = client.voices.create(
    store=False,
    voice={
        "model": "gemini-3.8-flash-tts",
        "type": "replicated",
        "replicated": {
            "source_audio": {"mime_type": "audio/wav", "data": source_b64},
            "consent_audio": {"mime_type": "audio/wav", "data": consent_b64},
        },
    },
)

# Pass replicated_voice.key ("voicekey_...") directly in voice_config
response = client.models.generate_content(
    model="gemini-3.8-flash-tts",
    contents=[{
        "role": "user",
        "parts": [{
            "text": "Hello! This audio uses a stateless client-managed voice key.",
            "speech_metadata": {"style": "warm and conversational"},
        }],
    }],
    config={
        "response_modalities": ["AUDIO"],
        "speech_config": {
            "voice_config": {"voice": replicated_voice.key}
        },
    },
)

JavaScript

import * as fs from "node:fs";
import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI();

const sourceB64 = fs.readFileSync("reference_speaker.wav").toString("base64");
const consentB64 = fs.readFileSync("speaker_consent.wav").toString("base64");

// Create a stateless client-managed voice key (store: false)
const replicatedVoice = await ai.voices.create({
  store: false,
  voice: {
    model: "gemini-3.8-flash-tts",
    type: "replicated",
    replicated: {
      source_audio: { mime_type: "audio/wav", data: sourceB64 },
      consent_audio: { mime_type: "audio/wav", data: consentB64 },
    },
  },
});

// Pass replicatedVoice.key ("voicekey_...") directly in voiceConfig
const response = await ai.models.generateContent({
  model: "gemini-3.8-flash-tts",
  contents: [{
    role: "user",
    parts: [{
      text: "Hello! This audio uses a stateless client-managed voice key.",
      speechMetadata: { style: "warm and conversational" },
    }],
  }],
  config: {
    responseModalities: ["AUDIO"],
    speechConfig: {
      voiceConfig: { voice: replicatedVoice.key },
    },
  },
});

REST

SOURCE_B64=$(base64 -w 0 reference_speaker.wav)
CONSENT_B64=$(base64 -w 0 speaker_consent.wav)

curl "https://generativelanguage.googleapis.com/v1beta/voices" \
  -H "x-goog-api-key: *** \
  -H "Content-Type: application/json" \
  -X POST \
  -d "{
    \"store\": false,
    \"voice\": {
      \"model\": \"gemini-3.8-flash-tts\",
      \"type\": \"replicated\",
      \"replicated\": {
        \"source_audio\": {\"mime_type\": \"audio/wav\", \"data\": \"$SOURCE_B64\"},
        \"consent_audio\": {\"mime_type\": \"audio/wav\", \"data\": \"$CONSENT_B64\"}
      }
    }
  }"

언어별 지원 동의 문구

동의 오디오는 30개 지원 언어 로케일 중 하나로 정확한 문구를 또렷이 낭독해야 해요:

언어 로케일 ( lang_id ) 원문 동의 문구
Arabic ar-XA أنا مالك هذا الصوت وأوافق على أن تستخدم Google هذا الصوت لإنشاء نموذج صوتي اصطناعي.
Bengali bn-IN আমি এই ভয়েসের মালিক এবং আমি একটি সিন্থেটিক ভয়েস মডেল তৈরি করতে এই ভয়েস ব্যবহার করে Google-এর সাথে সম্মতি দিচ্ছি।
Chinese (Simplified) zh-CN 我是此声音的拥有者并授权谷歌使用此声音创建语音合成模型
Dutch nl-NL Ik ben de eigenaar van deze stem en ik geef Google toestemming om deze stem te gebruiken om een synthetisch stemmodel te maken.
English (US) en-US I am the owner of this voice and I consent to Google using this voice to create a synthetic voice model.
English (UK) en-GB I am the owner of this voice and I consent to Google using this voice to create a synthetic voice model.
English (India) en-IN I am the owner of this voice and I consent to Google using this voice to create a synthetic voice model.
English (Australia) en-AU I am the owner of this voice and I consent to Google using this voice to create a synthetic voice model.
French (France) fr-FR Je suis le propriétaire de cette voix et j'autorise Google à utiliser cette voix pour créer un modèle de voix synthétique.
French (Canada) fr-CA Je suis le propriétaire de cette voix et j'autorise Google à utiliser cette voix pour créer un modèle de voix synthétique.
German de-DE Ich bin der Eigentümer dieser Stimme und bin damit einverstanden, dass Google diese Stimme zur Erstellung eines synthetischen Stimmmodells verwendet.
Gujarati gu-IN હું આ વોઈસનો માલિક છું અને સિન્થેટિક વોઈસ મોડલ બનાવવા માટે આ વોઈસનો ઉપયોગ કરીને google ને હું સંમતિ આપું છું
Hindi hi-IN मैं इस आवाज का मालिक हूं और मैं सिंथेटिक आवाज मॉडल बनाने के लिए Google को इस आवाज का उपयोग करने की सहमति देता हूं
Indonesian id-ID Saya pemilik suara ini dan saya menyetujui Google menggunakan suara ini untuk membuat model suara sintetis.
Italian it-IT Sono il proprietario di questa voce e acconsento che Google la utilizzi per creare un modello di voce sintetica.
Japanese ja-JP 私はこの音声の所有者であり、Googleがこの音声を使用して音声合成モデルを作成することを承認します。
Kannada kn-IN ನಾನು ಈ ಧ್ವನಿಯ ಮಾಲಿಕ ಮತ್ತು ಸಂಶ್ಲೇಷಿತ ಧ್ವನಿ ಮಾದರಿಯನ್ನು ರಚಿಸಲು ಈ ಧ್ವನಿಯನ್ನು ಬಳಸಿಕೊಂಡುಗೂಗಲ್ ಗೆ ನಾನು ಸಮ್ಮತಿಸುತ್ತೇನೆ.
Korean ko-KR 나는 이 음성의 소유자이며 구글이 이 음성을 사용하여 음성 합성 모델을 생성할 것을 허용합니다.
Malayalam ml-IN ഈ ശബ്ദത്തിന്റെ ഉടമ ഞാനാണ്, ഒരു സിന്തറ്റിക് വോയ്സ് മോഡൽ സൃഷ്ടിക്കാൻ ഈ ശബ്ദം ഉപയോഗിക്കുന്നതിന് ഞാൻ Google-ന് സമ്മതം നൽകുന്നു.
Marathi mr-IN मी या आवाजाचा मालक आहे आणि सिंथेटिक व्हॉइस मॉडेल तयार करण्यासाठी हा आवाज वापरण्यासाठी मी Google ला संमती देतो
Polish pl-PL Jestem właścicielem tego głosu i wyrażam zgodę na wykorzystanie go przez Google w celu utworzenia syntetycznego modelu głosu.
Portuguese (Brazil) pt-BR Eu sou o proprietário desta voz e autorizo o Google a usá-la para criar um modelo de voz sintética.
Russian ru-RU Я являюсь владельцем этого голоса и даю согласие Google на использование этого голоса для создания модели синтетического голоса.
Spanish (Spain) es-ES Soy el propietario de esta voz y doy mi consentimiento para que Google la utilice para crear un modelo de voz sintética.
Spanish (US) es-US Soy el propietario de esta voz y doy mi consentimiento para que Google la utilice para crear un modelo de voz sintética.
Tamil ta-IN நான் இந்த குரலின் உரிமையாளர் மற்றும் செயற்கை குரல் மாதிரியை உருவாக்க இந்த குரலை பயன்படுத்த குகல்க்கு நான் ஒப்புக்கொள்கிறேன்.
Telugu te-IN నేను ఈ వాయిస్ యజమానిని మరియు సింతటిక్ వాయిస్ మోడల్ ని రూపొందించడానికి ఈ వాయిస్ ని ఉపయోగించడానికి googleకి నేను సమ్మతిస్తున్నాను.
Thai th-TH ฉันเป็นเจ้าของเสียงนี้ และฉันยินยอมให้ Google ใช้เสียงนี้เพื่อสร้างแบบจำลองเสียงสังเคราะห์
Turkish tr-TR Bu sesin sahibi benim ve Google'ın bu sesi kullanarak sentetik bir ses modeli oluşturmasına izin veriyorum.
Vietnamese vi-VN Tôi là chủ sở hữu giọng nói này và tôi đồng ý cho Google sử dụng giọng nói này để tạo mô hình giọng nói tổng hợp.

레퍼런스 오디오 녹음 모범 사례

  • 조용한 환경에서 녹음: 방의 잔향, 배경 소음, 음악, 겹치는 목소리를 최소화하세요.
  • 녹음 조건 일치: source_audio와 consent_audio를 같은 마이크·같은 음향 환경에서 녹음해 화자 검증 검사가 안정적으로 성공하게 하세요.
  • 24kHz 모노 WAV로 변환: 최상의 결과를 위해 인코딩 전에 입력 오디오를 24kHz 모노 16비트 PCM WAV로 리샘플링하세요.

다음으로

  • 텍스트 설명으로 커스텀 페르소나를 만드는 방법은 Voice design에서.
  • 턴 수준 스타일링, 인라인 태그, 다중 화자 대화는 Text-to-speech 가이드에서.

더 알아보기 (Learn more)