diegosouzacdv commited on
Commit
f1ddf04
·
verified ·
1 Parent(s): c7265ad

Improve TTS: sentence-aware split, error handling, text truncation, cleanup

Browse files
Files changed (1) hide show
  1. app.py +78 -41
app.py CHANGED
@@ -1,63 +1,100 @@
1
  import gradio as gr
2
  from kokoro import KPipeline
3
- import soundfile as sf
4
- import torch
5
  import numpy as np
6
 
7
- # Inicialização das Pipelines por idioma
8
- # 'a' = American English, 'b' = British English, 'p' = Brazilian Portuguese
9
  pipelines = {
10
- 'a': KPipeline(lang_code='a'),
11
- 'b': KPipeline(lang_code='b'),
12
- 'p': KPipeline(lang_code='p')
13
  }
14
 
15
- def get_pipeline_for_voice(voice_id):
16
- if voice_id.startswith(('af_', 'am_')):
17
- return pipelines['a']
18
- elif voice_id.startswith(('bf_', 'bm_')):
19
- return pipelines['b']
20
- elif voice_id.startswith(('pf_', 'pm_')):
21
- return pipelines['p']
22
- return pipelines['a']
23
-
24
- def generate_tts(text, voice_id, speed):
25
- pipeline = get_pipeline_for_voice(voice_id)
26
- generator = pipeline(
27
- text, voice=voice_id,
28
- speed=speed, split_pattern=r'\n+'
29
- )
30
-
31
- full_audio = []
32
- for i, (gs, ps, audio) in enumerate(generator):
33
- full_audio.append(audio)
34
-
35
- if not full_audio:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  return None
37
-
38
- combined_audio = np.concatenate(full_audio)
39
- return (24000, combined_audio)
40
 
41
- # Lista de vozes conforme documentação kokoro-82M
 
42
  VOICES = [
43
- "af_heart", "af_alloy", "af_aoede", "af_bella", "af_jessica",
 
44
  "af_kore", "af_nicole", "af_nova", "af_river", "af_sarah", "af_sky",
45
- "am_adam", "am_echo", "am_eric", "am_fenrir", "am_liam", "am_michael",
46
- "am_onyx", "am_orion", "am_puck",
47
- "bf_alice", "bf_emma", "bf_isabella", "bm_george", "bm_lewis",
48
- "pf_dora", "pm_alex", "pm_santa"
 
 
 
 
49
  ]
50
 
51
  demo = gr.Interface(
52
  fn=generate_tts,
53
  inputs=[
54
- gr.Textbox(label="Texto", lines=3),
55
  gr.Dropdown(choices=VOICES, value="af_heart", label="Voz"),
56
- gr.Slider(minimum=0.5, maximum=2.0, value=1.0, step=0.1, label="Velocidade")
57
  ],
58
  outputs=gr.Audio(label="Áudio Gerado"),
59
- title="Kokoro-82M Multi-Language TTS",
60
- description="Suporte a Inglês (US/UK) e Português (BR)."
 
61
  )
62
 
63
  if __name__ == "__main__":
 
1
  import gradio as gr
2
  from kokoro import KPipeline
 
 
3
  import numpy as np
4
 
5
+ # Inicialização das Pipelines por idioma (carregadas uma vez ao iniciar)
6
+ # a = American English, b = British English, p = Brazilian Portuguese
7
  pipelines = {
8
+ "a": KPipeline(lang_code="a"),
9
+ "b": KPipeline(lang_code="b"),
10
+ "p": KPipeline(lang_code="p"),
11
  }
12
 
13
+
14
+ def get_pipeline_for_voice(voice_id: str) -> KPipeline:
15
+ if voice_id.startswith(("af_", "am_")):
16
+ return pipelines["a"]
17
+ elif voice_id.startswith(("bf_", "bm_")):
18
+ return pipelines["b"]
19
+ elif voice_id.startswith(("pf_", "pm_")):
20
+ return pipelines["p"]
21
+ return pipelines["a"]
22
+
23
+
24
+ def generate_tts(text: str, voice_id: str, speed: float):
25
+ """
26
+ Gera áudio TTS a partir de texto usando Kokoro-82M.
27
+ Retorna tupla (sample_rate, audio_array) para gr.Audio.
28
+ """
29
+ try:
30
+ text = (text or "").strip()
31
+ if not text:
32
+ return None
33
+
34
+ # Trunca textos muito longos para evitar timeout
35
+ MAX_CHARS = 600
36
+ if len(text) > MAX_CHARS:
37
+ print(f"[TTS] Texto truncado: {len(text)} -> {MAX_CHARS} chars")
38
+ text = text[:MAX_CHARS]
39
+
40
+ # Textos curtos: sem split (evita pausas desnecessárias)
41
+ # Textos longos: split por fronteira de sentença para melhor prosódia
42
+ split_pattern = None if len(text) < 80 else r"(?<=[.!?…])\s+"
43
+
44
+ pipeline = get_pipeline_for_voice(voice_id)
45
+ generator = pipeline(
46
+ text,
47
+ voice=voice_id,
48
+ speed=speed,
49
+ split_pattern=split_pattern,
50
+ )
51
+
52
+ segments = []
53
+ for _gs, _ps, audio in generator:
54
+ seg = np.array(audio, dtype=np.float32)
55
+ if seg.ndim > 1: # garante mono
56
+ seg = seg.mean(axis=-1)
57
+ if seg.size > 0:
58
+ segments.append(seg)
59
+
60
+ if not segments:
61
+ print("[TTS] Nenhum segmento gerado.")
62
+ return None
63
+
64
+ combined = np.concatenate(segments).astype(np.float32)
65
+ return (24000, combined)
66
+
67
+ except Exception as e:
68
+ print(f"[TTS] Erro: {e}")
69
  return None
 
 
 
70
 
71
+
72
+ # Vozes disponíveis (Kokoro-82M)
73
  VOICES = [
74
+ # American English — female
75
+ "af_heart", "af_alloy", "af_aoede", "af_bella", "af_jessica",
76
  "af_kore", "af_nicole", "af_nova", "af_river", "af_sarah", "af_sky",
77
+ # American English — male
78
+ "am_adam", "am_echo", "am_eric", "am_fenrir", "am_liam",
79
+ "am_michael", "am_onyx", "am_orion", "am_puck",
80
+ # British English — female / male
81
+ "bf_alice", "bf_emma", "bf_isabella",
82
+ "bm_george", "bm_lewis",
83
+ # Brazilian Portuguese — female / male
84
+ "pf_dora", "pm_alex", "pm_santa",
85
  ]
86
 
87
  demo = gr.Interface(
88
  fn=generate_tts,
89
  inputs=[
90
+ gr.Textbox(label="Texto", lines=3, placeholder="Digite o texto aqui..."),
91
  gr.Dropdown(choices=VOICES, value="af_heart", label="Voz"),
92
+ gr.Slider(minimum=0.5, maximum=2.0, value=1.0, step=0.1, label="Velocidade"),
93
  ],
94
  outputs=gr.Audio(label="Áudio Gerado"),
95
+ title="Kokoro-82M TTS",
96
+ description="Inglês Americano / Britânico e Português Brasileiro.",
97
+ cache_examples=False,
98
  )
99
 
100
  if __name__ == "__main__":