Convert PCM audio responses to MP3 in speech endpoint

Gemini TTS (via LiteLLM/OpenRouter) returns `audio/pcm` instead of
MP3. Add `convert_pcm_to_mp3` to handle PCM-to-MP3 conversion using
parameters from the Content-Type header (rate, channels), and call it
asynchronously when a PCM response is detected.
This commit is contained in:
Dara Adib 2026-04-25 23:12:03 -04:00
parent 4e2240aada
commit 08bdb235de

View file

@ -13,6 +13,7 @@ from typing import Optional
from fnmatch import fnmatch
import aiohttp
import aiofiles
import asyncio
import requests
import mimetypes
@ -127,6 +128,42 @@ def convert_audio_to_mp3(file_path):
return None
def convert_pcm_to_mp3(file_path: str, content_type: str) -> str:
"""Convert a 16-bit PCM file to MP3 using parameters from Content-Type.
Deletes the original PCM file and returns the path of the created MP3 file.
Example Content-Type: "audio/pcm;rate=24000;channels=1"
"""
mime_components = content_type.lower().split(";")
if mime_components[0].strip() != 'audio/pcm':
raise ValueError(f'Error converting non-PCM audio: {content_type}')
params: dict[str, int] = {}
for mime_component in mime_components[1:]:
k, sep, v = mime_component.strip().partition('=')
if sep and v.isdigit():
params[k] = int(v)
output_path = os.path.splitext(file_path)[0] + '.mp3'
try:
audio = AudioSegment.from_raw(
file_path,
sample_width=2,
frame_rate=params.get('rate', 24000),
channels=params.get('channels', 1),
)
audio.export(output_path, format='mp3')
log.info(f'Converted {file_path} to {output_path}')
except Exception:
if os.path.exists(output_path):
os.remove(output_path)
raise
finally:
if file_path != output_path and os.path.exists(file_path):
os.remove(file_path)
return output_path
def set_faster_whisper_model(model: str, auto_update: bool = False):
whisper_model = None
if model:
@ -387,8 +424,20 @@ async def speech(request: Request, user=Depends(get_verified_user)):
r.raise_for_status()
async with aiofiles.open(file_path, 'wb') as f:
await f.write(await r.read())
# Gemini-TTS does not support MP3 output (neither via LiteLLM
# nor OpenRouter) and OpenRouter TTS returns PCM by default.
content_type = r.headers.get('Content-Type', '')
mime_type = content_type.lower().split(';')[0].strip()
if mime_type == 'audio/pcm':
pcm_path = os.path.splitext(file_path)[0] + '.pcm'
async with aiofiles.open(pcm_path, 'wb') as f:
await f.write(await r.read())
file_path = await asyncio.to_thread(
convert_pcm_to_mp3, pcm_path, content_type,
)
else:
async with aiofiles.open(file_path, 'wb') as f:
await f.write(await r.read())
async with aiofiles.open(file_body_path, 'w') as f:
await f.write(json.dumps(payload))