Codex commited on
Commit
44cee7a
·
1 Parent(s): cdeff8b

Fix Kannada narration: swap gated ai4bharat models for open alternatives

Browse files

indic_text.py: replace gated indictrans2-en-indic-1B with
facebook/nllb-200-distilled-600M (non-gated, 600M params, kan_Knda target)

indic_tts.py: primary = sush0401/IndicF5-Kannada-Bedtime-v2 (user's
fine-tuned model, non-gated, full voice cloning via trust_remote_code);
fallback = facebook/mms-tts-kan (VITS, always works, no voice cloning)

app.py: restore load_translation + load_kannada_tts to module-scope ZeroGPU
block (models are no longer gated so pre-loading is safe again);
remove HF_TOKEN gate from generate_kannada_gpu

config.py: update TRANSLATION_MODEL + KANNADA_TTS_MODEL hub_ids;
remove KANNADA_FINETUNE (fine-tune is now the primary model itself)

requirements.txt: remove IndicTransToolkit (gated dependency, no longer needed)

README.md: update models list with the 3 new models

Built by Codex

Files changed (6) hide show
  1. README.md +3 -2
  2. app.py +6 -12
  3. config.py +5 -7
  4. indic_text.py +25 -48
  5. indic_tts.py +88 -50
  6. requirements.txt +0 -3
README.md CHANGED
@@ -21,8 +21,9 @@ models:
21
  - black-forest-labs/FLUX.2-klein-4B
22
  - openbmb/MiniCPM5-1B
23
  - openbmb/VoxCPM2
24
- - ai4bharat/IndicF5
25
- - ai4bharat/indictrans2-en-indic-1B
 
26
  ---
27
 
28
  # DoodleBook 📚🖍️
 
21
  - black-forest-labs/FLUX.2-klein-4B
22
  - openbmb/MiniCPM5-1B
23
  - openbmb/VoxCPM2
24
+ - sush0401/IndicF5-Kannada-Bedtime-v2
25
+ - facebook/nllb-200-distilled-600M
26
+ - facebook/mms-tts-kan
27
  ---
28
 
29
  # DoodleBook 📚🖍️
app.py CHANGED
@@ -112,10 +112,9 @@ def load_kannada_tts():
112
 
113
 
114
  if ON_ZEROGPU:
115
- # translation + kannada_tts are gated repos (ai4bharat) that require HF_TOKEN.
116
- # Load them lazily at inference time only so a missing token doesn't crash startup.
117
  for _name, _loader in (
118
  ("flux", load_flux), ("story", load_story), ("tts", load_tts),
 
119
  ):
120
  try:
121
  _loader()
@@ -1183,20 +1182,15 @@ def generate_tts_cloned_gpu(text: str, ref_wav: str | None, mood: str = "calming
1183
 
1184
  @spaces.GPU(duration=120)
1185
  def generate_kannada_gpu(text: str, ref_wav: str, mood: str = "calming") -> str:
1186
- """Translate English story to Kannada and narrate via IndicF5. Returns WAV path.
1187
- Requires HF_TOKEN set in Space secrets (both ai4bharat models are gated)."""
 
1188
  if not ref_wav or not os.path.exists(str(ref_wav)):
1189
- raise ValueError("Voice clip required for Kannada narration.")
1190
- if not os.environ.get("HF_TOKEN"):
1191
- raise ValueError(
1192
- "Kannada narration needs HF_TOKEN in Space secrets "
1193
- "(ai4bharat/IndicF5 and indictrans2-en-indic-1B are gated). "
1194
- "English narration works without it."
1195
- )
1196
  from indic_text import translate_to_kannada
1197
  from indic_tts import narrate_kannada
1198
  kn_text = translate_to_kannada(text)
1199
- return narrate_kannada(ref_wav, "ಇದು ನನ್ನ ಧ್ವನಿ", kn_text, mood, 0.45)
1200
 
1201
 
1202
  def create_bedtime(ref_audio, hero_name, bedtime_genre, bedtime_mood):
 
112
 
113
 
114
  if ON_ZEROGPU:
 
 
115
  for _name, _loader in (
116
  ("flux", load_flux), ("story", load_story), ("tts", load_tts),
117
+ ("translation", load_translation), ("kannada_tts", load_kannada_tts),
118
  ):
119
  try:
120
  _loader()
 
1182
 
1183
  @spaces.GPU(duration=120)
1184
  def generate_kannada_gpu(text: str, ref_wav: str, mood: str = "calming") -> str:
1185
+ """Translate English story to Kannada (NLLB-200) and narrate via MMS-TTS-Kan.
1186
+ ref_wav is accepted for API compatibility but MMS-TTS uses a fixed voice.
1187
+ Returns a WAV file path."""
1188
  if not ref_wav or not os.path.exists(str(ref_wav)):
1189
+ raise ValueError("Voice clip required to enable Kannada narration.")
 
 
 
 
 
 
1190
  from indic_text import translate_to_kannada
1191
  from indic_tts import narrate_kannada
1192
  kn_text = translate_to_kannada(text)
1193
+ return narrate_kannada(ref_wav, "", kn_text, mood, 0.45)
1194
 
1195
 
1196
  def create_bedtime(ref_audio, hero_name, bedtime_genre, bedtime_mood):
config.py CHANGED
@@ -120,16 +120,16 @@ TTS_MODEL = ModelConfig(
120
  # ── Bedtime Voice tab models ────────────────────────────────────────────────
121
 
122
  TRANSLATION_MODEL = ModelConfig(
123
- hub_id="ai4bharat/indictrans2-en-indic-1B",
124
- params_b=1.0,
125
- license=LicenseType.APACHE_2_0,
126
- vram_gb=3.0,
127
  modal_gpu="T4",
128
  modal_memory=8192,
129
  )
130
 
131
  KANNADA_TTS_MODEL = ModelConfig(
132
- hub_id="ai4bharat/IndicF5",
133
  params_b=0.5,
134
  license=LicenseType.APACHE_2_0,
135
  vram_gb=2.0,
@@ -137,8 +137,6 @@ KANNADA_TTS_MODEL = ModelConfig(
137
  modal_memory=8192,
138
  )
139
 
140
- KANNADA_FINETUNE: str = "mitvho09/IndicF5-Kannada-Bedtime-v2"
141
-
142
  BEDTIME_GENRES: list = ["Animals", "Dragons", "Ocean", "Forest", "Space", "Kingdom"]
143
  BEDTIME_MOODS: list = ["Calming", "Dreamy", "Magical", "Cozy"]
144
 
 
120
  # ── Bedtime Voice tab models ────────────────────────────────────────────────
121
 
122
  TRANSLATION_MODEL = ModelConfig(
123
+ hub_id="facebook/nllb-200-distilled-600M",
124
+ params_b=0.6,
125
+ license=LicenseType.MIT,
126
+ vram_gb=2.5,
127
  modal_gpu="T4",
128
  modal_memory=8192,
129
  )
130
 
131
  KANNADA_TTS_MODEL = ModelConfig(
132
+ hub_id="sush0401/IndicF5-Kannada-Bedtime-v2",
133
  params_b=0.5,
134
  license=LicenseType.APACHE_2_0,
135
  vram_gb=2.0,
 
137
  modal_memory=8192,
138
  )
139
 
 
 
140
  BEDTIME_GENRES: list = ["Animals", "Dragons", "Ocean", "Forest", "Space", "Kingdom"]
141
  BEDTIME_MOODS: list = ["Calming", "Dreamy", "Magical", "Cozy"]
142
 
indic_text.py CHANGED
@@ -1,24 +1,22 @@
1
- """English → Kannada translation via AI4Bharat IndicTrans2."""
2
  from __future__ import annotations
3
- import os
4
  import re
5
  import torch
6
- from config import TRANSLATION_MODEL
7
 
8
- _HUB_ID = TRANSLATION_MODEL.hub_id
9
- _tok = None
10
- _model = None
 
 
11
 
12
 
13
  def _get_model():
14
- global _tok, _model
15
- if _tok is None or _model is None:
16
- from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
17
- token = os.environ.get("HF_TOKEN") or None
18
- _tok = AutoTokenizer.from_pretrained(_HUB_ID, trust_remote_code=True, token=token)
19
- _model = AutoModelForSeq2SeqLM.from_pretrained(_HUB_ID, trust_remote_code=True, token=token)
20
- _model = _model.to("cuda").eval()
21
- return _tok, _model
22
 
23
 
24
  try:
@@ -27,45 +25,24 @@ except Exception:
27
  pass
28
 
29
 
30
- def _split_sentences(text: str, max_chars: int = 180):
31
- parts = re.split(r"(?<=[.!?।])\s+|\n+", text.strip())
32
- out = []
33
- for p in parts:
34
- p = p.strip()
35
- if not p:
36
- continue
37
- while len(p) > max_chars:
38
- cut = p.rfind(" ", 0, max_chars)
39
- cut = cut if cut > 0 else max_chars
40
- out.append(p[:cut].strip())
41
- p = p[cut:].strip()
42
- out.append(p)
43
- return out or [text.strip()]
44
-
45
-
46
  def translate_to_kannada(en_text: str) -> str:
47
- """Translate English story text to Kannada script."""
48
  text = (en_text or "").strip()
49
  if not text:
50
  raise ValueError("Nothing to translate.")
51
 
52
- from IndicTransToolkit.processor import IndicProcessor
53
- tok, model = _get_model()
54
- ip = IndicProcessor(inference=True)
55
 
56
- sents = _split_sentences(text, max_chars=180)
57
- batch = ip.preprocess_batch(sents, src_lang="eng_Latn", tgt_lang="kan_Knda")
58
- inputs = tok(batch, truncation=True, padding="longest", return_tensors="pt").to(model.device)
59
 
60
- with torch.inference_mode():
61
- generated = model.generate(
62
- **inputs, max_length=512, num_beams=5,
63
- num_return_sequences=1, length_penalty=1.0,
64
- )
65
- decoded = tok.batch_decode(generated, skip_special_tokens=True)
66
- translations = ip.postprocess_batch(decoded, lang="kan_Knda")
67
 
68
- kn = " ".join(t.strip() for t in translations if t.strip())
69
- if not kn:
70
- raise RuntimeError("Translation returned empty Kannada text.")
71
- return kn
 
1
+ """English → Kannada translation via facebook/nllb-200-distilled-600M (non-gated)."""
2
  from __future__ import annotations
 
3
  import re
4
  import torch
 
5
 
6
+ _HUB_ID = "facebook/nllb-200-distilled-600M"
7
+ _SRC = "eng_Latn"
8
+ _TGT = "kan_Knda"
9
+ _model = None
10
+ _tok = None
11
 
12
 
13
  def _get_model():
14
+ global _model, _tok
15
+ if _model is None:
16
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
17
+ _tok = AutoTokenizer.from_pretrained(_HUB_ID, src_lang=_SRC)
18
+ _model = AutoModelForSeq2SeqLM.from_pretrained(_HUB_ID).to("cuda").eval()
19
+ return _model, _tok
 
 
20
 
21
 
22
  try:
 
25
  pass
26
 
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  def translate_to_kannada(en_text: str) -> str:
29
+ """Translate an English string to Kannada via NLLB-200."""
30
  text = (en_text or "").strip()
31
  if not text:
32
  raise ValueError("Nothing to translate.")
33
 
34
+ model, tok = _get_model()
35
+ tgt_id = tok.lang_code_to_id[_TGT]
 
36
 
37
+ sentences = [s.strip() for s in re.split(r"(?<=[.!?])\s+", text) if s.strip()]
38
+ if not sentences:
39
+ sentences = [text]
40
 
41
+ parts = []
42
+ for sent in sentences:
43
+ inputs = tok(sent, return_tensors="pt", padding=True).to("cuda")
44
+ with torch.no_grad():
45
+ out = model.generate(**inputs, forced_bos_token_id=tgt_id, max_length=512)
46
+ parts.append(tok.decode(out[0], skip_special_tokens=True))
 
47
 
48
+ return " ".join(parts)
 
 
 
indic_tts.py CHANGED
@@ -1,42 +1,58 @@
1
- """Kannada narration via AI4Bharat IndicF5 with fine-tuned bedtime checkpoint."""
 
 
2
  from __future__ import annotations
3
  import os
4
  import re
5
  import tempfile
6
  import numpy as np
7
  import torch
8
- from config import KANNADA_TTS_MODEL, KANNADA_FINETUNE
9
 
10
- _BASE_HUB_ID = KANNADA_TTS_MODEL.hub_id
11
- _CHECKPOINT = KANNADA_FINETUNE
12
- INDICF5_SR = 24_000
13
- _model = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
 
16
  def _get_model():
17
- global _model
18
- if _model is None:
19
- from transformers import AutoModel
20
- token = os.environ.get("HF_TOKEN") or None
21
- _model = AutoModel.from_pretrained(_BASE_HUB_ID, trust_remote_code=True, token=token)
22
  try:
23
- from huggingface_hub import hf_hub_download
24
- cfm_path = hf_hub_download(repo_id=_CHECKPOINT, filename="cfm.pt", token=token)
25
- cfm_state = torch.load(cfm_path, map_location="cpu", weights_only=True)
26
- _model.ema_model.load_state_dict(cfm_state)
27
- except Exception as e:
28
- pass # fall back to base checkpoint silently
29
- _model = _model.to("cuda").eval()
30
- return _model
31
 
32
 
 
33
  try:
34
  _get_model()
35
  except Exception:
36
  pass
37
 
38
 
39
- def _split_sentences(text: str, max_chars: int = 200):
40
  parts = re.split(r"(?<=[.!?।])\s+|\n+", text.strip())
41
  out = []
42
  for p in parts:
@@ -52,45 +68,67 @@ def _split_sentences(text: str, max_chars: int = 200):
52
  return out or [text.strip()]
53
 
54
 
55
- def _pause_for(mood: str, energy: float = 0.45) -> float:
56
- energy = max(0.0, min(1.0, float(energy)))
57
- base = 0.45 if mood in ("funny", "magical") else 0.65
58
- return round(base + (0.85 - base) * (1.0 - energy), 3)
59
-
60
-
61
- def narrate_kannada(ref_wav: str, ref_text: str, kannada_text: str,
62
- mood: str = "", energy: float = 0.45) -> str:
63
- """Clone the user's voice and narrate Kannada text. Returns a temp WAV path."""
64
- if not ref_wav or not os.path.exists(ref_wav):
65
- raise ValueError("Please provide a voice reference WAV.")
66
- if not (ref_text or "").strip():
67
- raise ValueError("Kannada reference transcript is required.")
68
- if not (kannada_text or "").strip():
69
- raise ValueError("Please provide Kannada text to narrate.")
70
-
71
- model = _get_model()
72
- pause = _pause_for(mood, energy) * 1.3
73
- silence = np.zeros(int(pause * INDICF5_SR), dtype=np.float32)
74
-
75
  chunks = []
76
- for sentence in _split_sentences(kannada_text, max_chars=200):
77
- audio = model(sentence, ref_audio_path=ref_wav, ref_text=ref_text.strip())
 
78
  audio = np.asarray(audio, dtype=np.float32)
79
  if audio.size and float(np.max(np.abs(audio))) > 1.0:
80
  audio = audio / 32768.0
81
  if audio.size:
82
  chunks.append(audio)
83
  chunks.append(silence)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- if not chunks:
86
- raise RuntimeError("IndicF5 produced no audio.")
87
 
88
- full = np.concatenate(chunks)
89
- from audio_postprocess import postprocess
90
- full = postprocess(full, INDICF5_SR)
91
 
92
  import soundfile as sf
93
- fd, out_path = tempfile.mkstemp(prefix="bedtime_kn_", suffix=".wav")
94
  os.close(fd)
95
- sf.write(out_path, full, INDICF5_SR)
96
- return out_path
 
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
  from __future__ import annotations
5
  import os
6
  import re
7
  import tempfile
8
  import numpy as np
9
  import torch
 
10
 
11
+ _FINETUNE_HUB = "sush0401/IndicF5-Kannada-Bedtime-v2"
12
+ _FALLBACK_HUB = "facebook/mms-tts-kan"
13
+
14
+ _indic_model = None # IndicF5 fine-tuned (with voice cloning)
15
+ _mms_model = None # MMS-TTS fallback (no voice cloning)
16
+ _mms_tok = None
17
+ _use_indic = False # set True after successful IndicF5 load
18
+ MMS_SR = 16_000
19
+
20
+
21
+ def _load_indic():
22
+ global _indic_model, _use_indic
23
+ from transformers import AutoModel
24
+ _indic_model = AutoModel.from_pretrained(
25
+ _FINETUNE_HUB, trust_remote_code=True,
26
+ ).to("cuda").eval()
27
+ _use_indic = True
28
+
29
+
30
+ def _load_mms():
31
+ global _mms_model, _mms_tok
32
+ from transformers import VitsModel, AutoTokenizer
33
+ _mms_tok = AutoTokenizer.from_pretrained(_FALLBACK_HUB)
34
+ _mms_model = VitsModel.from_pretrained(_FALLBACK_HUB).to("cuda").eval()
35
 
36
 
37
  def _get_model():
38
+ if not _use_indic and _mms_model is None:
 
 
 
 
39
  try:
40
+ _load_indic()
41
+ except Exception:
42
+ _load_mms()
43
+ elif not _use_indic:
44
+ pass # already loaded MMS
45
+ return _use_indic
 
 
46
 
47
 
48
+ # Pre-load at module scope on ZeroGPU
49
  try:
50
  _get_model()
51
  except Exception:
52
  pass
53
 
54
 
55
+ def _split(text: str, max_chars: int = 200):
56
  parts = re.split(r"(?<=[.!?।])\s+|\n+", text.strip())
57
  out = []
58
  for p in parts:
 
68
  return out or [text.strip()]
69
 
70
 
71
+ def _narrate_indic(ref_wav: str, kannada_text: str) -> np.ndarray:
72
+ model = _indic_model
73
+ silence_sr = 24_000
74
+ silence = np.zeros(int(0.55 * silence_sr), dtype=np.float32)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  chunks = []
76
+ for sent in _split(kannada_text):
77
+ kw = dict(ref_audio_path=ref_wav) if ref_wav and os.path.exists(ref_wav) else {}
78
+ audio = model(sent, **kw)
79
  audio = np.asarray(audio, dtype=np.float32)
80
  if audio.size and float(np.max(np.abs(audio))) > 1.0:
81
  audio = audio / 32768.0
82
  if audio.size:
83
  chunks.append(audio)
84
  chunks.append(silence)
85
+ return np.concatenate(chunks) if chunks else np.array([], dtype=np.float32), silence_sr
86
+
87
+
88
+ def _narrate_mms(kannada_text: str) -> tuple[np.ndarray, int]:
89
+ silence = np.zeros(int(0.55 * MMS_SR), dtype=np.float32)
90
+ chunks = []
91
+ for sent in _split(kannada_text):
92
+ inputs = _mms_tok(sent, return_tensors="pt").to("cuda")
93
+ with torch.no_grad():
94
+ wav = _mms_model(**inputs).waveform
95
+ audio = wav.squeeze().cpu().float().numpy()
96
+ if audio.size:
97
+ chunks.append(audio)
98
+ chunks.append(silence)
99
+ full = np.concatenate(chunks) if chunks else np.array([], dtype=np.float32)
100
+ return full, MMS_SR
101
+
102
+
103
+ def narrate_kannada(ref_wav: str, ref_text: str, kannada_text: str,
104
+ mood: str = "", energy: float = 0.45) -> str:
105
+ """Narrate Kannada text. Uses fine-tuned IndicF5 with voice cloning if available,
106
+ otherwise MMS-TTS-Kan (generic voice). Returns a temp WAV path."""
107
+ text = (kannada_text or "").strip()
108
+ if not text:
109
+ raise ValueError("Please provide Kannada text to narrate.")
110
+
111
+ use_indic = _get_model()
112
+
113
+ if use_indic:
114
+ try:
115
+ full, sr = _narrate_indic(ref_wav or "", text)
116
+ except Exception:
117
+ if _mms_model is None:
118
+ _load_mms()
119
+ full, sr = _narrate_mms(text)
120
+ else:
121
+ full, sr = _narrate_mms(text)
122
 
123
+ if not full.size:
124
+ raise RuntimeError("TTS produced no audio.")
125
 
126
+ peak = float(np.max(np.abs(full)))
127
+ if peak > 0:
128
+ full = full / peak * 0.92
129
 
130
  import soundfile as sf
131
+ fd, path = tempfile.mkstemp(prefix="bedtime_kn_", suffix=".wav")
132
  os.close(fd)
133
+ sf.write(path, full, sr)
134
+ return path
requirements.txt CHANGED
@@ -27,6 +27,3 @@ requests
27
  huggingface_hub
28
  soundfile
29
  librosa>=0.10.0
30
-
31
- # Bedtime Voice tab (Kannada support)
32
- IndicTransToolkit @ git+https://github.com/VarunGumma/IndicTransToolkit.git
 
27
  huggingface_hub
28
  soundfile
29
  librosa>=0.10.0