venthrive-api / routes /transcribe.py
GitHub Actions
Sync from GitHub · 2026-09-26 23:46 UTC
020705e
Raw History Blame Contribute Delete
2.09 kB
from fastapi import APIRouter, UploadFile, File, Depends, HTTPException
from utils.rate_limiter import with_rate_limit
from utils.groq_client import call_groq_transcribe
router = APIRouter()
SUPPORTED_AUDIO_TYPES = {
"audio/webm", "audio/mp4", "audio/mpeg", "audio/wav",
"audio/ogg", "audio/flac", "audio/x-m4a",
}
MAX_AUDIO_BYTES = 25 * 1024 * 1024 # 25 MB — Groq Whisper limit
@router.post("/transcribe")
async def transcribe_audio(
audio: UploadFile = File(...),
user: dict = Depends(with_rate_limit("transcription")),
):
"""
Accepts a browser-recorded audio blob, sends it to Groq Whisper,
and returns the transcript text.
Voice input is NEVER stored — the audio bytes are discarded after
transcription returns. Only the resulting text is saved by the caller.
See VUL-019 (no audio persistence).
"""
# Basic content-type guard — not a hard security boundary since the
# browser may send 'application/octet-stream'. Real validation happens
# when Groq rejects unsupported formats.
content_type = audio.content_type or "audio/webm"
if content_type not in SUPPORTED_AUDIO_TYPES and content_type != "application/octet-stream":
raise HTTPException(
status_code=415,
detail=f"Unsupported audio format: {content_type}",
)
audio_bytes = await audio.read()
if len(audio_bytes) > MAX_AUDIO_BYTES:
raise HTTPException(
status_code=413,
detail="Audio file exceeds 25 MB limit.",
)
if len(audio_bytes) == 0:
raise HTTPException(status_code=400, detail="Audio file is empty.")
try:
transcription = call_groq_transcribe(
file=(audio.filename or "recording.webm", audio_bytes, content_type),
response_format="json",
language="en",
fixture="transcript",
)
return {"transcript": transcription.text}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Transcription failed: {str(e)}",
)