
감정과 속도를 담은 역동적인 오디오 구축하기: Gemini 3.1 Flash TTS, Angular 및 Firebase Cloud
요약
Gemini 3.1 Flash TTS Preview 모델을 활용하여 감정과 속도가 조절된 역동적인 오디오를 생성하는 애플리케이션 구축 가이드를 제공합니다. Angular, Firebase Cloud Functions, Vertex AI를 결합하여 이미지 분석부터 오디오 스트리밍까지 이어지는 전체 워크플로우를 설명합니다.
핵심 포인트
- Gemini 3.1 Flash TTS의 Audio tags를 통한 감정 및 속도 제어
- Angular와 Firebase Cloud Functions를 이용한 오디오 스트리밍 구현
- Vertex AI를 활용한 엔터프라이즈급 AI 서비스 접근 방식
- 이미지 분석 및 생성된 텍스트의 오디오 변환 파이프라인 구축
Google은 Gemini API, Vertex AI의 Gemini, 그리고 Gemini AI Studio를 위한 AI 오디오 생성용 Gemini 3.1 Flash TTS Preview 모델을 출시했습니다. 이 모델은 표현력이 풍부한 인간의 감정, 속도 및 스타일을 나타내기 위한 새로운 Audio tags 기능을 도입했습니다.
이 애플리케이션은 Firebase AI Logic을 탐색하여 업로드된 이미지를 분석하고 추천 사항, 설명, 대체 태그 및 잘 알려지지 않은 사실(obscure fact)을 생성합니다. 이 잘 알려지지 않은 사실은 Firebase Cloud Function으로 전송되어 Gemini TTS 모델을 사용하여 오디오를 생성합니다. Cloud Function은 Angular 애플리케이션으로 스트림을 반환하며, 애플리케이션은 이를 Blob URL 객체로 변환합니다. 오디오 플레이어는 해당 URL을 소스(source)로 설정하며, 사용자는 재생(Play) 버튼을 클릭하여 스트림을 재생할 수 있습니다.
이 블로그 포스트에서 저는 애플리케이션을 Gemini 3.1 Flash TTS Preview 모델을 사용하도록 마이그레이션하고, 장면(scene), 감정(emotion), 속도(pace)를 입력할 수 있는 Angular 시그널 폼(signal form)을 생성합니다. 그런 다음, Angular 애플리케이션은 폼 값과 잘 알려지지 않은 사실을 Firebase Cloud Function에 제공하여 GenAI TypeScript SDK를 사용하여 표현력이 풍부한 목소리를 생성합니다.
사전 요구 사항 (Prerequisites)
프로젝트의 기술 스택:
- Angular 21: 2026년 5월 기준 최신 버전.
- Node.js LTS: 2026년 5월 기준 LTS 버전.
- Firebase Remote Config: 동적 파라미터 관리를 위함.
- Firebase Cloud Functions: 프론트엔드에서 호출될 때 표현력이 풍부한 인간의 목소리를 생성하기 위함.
- Firebase Local Emulator Suite:
http://localhost:5001에서 로컬로 함수를 테스트하기 위함. - Gemini in Vertex AI: 비디오를 생성하고 Firebase Cloud Storage에 저장하기 위함.
공용 Google AI Studio API는 제 지역(홍콩)에서 제한되어 있습니다. 하지만 Vertex AI (Google Cloud)는 이곳에서 안정적으로 작동하는 엔터프라이즈 액세스를 제공하므로, 이번 데모를 위해 Vertex AI를 선택했습니다.
npm i -g firebase-tools
npm을 사용하여 firebase-tools를 전역(globally)으로 설치합니다.
firebase logout
firebase login
적절한 Firebase 인증을 수행하기 위해 Firebase에서 로그아웃한 후 다시 로그인합니다.
firebase init
firebase init을 실행하고 안내에 따라 Firebase Cloud Functions, Firebase Local Emulator Suite, Firebase Cloud Storage 및 Firebase Remote Config를 설정합니다.
기존 프로젝트가 있거나 여러 프로젝트가 있는 경우, 명령줄에서 프로젝트 ID를 지정할 수 있습니다.
firebase init --project <PROJECT_ID>
두 경우 모두 Firebase CLI가 firebase-admin 및 firebase-functions 종속성 (dependencies)을 자동으로 설치합니다.
설정 단계를 완료하면 Firebase 도구가 functions emulator, functions, storage rules 파일, remote config 템플릿, 그리고 .firebaserc 및 firebase.json과 같은 설정 파일들을 생성합니다.
- Angular 종속성 (dependency)
npm i firebase
Angular 애플리케이션은 Firebase 앱을 초기화하고, remote config를 로드하며, 비디오 생성을 위해 Firebase Cloud Functions를 호출하기 위해 firebase 종속성이 필요합니다.
- Firebase 종속성 (dependencies)
npm i @cfworker/json-schema @google/genai @modelcontextprotocol/sdk
Vertex AI에서 Gemini에 액세스하려면 위의 종속성들을 설치하세요. @google/genai는 @cfworker/json-schema 및 @modelcontextprotocol/sdk에 의존합니다. 이것들이 없으면 Cloud Functions를 시작할 수 없습니다.
프로젝트 설정이 완료되었으므로, 프론트엔드(frontend)와 백엔드(backend)가 어떻게 통신하는지 살펴보겠습니다.
아키텍처 (Architecture)
사용자가 Angular 애플리케이션에서 이미지를 업로드하면, Gemini 3.1 Flash Lite Preview 모델에 이미지를 개선하기 위한 몇 가지 권장 사항, 설명 및 대체 태그를 생성하도록 프롬프트(prompt)를 보냅니다. 또한 사용자는 동일한 모델과 Google Search 도구를 사용하여 이미지와 관련된 잘 알려지지 않은 사실을 찾습니다.
사용자가 실험적인 신호 형식으로 장면(scene), 감정(emotion), 속도(pace)를 입력합니다. 사용자가 오디오 생성(generate audio) 버튼을 클릭하면, Angular 애플리케이션은 폼(form) 값과 모호한 사실(obscure fact)을 Firebase Cloud Function으로 전송하여 GenAI TypeScript SDK와 Gemini 3.1 Flash TTS Preview 모델을 사용해 표현력이 풍부한 목소리를 생성합니다.
Gemini 3.1 Flash TTS Preview 모델의 제한 사항
- 모델은 텍스트 입력만 수락하고 오디오 출력만 생성할 수 있습니다.
- 컨텍스트 윈도우(Context window)는 32K 토큰입니다.
- TTS는 스트리밍(streaming)을 지원하지 않습니다.
- 지원되는 언어는 https://ai.google.dev/gemini-api/docs/speech-generation#languages에서 확인할 수 있습니다. 저의 모국어인 광둥어(Cantonese)는 현재 지원되지 않습니다.
Firebase 통합
1. 환경 변수(Environment Variables) 설정
Firebase 프로젝트에서 환경 변수를 정의하면 함수가 Google Cloud 프로젝트의 리전(region), Firebase Cloud Function 위치, 그리고 필요한 TTS 모델을 인식할 수 있습니다.
.env.example
GOOGLE_CLOUD_LOCATION="global"
GOOGLE_FUNCTION_LOCATION="asia-east2"
GEMINI_TTS_MODEL_NAME="gemini-3.1-flash-tts-preview"
...
| 변수 | 설명 |
|---|---|
| GOOGLE_CLOUD_LOCATION | Google Cloud 프로젝트의 리전입니다. Firebase 프로젝트가 최신 Gemini 3.1 Flash TTS preview 모델에 접근할 수 있도록 global을 선택했습니다. |
| ... |
http://localhost:4200은 제 로컬 Angular 애플리케이션의 호스트 및 포트 번호입니다.
2. 환경 변수 검증
Cloud Function이 AI 호출을 진행하기 전에, 모든 필요한 환경 변수가 존재하는지 확인하는 것이 매우 중요합니다. 저는 TTS 모델 이름, Google Cloud 프로젝트 ID, 위치와 같은 환경 변수를 검증하기 위해 AUDIO_CONFIG IIFE (즉시 실행 함수 표현식, Immediately Invoked Function Expression)를 구현했습니다.
import logger from "firebase-functions/logger";
export function validate(value: string | undefined, fieldName: string, missingKeys: string[]) {
...
export const AUDIO_CONFIG = (() => {
logger.info("AUDIO_CONFIG initialization: Loading environment variables and validating configuration...");
...
저는 2026년 5월 기준으로 Node 24를 사용하고 있습니다. Node 20부터는 .env 파일에서 환경 변수 (environment variables)를 로드하는 내장 함수인 process.loadEnvFile을 사용할 수 있습니다.
env.ts에서 try-catch 블록은 .env 파일로부터 환경 변수를 로드하려고 시도합니다.
try {
process.loadEnvFile();
} catch {
...
src/index.ts에서는 다른 파일과 라이브러리를 임포트 (import)하기 전에 첫 번째 문장으로 env.ts를 임포트합니다.
import "./env";
... 기타 임포트 문 ...
만약 process.loadEnvfile을 지원하지 않는 Node 버전을 사용 중이라면, 환경 변수를 로드하기 위해 dotenv를 설치하는 것이 대안입니다.
npm i dotenv
import dotenv from "dotenv";
dotenv.config();
Firebase는 GCLOUD_PROJECT 변수를 제공하므로, 이 변수는 .env 파일에 정의하지 않습니다.
missingKeys 배열이 비어 있지 않으면, AUDIO_CONFIG는 누락된 모든 변수 이름을 나열하며 에러를 발생시킵니다. 검증이 성공하면 genAIOptions와 model이 반환됩니다. genAIOptions는 GoogleGenAI를 초기화하는 데 사용되며, model은 선택된 TTS 모델 이름입니다.
3. 프롬프트 입력값 정제 (Sanitize the Prompt Inputs)
Cloud Function은 오디오 프롬프트를 구성하기 전에 장면 (scene)과 스크립트 (transcript)를 정제합니다.
sanitizeScene 함수는 줄 바꿈 문자 (newline character, '\n')를 '\n'으로 이스케이프 (escaping) 처리하여 장면을 수용합니다. 줄 바꿈 문자는 빈 줄을 만들고 종종 블록의 끝을 나타냅니다. 이 정제 과정을 통해 장면은 효과적으로 하나의 연속된 데이터 라인으로 평탄화 (flatten)되며, LLM의 마크다운 (Markdown) 파서 (parser)는 이를 하나의 안전한 단락으로 인식합니다. 또한 정제 과정에서 장면에 삽입된 모든 마크다운 헤더 (Markdown headers)를 제거합니다.
function sanitizeScene(text: string): string {
return (text || "").trim().replace(/\r?\n/g, "\\n").replace(/^[#\s]+/gm, "");
}
sanitizeTranscript 함수는 주입된 모든 Markdown 헤더와 삼중 따옴표를 제거하여 전사(transcript)를 정제합니다.
function sanitizeTranscript(text: string): string {
return (text || "").trim().replace(/^#+/gm, "").replace(/"""/g, '"');
}
4. 오디오 프롬프트(Audio Prompt) 구축하기
AudioPrompt 인터페이스는 장면(scene), 감정(emotion), 속도(pace), 전사(transcript), 그리고 음성 옵션(voice option)을 캡슐화하여 오디오의 위치, 오디오 태그, 텍스트 및 페르소나를 설정합니다.
export type AudioPrompt = {
scene: string;
emotion: string;
...
SCENE_DICTIONARY는 장면들의 배열입니다. 사용자가 장면을 제공하지 않으면, 배열에서 무작위로 장면이 선택됩니다.
export const SCENE_DICTIONARY = [
"A dimly lit, dusty library filled with ancient leather-bound books.\n" +
"The air is thick with history. A scholarly archivist is leaning closely into a warm, vintage ribbon microphone.\n"
+
...
저는 고급 오디오 프롬프트를 구성하기 위해 buildAudioPrompt 함수를 정의합니다.
감정이 정의되면 태그는 [<emotion>]이 됩니다. 속도가 정의되면 태그는 [<pace>]가 됩니다. 결합된 오디오 태그는 적절한 토큰 경계(token boundary)를 만들기 위해 [<emotion>] [<pace>]<공백> 형식을 취합니다.
insertAudioTagsToTranscript는 정규 표현식(regular expression)을 사용하여 전사를 줄 단위로 나누고, 각 줄 앞에 결합된 오디오 태그를 삽입한 다음, 빈 문자열로 다시 결합합니다.
buildAudioPrompt는 장면과 표현력이 풍부한 전사를 하나의 문자열로 연결한 후 반환합니다.
import { SCENE_DICTIONARY } from './constants/scenes.const';
import { AudioPrompt } from './types/audio-prompt.type';
...
프롬프트의 출력 결과는 다음과 같습니다:
## Scene:
<scene>
...
5. Firebase Cloud Function에서 표현력이 풍부한 인간 음성 생성하기
createVoiceConfig 함수는 주어진 음성 이름(voice name)으로 내레이션되는 음성을 출력하는 GenerateContentConfig 인스턴스를 생성합니다.
import { GenerateContentConfig } from "@google/genai";
export function createVoiceConfig(voiceName = "Kore"): GenerateContentConfig {
...
const splitList = (whitelist?: string) => (whitelist || "").split(",").map((origin) => origin.trim());
export const whitelist = splitList(process.env.WHITELIST);
...
모든 Cloud Functions는 App Check, CORS, 그리고 600초의 타임아웃 기간(timeout period)을 강제합니다. 만약 WHITELIST가 지정되지 않은 경우, CORS는 기본적으로 true로 설정됩니다. 데모 환경에서는 허용 가능하지만, 프로덕션(production) 환경에서는 무단 액세스를 방지하기 위해 CORS를 특정 도메인으로 설정하거나 false로 설정하십시오.
readFact 클라우드 함수는 isStreaming이 true일 때 readFactStreamFunction으로 위임합니다. 그렇지 않으면 readFactFunction으로 위임됩니다.
readFactFunction 함수는 인코딩된 base64 문자열인 Promise<string>을 반환합니다.
readFactStreamFunction 함수는 WAV 헤더 바이트의 버퍼를 나타내는 Promise<number[] | undefined>를 반환합니다.
import { onCall } from "firebase-functions/v2/https";
import { cors } from "../auth";
import { buildAudioPrompt } from './audio-prompt';
...
withAIAudio 함수는 오디오 스트림(audio stream)을 생성하기 위해 콜백(callback)을 호출하는 고차 함수(high-order function)입니다.
async function withAIAudio(callback: (ai: GoogleGenAI, model: string) => Promise<string | number[] | undefined>) {
try {
const variables = AUDIO_CONFIG;
...
generateAudio는 Gemini 3.1 Flash TTS Preview 모델을 사용하여 응답을 생성하는 콜백 함수입니다. getBase64DataUrl은 extractInlineAudioData를 호출하여 응답에서 원시 데이터(raw data)와 MIME 타입(mime type)을 추출합니다. encodeBase64String 함수는 먼저 원시 데이터를 WAV 형식으로 변환한 다음, 이를 base64 형식으로 인코딩하고, 최종적으로 base64 문자열을 반환합니다.
createAudioParams 함수는 Gemini TTS 모델, 오디오 프롬프트(audio prompt), 그리고 음성 설정(speech configuration)을 포함하는 파라미터를 구성합니다.
async function generateAudio(aiTTS: AIAudio, prompt: string, voiceOption: GenerateContentConfig) {
try {
const { ai, model } = aiTTS;
...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기