crazydev919 commited on
Commit
165c75f
Β·
verified Β·
1 Parent(s): d991b5f

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +139 -91
app.py CHANGED
@@ -4,50 +4,56 @@ import torch, yaml, os, sys, glob, re
4
  import librosa
5
  import soundfile as sf
6
  import torchaudio
 
7
  from huggingface_hub import snapshot_download
8
  from munch import Munch
9
  from nltk.tokenize import word_tokenize
10
  import nltk
11
  nltk.download("punkt_tab", quiet=True)
12
 
13
- # ── Clone StyleTTS2 ────────────────────────────────────────
14
- os.system("git clone https://github.com/yl4579/StyleTTS2 /app/StyleTTS2 2>/dev/null || true")
 
 
15
 
16
- # ── Patch models.py for PyTorch 2.6 weights_only ──────────
17
- models_path = "/app/StyleTTS2/models.py"
18
- with open(models_path) as f:
19
- code = f.read()
 
20
 
21
- patched = re.sub(
22
- r'torch\.load\(([^)]+)\)',
23
- lambda m: m.group(0) if 'weights_only' in m.group(1)
24
- else f'torch.load({m.group(1)}, weights_only=False)',
25
- code
26
- )
27
- with open(models_path, "w") as f:
28
- f.write(patched)
29
-
30
- # Also patch utils.py
31
- utils_path = "/app/StyleTTS2/utils.py"
32
- with open(utils_path) as f:
33
- code2 = f.read()
34
-
35
- patched2 = re.sub(
36
- r'torch\.load\(([^)]+)\)',
37
- lambda m: m.group(0) if 'weights_only' in m.group(1)
38
- else f'torch.load({m.group(1)}, weights_only=False)',
39
- code2
40
- )
41
- with open(utils_path, "w") as f:
42
- f.write(patched2)
43
 
 
 
 
 
 
 
 
 
 
 
 
44
  print("βœ… Patched torch.load calls")
45
 
46
  sys.path.insert(0, "/app/StyleTTS2")
47
  os.chdir("/app/StyleTTS2")
48
 
49
- MODEL_REPO = "crazydev919/luhya-tts"
50
- model_dir = snapshot_download(MODEL_REPO)
51
 
52
  from models import *
53
  from utils import *
@@ -58,49 +64,45 @@ device = "cuda" if torch.cuda.is_available() else "cpu"
58
  print(f"Running on: {device}")
59
  textcleaner = TextCleaner()
60
 
61
- # ── Load config ────────────────────────────────────────────
62
  config = yaml.safe_load(open(f"{model_dir}/config.yml"))
63
  config["ASR_path"] = f"{model_dir}/Utils/ASR/epoch_00080.pth"
64
  config["ASR_config"] = f"{model_dir}/Utils/ASR/config.yml"
65
  config["F0_path"] = f"{model_dir}/Utils/JDC/bst.t7"
66
  config["PLBERT_dir"] = f"{model_dir}/Utils/PLBERT/"
67
 
68
- # ── Load models ────────────────────────────────────────────
69
  text_aligner = load_ASR_models(config["ASR_path"], config["ASR_config"])
70
  pitch_extractor = load_F0_models(config["F0_path"])
71
  from Utils.PLBERT.util import load_plbert
72
  plbert = load_plbert(config["PLBERT_dir"])
73
 
74
  model_params = recursive_munch(config["model_params"])
75
- model = build_model(model_params, text_aligner, pitch_extractor, plbert)
76
- _ = [model[key].eval() for key in model]
77
- _ = [model[key].to(device) for key in model]
78
 
79
  params = torch.load(
80
  f"{model_dir}/model.pth", map_location="cpu", weights_only=False
81
  )["net"]
82
- for key in model:
83
  if key in params:
84
  try:
85
- model[key].load_state_dict(params[key])
86
  except:
87
  from collections import OrderedDict
88
  sd = OrderedDict()
89
  for k, v in params[key].items():
90
  sd[k[7:] if k.startswith("module.") else k] = v
91
- model[key].load_state_dict(sd, strict=False)
92
- _ = [model[key].eval() for key in model]
93
- print("βœ… Model loaded")
94
 
95
- # ── Diffusion sampler ──────────────────────────────────────
96
  sampler = DiffusionSampler(
97
- model.diffusion.diffusion,
98
  sampler=ADPM2Sampler(),
99
  sigma_schedule=KarrasSchedule(sigma_min=0.0001, sigma_max=3.0, rho=9.0),
100
  clamp=False
101
  )
102
 
103
- # ── Helpers ────────────────────────────────────────────────
104
  to_mel = torchaudio.transforms.MelSpectrogram(
105
  n_mels=80, n_fft=2048, win_length=1200, hop_length=300)
106
  mean, std = -4, 4
@@ -122,16 +124,19 @@ def compute_style(path):
122
  audio, _ = librosa.effects.trim(wave, top_db=30)
123
  mel = preprocess(audio).to(device)
124
  with torch.no_grad():
125
- ref_s = model.style_encoder(mel.unsqueeze(1))
126
- ref_p = model.predictor_encoder(mel.unsqueeze(1))
127
  return torch.cat([ref_s, ref_p], dim=1)
128
 
129
- DEFAULT_REF = sorted(glob.glob(f"{model_dir}/ref_wavs/*.wav"))[0]
 
 
 
 
130
  DEFAULT_STYLE = compute_style(DEFAULT_REF)
131
- print(f"Default reference: {DEFAULT_REF}")
132
 
133
- # ── Inference ──────────────────────────────────────────────
134
- def synthesize(text, ref_audio=None, alpha=0.3, beta=0.7, steps=5):
135
  import phonemizer
136
  pb = phonemizer.backend.EspeakBackend(
137
  language="sw", preserve_punctuation=True, with_stress=True)
@@ -145,9 +150,9 @@ def synthesize(text, ref_audio=None, alpha=0.3, beta=0.7, steps=5):
145
  with torch.no_grad():
146
  il = torch.LongTensor([tokens.shape[-1]]).to(device)
147
  tm = length_to_mask(il).to(device)
148
- t_en = model.text_encoder(tokens, il, tm)
149
- bd = model.bert(tokens, attention_mask=(~tm).int())
150
- d_en = model.bert_encoder(bd).transpose(-1, -2)
151
 
152
  sp = sampler(
153
  noise=torch.randn((1, 256)).unsqueeze(1).to(device),
@@ -158,9 +163,9 @@ def synthesize(text, ref_audio=None, alpha=0.3, beta=0.7, steps=5):
158
  s = beta * sp[:, 128:] + (1 - beta) * ref_s[:, 128:]
159
  ref = alpha * sp[:, :128] + (1 - alpha) * ref_s[:, :128]
160
 
161
- d = model.predictor.text_encoder(d_en, s, il, tm)
162
- x, _ = model.predictor.lstm(d)
163
- dur = torch.sigmoid(model.predictor.duration_proj(x)).sum(axis=-1)
164
  pd = torch.round(dur.squeeze()).clamp(min=1)
165
 
166
  at = torch.zeros(il, int(pd.sum().data))
@@ -181,43 +186,86 @@ def synthesize(text, ref_audio=None, alpha=0.3, beta=0.7, steps=5):
181
  asr_new[:, :, 1:] = asr[:, :, :-1]
182
  en, asr = en_new, asr_new
183
 
184
- F0, N = model.predictor.F0Ntrain(en, s)
185
- out = model.decoder(asr, F0, N, ref.squeeze().unsqueeze(0))
186
 
187
  wav = out.squeeze().cpu().numpy()[..., :-50]
188
- sf.write("/tmp/output.wav", wav, 24000)
189
- return "/tmp/output.wav"
190
-
191
- # ── Gradio UI ──────────────────────────────────────────────
192
- demo = gr.Interface(
193
- fn = synthesize,
194
- inputs = [
195
- gr.Textbox(
196
- label = "Luhya Text",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  value = "mirembe. obulani lwa bwana nyasaye.",
198
- lines = 3
199
- ),
200
- gr.Audio(
201
- label = "Reference Voice (optional)",
202
- type = "filepath",
203
- value = None
204
- ),
205
- gr.Slider(0.0, 1.0, value=0.3, step=0.1, label="Alpha (style)"),
206
- gr.Slider(0.0, 1.0, value=0.7, step=0.1, label="Beta (prosody)"),
207
- gr.Slider(1, 10, value=5, step=1, label="Diffusion steps"),
208
- ],
209
- outputs = gr.Audio(label="Generated Speech", type="filepath"),
210
- title = "Luhya (Lunyore) TTS",
211
- description = (
212
- "Type Luhya text and get speech audio.\n"
213
- "Fine-tuned StyleTTS2 on Luhya Lunyore data.\n\n"
214
- "**API:** POST to `/api/predict` with "
215
- "`{\"data\": [\"your luhya text\", null, 0.3, 0.7, 5]}`"
216
- ),
217
- examples = [
218
- ["mirembe. obulani lwa bwana nyasaye.", None, 0.3, 0.7, 5],
219
- ["nyasaye nareba, adam olithena?", None, 0.3, 0.7, 5],
220
- ],
221
- cache_examples = False,
222
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  demo.launch(prevent_thread_lock=True)
 
4
  import librosa
5
  import soundfile as sf
6
  import torchaudio
7
+ import numpy as np
8
  from huggingface_hub import snapshot_download
9
  from munch import Munch
10
  from nltk.tokenize import word_tokenize
11
  import nltk
12
  nltk.download("punkt_tab", quiet=True)
13
 
14
+ # ══════════════════════════════════════════════════════════
15
+ # KIKUYU β€” lightweight VITS model
16
+ # ══════════════════════════════════════════════════════════
17
+ from transformers import VitsModel, AutoTokenizer
18
 
19
+ print("Loading Kikuyu TTS...")
20
+ kikuyu_model = VitsModel.from_pretrained("gateremark/kikuyu-tts-v1")
21
+ kikuyu_tokenizer = AutoTokenizer.from_pretrained("gateremark/kikuyu-tts-v1")
22
+ kikuyu_model.eval()
23
+ print("βœ… Kikuyu model loaded")
24
 
25
+ def synthesize_kikuyu(text):
26
+ inputs = kikuyu_tokenizer(text=text.strip(), return_tensors="pt")
27
+ with torch.no_grad():
28
+ output = kikuyu_model(**inputs)
29
+ waveform = output.waveform.squeeze().cpu().numpy()
30
+ sr = kikuyu_model.config.sampling_rate
31
+ sf.write("/tmp/kikuyu_output.wav", waveform, sr)
32
+ return "/tmp/kikuyu_output.wav"
33
+
34
+ # ══════════════════════════════════════════════════════════
35
+ # LUHYA β€” StyleTTS2 fine-tuned model
36
+ # ══════════════════════════════════════════════════════════
37
+ os.system("git clone https://github.com/yl4579/StyleTTS2 /app/StyleTTS2 2>/dev/null || true")
 
 
 
 
 
 
 
 
 
38
 
39
+ for fpath in ["/app/StyleTTS2/models.py", "/app/StyleTTS2/utils.py"]:
40
+ with open(fpath) as f:
41
+ code = f.read()
42
+ patched = re.sub(
43
+ r'torch\.load\(([^)]+)\)',
44
+ lambda m: m.group(0) if 'weights_only' in m.group(1)
45
+ else f'torch.load({m.group(1)}, weights_only=False)',
46
+ code
47
+ )
48
+ with open(fpath, "w") as f:
49
+ f.write(patched)
50
  print("βœ… Patched torch.load calls")
51
 
52
  sys.path.insert(0, "/app/StyleTTS2")
53
  os.chdir("/app/StyleTTS2")
54
 
55
+ LUHYA_REPO = "crazydev919/luhya-tts"
56
+ model_dir = snapshot_download(LUHYA_REPO)
57
 
58
  from models import *
59
  from utils import *
 
64
  print(f"Running on: {device}")
65
  textcleaner = TextCleaner()
66
 
 
67
  config = yaml.safe_load(open(f"{model_dir}/config.yml"))
68
  config["ASR_path"] = f"{model_dir}/Utils/ASR/epoch_00080.pth"
69
  config["ASR_config"] = f"{model_dir}/Utils/ASR/config.yml"
70
  config["F0_path"] = f"{model_dir}/Utils/JDC/bst.t7"
71
  config["PLBERT_dir"] = f"{model_dir}/Utils/PLBERT/"
72
 
 
73
  text_aligner = load_ASR_models(config["ASR_path"], config["ASR_config"])
74
  pitch_extractor = load_F0_models(config["F0_path"])
75
  from Utils.PLBERT.util import load_plbert
76
  plbert = load_plbert(config["PLBERT_dir"])
77
 
78
  model_params = recursive_munch(config["model_params"])
79
+ luhya_model = build_model(model_params, text_aligner, pitch_extractor, plbert)
80
+ _ = [luhya_model[key].eval() for key in luhya_model]
81
+ _ = [luhya_model[key].to(device) for key in luhya_model]
82
 
83
  params = torch.load(
84
  f"{model_dir}/model.pth", map_location="cpu", weights_only=False
85
  )["net"]
86
+ for key in luhya_model:
87
  if key in params:
88
  try:
89
+ luhya_model[key].load_state_dict(params[key])
90
  except:
91
  from collections import OrderedDict
92
  sd = OrderedDict()
93
  for k, v in params[key].items():
94
  sd[k[7:] if k.startswith("module.") else k] = v
95
+ luhya_model[key].load_state_dict(sd, strict=False)
96
+ _ = [luhya_model[key].eval() for key in luhya_model]
97
+ print("βœ… Luhya model loaded")
98
 
 
99
  sampler = DiffusionSampler(
100
+ luhya_model.diffusion.diffusion,
101
  sampler=ADPM2Sampler(),
102
  sigma_schedule=KarrasSchedule(sigma_min=0.0001, sigma_max=3.0, rho=9.0),
103
  clamp=False
104
  )
105
 
 
106
  to_mel = torchaudio.transforms.MelSpectrogram(
107
  n_mels=80, n_fft=2048, win_length=1200, hop_length=300)
108
  mean, std = -4, 4
 
124
  audio, _ = librosa.effects.trim(wave, top_db=30)
125
  mel = preprocess(audio).to(device)
126
  with torch.no_grad():
127
+ ref_s = luhya_model.style_encoder(mel.unsqueeze(1))
128
+ ref_p = luhya_model.predictor_encoder(mel.unsqueeze(1))
129
  return torch.cat([ref_s, ref_p], dim=1)
130
 
131
+ ref_candidates = (
132
+ glob.glob(f"{model_dir}/ref_wavs/*.wav") +
133
+ glob.glob(f"{model_dir}/*.wav")
134
+ )
135
+ DEFAULT_REF = sorted(ref_candidates)[0]
136
  DEFAULT_STYLE = compute_style(DEFAULT_REF)
137
+ print(f"βœ… Default reference: {DEFAULT_REF}")
138
 
139
+ def synthesize_luhya(text, ref_audio=None, alpha=0.3, beta=0.7, steps=5):
 
140
  import phonemizer
141
  pb = phonemizer.backend.EspeakBackend(
142
  language="sw", preserve_punctuation=True, with_stress=True)
 
150
  with torch.no_grad():
151
  il = torch.LongTensor([tokens.shape[-1]]).to(device)
152
  tm = length_to_mask(il).to(device)
153
+ t_en = luhya_model.text_encoder(tokens, il, tm)
154
+ bd = luhya_model.bert(tokens, attention_mask=(~tm).int())
155
+ d_en = luhya_model.bert_encoder(bd).transpose(-1, -2)
156
 
157
  sp = sampler(
158
  noise=torch.randn((1, 256)).unsqueeze(1).to(device),
 
163
  s = beta * sp[:, 128:] + (1 - beta) * ref_s[:, 128:]
164
  ref = alpha * sp[:, :128] + (1 - alpha) * ref_s[:, :128]
165
 
166
+ d = luhya_model.predictor.text_encoder(d_en, s, il, tm)
167
+ x, _ = luhya_model.predictor.lstm(d)
168
+ dur = torch.sigmoid(luhya_model.predictor.duration_proj(x)).sum(axis=-1)
169
  pd = torch.round(dur.squeeze()).clamp(min=1)
170
 
171
  at = torch.zeros(il, int(pd.sum().data))
 
186
  asr_new[:, :, 1:] = asr[:, :, :-1]
187
  en, asr = en_new, asr_new
188
 
189
+ F0, N = luhya_model.predictor.F0Ntrain(en, s)
190
+ out = luhya_model.decoder(asr, F0, N, ref.squeeze().unsqueeze(0))
191
 
192
  wav = out.squeeze().cpu().numpy()[..., :-50]
193
+ sf.write("/tmp/luhya_output.wav", wav, 24000)
194
+ return "/tmp/luhya_output.wav"
195
+
196
+ # ══════════════════════════════════════════════════════════
197
+ # UNIFIED FUNCTION β€” language param routes to correct model
198
+ # ══════════════════════════════════════════════════════════
199
+ def synthesize(language, text, ref_audio=None, alpha=0.3, beta=0.7, steps=5):
200
+ if language == "Kikuyu":
201
+ return synthesize_kikuyu(text)
202
+ else:
203
+ return synthesize_luhya(text, ref_audio, alpha, beta, steps)
204
+
205
+ # ══════════════════════════════════════════════════════════
206
+ # GRADIO UI
207
+ # ══════════════════════════════════════════════════════════
208
+ with gr.Blocks(title="Kenyan Languages TTS") as demo:
209
+ gr.Markdown("# πŸ—£οΈ Kenyan Languages TTS\nText-to-speech for Luhya (Lunyore) and Kikuyu.")
210
+
211
+ with gr.Row():
212
+ language = gr.Radio(
213
+ choices = ["Luhya", "Kikuyu"],
214
+ value = "Luhya",
215
+ label = "Language"
216
+ )
217
+
218
+ with gr.Row():
219
+ text = gr.Textbox(
220
+ label = "Text",
221
  value = "mirembe. obulani lwa bwana nyasaye.",
222
+ lines = 4
223
+ )
224
+
225
+ # Luhya-only controls
226
+ with gr.Group(visible=True) as luhya_controls:
227
+ gr.Markdown("**Luhya voice controls**")
228
+ with gr.Row():
229
+ ref_audio = gr.Audio(
230
+ label = "Reference Voice (optional)",
231
+ type = "filepath",
232
+ value = None
233
+ )
234
+ with gr.Row():
235
+ alpha = gr.Slider(0.0, 1.0, value=0.3, step=0.1, label="Alpha (style)")
236
+ beta = gr.Slider(0.0, 1.0, value=0.7, step=0.1, label="Beta (prosody)")
237
+ steps = gr.Slider(1, 10, value=5, step=1, label="Diffusion steps")
238
+
239
+ output_audio = gr.Audio(label="Generated Speech", type="filepath")
240
+ generate_btn = gr.Button("Generate", variant="primary")
241
+
242
+ # Show/hide luhya controls based on language
243
+ def toggle_controls(lang):
244
+ return gr.update(visible=(lang == "Luhya"))
245
+
246
+ language.change(fn=toggle_controls, inputs=language, outputs=luhya_controls)
247
+
248
+ # Update placeholder text based on language
249
+ def update_placeholder(lang):
250
+ examples = {
251
+ "Luhya" : "mirembe. obulani lwa bwana nyasaye.",
252
+ "Kikuyu" : "MΕ©tΕ©Ε©rΔ©re wa ndΕ©ire nΔ© kΔ©heo kΔ©a mwanya mΕ©no.",
253
+ }
254
+ return gr.update(value=examples[lang])
255
+
256
+ language.change(fn=update_placeholder, inputs=language, outputs=text)
257
+
258
+ generate_btn.click(
259
+ fn = synthesize,
260
+ inputs = [language, text, ref_audio, alpha, beta, steps],
261
+ outputs = output_audio
262
+ )
263
+
264
+ gr.Markdown("""
265
+ **API usage:**
266
+ POST /api/predict
267
+ {"data": ["Luhya", "mirembe.", null, 0.3, 0.7, 5]}
268
+ {"data": ["Kikuyu", "MΕ©tΕ©Ε©rΔ©re wa ndΕ©ire nΔ© kΔ©heo.", null, 0.3, 0.7, 5]}
269
+ """)
270
+
271
  demo.launch(prevent_thread_lock=True)