Codex Codex commited on
Commit
e0d2309
Β·
1 Parent(s): 3e8abf3

Fix Kannada narration: 3-tier TTS + error surfacing + longer English audio

Browse files

Kannada was silently failing with no diagnostic info. Three changes:

1. Error surfacing: generate_kannada_gpu now logs each step (translation
success/failure, TTS success/failure) and propagates the actual error
message to the UI status bar so the user can see what's going wrong.

2. 3-tier Kannada TTS in indic_tts.py:
- Tier 1: sush0401/IndicF5-Kannada-Bedtime-v2 (user's fine-tuned model)
- Tier 2: facebook/mms-tts-kan (VITS fallback, GPU)
- Tier 3: gTTS Kannada (Google, no GPU, always works)
Each tier logs its result. Tier 3 guarantees Kannada audio is always
produced regardless of GPU model availability.

3. English audio length: voice-cloning sentence cap raised from 6 to 15
(6 sentences gave ~26s; 15 sentences gives ~1-1.5 minutes of audio).

Co-Authored-By: Codex <noreply@codex.ai>

Files changed (3) hide show
  1. app.py +19 -6
  2. indic_tts.py +65 -18
  3. requirements.txt +1 -0
app.py CHANGED
@@ -1157,9 +1157,9 @@ def generate_tts_cloned_gpu(text: str, ref_wav: str | None, mood: str = "calming
1157
  sentences = [text.strip() or "Sweet dreams."]
1158
 
1159
  has_ref = bool(ref_wav and os.path.exists(str(ref_wav)))
1160
- # Voice cloning is ~10-15s per sentence β€” cap at 6 to stay within 180s budget
1161
  if has_ref:
1162
- sentences = sentences[:6]
1163
 
1164
  silence = np.zeros(int(0.65 * sr), dtype=np.float32)
1165
  pieces = []
@@ -1196,9 +1196,19 @@ def generate_kannada_gpu(text: str, ref_wav: str, mood: str = "calming") -> str:
1196
  if not ref_wav or not os.path.exists(str(ref_wav)):
1197
  raise ValueError("Voice clip required to enable Kannada narration.")
1198
  from indic_text import translate_to_kannada
1199
- from indic_tts import narrate_kannada
1200
- kn_text = translate_to_kannada(text)
1201
- return narrate_kannada(ref_wav, "", kn_text, mood, 0.45)
 
 
 
 
 
 
 
 
 
 
1202
 
1203
 
1204
  def create_bedtime(ref_audio, hero_name, bedtime_genre, bedtime_mood):
@@ -1237,16 +1247,19 @@ def create_bedtime(ref_audio, hero_name, bedtime_genre, bedtime_mood):
1237
  yield (story_html, f"{title} β€” {kn_note}", en_audio_path, None)
1238
 
1239
  kn_audio_path = None
 
1240
  if ref_audio:
1241
  try:
1242
  kn_audio_path = generate_kannada_gpu(kn_source, ref_audio, mood)
1243
  except Exception as e:
 
1244
  logger.warning(f"Kannada TTS failed: {e}")
1245
 
1246
  total = round(time.perf_counter() - t0, 2)
1247
  done_msg = f"Done: {title} Β· {total}s"
1248
  if ref_audio and not kn_audio_path:
1249
- done_msg += " Β· Kannada narration failed"
 
1250
  elif not ref_audio:
1251
  done_msg += " Β· record your voice to get Kannada narration"
1252
 
 
1157
  sentences = [text.strip() or "Sweet dreams."]
1158
 
1159
  has_ref = bool(ref_wav and os.path.exists(str(ref_wav)))
1160
+ # Voice cloning ~5-8s/sentence; 15 sentences β‰ˆ 75-120s, well within 180s budget
1161
  if has_ref:
1162
+ sentences = sentences[:15]
1163
 
1164
  silence = np.zeros(int(0.65 * sr), dtype=np.float32)
1165
  pieces = []
 
1196
  if not ref_wav or not os.path.exists(str(ref_wav)):
1197
  raise ValueError("Voice clip required to enable Kannada narration.")
1198
  from indic_text import translate_to_kannada
1199
+ from indic_tts import narrate_kannada, _use_indic, _mms_model
1200
+ logger.info(f"Kannada: start. indic={_use_indic}, mms_loaded={_mms_model is not None}")
1201
+ try:
1202
+ kn_text = translate_to_kannada(text)
1203
+ except Exception as te:
1204
+ raise RuntimeError(f"Translation failed: {te}") from te
1205
+ logger.info(f"Kannada: translated {len(kn_text)} chars β†’ {kn_text[:80]!r}")
1206
+ try:
1207
+ path = narrate_kannada(ref_wav, "", kn_text, mood, 0.45)
1208
+ except Exception as te:
1209
+ raise RuntimeError(f"Kannada TTS failed: {te}") from te
1210
+ logger.info(f"Kannada: done β†’ {path}")
1211
+ return path
1212
 
1213
 
1214
  def create_bedtime(ref_audio, hero_name, bedtime_genre, bedtime_mood):
 
1247
  yield (story_html, f"{title} β€” {kn_note}", en_audio_path, None)
1248
 
1249
  kn_audio_path = None
1250
+ kn_error = None
1251
  if ref_audio:
1252
  try:
1253
  kn_audio_path = generate_kannada_gpu(kn_source, ref_audio, mood)
1254
  except Exception as e:
1255
+ kn_error = str(e)
1256
  logger.warning(f"Kannada TTS failed: {e}")
1257
 
1258
  total = round(time.perf_counter() - t0, 2)
1259
  done_msg = f"Done: {title} Β· {total}s"
1260
  if ref_audio and not kn_audio_path:
1261
+ short_err = (kn_error or "unknown error")[:80]
1262
+ done_msg += f" Β· Kannada failed: {short_err}"
1263
  elif not ref_audio:
1264
  done_msg += " Β· record your voice to get Kannada narration"
1265
 
indic_tts.py CHANGED
@@ -1,17 +1,21 @@
1
- """Kannada TTS β€” primary: sush0401/IndicF5-Kannada-Bedtime-v2 (fine-tuned, non-gated).
2
- Fallback: facebook/mms-tts-kan (VITS, 16kHz, no voice cloning).
 
3
 
4
- ZeroGPU pattern: models loaded to CPU at module scope so ZeroGPU packs their
5
  tensors. Inside inference functions (called from @spaces.GPU), .to("cuda") is
6
  called and ZeroGPU transfers packed tensors to GPU β€” no re-download needed.
7
  """
8
  from __future__ import annotations
 
9
  import os
10
  import re
11
  import tempfile
12
  import numpy as np
13
  import torch
14
 
 
 
15
  _FINETUNE_HUB = "sush0401/IndicF5-Kannada-Bedtime-v2"
16
  _FALLBACK_HUB = "facebook/mms-tts-kan"
17
 
@@ -25,27 +29,35 @@ MMS_SR = 16_000
25
  def _load_indic():
26
  global _indic_model, _use_indic
27
  from transformers import AutoModel
 
28
  # Load to CPU β€” ZeroGPU packs these tensors.
29
  _indic_model = AutoModel.from_pretrained(
30
  _FINETUNE_HUB, trust_remote_code=True,
31
  )
32
  _use_indic = True
 
33
 
34
 
35
  def _load_mms():
36
  global _mms_model, _mms_tok
37
  from transformers import VitsModel, AutoTokenizer
 
38
  _mms_tok = AutoTokenizer.from_pretrained(_FALLBACK_HUB)
39
  # Load to CPU β€” ZeroGPU packs these tensors.
40
  _mms_model = VitsModel.from_pretrained(_FALLBACK_HUB)
 
41
 
42
 
43
  def _get_model():
44
  if not _use_indic and _mms_model is None:
45
  try:
46
  _load_indic()
47
- except Exception:
48
- _load_mms()
 
 
 
 
49
  return _use_indic
50
 
51
 
@@ -73,8 +85,7 @@ def _split(text: str, max_chars: int = 200):
73
 
74
 
75
  def _narrate_indic(ref_wav: str, kannada_text: str) -> tuple[np.ndarray, int]:
76
- # Move to GPU β€” ZeroGPU intercepts this inside @spaces.GPU.
77
- model = _indic_model.to("cuda")
78
  silence_sr = 24_000
79
  silence = np.zeros(int(0.55 * silence_sr), dtype=np.float32)
80
  chunks = []
@@ -87,15 +98,16 @@ def _narrate_indic(ref_wav: str, kannada_text: str) -> tuple[np.ndarray, int]:
87
  if audio.size:
88
  chunks.append(audio)
89
  chunks.append(silence)
90
- return np.concatenate(chunks) if chunks else np.array([], dtype=np.float32), silence_sr
91
 
92
 
93
  def _narrate_mms(kannada_text: str) -> tuple[np.ndarray, int]:
94
- # Move to GPU β€” ZeroGPU intercepts this inside @spaces.GPU.
95
- model = _mms_model.to("cuda")
96
  silence = np.zeros(int(0.55 * MMS_SR), dtype=np.float32)
97
  chunks = []
98
  for sent in _split(kannada_text):
 
 
99
  inputs = _mms_tok(sent, return_tensors="pt").to(model.device)
100
  with torch.no_grad():
101
  wav = model(**inputs).waveform
@@ -107,25 +119,60 @@ def _narrate_mms(kannada_text: str) -> tuple[np.ndarray, int]:
107
  return full, MMS_SR
108
 
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  def narrate_kannada(ref_wav: str, ref_text: str, kannada_text: str,
111
  mood: str = "", energy: float = 0.45) -> str:
112
- """Narrate Kannada text. Uses fine-tuned IndicF5 with voice cloning if available,
113
- otherwise MMS-TTS-Kan (generic voice). Returns a temp WAV path."""
114
  text = (kannada_text or "").strip()
115
  if not text:
116
- raise ValueError("Please provide Kannada text to narrate.")
117
 
118
  use_indic = _get_model()
 
 
119
 
 
120
  if use_indic:
121
  try:
122
  full, sr = _narrate_indic(ref_wav or "", text)
123
- except Exception:
124
- if _mms_model is None:
125
- _load_mms()
 
 
 
 
126
  full, sr = _narrate_mms(text)
127
- else:
128
- full, sr = _narrate_mms(text)
 
 
 
 
 
 
 
 
 
129
 
130
  if not full.size:
131
  raise RuntimeError("TTS produced no audio.")
 
1
+ """Kannada TTS β€” tier 1: sush0401/IndicF5-Kannada-Bedtime-v2 (fine-tuned).
2
+ Tier 2: facebook/mms-tts-kan (VITS, 16kHz, no voice cloning).
3
+ Tier 3: gTTS (Google, always works, generic Kannada voice).
4
 
5
+ ZeroGPU pattern: GPU models loaded to CPU at module scope so ZeroGPU packs their
6
  tensors. Inside inference functions (called from @spaces.GPU), .to("cuda") is
7
  called and ZeroGPU transfers packed tensors to GPU β€” no re-download needed.
8
  """
9
  from __future__ import annotations
10
+ import logging
11
  import os
12
  import re
13
  import tempfile
14
  import numpy as np
15
  import torch
16
 
17
+ logger = logging.getLogger(__name__)
18
+
19
  _FINETUNE_HUB = "sush0401/IndicF5-Kannada-Bedtime-v2"
20
  _FALLBACK_HUB = "facebook/mms-tts-kan"
21
 
 
29
  def _load_indic():
30
  global _indic_model, _use_indic
31
  from transformers import AutoModel
32
+ logger.info(f"Loading IndicF5 from {_FINETUNE_HUB}")
33
  # Load to CPU β€” ZeroGPU packs these tensors.
34
  _indic_model = AutoModel.from_pretrained(
35
  _FINETUNE_HUB, trust_remote_code=True,
36
  )
37
  _use_indic = True
38
+ logger.info("IndicF5 loaded OK")
39
 
40
 
41
  def _load_mms():
42
  global _mms_model, _mms_tok
43
  from transformers import VitsModel, AutoTokenizer
44
+ logger.info(f"Loading MMS-TTS from {_FALLBACK_HUB}")
45
  _mms_tok = AutoTokenizer.from_pretrained(_FALLBACK_HUB)
46
  # Load to CPU β€” ZeroGPU packs these tensors.
47
  _mms_model = VitsModel.from_pretrained(_FALLBACK_HUB)
48
+ logger.info("MMS-TTS loaded OK")
49
 
50
 
51
  def _get_model():
52
  if not _use_indic and _mms_model is None:
53
  try:
54
  _load_indic()
55
+ except Exception as e:
56
+ logger.warning(f"IndicF5 load failed ({e}); trying MMS-TTS")
57
+ try:
58
+ _load_mms()
59
+ except Exception as e2:
60
+ logger.warning(f"MMS-TTS load failed too ({e2}); will use gTTS fallback")
61
  return _use_indic
62
 
63
 
 
85
 
86
 
87
  def _narrate_indic(ref_wav: str, kannada_text: str) -> tuple[np.ndarray, int]:
88
+ model = _indic_model.to("cuda") # ZeroGPU intercepts inside @spaces.GPU
 
89
  silence_sr = 24_000
90
  silence = np.zeros(int(0.55 * silence_sr), dtype=np.float32)
91
  chunks = []
 
98
  if audio.size:
99
  chunks.append(audio)
100
  chunks.append(silence)
101
+ return (np.concatenate(chunks) if chunks else np.array([], dtype=np.float32)), silence_sr
102
 
103
 
104
  def _narrate_mms(kannada_text: str) -> tuple[np.ndarray, int]:
105
+ model = _mms_model.to("cuda") # ZeroGPU intercepts inside @spaces.GPU
 
106
  silence = np.zeros(int(0.55 * MMS_SR), dtype=np.float32)
107
  chunks = []
108
  for sent in _split(kannada_text):
109
+ if not sent.strip():
110
+ continue
111
  inputs = _mms_tok(sent, return_tensors="pt").to(model.device)
112
  with torch.no_grad():
113
  wav = model(**inputs).waveform
 
119
  return full, MMS_SR
120
 
121
 
122
+ def _narrate_gtts(kannada_text: str) -> tuple[np.ndarray, int]:
123
+ """Last-resort: gTTS Kannada (generic Google voice, no GPU needed)."""
124
+ import io
125
+ import librosa
126
+ from gtts import gTTS
127
+ logger.info("Using gTTS Kannada fallback")
128
+ tts = gTTS(text=kannada_text, lang="kn", slow=True)
129
+ fd, mp3_path = tempfile.mkstemp(suffix=".mp3")
130
+ os.close(fd)
131
+ try:
132
+ tts.save(mp3_path)
133
+ data, sr = librosa.load(mp3_path, sr=22050, mono=True)
134
+ finally:
135
+ try:
136
+ os.unlink(mp3_path)
137
+ except OSError:
138
+ pass
139
+ return data.astype(np.float32), sr
140
+
141
+
142
  def narrate_kannada(ref_wav: str, ref_text: str, kannada_text: str,
143
  mood: str = "", energy: float = 0.45) -> str:
144
+ """Narrate Kannada text. Tries: IndicF5 β†’ MMS-TTS β†’ gTTS. Returns WAV path."""
 
145
  text = (kannada_text or "").strip()
146
  if not text:
147
+ raise ValueError("No Kannada text to narrate.")
148
 
149
  use_indic = _get_model()
150
+ full = np.array([], dtype=np.float32)
151
+ sr = MMS_SR
152
 
153
+ # Tier 1: user's fine-tuned IndicF5
154
  if use_indic:
155
  try:
156
  full, sr = _narrate_indic(ref_wav or "", text)
157
+ logger.info("IndicF5 narration OK")
158
+ except Exception as e:
159
+ logger.warning(f"IndicF5 narration failed ({e}); trying MMS-TTS")
160
+
161
+ # Tier 2: MMS-TTS-Kan
162
+ if not full.size and _mms_model is not None:
163
+ try:
164
  full, sr = _narrate_mms(text)
165
+ logger.info("MMS-TTS narration OK")
166
+ except Exception as e:
167
+ logger.warning(f"MMS-TTS narration failed ({e}); trying gTTS")
168
+
169
+ # Tier 3: gTTS (always works, no GPU needed)
170
+ if not full.size:
171
+ try:
172
+ full, sr = _narrate_gtts(text)
173
+ logger.info("gTTS narration OK")
174
+ except Exception as e:
175
+ raise RuntimeError(f"All Kannada TTS tiers failed. Last error: {e}") from e
176
 
177
  if not full.size:
178
  raise RuntimeError("TTS produced no audio.")
requirements.txt CHANGED
@@ -27,3 +27,4 @@ requests
27
  huggingface_hub
28
  soundfile
29
  librosa>=0.10.0
 
 
27
  huggingface_hub
28
  soundfile
29
  librosa>=0.10.0
30
+ gtts