OpenAI Whisper large-v3 기반 STT(Speech-to-Text) API입니다. WAV, MP3, M4A, OGG, FLAC 형식의 오디오를 전사(transcript)로 변환합니다. 언어 힌트 지정 또는 자동 감지, 타임스탬프 세그먼트 포함 조회가 가능합니다. 대용량 오디오는 Multipart 모드로 전송해 base64 오버헤드를 제거할 수 있습니다.
⚠ STT 태스크 활성화 필요
다른 태스크와 함께 실행하면 VRAM 부족이 발생합니다. 반드시 STT만 단독으로 활성화하세요.
# .env REDGX_GPU_RELAY_ENABLED=true REDGX_GPU_STT_ENABLED=true # 나머지는 false
# 1. POST → 202 Accepted (request_id 반환)
POST /api/v1/ns/{ns}/gpu/stt
Content-Type: multipart/form-data 또는 application/json (audio_b64)
→ 202 { "request_id": "stt-1710000000000-a1b2c3d4" }
# 2. GET 폴링 → 완료 시 200
GET /api/v1/ns/{ns}/gpu/stt/{request_id}
→ 202 { "status": "queued" }
→ 200 { "status": "completed", "transcript": "..." }
# 데이터 흐름
Client → POST /gpu/stt (오디오 bytes) → Redis Queue → STTWorker
→ Whisper large-v3 → transcript → Redis Outbox → Client
| 항목 | 값 |
|---|---|
| STT 엔진 | OpenAI Whisper large-v3 (transformers 구현) |
| 모델 선정 | RTX 3060 12GB 채택 — ~3GB VRAM, 99개 언어 지원 float16 (GPU), float32 (CPU fallback) |
| 지원 포맷 | WAV MP3 M4A OGG FLAC (magic bytes 검증) |
| 최대 파일 크기 | 100 MB |
| 오디오 처리 | openai-whisper load_audio() → 16kHz float32 (ffmpeg 기반) |
| 전송 모드 | JSON: audio_b64 (base64) / Multipart: file 파트 (권장, base64 오버헤드 없음) |
| 캐시 | SHA-256(모델명 + 오디오 bytes) — 동일 오디오 GPU 우회 |
| 캐시 TTL | 24시간 (86400초) |
| 폴링 권장 간격 | 2초 (오디오 길이에 따라 가변) |
오디오 파일을 업로드하고 전사 결과를 조회합니다. Multipart 모드(권장)와 JSON Base64 모드 두 가지를 지원합니다. 아래 샘플 파일을 클릭하거나 직접 파일을 드래그하세요.
curl 명령 보기
language 파라미터로 입력 언어를 지정하면 감지 오류를 방지하고 정확도가 향상됩니다.
생략 시 Whisper가 자동으로 언어를 감지하며, 응답의 language 필드에 감지 결과가 반환됩니다.
단일 언어 오디오의 경우 힌트를 지정하면 정확도와 속도가 모두 향상됩니다.
curl 명령 보기
include_segments=true를 설정하면 전사 결과와 함께 구간별 타임스탬프를 반환합니다.
자막 생성, 특정 구간 검색 등에 활용할 수 있습니다.
segments[] 배열의 각 항목에 id, start (초), end (초), text 포함.
curl 명령 보기
동일한 오디오 파일을 두 번 전송하면 두 번째는 GPU 추론 없이 즉시 반환됩니다. 캐시 키는 SHA-256(모델명 + 오디오 bytes)로 계산됩니다.
language 힌트를 주면 캐시 미스입니다 (캐시는 bytes 기반, language는 캐시 키 제외).
curl 명령 보기
결과는 outbox에 1시간(3600초) 보관됩니다.
curl 명령 보기
curl 명령 보기
curl 명령 보기
여러 request_id 상태를 한 번의 API 호출로 조회합니다.
curl 명령 보기
STT 전용 에러(INVALID_AUDIO_FORMAT)를 포함한 다양한 오류 상황을 테스트합니다.
• JSON 모드 (
Content-Type: application/json): audio_b64 필드에 Base64 인코딩 오디오• Multipart 모드 (
Content-Type: multipart/form-data): file 파트에 원본 오디오 bytes (권장 — base64 오버헤드 없음)
| 필드 | 타입 | 기본값 | 설명 |
|---|---|---|---|
audio_b64 | string (JSON) | 필수 | Base64 인코딩 오디오 데이터 (JSON 모드) |
file | binary (Multipart) | 필수 | 오디오 파일 원본 bytes (Multipart 모드) |
language | string | null | null (자동 감지) | 입력 언어 힌트 (ko, en, ja …). 지정 시 정확도 향상 |
model | string | null | 서버 기본값 | Whisper 모델명 (null = 서버 설정값) |
include_segments | bool | false | 타임스탬프 세그먼트 목록 반환 여부 |
{
"ok": true,
"data": {
"request_id": "stt-1710567890123-a1b2c3d4",
"task_type": "stt",
"hint": "text_recommended" // 4KB 미만 업로드 시에만 포함 — JSON 모드 권장 힌트
}
}
hint 필드는 multipart 업로드가 4KB 미만일 때만 응답에 포함됩니다 (요청은 거부되지 않음). 작은 페이로드에는 base64 JSON 모드가 효율적입니다.
// include_segments=false (기본)
{
"ok": true,
"data": {
"request_id": "stt-1710567890123-a1b2c3d4",
"status": "completed",
"transcript": "Ask not what your country can do for you...",
"language": "en", // 감지된 언어
"duration_seconds": 13.82, // 오디오 길이
"segments": null, // include_segments=false 시 null
"model": "Systran/faster-whisper-large-v3",
"cached": false,
"elapsed_ms": 2350.0
}
}
// include_segments=true
{
"data": {
"transcript": "Ask not what your country...",
"segments": [
{ "id": 0, "start": 0.0, "end": 3.5, "text": "Ask not what your country", "confidence": null },
{ "id": 1, "start": 3.5, "end": 6.2, "text": "can do for you", "confidence": null },
...
]
}
}
| 필드 | 설명 |
|---|---|
request_id | 요청 ID |
status | completed 또는 failed |
transcript | 전체 전사 텍스트 |
language | 감지(또는 지정)된 언어 코드 |
duration_seconds | 오디오 길이 (초) |
segments | 타임스탬프 세그먼트 배열 (include_segments=false 시 null) |
model | 실제 사용된 Whisper 모델명 |
cached | 캐시 적중 여부 |
elapsed_ms | 전체 처리 시간 (ms) |
# Multipart 모드 (권장 — 대용량 오디오)
curl -X POST https://localhost:1443/api/v1/ns/HRM/gpu/stt \
-k \
-H "X-API-Key: <your-key>" \
-F "[email protected]" \
-F "language=ko" \
-F "include_segments=true"
# JSON 모드 (소용량)
curl -X POST https://localhost:1443/api/v1/ns/HRM/gpu/stt \
-k \
-H "X-API-Key: <your-key>" \
-H "Content-Type: application/json" \
-d '{"audio_b64":"'"$(base64 -w0 audio.mp3)"'","language":"en"}'
# 결과 조회 (폴링)
curl https://localhost:1443/api/v1/ns/HRM/gpu/stt/stt-xxx -k \
-H "X-API-Key: <your-key>"
# 결과 조회 후 자동 삭제
curl "https://localhost:1443/api/v1/ns/HRM/gpu/stt/stt-xxx?auto_clear=true" \
-k -H "X-API-Key: <your-key>"
| 코드 | HTTP | 설명 |
|---|---|---|
INVALID_AUDIO_FORMAT | 400 | 지원하지 않는 오디오 포맷 (magic bytes 검증 실패) |
MISSING_AUDIO | 400 | JSON 모드: audio_b64 필드 누락 또는 빈 값 |
MISSING_FILE | 400 | Multipart 모드: file 파트 누락 |
EMPTY_FILE | 400 | 업로드 파일이 빈 바이트 (multipart 또는 base64 디코딩 결과 0 byte) |
INVALID_MULTIPART | 400 | multipart/form-data 파싱 실패 |
INVALID_JSON | 400 | JSON 본문 파싱 실패 |
INVALID_BASE64 | 400 | audio_b64가 유효한 base64가 아님 |
UNAUTHORIZED | 401 | API Key 누락 또는 잘못됨 |
NAMESPACE_DENIED | 403 | Namespace 접근 권한 없음 |
GPU_NOT_FOUND | 404 | Request ID 없음 또는 만료 (TTL 3600s) |
GPU_PAYLOAD_TOO_LARGE | 413 | 오디오 파일 크기 초과 (max_file_size_mb, 기본 100 MB) |
GPU_TASK_DISABLED | 503 | STT 태스크 비활성 (REDGX_GPU_STT_ENABLED=false) |
GPU_UNAVAILABLE | 503 | CUDA 불가 또는 Whisper 모델 로드 실패 |
GPU_QUEUE_FULL | 503 | 큐 용량 초과 |
GPU_PROCESSING | 409 | 취소 불가 — 이미 처리 중 |
Redis Pub/Sub으로 완료 알림을 수신 후 즉시 인식 결과를 전송합니다. STT는 오디오 길이에 따라 처리 시간이 길어지므로 폴링보다 이 방식이 적합합니다.
// 인증: Sec-WebSocket-Protocol 헤더로 API 키 전달 (URL 쿼리 미지원) // 브라우저: new WebSocket(url, ["redgx_ak_hrm_..."]) wss://<host>/api/v1/ns/HRM/gpu/stt/stt-xxx/wait?timeout=120 // 장시간 오디오는 최대 120s 권장
// 완료 — REST GET과 동일 구조
{ "ok": true, "data": { "request_id": "stt-...", "status": "completed",
"transcript": "인식된 텍스트 전체", "language": "ko",
"duration_seconds": 13.82, "segments": null,
"model": "Systran/faster-whisper-large-v3", "cached": false, "elapsed_ms": 1850.0 } }
// 실패
{ "ok": true, "data": { "request_id": "stt-...", "status": "failed", "error": {...} } }
// 인증 실패 → HTTP 403 (upgrade 거부, accept 전 — 4001 close 프레임 미전송)
// Not Found → { "ok": false, "error": { "code": "GPU_NOT_FOUND" } } + close(4004)
// Timeout → { "ok": false, "error": { "code": "GPU_TIMEOUT" } } + close(4008)