refac
This commit is contained in:
@@ -5,7 +5,6 @@ import os
|
||||
import uuid
|
||||
import html
|
||||
import base64
|
||||
from functools import lru_cache
|
||||
from pydub import AudioSegment
|
||||
from pydub.silence import split_on_silence
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@@ -421,7 +420,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
|
||||
elif request.app.state.config.TTS_ENGINE == 'elevenlabs':
|
||||
voice_id = payload.get('voice', '')
|
||||
|
||||
if voice_id not in get_available_voices(request):
|
||||
if voice_id not in await get_available_voices(request):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail='Invalid voice id',
|
||||
@@ -1295,40 +1294,53 @@ async def transcription(
|
||||
)
|
||||
|
||||
|
||||
def get_available_models(request: Request) -> list[dict]:
|
||||
async def get_available_models(request: Request) -> list[dict]:
|
||||
available_models = []
|
||||
if request.app.state.config.TTS_ENGINE == 'openai':
|
||||
# Use custom endpoint if not using the official OpenAI API URL
|
||||
if not request.app.state.config.TTS_OPENAI_API_BASE_URL.startswith('https://api.openai.com'):
|
||||
try:
|
||||
response = requests.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/models',
|
||||
timeout=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
available_models = data.get('models', [])
|
||||
except Exception as e:
|
||||
log.error(f'Error fetching models from custom endpoint: {str(e)}')
|
||||
available_models = [{'id': 'tts-1'}, {'id': 'tts-1-hd'}]
|
||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
|
||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
try:
|
||||
async with session.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/models',
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
available_models = data.get('models', [])
|
||||
except Exception as e:
|
||||
log.debug(f'/audio/models not available, trying /models fallback: {str(e)}')
|
||||
# Fallback to standard OpenAI-compatible /models endpoint
|
||||
# (used by KokoroTTS and similar custom TTS servers)
|
||||
try:
|
||||
async with session.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/models',
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
# OpenAI /models returns {"data": [...]}, /audio/models returns {"models": [...]}
|
||||
available_models = data.get('data', data.get('models', []))
|
||||
except Exception as e2:
|
||||
log.error(f'Error fetching models from custom endpoint: {str(e2)}')
|
||||
available_models = [{'id': 'tts-1'}, {'id': 'tts-1-hd'}]
|
||||
else:
|
||||
available_models = [{'id': 'tts-1'}, {'id': 'tts-1-hd'}]
|
||||
elif request.app.state.config.TTS_ENGINE == 'elevenlabs':
|
||||
try:
|
||||
response = requests.get(
|
||||
f'{ELEVENLABS_API_BASE_URL}/v1/models',
|
||||
headers={
|
||||
'xi-api-key': request.app.state.config.TTS_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
response.raise_for_status()
|
||||
models = response.json()
|
||||
|
||||
available_models = [{'name': model['name'], 'id': model['model_id']} for model in models]
|
||||
except requests.RequestException as e:
|
||||
log.error(f'Error fetching voices: {str(e)}')
|
||||
timeout = aiohttp.ClientTimeout(total=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
async with session.get(
|
||||
f'{ELEVENLABS_API_BASE_URL}/v1/models',
|
||||
headers={
|
||||
'xi-api-key': request.app.state.config.TTS_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
models = await response.json()
|
||||
available_models = [{'name': model['name'], 'id': model['model_id']} for model in models]
|
||||
except Exception as e:
|
||||
log.error(f'Error fetching models: {str(e)}')
|
||||
elif request.app.state.config.TTS_ENGINE == 'mistral':
|
||||
available_models = [{'id': 'mistral-tts-latest'}]
|
||||
return available_models
|
||||
@@ -1336,24 +1348,25 @@ def get_available_models(request: Request) -> list[dict]:
|
||||
|
||||
@router.get('/models')
|
||||
async def get_models(request: Request, user=Depends(get_verified_user)):
|
||||
return {'models': get_available_models(request)}
|
||||
return {'models': await get_available_models(request)}
|
||||
|
||||
|
||||
def get_available_voices(request) -> dict:
|
||||
async def get_available_voices(request) -> dict:
|
||||
"""Returns {voice_id: voice_name} dict"""
|
||||
available_voices = {}
|
||||
if request.app.state.config.TTS_ENGINE == 'openai':
|
||||
# Use custom endpoint if not using the official OpenAI API URL
|
||||
if not request.app.state.config.TTS_OPENAI_API_BASE_URL.startswith('https://api.openai.com'):
|
||||
try:
|
||||
response = requests.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/voices',
|
||||
timeout=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
voices_list = data.get('voices', [])
|
||||
available_voices = {voice['id']: voice['name'] for voice in voices_list}
|
||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
|
||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
async with session.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/voices',
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
voices_list = data.get('voices', [])
|
||||
available_voices = {voice['id']: voice['name'] for voice in voices_list}
|
||||
except Exception as e:
|
||||
log.error(f'Error fetching voices from custom endpoint: {str(e)}')
|
||||
available_voices = {
|
||||
@@ -1375,7 +1388,7 @@ def get_available_voices(request) -> dict:
|
||||
}
|
||||
elif request.app.state.config.TTS_ENGINE == 'elevenlabs':
|
||||
try:
|
||||
available_voices = get_elevenlabs_voices(api_key=request.app.state.config.TTS_API_KEY)
|
||||
available_voices = await get_elevenlabs_voices(api_key=request.app.state.config.TTS_API_KEY)
|
||||
except Exception:
|
||||
# Avoided @lru_cache with exception
|
||||
pass
|
||||
@@ -1386,13 +1399,15 @@ def get_available_voices(request) -> dict:
|
||||
url = (base_url or f'https://{region}.tts.speech.microsoft.com') + '/cognitiveservices/voices/list'
|
||||
headers = {'Ocp-Apim-Subscription-Key': request.app.state.config.TTS_API_KEY}
|
||||
|
||||
response = requests.get(url, headers=headers, timeout=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
|
||||
response.raise_for_status()
|
||||
voices = response.json()
|
||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
|
||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
async with session.get(url, headers=headers) as response:
|
||||
response.raise_for_status()
|
||||
voices = await response.json()
|
||||
|
||||
for voice in voices:
|
||||
available_voices[voice['ShortName']] = f'{voice["DisplayName"]} ({voice["ShortName"]})'
|
||||
except requests.RequestException as e:
|
||||
for voice in voices:
|
||||
available_voices[voice['ShortName']] = f'{voice["DisplayName"]} ({voice["ShortName"]})'
|
||||
except Exception as e:
|
||||
log.error(f'Error fetching voices: {str(e)}')
|
||||
elif request.app.state.config.TTS_ENGINE == 'mistral':
|
||||
api_key = request.app.state.config.TTS_MISTRAL_API_KEY
|
||||
@@ -1400,29 +1415,29 @@ def get_available_voices(request) -> dict:
|
||||
|
||||
if api_key:
|
||||
try:
|
||||
response = requests.get(
|
||||
f'{api_base_url}/audio/voices',
|
||||
headers={
|
||||
'Authorization': f'Bearer {api_key}',
|
||||
},
|
||||
timeout=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
|
||||
)
|
||||
response.raise_for_status()
|
||||
voices_data = response.json()
|
||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
|
||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
async with session.get(
|
||||
f'{api_base_url}/audio/voices',
|
||||
headers={
|
||||
'Authorization': f'Bearer {api_key}',
|
||||
},
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
voices_data = await response.json()
|
||||
|
||||
for voice in voices_data:
|
||||
voice_id = voice.get('voice_id', voice.get('id', ''))
|
||||
voice_name = voice.get('name', voice_id)
|
||||
if voice_id:
|
||||
available_voices[voice_id] = voice_name
|
||||
except requests.RequestException as e:
|
||||
for voice in voices_data:
|
||||
voice_id = voice.get('voice_id', voice.get('id', ''))
|
||||
voice_name = voice.get('name', voice_id)
|
||||
if voice_id:
|
||||
available_voices[voice_id] = voice_name
|
||||
except Exception as e:
|
||||
log.error(f'Error fetching Mistral voices: {str(e)}')
|
||||
|
||||
return available_voices
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_elevenlabs_voices(api_key: str) -> dict:
|
||||
async def get_elevenlabs_voices(api_key: str) -> dict:
|
||||
"""
|
||||
Note, set the following in your .env file to use Elevenlabs:
|
||||
AUDIO_TTS_ENGINE=elevenlabs
|
||||
@@ -1433,22 +1448,22 @@ def get_elevenlabs_voices(api_key: str) -> dict:
|
||||
|
||||
try:
|
||||
# TODO: Add retries
|
||||
response = requests.get(
|
||||
f'{ELEVENLABS_API_BASE_URL}/v1/voices',
|
||||
headers={
|
||||
'xi-api-key': api_key,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
|
||||
)
|
||||
response.raise_for_status()
|
||||
voices_data = response.json()
|
||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
|
||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
async with session.get(
|
||||
f'{ELEVENLABS_API_BASE_URL}/v1/voices',
|
||||
headers={
|
||||
'xi-api-key': api_key,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
voices_data = await response.json()
|
||||
|
||||
voices = {}
|
||||
for voice in voices_data.get('voices', []):
|
||||
voices[voice['voice_id']] = voice['name']
|
||||
except requests.RequestException as e:
|
||||
# Avoid @lru_cache with exception
|
||||
voices = {}
|
||||
for voice in voices_data.get('voices', []):
|
||||
voices[voice['voice_id']] = voice['name']
|
||||
except Exception as e:
|
||||
log.error(f'Error fetching voices: {str(e)}')
|
||||
raise RuntimeError(f'Error fetching voices: {str(e)}')
|
||||
|
||||
@@ -1457,4 +1472,4 @@ def get_elevenlabs_voices(api_key: str) -> dict:
|
||||
|
||||
@router.get('/voices')
|
||||
async def get_voices(request: Request, user=Depends(get_verified_user)):
|
||||
return {'voices': [{'id': k, 'name': v} for k, v in get_available_voices(request).items()]}
|
||||
return {'voices': [{'id': k, 'name': v} for k, v in (await get_available_voices(request)).items()]}
|
||||
|
||||
Reference in New Issue
Block a user