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