Fix Kannada narration: swap gated ai4bharat models for open alternatives
Browse filesindic_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
- README.md +3 -2
- app.py +6 -12
- config.py +5 -7
- indic_text.py +25 -48
- indic_tts.py +88 -50
- requirements.txt +0 -3
|
@@ -21,8 +21,9 @@ models:
|
|
| 21 |
- black-forest-labs/FLUX.2-klein-4B
|
| 22 |
- openbmb/MiniCPM5-1B
|
| 23 |
- openbmb/VoxCPM2
|
| 24 |
-
-
|
| 25 |
-
-
|
|
|
|
| 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 📚🖍️
|
|
@@ -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
|
| 1187 |
-
|
|
|
|
| 1188 |
if not ref_wav or not os.path.exists(str(ref_wav)):
|
| 1189 |
-
raise ValueError("Voice clip required
|
| 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, "
|
| 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):
|
|
@@ -120,16 +120,16 @@ TTS_MODEL = ModelConfig(
|
|
| 120 |
# ── Bedtime Voice tab models ────────────────────────────────────────────────
|
| 121 |
|
| 122 |
TRANSLATION_MODEL = ModelConfig(
|
| 123 |
-
hub_id="
|
| 124 |
-
params_b=
|
| 125 |
-
license=LicenseType.
|
| 126 |
-
vram_gb=
|
| 127 |
modal_gpu="T4",
|
| 128 |
modal_memory=8192,
|
| 129 |
)
|
| 130 |
|
| 131 |
KANNADA_TTS_MODEL = ModelConfig(
|
| 132 |
-
hub_id="
|
| 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 |
|
|
@@ -1,24 +1,22 @@
|
|
| 1 |
-
"""English → Kannada translation via
|
| 2 |
from __future__ import annotations
|
| 3 |
-
import os
|
| 4 |
import re
|
| 5 |
import torch
|
| 6 |
-
from config import TRANSLATION_MODEL
|
| 7 |
|
| 8 |
-
_HUB_ID
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
def _get_model():
|
| 14 |
-
global
|
| 15 |
-
if
|
| 16 |
-
from transformers import
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 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
|
| 48 |
text = (en_text or "").strip()
|
| 49 |
if not text:
|
| 50 |
raise ValueError("Nothing to translate.")
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
ip = IndicProcessor(inference=True)
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
translations = ip.postprocess_batch(decoded, lang="kan_Knda")
|
| 67 |
|
| 68 |
-
|
| 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)
|
|
|
|
|
|
|
|
|
|
@@ -1,42 +1,58 @@
|
|
| 1 |
-
"""Kannada
|
|
|
|
|
|
|
| 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 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def _get_model():
|
| 17 |
-
|
| 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 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 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
|
| 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
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 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
|
| 77 |
-
|
|
|
|
| 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
|
| 86 |
-
raise RuntimeError("
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
|
| 92 |
import soundfile as sf
|
| 93 |
-
fd,
|
| 94 |
os.close(fd)
|
| 95 |
-
sf.write(
|
| 96 |
-
return
|
|
|
|
| 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
|
|
@@ -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
|
|
|
|
|
|
|
|
|