Harden audio transcription error handling

This commit is contained in:
adavyas 2026-04-08 23:26:23 -04:00
parent 0664b77912
commit dc0531142c
2 changed files with 51 additions and 7 deletions

View File

@ -10,6 +10,7 @@ from typing import Any, Protocol
from fastapi import UploadFile
from nanoid import generate as generate_nanoid
from openai import APIError
from sqlalchemy import Integer, select
from sqlalchemy.ext.asyncio import AsyncSession
@ -34,6 +35,7 @@ AUDIO_EXTENSION_CONTENT_TYPES = {
".wav": "audio/wav",
}
UPLOAD_VALIDATION_CHUNK_BYTES = 1024 * 1024
AUDIO_PROBE_TIMEOUT_SECONDS = 10
GENERIC_CONTENT_TYPES = {
"",
"application/octet-stream",
@ -156,11 +158,14 @@ class AudioProcessor:
content,
suffix,
)
text = await transcribe_audio(
content,
filename=normalized_filename,
content_type=normalized_content_type,
)
try:
text = await transcribe_audio(
content,
filename=normalized_filename,
content_type=normalized_content_type,
)
except APIError as exc:
raise FileProcessingError("Audio transcription failed") from exc
return ExtractedFileText(
text=text,
metadata={
@ -218,12 +223,15 @@ class AudioProcessor:
capture_output=True,
text=True,
check=True,
timeout=AUDIO_PROBE_TIMEOUT_SECONDS,
)
return max(float(result.stdout.strip()), 0.0)
except FileNotFoundError as exc:
raise ValidationException(
"Audio uploads require ffmpeg and ffprobe to be installed on the server"
) from exc
except subprocess.TimeoutExpired as exc:
raise ValidationException("Audio validation timed out") from exc
except (subprocess.CalledProcessError, ValueError) as exc:
raise ValidationException("Uploaded audio is invalid or unreadable") from exc

View File

@ -1,16 +1,19 @@
import asyncio
import io
import subprocess
from pathlib import Path
from types import TracebackType
from unittest.mock import AsyncMock, patch
import httpx
import pytest
from fastapi import UploadFile
from openai import AsyncOpenAI
from openai import APIError, AsyncOpenAI
from starlette.datastructures import Headers
import src.utils.files as file_utils
from src.config import settings
from src.exceptions import ValidationException
from src.exceptions import FileProcessingError, ValidationException
from src.utils.clients import CLIENTS, transcribe_audio
from src.utils.files import AudioProcessor, FileProcessingService
@ -60,6 +63,19 @@ def test_probe_audio_duration_cleans_up_temp_file_on_write_failure():
mock_unlink.assert_called_once_with(missing_ok=True)
def test_probe_audio_duration_timeout_raises_validation_exception():
processor = AudioProcessor()
with (
patch(
"src.utils.files.subprocess.run",
side_effect=subprocess.TimeoutExpired(cmd="ffprobe", timeout=10),
),
pytest.raises(ValidationException, match="Audio validation timed out"),
):
processor.probe_audio_duration_seconds_from_path(Path("/tmp/audio.mp3"))
@pytest.mark.asyncio
async def test_audio_upload_requires_openai_client_before_processing():
file = UploadFile(
@ -181,6 +197,26 @@ async def test_audio_processor_extract_text_transcribes_directly():
assert "transcription_fallback_used" not in extracted.metadata
@pytest.mark.asyncio
async def test_audio_processor_extract_text_wraps_provider_errors():
processor = AudioProcessor()
request = httpx.Request("POST", "https://api.openai.com/v1/audio/transcriptions")
with (
patch.object(processor, "_probe_audio_duration_seconds", return_value=1.0),
patch(
"src.utils.files.transcribe_audio",
side_effect=APIError("provider failed", request=request, body=None),
),
pytest.raises(FileProcessingError, match="Audio transcription failed"),
):
await processor.extract_text(
b"audio-bytes",
filename="seg-0.mp3",
content_type="audio/mpeg",
)
@pytest.mark.asyncio
async def test_audio_processor_extract_text_probes_in_background_thread():
processor = AudioProcessor()