Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
|
@@ -115,13 +115,9 @@ PRESET_VOICES = [
|
|
| 115 |
]
|
| 116 |
|
| 117 |
# YourVoic voices mapped by language
|
| 118 |
-
#
|
| 119 |
YOURVOIC_VOICE_MAP = {
|
| 120 |
-
#
|
| 121 |
-
"Afrikaans": ["Annika", "Willem"],
|
| 122 |
-
"Amharic": ["Abebe", "Meron"],
|
| 123 |
-
"Swahili": ["Jabari", "Amara"],
|
| 124 |
-
# Indian
|
| 125 |
"Hindi": ["Rahul", "Deepika", "Aditya"],
|
| 126 |
"Bengali": ["Sneha", "Aryan"],
|
| 127 |
"Marathi": ["Anjali", "Rohan"],
|
|
@@ -131,6 +127,10 @@ YOURVOIC_VOICE_MAP = {
|
|
| 131 |
"Kannada": ["Divya", "Karthik"],
|
| 132 |
"Malayalam": ["Nikhil", "Ammu"],
|
| 133 |
"Punjabi": ["Vikram", "Simran"],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
# English fallback
|
| 135 |
"English": ["Peter", "Sarah", "Caleb"],
|
| 136 |
}
|
|
@@ -146,21 +146,25 @@ for lang, voices in YOURVOIC_VOICE_MAP.items():
|
|
| 146 |
|
| 147 |
def get_yourvoic_voice_for_language(language, selected_voice):
|
| 148 |
"""Get a valid voice name for the given language.
|
| 149 |
-
|
| 150 |
voice_name = get_voice_name(selected_voice)
|
| 151 |
valid_voices = YOURVOIC_VOICE_MAP.get(language, [])
|
| 152 |
|
| 153 |
-
# If selected voice is
|
| 154 |
if voice_name in valid_voices:
|
| 155 |
return voice_name
|
| 156 |
|
| 157 |
-
#
|
| 158 |
if valid_voices:
|
| 159 |
return valid_voices[0]
|
| 160 |
|
| 161 |
-
#
|
| 162 |
yourvoic_lang = LANGUAGES.get(language, {}).get("yourvoic", "en-US")
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
|
| 166 |
# Cache for API-fetched voices
|
|
@@ -173,7 +177,7 @@ def _fetch_yourvoic_voice(yourvoic_lang):
|
|
| 173 |
|
| 174 |
yv_key = os.environ.get("YOURVOIC_API_KEY", "")
|
| 175 |
if not yv_key:
|
| 176 |
-
return
|
| 177 |
|
| 178 |
try:
|
| 179 |
resp = http_requests.get(
|
|
@@ -182,22 +186,24 @@ def _fetch_yourvoic_voice(yourvoic_lang):
|
|
| 182 |
timeout=15,
|
| 183 |
)
|
| 184 |
print(f"[YourVoic] Voices API for {yourvoic_lang}: status={resp.status_code}")
|
|
|
|
| 185 |
if resp.status_code == 200:
|
| 186 |
data = resp.json()
|
|
|
|
| 187 |
voices = data if isinstance(data, list) else data.get("voices", data.get("data", []))
|
| 188 |
if voices:
|
| 189 |
-
# Get first voice name
|
| 190 |
if isinstance(voices[0], dict):
|
| 191 |
-
voice_name = voices[0].get("name", voices[0].get("voice_id", "
|
| 192 |
else:
|
| 193 |
voice_name = str(voices[0])
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
| 197 |
except Exception as e:
|
| 198 |
print(f"[YourVoic] Voice lookup failed for {yourvoic_lang}: {e}")
|
| 199 |
|
| 200 |
-
return
|
| 201 |
|
| 202 |
YOURVOIC_MODELS = [
|
| 203 |
"aura-prime -- Balanced quality and speed (recommended)",
|
|
@@ -715,6 +721,15 @@ And he would smile - that slow, careful smile that seemed to cost him something
|
|
| 715 |
DESCRIPTION = """
|
| 716 |
# Audiobook Generator
|
| 717 |
### English Text to Multi-Language Audiobook
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
"""
|
| 719 |
|
| 720 |
# Build language dropdown
|
|
@@ -868,7 +883,13 @@ with gr.Blocks(title="Audiobook Generator") as demo:
|
|
| 868 |
|
| 869 |
gr.Markdown(
|
| 870 |
"---\n"
|
| 871 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
|
| 873 |
if __name__ == "__main__":
|
| 874 |
demo.launch()
|
|
|
|
| 115 |
]
|
| 116 |
|
| 117 |
# YourVoic voices mapped by language
|
| 118 |
+
# Only include CONFIRMED working voice names
|
| 119 |
YOURVOIC_VOICE_MAP = {
|
| 120 |
+
# Indian - confirmed working
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
"Hindi": ["Rahul", "Deepika", "Aditya"],
|
| 122 |
"Bengali": ["Sneha", "Aryan"],
|
| 123 |
"Marathi": ["Anjali", "Rohan"],
|
|
|
|
| 127 |
"Kannada": ["Divya", "Karthik"],
|
| 128 |
"Malayalam": ["Nikhil", "Ammu"],
|
| 129 |
"Punjabi": ["Vikram", "Simran"],
|
| 130 |
+
# African - NOT confirmed, will use API lookup
|
| 131 |
+
# "Afrikaans": [], # unknown - use API
|
| 132 |
+
# "Amharic": [], # unknown - use API
|
| 133 |
+
# "Swahili": [], # unknown - use API
|
| 134 |
# English fallback
|
| 135 |
"English": ["Peter", "Sarah", "Caleb"],
|
| 136 |
}
|
|
|
|
| 146 |
|
| 147 |
def get_yourvoic_voice_for_language(language, selected_voice):
|
| 148 |
"""Get a valid voice name for the given language.
|
| 149 |
+
Uses API lookup for languages without confirmed voices."""
|
| 150 |
voice_name = get_voice_name(selected_voice)
|
| 151 |
valid_voices = YOURVOIC_VOICE_MAP.get(language, [])
|
| 152 |
|
| 153 |
+
# If selected voice is confirmed valid for this language, use it
|
| 154 |
if voice_name in valid_voices:
|
| 155 |
return voice_name
|
| 156 |
|
| 157 |
+
# If we have confirmed voices for this language, use the first one
|
| 158 |
if valid_voices:
|
| 159 |
return valid_voices[0]
|
| 160 |
|
| 161 |
+
# No confirmed voices - query the API
|
| 162 |
yourvoic_lang = LANGUAGES.get(language, {}).get("yourvoic", "en-US")
|
| 163 |
+
api_voice = _fetch_yourvoic_voice(yourvoic_lang)
|
| 164 |
+
if api_voice:
|
| 165 |
+
return api_voice
|
| 166 |
+
|
| 167 |
+
return "Peter" # ultimate fallback
|
| 168 |
|
| 169 |
|
| 170 |
# Cache for API-fetched voices
|
|
|
|
| 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(
|
|
|
|
| 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)",
|
|
|
|
| 721 |
DESCRIPTION = """
|
| 722 |
# Audiobook Generator
|
| 723 |
### English Text to Multi-Language Audiobook
|
| 724 |
+
**Three Voice Engines**
|
| 725 |
+
|
| 726 |
+
| Engine | Languages | Best for |
|
| 727 |
+
|--------|-----------|----------|
|
| 728 |
+
| **Qwen Preset** (20 voices) | English, Chinese, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian, Arabic | General audiobooks |
|
| 729 |
+
| **Qwen Clone** (your voice) | Same 10 core (excl. Arabic) | Personalized narration |
|
| 730 |
+
| **YourVoic** (1000+ emotional voices) | Hindi, Bengali, Marathi, Telugu, Tamil, Gujarati, Kannada, Malayalam, Punjabi, Swahili, Amharic, Afrikaans | Indian/African languages |
|
| 731 |
+
|
| 732 |
+
The app automatically selects the right engine based on your chosen language.
|
| 733 |
"""
|
| 734 |
|
| 735 |
# Build language dropdown
|
|
|
|
| 883 |
|
| 884 |
gr.Markdown(
|
| 885 |
"---\n"
|
| 886 |
+
"**Engines:**\n\n"
|
| 887 |
+
"**Qwen Preset:** 11 languages (EN, ZH, JA, KO, DE, FR, RU, PT, ES, IT, AR) via Qwen3.5-Omni-Plus\n\n"
|
| 888 |
+
"**Qwen Clone:** 10 languages (same minus Arabic) via Qwen3-TTS-VC\n\n"
|
| 889 |
+
"**YourVoic:** Hindi, Bengali, Marathi, Telugu, Tamil, Gujarati, Kannada, Malayalam, Punjabi, "
|
| 890 |
+
"Swahili, Amharic, Afrikaans via YourVoic API with emotional voices\n\n"
|
| 891 |
+
"Built with Gradio | Qwen by Alibaba | YourVoic by YourVoic Private Limited"
|
| 892 |
+
)
|
| 893 |
|
| 894 |
if __name__ == "__main__":
|
| 895 |
demo.launch()
|