본문으로 건너뛰기

오디오 전사

TanStack AI는 전용 전사 어댑터를 통해 오디오 전사(음성-텍스트)를 지원합니다. 이 가이드에서는 OpenAI의 Whisper 및 GPT-4o 전사 모델, Groq에서 호스팅하는 Whisper 모델, fal.ai STT 모델을 사용해 음성 오디오를 텍스트로 변환하는 방법을 설명합니다.

개요​

오디오 전사는 TanStack AI의 다른 어댑터와 동일한 트리 셰이킹 가능한 아키텍처를 따르는 전사 어댑터가 처리합니다.

현재 지원되는 서비스:

  • OpenAI: Whisper-1, GPT-4o-transcribe, GPT-4o-mini-transcribe, GPT-4o-transcribe-diarize
  • Groq: whisper-large-v3-turbo, whisper-large-v3
  • BytePlus: Seed Speech ASR (seed-asr)
  • fal.ai: Whisper, Wizper, speech-to-text turbo, ElevenLabs speech-to-text

기본 사용법​

OpenAI 전사​

import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { audioBuffer } from './audio'

// Transcribe audio from a file (the adapter uses OPENAI_API_KEY from environment)
const audioFile = new File([audioBuffer], 'audio.mp3', { type: 'audio/mpeg' })

const result = await generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: audioFile,
language: 'en',
})

console.log(result.text) // The transcribed text

Base64 오디오 사용​

import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { readFile } from 'fs/promises'

// Read audio file as base64
const audioBuffer = await readFile('recording.mp3')
const base64Audio = audioBuffer.toString('base64')

const result = await generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: base64Audio,
})

console.log(result.text)

데이터 URL 사용​

import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { base64AudioData } from './audio'

const dataUrl = `data:audio/mpeg;base64,${base64AudioData}`

const result = await generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: dataUrl,
})

Groq 전사​

Groq는 빠른 추론 스택에서 Whisper large-v3 및 large-v3-turbo를 호스팅합니다. audio 입력에는 File, Blob, ArrayBuffer, base64 문자열, 데이터 URL 또는 https:// URL을 사용할 수 있으며, URL은 다시 업로드하지 않고 Groq로 전달됩니다.

import { generateTranscription } from '@tanstack/ai'
import { groqTranscription } from '@tanstack/ai-groq'

const result = await generateTranscription({
adapter: groqTranscription('whisper-large-v3-turbo'),
audio: 'https://example.com/recording.mp3',
language: 'en',
})

console.log(result.text)
console.log(result.language)

// verbose_json is the default — segments carry segment-level start/end timestamps
for (const segment of result.segments ?? []) {
console.log(`[${segment.start}s → ${segment.end}s] ${segment.text}`)
}

참고: Groq는 responseFormat 값으로 json, text, verbose_json(기본값)을 지원합니다. srt와 vtt는 지원되지 않으며 전달하면 오류가 발생합니다. 공급자별 modelOptions는 temperature와 timestamp_granularities(['word'], ['segment'] 또는 둘 다)입니다.

BytePlus 전사​

BytePlus Seed Speech ASR은 동기식으로 동작합니다. 오디오를 입력하면 전사 결과를 반환하며 폴링이 필요하지 않습니다. Seed Speech 제품에 속하므로 BYTEPLUS_VOICE_API_KEY를 읽습니다. BytePlus 채팅·이미지·비디오 어댑터에서 사용하는 ARK_API_KEY와는 다릅니다. 오디오는 File, base64, 데이터 URL 또는 공개 URL일 수 있으며, 최대 2시간 및 100MB까지 지원합니다.

import { generateTranscription } from '@tanstack/ai'
import { byteplusTranscription } from '@tanstack/ai-byteplus'

const result = await generateTranscription({
adapter: byteplusTranscription('seed-asr'),
audio: 'https://example.com/recording.mp3',
language: 'en',
modelOptions: { enable_punc: true, enable_speaker_info: true },
})

console.log(result.text)

// Speaker labels land on the segment when enable_speaker_info is set
for (const segment of result.segments ?? []) {
console.log(`[${segment.speaker ?? 'unknown'}] ${segment.text}`)
}

참고: 공급자별 modelOptions는 enable_itn(말한 숫자와 날짜를 숫자로 표시), enable_punc(구두점), enable_ddc(간투사와 더듬거림 제거), enable_speaker_info(화자 레이블), show_utterances(발화별 세부 분석, 기본적으로 활성화됨)입니다.

fal.ai 전사​

fal.ai는 Whisper, Wizper 및 기타 STT 모델을 제공합니다. audio 입력에는 URL, File, Blob 또는 ArrayBuffer를 사용할 수 있으며, ArrayBuffer는 자동으로 Blob으로 래핑됩니다.

import { generateTranscription } from '@tanstack/ai'
import { falTranscription } from '@tanstack/ai-fal'

const result = await generateTranscription({
adapter: falTranscription('fal-ai/whisper'),
audio: 'https://example.com/recording.mp3',
language: 'en',
})

console.log(result.text)
console.log(result.language)

// Models that return word/chunk timestamps populate result.segments
for (const segment of result.segments ?? []) {
console.log(`[${segment.start}s → ${segment.end}s] ${segment.text}`)
}

옵션​

공통 옵션​

옵션타입설명
audioFile | string오디오 데이터(File 객체 또는 base64 문자열) - 필수
languagestring언어 코드(예: "en", "es", "fr")
promptstring전사 스타일이나 용어를 안내하는 선택적 프롬프트입니다. gpt-4o-transcribe-diarize에서는 지원되지 않습니다.
responseFormat'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'공통 출력 형식

지원 언어​

Whisper는 여러 언어를 지원합니다. 일반적인 코드는 다음과 같습니다.

코드언어
en영어
es스페인어
fr프랑스어
de독일어
it이탈리아어
pt포르투갈어
ja일본어
ko한국어
zh중국어
ru러시아어

팁: 올바른 언어 코드를 제공하면 정확도가 향상되고 지연 시간이 줄어듭니다.

전체 예제​

import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { readFile } from 'fs/promises'

async function transcribeAudio(filepath: string) {
// Read the audio file
const audioBuffer = await readFile(filepath)
const audioFile = new File(
[audioBuffer],
filepath.split('/').pop()!,
{ type: 'audio/mpeg' }
)

// Transcribe with detailed output
const result = await generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: audioFile,
language: 'en',
responseFormat: 'verbose_json',
modelOptions: {
timestamp_granularities: ['segment', 'word'],
},
})

console.log('Full text:', result.text)
console.log('Duration:', result.duration, 'seconds')

// Print segments with timestamps
if (result.segments) {
for (const segment of result.segments) {
console.log(`[${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s]: ${segment.text}`)
}
}

return result
}

// Usage
await transcribeAudio('./meeting-recording.mp3')

브라우저에서 사용​

녹음 및 전사​

async function recordAndTranscribe() {
// Request microphone access
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
const mediaRecorder = new MediaRecorder(stream)
const chunks: Blob[] = []

mediaRecorder.ondataavailable = (e) => chunks.push(e.data)

mediaRecorder.onstop = async () => {
const audioBlob = new Blob(chunks, { type: 'audio/webm' })
const audioFile = new File([audioBlob], 'recording.webm', { type: 'audio/webm' })

// Send to your API endpoint for transcription
const formData = new FormData()
formData.append('audio', audioFile)

const response = await fetch('/api/transcribe', {
method: 'POST',
body: formData,
})

const result = await response.json()
console.log('Transcription:', result.text)
}

// Start recording
mediaRecorder.start()

// Stop after 10 seconds
setTimeout(() => mediaRecorder.stop(), 10000)
}

서버 API 엔드포인트​

// api/transcribe.ts
import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'

export async function POST(request: Request) {
const formData = await request.formData()
const audioFile = formData.get('audio')
if (!(audioFile instanceof File)) {
throw new Error('Expected an audio file under "audio"')
}

const result = await generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: audioFile,
})

return Response.json(result)
}

풀스택 사용​

TanStack AI는 React 훅과 서버 측 스트리밍 헬퍼를 제공하므로 보일러플레이트를 최소화해 풀스택 오디오 전사 기능을 구축할 수 있습니다.

참고: 큰 파일의 전사는 오래 걸릴 수 있습니다. Generation Persistence를 사용하면 새로고침하거나 연결이 끊긴 경우에도 상태와 결과를 유지할 수 있습니다.

스트리밍 모드(서버 라우트 + 클라이언트 훅)​

서버 — generateTranscription을 스트리밍 응답으로 감싸는 API 라우트를 만듭니다.

// routes/api/transcribe.ts
import {
generateTranscription,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/transcribe')({
server: {
handlers: {
POST: async ({ request }) => {
const body = await request.json()
const { audio, language, model } = body.data

const stream = generateTranscription({
adapter: openaiTranscription(model ?? 'whisper-1'),
audio,
language,
stream: true,
})

return toServerSentEventsResponse(stream)
},
},
},
})

참고: 브라우저에서 녹음한 오디오는 일반적으로 JSON 본문에 base64 문자열로 전달합니다. 파일 업로드에는 대신 FormData 기반 엔드포인트를 사용합니다(위의 브라우저에서 사용 참고).

클라이언트 — 연결 어댑터와 함께 useTranscription 훅을 사용합니다.

import { useTranscription, fetchServerSentEvents } from '@tanstack/ai-react'

function AudioTranscriber() {
const { generate, result, isLoading, error } = useTranscription({
connection: fetchServerSentEvents('/api/transcribe'),
})

const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return

// Convert to base64 for JSON transport
const buffer = await file.arrayBuffer()
const base64 = btoa(
new Uint8Array(buffer).reduce((s, b) => s + String.fromCharCode(b), ''),
)
const dataUrl = `data:${file.type};base64,${base64}`

await generate({ audio: dataUrl, language: 'en' })
}

return (
<div>
<input type="file" accept="audio/*" onChange={handleFileUpload} />
{isLoading && <p>Transcribing...</p>}
{error && <p>Error: {error.message}</p>}
{result && (
<div>
<p>{result.text}</p>
{result.duration && <p>Duration: {result.duration}s</p>}
</div>
)}
</div>
)
}

나머지 두 전송 방식(JSON을 반환하는 서버 함수 또는 SSE Response를 반환하는 방식)도 여기서 동일하게 작동합니다. 고급: 기타 전송 방식에 설명되어 있으며, 생성에서 한 번 더 설명합니다.

훅 API​

useTranscription 훅은 다음을 받습니다.

옵션타입설명
connectionConnectionAdapter스트리밍 전송(SSE, HTTP 스트림, 사용자 지정)
fetcher(input) => Promise<TranscriptionResult | Response>직접 호출하는 비동기 함수 또는 SSE Response를 반환하는 서버 함수
onResult(result) => TOutput | null | void전사가 완료될 때의 콜백입니다. 변환된 값을 반환해 result로 저장할 수 있습니다.
onError(error) => void오류 발생 시의 콜백
onProgress(progress, message?) => void진행률 업데이트(0-100)

다음 값을 반환합니다.

속성타입설명
generate(input: TranscriptionGenerateInput) => Promise<void>전사 시작
resultTranscriptionResult | null텍스트와 세그먼트가 포함된 결과 또는 null
isLoadingboolean전사가 진행 중인지 여부
errorError | undefined현재 오류(있는 경우)
statusGenerationClientState'idle' | 'generating' | 'success' | 'error'
stop() => void현재 전사를 중단합니다.
reset() => void결과와 오류를 지우고 유휴 상태로 돌아갑니다.

고급​

이 기능을 작동시키는 데 필요하지 않은 참고 세부 정보입니다.

기타 전송 방식​

직접 모드(서버 함수 + fetcher)​

TanStack Start 서버 함수에서 스트리밍하지 않고 사용하려면 다음과 같이 합니다.

// lib/server-functions.ts
import { createServerFn } from '@tanstack/react-start'
import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'

export const transcribeFn = createServerFn({ method: 'POST' })
.inputValidator((data: { audio: string; language?: string }) => data)
.handler(async ({ data }) => {
return generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: data.audio,
language: data.language,
})
})
import { useTranscription } from '@tanstack/ai-react'
import { transcribeFn } from '../lib/server-functions'

function AudioTranscriber() {
const { generate, result, isLoading } = useTranscription({
fetcher: (input) => transcribeFn({ data: input }),
})
// ... same UI as above
}

서버 함수 스트리밍(fetcher + Response)​

결과를 스트리밍하는 TanStack Start 서버 함수에서는 fetcher가 타입 안전한 입력을 받고 SSE Response를 반환합니다. 클라이언트가 이를 자동으로 파싱합니다.

// lib/server-functions.ts
import { createServerFn } from '@tanstack/react-start'
import { generateTranscription, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'

export const transcribeStreamFn = createServerFn({ method: 'POST' })
.inputValidator((data: { audio: string; language?: string }) => data)
.handler(({ data }) => {
return toServerSentEventsResponse(
generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: data.audio,
language: data.language,
stream: true,
}),
)
})
import { useTranscription } from '@tanstack/ai-react'
import { transcribeStreamFn } from '../lib/server-functions'

function AudioTranscriber() {
const { generate, result, isLoading } = useTranscription({
fetcher: (input) => {
if (typeof input.audio !== 'string') {
throw new Error('Expected base64 or data URL audio')
}
return transcribeStreamFn({
data: { ...input, audio: input.audio },
})
},
})
// ... same UI as above
}

모델 옵션​

OpenAI 모델 옵션​

import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { audioFile } from './audio'

const result = await generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: audioFile,
responseFormat: 'verbose_json', // Top-level: detailed output with timestamps
prompt: 'Technical terms: API, SDK, CLI', // Top-level: guide transcription
modelOptions: {
temperature: 0, // Lower = more deterministic (provider option)
timestamp_granularities: ['word', 'segment'],
},
})
옵션타입설명
temperaturenumber샘플링 온도(0~1)
timestamp_granularitiesArray<'word' | 'segment'>채울 타임스탬프 세분성(whisper-1 전용이며 최상위 responseFormat: 'verbose_json' 필요)
includestring[]응답에 포함할 추가 값(예: logprobs)
response_format'json' | 'text' | 'srt' | 'verbose_json' | 'vtt' | 'diarized_json'원시 OpenAI 응답 형식입니다. 화자 레이블이 있는 분리 출력에는 여기서 diarized_json을 사용합니다.
chunking_strategy'auto' | { type: 'server_vad', ... } | null오디오 청크 분할 전략입니다(모든 모델에서 사용 가능하며, 설정하지 않으면 오디오를 하나의 블록으로 전사합니다). 30초보다 긴 gpt-4o-transcribe-diarize 입력에는 OpenAI가 요구하며, 어댑터는 이 모델에서 기본값을 'auto'로 설정합니다.
known_speaker_namesstring[]화자 분리를 위한 최대 4개의 화자 레이블
known_speaker_referencesstring[]known_speaker_names와 일치하는 2~10초 길이의 데이터 URL 오디오 샘플

responseFormat과 prompt는 generateTranscription의 최상위 옵션이며 modelOptions 키가 아닙니다.

응답 형식​

형식설명
json텍스트가 포함된 단순 JSON
text일반 텍스트만 포함
srtSubRip 자막 형식
verbose_json타임스탬프와 세그먼트가 포함된 상세 JSON
vttWebVTT 자막 형식

OpenAI의 gpt-4o-transcribe-diarize는 화자 레이블이 있는 세그먼트를 위해 modelOptions.response_format: 'diarized_json'도 지원합니다.

화자 분리​

화자 레이블이 필요하면 gpt-4o-transcribe-diarize를 사용합니다. 응답 형식을 지정하지 않으면 TanStack AI는 요청의 기본값을 response_format: 'diarized_json'으로 설정하고, 직접 청크 분할 전략을 제공하지 않는 한 chunking_strategy: 'auto'를 전송합니다. 최상위 responseFormat: 'json' 또는 'text'를 전달하면 화자 세그먼트를 사용하지 않습니다.

import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { meetingAudioFile } from './audio'

const result = await generateTranscription({
adapter: openaiTranscription('gpt-4o-transcribe-diarize'),
audio: meetingAudioFile,
modelOptions: {
known_speaker_names: ['agent', 'customer'],
known_speaker_references: [
'data:audio/wav;base64,...',
'data:audio/wav;base64,...',
],
},
})

for (const segment of result.segments ?? []) {
console.log(segment.speaker, segment.start, segment.end, segment.text)
}

어댑터는 API를 호출하기 전에 다음 두 가지 제약을 적용합니다.

  • 알려진 화자 참조는 최대 4개입니다. known_speaker_names와 known_speaker_references는 길이가 일치하도록 함께 제공해야 합니다.
  • 화자 분리 모델은 prompt, include 또는 timestamp_granularities를 지원하지 않습니다. 이러한 조합은 거부됩니다.

응답 형식​

전사 결과에는 다음이 포함됩니다.

interface TranscriptionResult {
id: string // Unique identifier
model: string // Model used
text: string // Full transcribed text
language?: string // Detected/specified language
duration?: number // Audio duration in seconds
segments?: Array<{ // Timestamped segments
id: number // Segment identifier
start: number // Start time in seconds
end: number // End time in seconds
text: string // Segment text
confidence?: number // Confidence score (0-1), if available
speaker?: string // Speaker identifier, if diarization is enabled
}>
words?: Array<{ // Word-level timestamps (top-level)
word: string
start: number
end: number
}>
}

모델 사용 가능 여부​

OpenAI 모델​

모델설명사용 사례
whisper-1Whisper large-v2일반 전사
gpt-4o-transcribeGPT-4o 기반 전사더 높은 정확도
gpt-4o-transcribe-diarize화자 분리 지원여러 화자가 있는 오디오
gpt-4o-mini-transcribe더 빠르고 가벼운 모델비용 효율적

지원 오디오 형식​

OpenAI는 다음 오디오 형식을 지원합니다.

  • mp3 - MPEG Audio Layer 3
  • mp4 - MPEG-4 Audio
  • mpeg - MPEG Audio
  • mpga - MPEG Audio
  • m4a - MPEG-4 Audio
  • wav - Waveform Audio
  • webm - WebM Audio
  • flac - Free Lossless Audio Codec
  • ogg - Ogg Vorbis

참고: 최대 파일 크기는 25MB입니다.

오류 처리​

import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
import { audioFile } from './audio'

try {
const result = await generateTranscription({
adapter: openaiTranscription('whisper-1'),
audio: audioFile,
})
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('Invalid file format')) {
console.error('Unsupported audio format')
} else if (error.message.includes('File too large')) {
console.error('Audio file exceeds 25 MB limit')
} else if (error.message.includes('Audio file is too short')) {
console.error('Audio must be at least 0.1 seconds')
} else {
console.error('Transcription error:', error.message)
}
}
}

디버깅: 전사 결과가 무의미하거나 세그먼트가 비어 있거나 공급자가 오디오 형식을 거부하면 generateTranscription({...})에 debug: true를 전달해 전송 요청과 각 원시 공급자 청크를 기록합니다. 디버그 로깅을 참고합니다.

환경 변수​

전사 어댑터는 다음을 사용합니다.

  • OPENAI_API_KEY: OpenAI API 키
  • BYTEPLUS_VOICE_API_KEY: BytePlus Seed Speech 키(ModelArk 키가 아님)

명시적 API 키​

import { createOpenaiTranscription } from '@tanstack/ai-openai'

const adapter = createOpenaiTranscription('whisper-1', 'your-openai-api-key')

모범 사례​

  1. 오디오 품질: 오디오 품질이 높을수록 전사 정확도가 향상됩니다. 가능한 경우 배경 소음을 줄입니다.

  2. 언어 지정: 알고 있는 경우 항상 언어를 지정합니다. 정확도와 속도가 향상됩니다.

  3. 파일 크기: 오디오 파일을 25MB 미만으로 유지합니다. 더 긴 녹음은 청크로 나눕니다.

  4. 형식 선택: MP3는 품질과 크기의 균형이 좋습니다. 최고의 품질이 필요하면 WAV 또는 FLAC을 사용합니다.

  5. 프롬프트 사용: prompt 옵션으로 맥락이나 예상 어휘(예: 기술 용어, 이름)를 제공합니다.

  6. 타임스탬프: 자막이나 동기화에 시간 정보가 필요하면 responseFormat: 'verbose_json'을 요청하고 modelOptions.timestamp_granularities를 설정합니다.

  7. 화자 분리: 여러 화자가 있는 오디오에는 modelOptions.response_format: 'diarized_json' 출력을 사용하도록 gpt-4o-transcribe-diarize를 사용합니다. 사용자 지정 VAD 조정이 필요하지 않으면 chunking_strategy: 'auto'를 유지합니다.