LTTEAM commited on
Commit
8bb312c
·
verified ·
1 Parent(s): a2462e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -109
app.py CHANGED
@@ -1,30 +1,31 @@
1
  import os
2
  import random
3
  import re
4
- import torch
5
  import numpy as np
 
6
  import spaces
7
  from kokoro import KModel, KPipeline
8
  import gradio as gr
9
 
10
- # --- Cấu hình bản ---
 
11
  IS_LTTEAM = os.getenv('SPACE_ID', '').startswith('LTTEAM/')
12
- CUDA = torch.cuda.is_available()
13
  CHAR_LIMIT = None # Không giới hạn ký tự
14
 
15
- # Khởi tạo model CPU/GPU
16
  models = {
17
  use_gpu: KModel().to('cuda' if use_gpu else 'cpu').eval()
18
- for use_gpu in ([False] + ([True] if CUDA else []))
19
  }
20
 
21
- # Chuẩn bị pipeline cho phoneme groups 'a' và 'b'
22
- pipelines = {g: KPipeline(lang_code=g, model=False) for g in ['a', 'b']}
 
23
  pipelines['a'].g2p.lexicon.golds['kokoro'] = 'kˈOkəɹO'
24
  pipelines['b'].g2p.lexicon.golds['kokoro'] = 'kˈQkəɹQ'
25
 
26
- # Đăng toàn bộ giọng đọc
27
- VOICE_CHOICES = {
28
  '🇺🇸 👩 Heart ❤️ (Mỹ)': 'af_heart',
29
  '🇺🇸 👩 Bella 🔥 (Mỹ)': 'af_bella',
30
  '🇺🇸 👩 Nicole 🎧 (Mỹ)': 'af_nicole',
@@ -57,140 +58,151 @@ VOICE_CHOICES = {
57
  '🇬🇧 👨 Lewis (Anh)': 'bm_lewis',
58
  '🇬🇧 👨 Daniel (Anh)': 'bm_daniel',
59
  }
60
-
61
- for group in VOICE_CHOICES.values():
62
- for _, voice_id in group:
63
- pipelines[voice_id[0]].load_voice(voice_id)
64
 
65
  # --- Hàm tiện ích ---
66
  def split_into_chunks(text, max_chars=2000):
67
- """Chia văn bản thành các đoạn không vượt quá max_chars."""
68
- sentences = re.split(r'(?<=[.!?])\s+', text)
69
- chunks, cur = [], ""
70
  for s in sentences:
71
- if len(cur) + len(s) + 1 <= max_chars:
72
- cur = f"{cur} {s}".strip()
73
  else:
74
- chunks.append(cur)
75
- cur = s
76
- if cur:
77
- chunks.append(cur)
 
78
  return chunks
79
 
80
  @spaces.GPU(duration=30)
81
  def forward_gpu(ps, ref_s, speed):
82
  return models[True](ps, ref_s, speed)
83
 
84
- def generate_audio(text, voice, speed, use_gpu):
85
- """Tạo file audio ghép từ nhiều chunk."""
86
  text = text.strip()
87
  pipeline = pipelines[voice[0]]
88
  pack = pipeline.load_voice(voice)
89
- use_gpu = use_gpu and CUDA
90
 
91
- chunks = split_into_chunks(text)
92
- audios = []
93
- for chunk in chunks:
94
  for _, ps, _ in pipeline(chunk, voice, speed):
95
- ref_s = pack[len(ps)-1]
96
  try:
97
- out = forward_gpu(ps, ref_s, speed) if use_gpu else models[False](ps, ref_s, speed)
98
- except Exception as e:
99
  if use_gpu:
100
- gr.Warning(f"Lỗi GPU, chuyển sang CPU: {e}")
101
- out = models[False](ps, ref_s, speed)
102
  else:
103
  raise
104
- audios.append(out.numpy())
105
- # Thêm 0.2s im lặng giữa đoạn
106
- audios.append(np.zeros(int(0.2 * 24000)))
107
- return (24000, np.concatenate(audios, axis=0))
108
 
109
- def generate_stream(text, voice, speed, use_gpu):
110
- """Phát trực tiếp từng đoạn audio."""
111
  text = text.strip()
112
  pipeline = pipelines[voice[0]]
113
  pack = pipeline.load_voice(voice)
114
- use_gpu = use_gpu and CUDA
115
 
116
- for chunk in split_into_chunks(text):
117
  for _, ps, _ in pipeline(chunk, voice, speed):
118
- ref_s = pack[len(ps)-1]
119
  try:
120
- out = forward_gpu(ps, ref_s, speed) if use_gpu else models[False](ps, ref_s, speed)
121
- except Exception as e:
122
  if use_gpu:
123
- gr.Warning(f"Lỗi GPU, chuyển sang CPU: {e}")
124
- out = models[False](ps, ref_s, speed)
125
  else:
126
  raise
127
- yield 24000, out.numpy()
128
  yield 24000, np.zeros(int(0.2 * 24000))
129
 
130
- # --- Tải ví dụ văn bản ---
 
 
 
 
 
131
  with open('en.txt', 'r') as f:
132
- QUOTES = [l.strip() for l in f]
133
-
134
- def random_quote(): return random.choice(QUOTES)
135
- def load_gatsby(): return open('gatsby5k.md','r').read().strip()
136
- def load_frank(): return open('frankenstein5k.md','r').read().strip()
137
-
138
- # --- Xây dựng giao diện Gradio ---
139
- with gr.Blocks(css="""
140
- .block { max-width: 900px; margin: auto; }
141
- .voice-group { margin-bottom: 10px; }
142
- """) as app:
143
-
144
- gr.Markdown("""
145
- **TTS-82M** — Mô hình chuyển văn bản thành giọng nói 82M tham số, mở bởi LTTEAM.
146
- """)
147
-
148
- with gr.Row():
149
- with gr.Column(scale=2):
150
- inp_txt = gr.Textbox(label="Văn bản đầu vào", placeholder="Nhập hoặc dán văn bản...", lines=4)
151
- with gr.Accordion("Ví dụ nhanh", open=False):
152
- gr.Button("📜 Trích dẫn ngẫu nhiên").click(random_quote, [], inp_txt)
153
- gr.Button("📗 Gatsby dài").click(load_gatsby, [], inp_txt)
154
- gr.Button("📕 Frankenstein").click(load_frank, [], inp_txt)
155
 
156
- with gr.Row():
157
- voice_dd = gr.Dropdown(
158
- label="Chọn giọng đọc",
159
- choices=[(f"{grp} {name}", vid)
160
- for grp, lst in VOICE_CHOICES.items()
161
- for name, vid in lst],
162
- value='af_heart'
163
- )
164
- hw_dd = gr.Radio(
165
- ["GPU (Nhanh)", "CPU (Chậm)"],
166
- label="Phần cứng",
167
- value="GPU (Nhanh)" if CUDA else "CPU (Chậm)"
168
- )
169
- speed_sl = gr.Slider(0.5, 2.0, 1.0, step=0.1, label="Tốc độ phát")
170
-
171
- with gr.Column(scale=1):
172
- out_audio = gr.Audio(label="Kết quả âm thanh", interactive=False, streaming=False, autoplay=True)
173
- btn_gen = gr.Button("▶️ Tạo âm thanh", variant="primary")
174
 
175
  with gr.Tabs():
176
- with gr.TabItem("Phát trực tiếp"):
177
- out_stream = gr.Audio(label="Phát trực tiếp", interactive=False, streaming=True, autoplay=True)
178
- btn_stream = gr.Button("🔴 Bắt đầu", variant="primary")
179
- btn_stop = gr.Button("⏹ Dừng", variant="stop")
180
-
181
- # Liên kết sự kiện
182
- btn_gen.click(
183
- fn=generate_audio,
184
- inputs=[inp_txt, voice_dd, speed_sl, hw_dd.map({"GPU (Nhanh)": True, "CPU (Chậm)": False})],
185
- outputs=[out_audio]
186
- )
187
- btn_stream.click(
188
- fn=generate_stream,
189
- inputs=[inp_txt, voice_dd, speed_sl, hw_dd.map({"GPU (Nhanh)": True, "CPU (Chậm)": False})],
190
- outputs=[out_stream]
191
- ).then(
192
- None, [], [], cancels=btn_stream
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  )
194
-
195
- if __name__ == "__main__":
196
- app.queue(api_open=not IS_LTTEAM).launch(show_api=not IS_LTTEAM, ssr_mode=True)
 
1
  import os
2
  import random
3
  import re
 
4
  import numpy as np
5
+ import torch
6
  import spaces
7
  from kokoro import KModel, KPipeline
8
  import gradio as gr
9
 
10
+ # --- Cấu hình chung ---
11
+ CUDA_AVAILABLE = torch.cuda.is_available()
12
  IS_LTTEAM = os.getenv('SPACE_ID', '').startswith('LTTEAM/')
 
13
  CHAR_LIMIT = None # Không giới hạn ký tự
14
 
15
+ # Khởi tạo hình trên CPU/GPU
16
  models = {
17
  use_gpu: KModel().to('cuda' if use_gpu else 'cpu').eval()
18
+ for use_gpu in ( [False, True] if CUDA_AVAILABLE else [False] )
19
  }
20
 
21
+ # Chuẩn bị pipelines cho tự ngữ âm 'a' và 'b'
22
+ pipelines = {lang: KPipeline(lang_code=lang, model=False) for lang in ('a', 'b')}
23
+ # Ví dụ thêm lexicon tùy chỉnh
24
  pipelines['a'].g2p.lexicon.golds['kokoro'] = 'kˈOkəɹO'
25
  pipelines['b'].g2p.lexicon.golds['kokoro'] = 'kˈQkəɹQ'
26
 
27
+ # Danh sách giọng nói (cờ + biểu tượng + tên) -> mã nội bộ
28
+ LUA_CHON_GIONG = {
29
  '🇺🇸 👩 Heart ❤️ (Mỹ)': 'af_heart',
30
  '🇺🇸 👩 Bella 🔥 (Mỹ)': 'af_bella',
31
  '🇺🇸 👩 Nicole 🎧 (Mỹ)': 'af_nicole',
 
58
  '🇬🇧 👨 Lewis (Anh)': 'bm_lewis',
59
  '🇬🇧 👨 Daniel (Anh)': 'bm_daniel',
60
  }
61
+ # Tải trước tất cả giọng
62
+ for voice_code in LUA_CHON_GIONG.values():
63
+ pipelines[voice_code[0]].load_voice(voice_code)
 
64
 
65
  # --- Hàm tiện ích ---
66
  def split_into_chunks(text, max_chars=2000):
67
+ """Chia văn bản thành các khúc nhỏ không vượt quá max_chars."""
68
+ sentences = re.split(r'(?<=[\.!\?])\s+', text.strip())
69
+ chunks, current = [], ""
70
  for s in sentences:
71
+ if len(current) + len(s) + 1 <= max_chars:
72
+ current = f"{current} {s}".strip()
73
  else:
74
+ if current:
75
+ chunks.append(current)
76
+ current = s
77
+ if current:
78
+ chunks.append(current)
79
  return chunks
80
 
81
  @spaces.GPU(duration=30)
82
  def forward_gpu(ps, ref_s, speed):
83
  return models[True](ps, ref_s, speed)
84
 
85
+ def generate_unlimited(text, voice, speed, use_gpu, max_chars=2000):
86
+ """Chế độ không giới hạn: chia chunk rồi ghép thanh âm."""
87
  text = text.strip()
88
  pipeline = pipelines[voice[0]]
89
  pack = pipeline.load_voice(voice)
90
+ use_gpu = use_gpu and CUDA_AVAILABLE
91
 
92
+ all_audio = []
93
+ for chunk in split_into_chunks(text, max_chars):
 
94
  for _, ps, _ in pipeline(chunk, voice, speed):
95
+ ref_s = pack[len(ps) - 1]
96
  try:
97
+ audio = forward_gpu(ps, ref_s, speed) if use_gpu else models[False](ps, ref_s, speed)
98
+ except gr.Error as e:
99
  if use_gpu:
100
+ gr.Warning(f"Lỗi GPU: {e}\nChuyển sang CPU cho khúc này.")
101
+ audio = models[False](ps, ref_s, speed)
102
  else:
103
  raise
104
+ all_audio.append(audio.numpy())
105
+ # thêm 0.2s im lặng
106
+ all_audio.append(np.zeros(int(0.2 * 24000)))
107
+ return (24000, np.concatenate(all_audio, axis=0))
108
 
109
+ def generate_stream(text, voice, speed, use_gpu, max_chars=2000):
110
+ """Chế độ streaming: yield từng đoạn audio nhỏ."""
111
  text = text.strip()
112
  pipeline = pipelines[voice[0]]
113
  pack = pipeline.load_voice(voice)
114
+ use_gpu = use_gpu and CUDA_AVAILABLE
115
 
116
+ for chunk in split_into_chunks(text, max_chars):
117
  for _, ps, _ in pipeline(chunk, voice, speed):
118
+ ref_s = pack[len(ps) - 1]
119
  try:
120
+ audio = forward_gpu(ps, ref_s, speed) if use_gpu else models[False](ps, ref_s, speed)
121
+ except gr.Error as e:
122
  if use_gpu:
123
+ gr.Warning(f"Lỗi GPU: {e}\nChuyển sang CPU cho khúc này.")
124
+ audio = models[False](ps, ref_s, speed)
125
  else:
126
  raise
127
+ yield 24000, audio.numpy()
128
  yield 24000, np.zeros(int(0.2 * 24000))
129
 
130
+ def tokenize_first(text, voice):
131
+ for _, ps, _ in pipelines[voice[0]](text, voice):
132
+ return ps
133
+ return ""
134
+
135
+ # Các văn bản mẫu
136
  with open('en.txt', 'r') as f:
137
+ TRICH_DAN_NGAU_NHIEN = [l.strip() for l in f]
138
+ def random_quote(): return random.choice(TRICH_DAN_NGAU_NHIEN)
139
+ def load_gatsby(): return open('gatsby5k.md','r').read()
140
+ def load_frank(): return open('frankenstein5k.md','r').read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ # --- Giao diện Gradio ---
143
+ BANNER = """
144
+ # 📣 **TTS-82M**
145
+ hình TTS 82M tham số do LTTEAM mở.
146
+ [Tham gia nhóm FB](https://www.facebook.com/groups/622526090937760)
147
+ """
148
+
149
+ with gr.Blocks() as app:
150
+ gr.Markdown(BANNER)
 
 
 
 
 
 
 
 
 
151
 
152
  with gr.Tabs():
153
+ # Tab 1: Không giới hạn
154
+ with gr.TabItem("📝 TTS Không Giới Hạn"):
155
+ with gr.Row():
156
+ with gr.Column(scale=6):
157
+ txt_in = gr.Textbox(label="Văn bản đầu vào", placeholder="Nhập hoặc dán văn bản...", lines=5)
158
+ with gr.Row():
159
+ dd_voice = gr.Dropdown(list(LUA_CHON_GIONG.items()), value='af_heart', label="Chọn Giọng")
160
+ dd_hw = gr.Dropdown([('GPU (Nhanh)', True), ('CPU (Chậm)', False)],
161
+ value=CUDA_AVAILABLE, label="Thiết bị xử ", interactive=CUDA_AVAILABLE)
162
+ slider_speed = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Tốc độ phát âm")
163
+ with gr.Row():
164
+ btn_random = gr.Button("🎲 Trích ngẫu nhiên", variant='secondary')
165
+ btn_gatsby = gr.Button("📖 Gatsby dài", variant='secondary')
166
+ btn_frank = gr.Button("📖 Frankenstein dài", variant='secondary')
167
+
168
+ with gr.Column(scale=6):
169
+ out_audio = gr.Audio(label="Kết quả âm thanh", interactive=False, autoplay=True)
170
+ out_tokens = gr.Textbox(label="Tokens đầu ra", interactive=False)
171
+ btn_generate = gr.Button("▶️ Chuyển đổi", variant='primary')
172
+
173
+ # Tab 2: Streaming
174
+ with gr.TabItem("🔴 TTS Streaming"):
175
+ with gr.Row():
176
+ with gr.Column(scale=6):
177
+ txt_in2 = gr.Textbox(label="Văn bản đầu vào", placeholder="Nhập văn bản để phát trực tiếp...", lines=5)
178
+ with gr.Row():
179
+ dd_voice2 = gr.Dropdown(list(LUA_CHON_GIONG.items()), value='af_heart', label="Chọn Giọng")
180
+ dd_hw2 = gr.Dropdown([('GPU (Nhanh)', True), ('CPU (Chậm)', False)],
181
+ value=CUDA_AVAILABLE, label="Thiết bị xử lý", interactive=CUDA_AVAILABLE)
182
+ slider_speed2 = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Tốc độ phát âm")
183
+ btn_stream = gr.Button("🎙️ Bắt đầu Streaming", variant='primary')
184
+ btn_stop = gr.Button("⏹️ Dừng lại", variant='stop')
185
+ with gr.Column(scale=6):
186
+ out_stream = gr.Audio(label="Phát trực tiếp", streaming=True, autoplay=True)
187
+
188
+ # Sự kiện nút bấm Tab 1
189
+ btn_random.click(fn=random_quote, inputs=[], outputs=[txt_in])
190
+ btn_gatsby.click(fn=load_gatsby, inputs=[], outputs=[txt_in])
191
+ btn_frank.click(fn=load_frank, inputs=[], outputs=[txt_in])
192
+
193
+ btn_generate.click(fn=generate_unlimited,
194
+ inputs=[txt_in, dd_voice, slider_speed, dd_hw],
195
+ outputs=[out_audio])
196
+ # nếu cần hiện tokens: .click(fn=tokenize_first, inputs=[txt_in, dd_voice], outputs=[out_tokens])
197
+
198
+ # Sự kiện Tab 2
199
+ stream_event = btn_stream.click(fn=generate_stream,
200
+ inputs=[txt_in2, dd_voice2, slider_speed2, dd_hw2],
201
+ outputs=[out_stream])
202
+ btn_stop.click(fn=None, cancels=[stream_event])
203
+
204
+ # Khởi chạy
205
+ app.queue(api_open=not IS_LTTEAM).launch(
206
+ show_api=not IS_LTTEAM,
207
+ ssr_mode=True
208
  )