Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
|
@@ -170,41 +170,95 @@ def get_yourvoic_voice_for_language(language, selected_voice):
|
|
| 170 |
# Cache for API-fetched voices
|
| 171 |
_yourvoic_voice_cache = {}
|
| 172 |
|
| 173 |
-
def _fetch_yourvoic_voice(yourvoic_lang):
|
| 174 |
-
"""Query YourVoic /v1/voices endpoint to get
|
| 175 |
-
|
| 176 |
-
|
|
|
|
| 177 |
|
| 178 |
yv_key = os.environ.get("YOURVOIC_API_KEY", "")
|
| 179 |
if not yv_key:
|
| 180 |
return None
|
| 181 |
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
if
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
return None
|
| 207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
YOURVOIC_MODELS = [
|
| 209 |
"aura-prime -- Balanced quality and speed (recommended)",
|
| 210 |
"aura-lite -- Fast, good for previews",
|
|
@@ -483,14 +537,13 @@ def generate_speech_yourvoic(client, text, voice, yv_model, emotion, language, l
|
|
| 483 |
except Exception as e:
|
| 484 |
print(f"[YourVoic] Translation failed, using English: {e}")
|
| 485 |
|
| 486 |
-
# Build request -
|
| 487 |
yourvoic_lang = lang_config.get("yourvoic", "en-US")
|
| 488 |
-
|
| 489 |
-
print(f"[YourVoic] Language: {language}, requested voice: {voice}, using: {valid_voice}")
|
| 490 |
|
| 491 |
payload = {
|
| 492 |
"text": final_text,
|
| 493 |
-
"voice":
|
| 494 |
"language": yourvoic_lang,
|
| 495 |
"model": yv_model,
|
| 496 |
"speed": 0.9,
|
|
@@ -621,9 +674,9 @@ def generate_audiobook(text_input, file_input, target_language, voice_mode,
|
|
| 621 |
wav_path, transcript, error = None, None, None
|
| 622 |
|
| 623 |
if use_yourvoic:
|
| 624 |
-
yv_voice =
|
| 625 |
yv_model = get_yourvoic_model(yourvoic_model_label)
|
| 626 |
-
wav_path, transcript, error =
|
| 627 |
client, chunk, yv_voice, yv_model, yourvoic_emotion,
|
| 628 |
target_language, lang_config, translate,
|
| 629 |
yv_key, i, tmp_dir,
|
|
|
|
| 170 |
# Cache for API-fetched voices
|
| 171 |
_yourvoic_voice_cache = {}
|
| 172 |
|
| 173 |
+
def _fetch_yourvoic_voice(yourvoic_lang, model="aura-prime"):
|
| 174 |
+
"""Query YourVoic /v1/voices endpoint to get valid voices for a language + model."""
|
| 175 |
+
cache_key = f"{yourvoic_lang}:{model}"
|
| 176 |
+
if cache_key in _yourvoic_voice_cache:
|
| 177 |
+
return _yourvoic_voice_cache[cache_key]
|
| 178 |
|
| 179 |
yv_key = os.environ.get("YOURVOIC_API_KEY", "")
|
| 180 |
if not yv_key:
|
| 181 |
return None
|
| 182 |
|
| 183 |
+
# Try with model parameter first, then without
|
| 184 |
+
for url_params in [
|
| 185 |
+
f"?language={yourvoic_lang}&model={model}",
|
| 186 |
+
f"?language={yourvoic_lang}",
|
| 187 |
+
]:
|
| 188 |
+
try:
|
| 189 |
+
resp = http_requests.get(
|
| 190 |
+
f"{YOURVOIC_VOICES_URL}{url_params}",
|
| 191 |
+
headers={"X-API-Key": yv_key},
|
| 192 |
+
timeout=15,
|
| 193 |
+
)
|
| 194 |
+
print(f"[YourVoic] Voices API {url_params}: status={resp.status_code}")
|
| 195 |
+
if resp.status_code == 200:
|
| 196 |
+
data = resp.json()
|
| 197 |
+
voices = data if isinstance(data, list) else data.get("voices", data.get("data", []))
|
| 198 |
+
if voices and isinstance(voices[0], dict):
|
| 199 |
+
# Return all voice names for trying
|
| 200 |
+
all_names = []
|
| 201 |
+
for v in voices[:10]: # first 10
|
| 202 |
+
for field in ["id", "name", "voice_id", "voice"]:
|
| 203 |
+
if field in v and v[field]:
|
| 204 |
+
all_names.append(str(v[field]))
|
| 205 |
+
break
|
| 206 |
+
if all_names:
|
| 207 |
+
# Deduplicate preserving order
|
| 208 |
+
seen = set()
|
| 209 |
+
unique = [x for x in all_names if not (x in seen or seen.add(x))]
|
| 210 |
+
print(f"[YourVoic] Available voices for {yourvoic_lang}: {unique[:5]}")
|
| 211 |
+
_yourvoic_voice_cache[cache_key] = unique
|
| 212 |
+
return unique
|
| 213 |
+
except Exception as e:
|
| 214 |
+
print(f"[YourVoic] Voice lookup failed for {yourvoic_lang}: {e}")
|
| 215 |
|
| 216 |
return None
|
| 217 |
|
| 218 |
+
|
| 219 |
+
def generate_speech_yourvoic_with_retry(client, text, voice, yv_model, emotion, language, lang_config,
|
| 220 |
+
translate, api_key, chunk_index, output_dir):
|
| 221 |
+
"""Wrapper that tries multiple voice names if the first one fails."""
|
| 222 |
+
yourvoic_lang = lang_config.get("yourvoic", "en-US")
|
| 223 |
+
|
| 224 |
+
# Get list of candidate voices
|
| 225 |
+
candidates = []
|
| 226 |
+
|
| 227 |
+
# 1. Try user-selected voice
|
| 228 |
+
user_voice = get_voice_name(voice)
|
| 229 |
+
candidates.append(user_voice)
|
| 230 |
+
|
| 231 |
+
# 2. Try hardcoded voices
|
| 232 |
+
hardcoded = YOURVOIC_VOICE_MAP.get(language, [])
|
| 233 |
+
candidates.extend(hardcoded)
|
| 234 |
+
|
| 235 |
+
# 3. Try API-fetched voices
|
| 236 |
+
api_voices = _fetch_yourvoic_voice(yourvoic_lang, yv_model)
|
| 237 |
+
if api_voices:
|
| 238 |
+
candidates.extend(api_voices)
|
| 239 |
+
|
| 240 |
+
# Deduplicate preserving order
|
| 241 |
+
seen = set()
|
| 242 |
+
candidates = [x for x in candidates if not (x in seen or seen.add(x))]
|
| 243 |
+
|
| 244 |
+
# Try each candidate until one works
|
| 245 |
+
for i, candidate_voice in enumerate(candidates[:5]): # try up to 5
|
| 246 |
+
print(f"[YourVoic] Trying voice '{candidate_voice}' for {language} (attempt {i+1})")
|
| 247 |
+
wav_path, transcript, error = generate_speech_yourvoic(
|
| 248 |
+
client, text, candidate_voice, yv_model, emotion, language, lang_config,
|
| 249 |
+
translate, api_key, chunk_index, output_dir,
|
| 250 |
+
)
|
| 251 |
+
if wav_path:
|
| 252 |
+
# Cache this working voice for future chunks
|
| 253 |
+
if language not in YOURVOIC_VOICE_MAP or not YOURVOIC_VOICE_MAP.get(language):
|
| 254 |
+
YOURVOIC_VOICE_MAP[language] = [candidate_voice]
|
| 255 |
+
return wav_path, transcript, None
|
| 256 |
+
if error and "Invalid voice name" not in str(error):
|
| 257 |
+
# Non-voice error (credits, etc) - don't try more voices
|
| 258 |
+
return None, transcript, error
|
| 259 |
+
|
| 260 |
+
return None, text, f"No valid voice found for {language}. Tried: {candidates[:5]}"
|
| 261 |
+
|
| 262 |
YOURVOIC_MODELS = [
|
| 263 |
"aura-prime -- Balanced quality and speed (recommended)",
|
| 264 |
"aura-lite -- Fast, good for previews",
|
|
|
|
| 537 |
except Exception as e:
|
| 538 |
print(f"[YourVoic] Translation failed, using English: {e}")
|
| 539 |
|
| 540 |
+
# Build request - voice is passed directly (already resolved by caller)
|
| 541 |
yourvoic_lang = lang_config.get("yourvoic", "en-US")
|
| 542 |
+
print(f"[YourVoic] Language: {language}, voice: {voice}")
|
|
|
|
| 543 |
|
| 544 |
payload = {
|
| 545 |
"text": final_text,
|
| 546 |
+
"voice": voice,
|
| 547 |
"language": yourvoic_lang,
|
| 548 |
"model": yv_model,
|
| 549 |
"speed": 0.9,
|
|
|
|
| 674 |
wav_path, transcript, error = None, None, None
|
| 675 |
|
| 676 |
if use_yourvoic:
|
| 677 |
+
yv_voice = yourvoic_voice_label
|
| 678 |
yv_model = get_yourvoic_model(yourvoic_model_label)
|
| 679 |
+
wav_path, transcript, error = generate_speech_yourvoic_with_retry(
|
| 680 |
client, chunk, yv_voice, yv_model, yourvoic_emotion,
|
| 681 |
target_language, lang_config, translate,
|
| 682 |
yv_key, i, tmp_dir,
|