endpoints: filter /v1/models and /v1/voices by engine availability flags
This commit is contained in:
parent
f25731ca08
commit
e9c978b051
1 changed files with 29 additions and 44 deletions
73
speech.py
73
speech.py
|
|
@ -66,6 +66,17 @@ except ImportError:
|
||||||
torch = None
|
torch = None
|
||||||
F5TTS = None
|
F5TTS = None
|
||||||
|
|
||||||
|
def is_engine_available(model_id):
|
||||||
|
"""Whether the TTS engine for a given model_id is loadable in this process.
|
||||||
|
Used to filter advertised models/voices so we don't 503 on requests for
|
||||||
|
engines whose Python deps weren't installed. tts-1/tts-1-hd/tts-1-silero/
|
||||||
|
tts-1-kokoro are assumed available — they surface their own load errors."""
|
||||||
|
if model_id == 'tts-1-qwen':
|
||||||
|
return QWEN_TTS_AVAILABLE
|
||||||
|
if model_id == 'tts-1-f5':
|
||||||
|
return F5_TTS_AVAILABLE
|
||||||
|
return True
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
async def lifespan(app):
|
async def lifespan(app):
|
||||||
# Startup: Initialize voice caches in each worker process
|
# Startup: Initialize voice caches in each worker process
|
||||||
|
|
@ -74,10 +85,12 @@ async def lifespan(app):
|
||||||
default_exists('config/pre_process_map.yaml')
|
default_exists('config/pre_process_map.yaml')
|
||||||
default_exists('config/voice_to_speaker.yaml')
|
default_exists('config/voice_to_speaker.yaml')
|
||||||
|
|
||||||
# Build voice-to-model cache for fast lookups
|
# Build voice-to-model cache for fast lookups (skip engines we can't load)
|
||||||
with open('config/voice_to_speaker.yaml', 'r', encoding='utf8') as file:
|
with open('config/voice_to_speaker.yaml', 'r', encoding='utf8') as file:
|
||||||
voice_map = yaml.safe_load(file)
|
voice_map = yaml.safe_load(file)
|
||||||
for model_id, voices in voice_map.items():
|
for model_id, voices in voice_map.items():
|
||||||
|
if not is_engine_available(model_id):
|
||||||
|
continue
|
||||||
if isinstance(voices, dict):
|
if isinstance(voices, dict):
|
||||||
for voice_name in voices.keys():
|
for voice_name in voices.keys():
|
||||||
# First match wins (for duplicate voice names across models)
|
# First match wins (for duplicate voice names across models)
|
||||||
|
|
@ -85,9 +98,11 @@ async def lifespan(app):
|
||||||
voice_to_model_cache[voice_name] = model_id
|
voice_to_model_cache[voice_name] = model_id
|
||||||
print(f"Voice-to-model cache initialized with {len(voice_to_model_cache)} voices")
|
print(f"Voice-to-model cache initialized with {len(voice_to_model_cache)} voices")
|
||||||
|
|
||||||
# Build voices cache for /v1/voices endpoint
|
# Build voices cache for /v1/voices endpoint (skip engines we can't load)
|
||||||
models_data = []
|
models_data = []
|
||||||
for model_id, voices in voice_map.items():
|
for model_id, voices in voice_map.items():
|
||||||
|
if not is_engine_available(model_id):
|
||||||
|
continue
|
||||||
if isinstance(voices, dict):
|
if isinstance(voices, dict):
|
||||||
voice_list = list(voices.keys())
|
voice_list = list(voices.keys())
|
||||||
|
|
||||||
|
|
@ -731,51 +746,21 @@ def build_ffmpeg_args(response_format, input_format, sample_rate):
|
||||||
|
|
||||||
@app.get("/v1/models")
|
@app.get("/v1/models")
|
||||||
async def list_models():
|
async def list_models():
|
||||||
"""List all available TTS models (OpenAI-compatible format)"""
|
"""List all available TTS models (OpenAI-compatible format).
|
||||||
# Return minimal OpenAI-compatible model list (no extra fields)
|
|
||||||
# tts-1-qwen and tts-1-f5 enabled by default
|
Only advertise engines whose Python deps are actually loadable — otherwise
|
||||||
return {
|
clients try a model and get 503, which they treat as a transient outage.
|
||||||
"object": "list",
|
"""
|
||||||
"data": [
|
available = []
|
||||||
{
|
for model_id in ('tts-1-qwen', 'tts-1-f5'):
|
||||||
"id": "tts-1-qwen",
|
if is_engine_available(model_id):
|
||||||
|
available.append({
|
||||||
|
"id": model_id,
|
||||||
"object": "model",
|
"object": "model",
|
||||||
"created": 1700000000,
|
"created": 1700000000,
|
||||||
"owned_by": "uncloseai"
|
"owned_by": "uncloseai"
|
||||||
},
|
})
|
||||||
{
|
return {"object": "list", "data": available}
|
||||||
"id": "tts-1-f5",
|
|
||||||
"object": "model",
|
|
||||||
"created": 1700000000,
|
|
||||||
"owned_by": "uncloseai"
|
|
||||||
}
|
|
||||||
# Other models disabled by default:
|
|
||||||
# {
|
|
||||||
# "id": "tts-1",
|
|
||||||
# "object": "model",
|
|
||||||
# "created": 1700000000,
|
|
||||||
# "owned_by": "uncloseai"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "id": "tts-1-hd",
|
|
||||||
# "object": "model",
|
|
||||||
# "created": 1700000000,
|
|
||||||
# "owned_by": "uncloseai"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "id": "tts-1-silero",
|
|
||||||
# "object": "model",
|
|
||||||
# "created": 1700000000,
|
|
||||||
# "owned_by": "uncloseai"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "id": "tts-1-kokoro",
|
|
||||||
# "object": "model",
|
|
||||||
# "created": 1700000000,
|
|
||||||
# "owned_by": "uncloseai"
|
|
||||||
# }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
@app.get("/v1/voices")
|
@app.get("/v1/voices")
|
||||||
async def list_voices():
|
async def list_voices():
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue