PlotweaverModel commited on
Commit
3d91d95
·
verified ·
1 Parent(s): be31881

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -33
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 a valid voice for a language."""
175
- if yourvoic_lang in _yourvoic_voice_cache:
176
- return _yourvoic_voice_cache[yourvoic_lang]
 
177
 
178
  yv_key = os.environ.get("YOURVOIC_API_KEY", "")
179
  if not yv_key:
180
  return None
181
 
182
- try:
183
- resp = http_requests.get(
184
- f"{YOURVOIC_VOICES_URL}?language={yourvoic_lang}",
185
- headers={"X-API-Key": yv_key},
186
- timeout=15,
187
- )
188
- print(f"[YourVoic] Voices API for {yourvoic_lang}: status={resp.status_code}")
189
- print(f"[YourVoic] Voices response: {resp.text[:500]}")
190
- if resp.status_code == 200:
191
- data = resp.json()
192
- # Handle different response formats
193
- voices = data if isinstance(data, list) else data.get("voices", data.get("data", []))
194
- if voices:
195
- if isinstance(voices[0], dict):
196
- voice_name = voices[0].get("name", voices[0].get("voice_id", voices[0].get("voice", None)))
197
- else:
198
- voice_name = str(voices[0])
199
- if voice_name:
200
- print(f"[YourVoic] Found voice for {yourvoic_lang}: {voice_name}")
201
- _yourvoic_voice_cache[yourvoic_lang] = voice_name
202
- return voice_name
203
- except Exception as e:
204
- print(f"[YourVoic] Voice lookup failed for {yourvoic_lang}: {e}")
 
 
 
 
 
 
 
 
 
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 - auto-map voice to language
487
  yourvoic_lang = lang_config.get("yourvoic", "en-US")
488
- valid_voice = get_yourvoic_voice_for_language(language, voice)
489
- print(f"[YourVoic] Language: {language}, requested voice: {voice}, using: {valid_voice}")
490
 
491
  payload = {
492
  "text": final_text,
493
- "voice": valid_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 = get_voice_name(yourvoic_voice_label)
625
  yv_model = get_yourvoic_model(yourvoic_model_label)
626
- wav_path, transcript, error = generate_speech_yourvoic(
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,