File size: 4,609 Bytes
44cee7a 782f6e2 44cee7a 56412a3 44cee7a 782f6e2 44cee7a 782f6e2 44cee7a 782f6e2 56412a3 44cee7a 56412a3 44cee7a 56412a3 782f6e2 56412a3 44cee7a 56412a3 782f6e2 44cee7a 56412a3 44cee7a 56412a3 44cee7a 782f6e2 44cee7a 782f6e2 44cee7a 782f6e2 44cee7a 56412a3 44cee7a 56412a3 44cee7a 56412a3 44cee7a 56412a3 44cee7a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | """Kannada TTS β primary: sush0401/IndicF5-Kannada-Bedtime-v2 (fine-tuned, non-gated).
Fallback: facebook/mms-tts-kan (VITS, 16kHz, no voice cloning).
ZeroGPU pattern: models loaded to CPU at module scope so ZeroGPU packs their
tensors. Inside inference functions (called from @spaces.GPU), .to("cuda") is
called and ZeroGPU transfers packed tensors to GPU β no re-download needed.
"""
from __future__ import annotations
import os
import re
import tempfile
import numpy as np
import torch
_FINETUNE_HUB = "sush0401/IndicF5-Kannada-Bedtime-v2"
_FALLBACK_HUB = "facebook/mms-tts-kan"
_indic_model = None # IndicF5 fine-tuned (with voice cloning)
_mms_model = None # MMS-TTS fallback (no voice cloning)
_mms_tok = None
_use_indic = False # set True after successful IndicF5 load
MMS_SR = 16_000
def _load_indic():
global _indic_model, _use_indic
from transformers import AutoModel
# Load to CPU β ZeroGPU packs these tensors.
_indic_model = AutoModel.from_pretrained(
_FINETUNE_HUB, trust_remote_code=True,
)
_use_indic = True
def _load_mms():
global _mms_model, _mms_tok
from transformers import VitsModel, AutoTokenizer
_mms_tok = AutoTokenizer.from_pretrained(_FALLBACK_HUB)
# Load to CPU β ZeroGPU packs these tensors.
_mms_model = VitsModel.from_pretrained(_FALLBACK_HUB)
def _get_model():
if not _use_indic and _mms_model is None:
try:
_load_indic()
except Exception:
_load_mms()
return _use_indic
# Pre-load to CPU at module scope so ZeroGPU packs the tensors.
try:
_get_model()
except Exception:
pass
def _split(text: str, max_chars: int = 200):
parts = re.split(r"(?<=[.!?ΰ₯€])\s+|\n+", text.strip())
out = []
for p in parts:
p = p.strip()
if not p:
continue
while len(p) > max_chars:
cut = p.rfind(" ", 0, max_chars)
cut = cut if cut > 0 else max_chars
out.append(p[:cut].strip())
p = p[cut:].strip()
out.append(p)
return out or [text.strip()]
def _narrate_indic(ref_wav: str, kannada_text: str) -> tuple[np.ndarray, int]:
# Move to GPU β ZeroGPU intercepts this inside @spaces.GPU.
model = _indic_model.to("cuda")
silence_sr = 24_000
silence = np.zeros(int(0.55 * silence_sr), dtype=np.float32)
chunks = []
for sent in _split(kannada_text):
kw = dict(ref_audio_path=ref_wav) if ref_wav and os.path.exists(ref_wav) else {}
audio = model(sent, **kw)
audio = np.asarray(audio, dtype=np.float32)
if audio.size and float(np.max(np.abs(audio))) > 1.0:
audio = audio / 32768.0
if audio.size:
chunks.append(audio)
chunks.append(silence)
return np.concatenate(chunks) if chunks else np.array([], dtype=np.float32), silence_sr
def _narrate_mms(kannada_text: str) -> tuple[np.ndarray, int]:
# Move to GPU β ZeroGPU intercepts this inside @spaces.GPU.
model = _mms_model.to("cuda")
silence = np.zeros(int(0.55 * MMS_SR), dtype=np.float32)
chunks = []
for sent in _split(kannada_text):
inputs = _mms_tok(sent, return_tensors="pt").to(model.device)
with torch.no_grad():
wav = model(**inputs).waveform
audio = wav.squeeze().cpu().float().numpy()
if audio.size:
chunks.append(audio)
chunks.append(silence)
full = np.concatenate(chunks) if chunks else np.array([], dtype=np.float32)
return full, MMS_SR
def narrate_kannada(ref_wav: str, ref_text: str, kannada_text: str,
mood: str = "", energy: float = 0.45) -> str:
"""Narrate Kannada text. Uses fine-tuned IndicF5 with voice cloning if available,
otherwise MMS-TTS-Kan (generic voice). Returns a temp WAV path."""
text = (kannada_text or "").strip()
if not text:
raise ValueError("Please provide Kannada text to narrate.")
use_indic = _get_model()
if use_indic:
try:
full, sr = _narrate_indic(ref_wav or "", text)
except Exception:
if _mms_model is None:
_load_mms()
full, sr = _narrate_mms(text)
else:
full, sr = _narrate_mms(text)
if not full.size:
raise RuntimeError("TTS produced no audio.")
peak = float(np.max(np.abs(full)))
if peak > 0:
full = full / peak * 0.92
import soundfile as sf
fd, path = tempfile.mkstemp(prefix="bedtime_kn_", suffix=".wav")
os.close(fd)
sf.write(path, full, sr)
return path
|