Accordic commited on
Commit
38b750c
·
verified ·
1 Parent(s): c7448a0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +304 -323
app.py CHANGED
@@ -1,55 +1,38 @@
1
  # coding=utf-8
2
- # Qwen3-TTS Gradio Demo - Phiên bản CPU
3
  import os
4
  import gradio as gr
5
  import numpy as np
6
  import torch
7
  from huggingface_hub import snapshot_download, login
8
 
9
- # Đăng nhập HuggingFace (tùy chọn, chỉ cần nếu model yêu cầu quyền truy cập)
10
  HF_TOKEN = os.environ.get('HF_TOKEN')
11
  if HF_TOKEN:
12
  login(token=HF_TOKEN)
13
 
14
- # Lưu trữ các model đã tải - theo cặp (model_type, model_size)
15
  loaded_models = {}
16
-
17
- # Tùy chọn kích thước model
18
  MODEL_SIZES = ["0.6B", "1.7B"]
19
 
20
-
21
  def get_model_path(model_type: str, model_size: str) -> str:
22
- """Lấy đường dẫn model dựa trên loại và kích thước."""
23
  return snapshot_download(f"Qwen/Qwen3-TTS-12Hz-{model_size}-{model_type}")
24
 
25
-
26
  def get_model(model_type: str, model_size: str):
27
- """Lấy hoặc tải model theo loại và kích thước."""
28
  global loaded_models
29
  key = (model_type, model_size)
30
  if key not in loaded_models:
31
  from qwen_tts import Qwen3TTSModel
32
  model_path = get_model_path(model_type, model_size)
33
-
34
- # Tự động phát hiện thiết bị
35
  device = "cuda" if torch.cuda.is_available() else "cpu"
36
  dtype = torch.bfloat16 if device == "cuda" else torch.float32
37
-
38
- print(f"Đang tải model {model_type} {model_size} trên {device} với dtype {dtype}")
39
-
40
  loaded_models[key] = Qwen3TTSModel.from_pretrained(
41
- model_path,
42
- device_map=device,
43
- dtype=dtype,
44
- token=HF_TOKEN,
45
  )
46
  return loaded_models[key]
47
 
48
-
49
  def _normalize_audio(wav, eps=1e-12, clip=True):
50
- """Chuẩn hóa audio về float32 trong khoảng [-1, 1]."""
51
  x = np.asarray(wav)
52
-
53
  if np.issubdtype(x.dtype, np.integer):
54
  info = np.iinfo(x.dtype)
55
  if info.min < 0:
@@ -63,418 +46,416 @@ def _normalize_audio(wav, eps=1e-12, clip=True):
63
  if m > 1.0 + 1e-6:
64
  y = y / (m + eps)
65
  else:
66
- raise TypeError(f"Kiểu dữ liệu không được hỗ trợ: {x.dtype}")
67
-
68
  if clip:
69
  y = np.clip(y, -1.0, 1.0)
70
-
71
  if y.ndim > 1:
72
  y = np.mean(y, axis=-1).astype(np.float32)
73
-
74
  return y
75
 
76
-
77
  def _audio_to_tuple(audio):
78
- """Chuyển đổi đầu vào audio của Gradio thành tuple (wav, sr)."""
79
  if audio is None:
80
  return None
81
-
82
  if isinstance(audio, tuple) and len(audio) == 2 and isinstance(audio[0], int):
83
  sr, wav = audio
84
  wav = _normalize_audio(wav)
85
  return wav, int(sr)
86
-
87
  if isinstance(audio, dict) and "sampling_rate" in audio and "data" in audio:
88
  sr = int(audio["sampling_rate"])
89
  wav = _normalize_audio(audio["data"])
90
  return wav, sr
91
-
92
  return None
93
 
94
-
95
- # Danh sách giọng nói và ngôn ngữ cho model CustomVoice
96
- SPEAKERS = [
97
- "Aiden", "Dylan", "Eric", "Ono_anna", "Ryan", "Serena", "Sohee", "Uncle_fu", "Vivian"
98
- ]
99
  LANGUAGES = ["Tự động", "Tiếng Trung", "Tiếng Anh", "Tiếng Nhật", "Tiếng Hàn", "Tiếng Pháp", "Tiếng Đức", "Tiếng Tây Ban Nha", "Tiếng Bồ Đào Nha", "Tiếng Nga"]
100
  LANGUAGE_MAP = {
101
- "Tự động": "Auto",
102
- "Tiếng Trung": "Chinese",
103
- "Tiếng Anh": "English",
104
- "Tiếng Nhật": "Japanese",
105
- "Tiếng Hàn": "Korean",
106
- "Tiếng Pháp": "French",
107
- "Tiếng Đức": "German",
108
- "Tiếng Tây Ban Nha": "Spanish",
109
- "Tiếng Bồ Đào Nha": "Portuguese",
110
- "Tiếng Nga": "Russian"
111
  }
112
 
113
-
114
  def generate_voice_design(text, language, voice_description):
115
- """Tạo giọng nói bằng model Voice Design (chỉ 1.7B)."""
116
  if not text or not text.strip():
117
- return None, "❌ Lỗi: Vui lòng nhập văn bản."
118
  if not voice_description or not voice_description.strip():
119
- return None, "❌ Lỗi: Vui lòng nhập mô tả giọng nói."
120
-
121
  try:
122
- print("Bắt đầu tạo giọng nói tùy chỉnh...")
123
  tts = get_model("VoiceDesign", "1.7B")
124
  lang_code = LANGUAGE_MAP.get(language, "Auto")
125
  wavs, sr = tts.generate_voice_design(
126
- text=text.strip(),
127
- language=lang_code,
128
  instruct=voice_description.strip(),
129
- non_streaming_mode=True,
130
- max_new_tokens=2048,
131
  )
132
- print("Hoàn thành tạo giọng nói!")
133
- return (sr, wavs[0]), "✅ Tạo giọng nói tùy chỉnh thành công!"
134
  except Exception as e:
135
- import traceback
136
- error_msg = f"❌ Lỗi: {type(e).__name__}: {e}\n{traceback.format_exc()}"
137
- print(error_msg)
138
- return None, error_msg
139
-
140
 
141
  def generate_voice_clone(ref_audio, ref_text, target_text, language, use_xvector_only, model_size):
142
- """Tạo giọng nói bằng model Base (Nhân bản giọng nói)."""
143
  if not target_text or not target_text.strip():
144
- return None, "❌ Lỗi: Vui lòng nhập văn bản đích."
145
-
146
  audio_tuple = _audio_to_tuple(ref_audio)
147
  if audio_tuple is None:
148
- return None, "❌ Lỗi: Vui lòng tải lên audio tham chiếu."
149
-
150
  if not use_xvector_only and (not ref_text or not ref_text.strip()):
151
- return None, "❌ Lỗi: Vui lòng nhập văn bản tham chiếu khi không sử dụng chế độ 'Chỉ dùng x-vector'."
152
-
153
  try:
154
- print("Bắt đầu nhân bản giọng nói...")
155
  tts = get_model("Base", model_size)
156
  lang_code = LANGUAGE_MAP.get(language, "Auto")
157
  wavs, sr = tts.generate_voice_clone(
158
- text=target_text.strip(),
159
- language=lang_code,
160
  ref_audio=audio_tuple,
161
  ref_text=ref_text.strip() if ref_text else None,
162
- x_vector_only_mode=use_xvector_only,
163
- max_new_tokens=2048,
164
  )
165
- print("Hoàn thành nhân bản giọng nói!")
166
- return (sr, wavs[0]), "✅ Nhân bản giọng nói thành công!"
167
  except Exception as e:
168
- import traceback
169
- error_msg = f"❌ Lỗi: {type(e).__name__}: {e}\n{traceback.format_exc()}"
170
- print(error_msg)
171
- return None, error_msg
172
-
173
 
174
  def generate_custom_voice(text, language, speaker, instruct, model_size):
175
- """Tạo giọng nói bằng model CustomVoice."""
176
  if not text or not text.strip():
177
- return None, "❌ Lỗi: Vui lòng nhập văn bản."
178
  if not speaker:
179
- return None, "❌ Lỗi: Vui lòng chọn giọng nói."
180
-
181
  try:
182
- print("Bắt đầu tạo giọng nói...")
183
  tts = get_model("CustomVoice", model_size)
184
  lang_code = LANGUAGE_MAP.get(language, "Auto")
185
  wavs, sr = tts.generate_custom_voice(
186
- text=text.strip(),
187
- language=lang_code,
188
  speaker=speaker.lower().replace(" ", "_"),
189
  instruct=instruct.strip() if instruct else None,
190
- non_streaming_mode=True,
191
- max_new_tokens=2048,
192
  )
193
- print("Hoàn thành tạo giọng nói!")
194
- return (sr, wavs[0]), "✅ Tạo giọng nói thành công!"
195
  except Exception as e:
196
- import traceback
197
- error_msg = f"❌ Lỗi: {type(e).__name__}: {e}\n{traceback.format_exc()}"
198
- print(error_msg)
199
- return None, error_msg
200
-
201
 
202
- # Xây dựng giao diện Gradio
203
  def build_ui():
204
  theme = gr.themes.Soft(
205
- font=[gr.themes.GoogleFont("Source Sans Pro"), "Arial", "sans-serif"],
206
  primary_hue="blue",
207
- secondary_hue="slate",
208
  )
209
 
210
  css = """
 
211
  .gradio-container {
212
- max-width: 1400px !important;
213
- margin: auto;
214
  }
215
- .tab-content {
216
- padding: 25px;
217
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
218
- border-radius: 15px;
 
219
  }
220
- .input-box {
221
- border-radius: 10px;
222
- border: 2px solid #e0e0e0;
 
 
 
 
 
 
223
  }
224
- .generate-btn {
225
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
226
- border: none;
227
- font-size: 16px;
228
- font-weight: bold;
229
- padding: 12px 24px;
230
- border-radius: 10px;
231
- transition: all 0.3s ease;
 
 
 
232
  }
233
- .generate-btn:hover {
234
- transform: translateY(-2px);
235
- box-shadow: 0 10px 20px rgba(0,0,0,0.2);
 
 
 
236
  }
237
- h1 {
 
 
238
  text-align: center;
 
239
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
240
- -webkit-background-clip: text;
241
- -webkit-text-fill-color: transparent;
242
- font-size: 3em;
243
- margin-bottom: 10px;
244
  }
245
- .subtitle {
246
- text-align: center;
247
- color: #666;
248
- font-size: 1.2em;
249
- margin-bottom: 30px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  }
251
  """
252
 
253
- with gr.Blocks(theme=theme, css=css, title="Qwen3-TTS - Chuyển đổi Văn bản thành Giọng nói") as demo:
254
- gr.Markdown(
255
- """
256
- # 🎙️ Qwen3-TTS - Chuyển đổi Văn bản thành Giọng nói
257
- <p class="subtitle">Hệ thống tổng hợp giọng nói AI tiên tiến với 3 chế độ mạnh mẽ</p>
258
- """
259
- )
 
260
 
261
  with gr.Tabs():
262
  # Tab 1: Thiết kế Giọng nói
263
- with gr.Tab("🎨 Thiết kế Giọng nói"):
264
- gr.Markdown(
265
- """
266
- ### Tạo giọng nói tùy chỉnh bằng mô tả ngôn ngữ tự nhiên
267
- Sử dụng mô tả bằng lời để tạo giọng nói với phong cách và cảm xúc riêng biệt (Model 1.7B)
268
- """
 
 
269
  )
270
- with gr.Row():
271
- with gr.Column(scale=1):
272
- design_text = gr.Textbox(
273
- label="📝 Văn bản cần chuyển đổi",
274
- lines=5,
275
- placeholder="Nhập văn bản bạn muốn chuyển thành giọng nói...",
276
- value="Nó ở trong ngăn kéo trên cùng... chờ đã, trống rỗng ư? Không thể nào! Tôi chắc chắn đã để nó ở đó mà!",
277
- elem_classes="input-box"
278
- )
279
- design_language = gr.Dropdown(
280
- label="🌍 Ngôn ngữ",
281
- choices=LANGUAGES,
282
- value="Tự động",
283
- interactive=True,
284
- )
285
- design_instruct = gr.Textbox(
286
- label="🎭 Mô tả giọng nói",
287
- lines=4,
288
- placeholder="Mô tả đặc điểm giọng nói bạn muốn: cảm xúc, phong cách, tốc độ...",
289
- value="Nói với giọng điệu hoài nghi, nhưng với chút hoảng loạn bắt đầu len lỏi vào giọng nói.",
290
- elem_classes="input-box"
291
- )
292
- design_btn = gr.Button("🚀 Tạo giọng nói", variant="primary", elem_classes="generate-btn")
293
-
294
- with gr.Column(scale=1):
295
- design_audio_out = gr.Audio(label="🔊 Audio đã tạo", type="numpy")
296
- design_status = gr.Textbox(label="📊 Trạng thái", lines=3, interactive=False)
297
- gr.Markdown(
298
- """
299
- **💡 Mẹo:**
300
- - Mô tả chi tiết cảm xúc và phong cách giọng nói
301
- - Thử nghiệm với các yêu cầu sáng tạo
302
- - Model 1.7B cho kết quả tốt nhất
303
- """
304
- )
305
 
306
  design_btn.click(
307
  generate_voice_design,
308
  inputs=[design_text, design_language, design_instruct],
309
- outputs=[design_audio_out, design_status],
310
  )
311
 
312
- # Tab 2: Nhân bản Giọng nói
313
- with gr.Tab("🎤 Nhân bản Giọng nói"):
314
- gr.Markdown(
315
- """
316
- ### Sao chép giọng nói từ mẫu audio tham chiếu
317
- Tải lên một đoạn audio mẫu để nhân bản giọng nói cho văn bản mới
318
- """
319
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  with gr.Row():
321
- with gr.Column(scale=1):
322
- gr.Markdown("#### 📂 Audio & Văn bản Tham chiếu")
323
- clone_ref_audio = gr.Audio(
324
- label="🎵 Audio tham chiếu (Tải lên mẫu giọng cần nhân bản)",
325
- type="numpy",
326
- )
327
- clone_ref_text = gr.Textbox(
328
- label="📄 Văn bản tham chiếu (Nội dung trong audio tham chiếu)",
329
- lines=3,
330
- placeholder="Nhập chính xác nội dung được nói trong audio tham chiếu...",
331
- elem_classes="input-box"
332
- )
333
- clone_xvector = gr.Checkbox(
334
- label="⚡ Chỉ dùng x-vector (Không cần văn bản tham chiếu, chất lượng thấp hơn)",
335
- value=False,
336
- )
337
-
338
- with gr.Column(scale=1):
339
- gr.Markdown("#### 🎯 Văn bản Đích & Cài đặt")
340
- clone_target_text = gr.Textbox(
341
- label="✍️ Văn bản đích (Nội dung muốn giọng nhân bản nói)",
342
- lines=5,
343
- placeholder="Nhập văn bản bạn muốn giọng nhân bản đọc...",
344
- elem_classes="input-box"
345
- )
346
- with gr.Row():
347
- clone_language = gr.Dropdown(
348
- label="🌍 Ngôn ngữ",
349
- choices=LANGUAGES,
350
- value="Tự động",
351
- interactive=True,
352
- )
353
- clone_model_size = gr.Dropdown(
354
- label="⚙️ Kích thước Model",
355
- choices=MODEL_SIZES,
356
- value="0.6B",
357
- interactive=True,
358
- )
359
- clone_btn = gr.Button("🎬 Nhân bản & Tạo giọng", variant="primary", elem_classes="generate-btn")
360
-
361
- with gr.Row():
362
- with gr.Column(scale=1):
363
- clone_audio_out = gr.Audio(label="🔊 Audio đã tạo", type="numpy")
364
- with gr.Column(scale=1):
365
- clone_status = gr.Textbox(label="📊 Trạng thái", lines=3, interactive=False)
366
 
367
  clone_btn.click(
368
  generate_voice_clone,
369
- inputs=[clone_ref_audio, clone_ref_text, clone_target_text, clone_language, clone_xvector, clone_model_size],
370
- outputs=[clone_audio_out, clone_status],
 
371
  )
372
 
373
- # Tab 3: Giọng nói có sẵn
374
- with gr.Tab("🗣️ Giọng nói có sẵn"):
375
- gr.Markdown(
376
- """
377
- ### Chuyển văn bản thành giọng nói với các giọng đọc được huấn luyện trước
378
- Chọn từ nhiều giọng nói chuyên nghiệp với khả năng tùy chỉnh phong cách
379
- """
 
 
380
  )
 
381
  with gr.Row():
382
- with gr.Column(scale=1):
383
- tts_text = gr.Textbox(
384
- label="📝 Văn bản cần chuyển đổi",
385
- lines=6,
386
- placeholder="Nhập văn bản bạn muốn chuyển thành giọng nói...",
387
- value="Xin chào! Chào mừng đến với hệ thống chuyển văn bản thành giọng nói. Đây là bản demo khả năng TTS của chúng tôi.",
388
- elem_classes="input-box"
389
- )
390
- with gr.Row():
391
- tts_language = gr.Dropdown(
392
- label="🌍 Ngôn ngữ",
393
- choices=LANGUAGES,
394
- value="Tiếng Anh",
395
- interactive=True,
396
- )
397
- tts_speaker = gr.Dropdown(
398
- label="👤 Giọng đọc",
399
- choices=SPEAKERS,
400
- value="Ryan",
401
- interactive=True,
402
- )
403
- with gr.Row():
404
- tts_instruct = gr.Textbox(
405
- label="🎨 Hướng dẫn phong cách (Tùy chọn)",
406
- lines=2,
407
- placeholder="VD: Nói với giọng vui vẻ và tràn đầy năng lượng",
408
- elem_classes="input-box"
409
- )
410
- tts_model_size = gr.Dropdown(
411
- label="⚙️ Kích thước Model",
412
- choices=MODEL_SIZES,
413
- value="0.6B",
414
- interactive=True,
415
- )
416
- tts_btn = gr.Button("🎵 Tạo giọng nói", variant="primary", elem_classes="generate-btn")
417
-
418
- with gr.Column(scale=1):
419
- tts_audio_out = gr.Audio(label="🔊 Audio đã tạo", type="numpy")
420
- tts_status = gr.Textbox(label="📊 Trạng thái", lines=3, interactive=False)
421
- gr.Markdown(
422
- """
423
- **👥 Giọng đọc có sẵn:**
424
- - **Aiden, Dylan, Eric, Ryan**: Giọng nam
425
- - **Serena, Vivian**: Giọng nữ
426
- - **Ono_anna, Sohee**: Giọng châu Á
427
- - **Uncle_fu**: Giọng trưởng thành
428
-
429
- **💡 Mẹo:**
430
- - Dùng model 0.6B để tạo nhanh hơn
431
- - Thêm hướng dẫn phong cách để tùy chỉnh cảm xúc
432
- """
433
- )
434
 
435
  tts_btn.click(
436
  generate_custom_voice,
437
  inputs=[tts_text, tts_language, tts_speaker, tts_instruct, tts_model_size],
438
- outputs=[tts_audio_out, tts_status],
439
  )
440
 
441
- gr.Markdown(
442
- """
443
- ---
444
-
445
- ### ⚙️ Thông tin Hệ thống
446
-
447
- **⚠️ Lưu ý khi chạy trên CPU:**
448
- - Phiên bản này chạy trên CPU và sẽ **chậm hơn đáng kể** so với GPU
449
- - Thời gian ước tính: **30 giây đến vài phút** mỗi lần tạo (tùy độ dài văn bản và model)
450
- - Lần tạo đầu tiên sẽ chậm hơn do phải tải model vào bộ nhớ
451
-
452
- **📋 Mẹo Tối ưu Hiệu suất:**
453
- - ✅ Sử dụng model **0.6B** để tạo nhanh hơn (khi có sẵn)
454
- - ✅ Giữ văn bản **ngắn gọn** (1-2 câu) để thời gian chờ hợp lý
455
- - ✅ Đóng các ứng dụng khác để **giải phóng RAM**
456
- - ✅ **Kiên nhẫn** - Model đang xử lý ở chế độ nền
457
-
458
- **💻 Yêu cầu Hệ thống:**
459
- - 🔹 **RAM**: Tối thiểu 8GB (khuyến nghị 16GB cho model 1.7B)
460
- - 🔹 **Lưu trữ**: ~5GB cho mỗi model
461
- - 🔹 **CPU**: Khuyến nghị bộ xử lý đa nhân
462
-
463
- **📚 Được xây dựng với:** [Qwen3-TTS](https://github.com/QwenLM/Qwen3-TTS) bởi Alibaba Qwen Team
464
-
465
  ---
466
- <p style="text-align: center; color: #888;">Made with ❤️ using Qwen3-TTS | Phiên bản CPU</p>
467
- """
468
- )
 
469
 
470
  return demo
471
 
472
-
473
  if __name__ == "__main__":
474
  demo = build_ui()
475
  demo.launch(
476
  server_name="0.0.0.0",
477
  server_port=7860,
478
- share=False,
479
- show_error=True
480
  )
 
1
  # coding=utf-8
2
+ # Qwen3-TTS Gradio Demo - Giao diện Responsive
3
  import os
4
  import gradio as gr
5
  import numpy as np
6
  import torch
7
  from huggingface_hub import snapshot_download, login
8
 
9
+ # Đăng nhập HuggingFace
10
  HF_TOKEN = os.environ.get('HF_TOKEN')
11
  if HF_TOKEN:
12
  login(token=HF_TOKEN)
13
 
 
14
  loaded_models = {}
 
 
15
  MODEL_SIZES = ["0.6B", "1.7B"]
16
 
 
17
  def get_model_path(model_type: str, model_size: str) -> str:
 
18
  return snapshot_download(f"Qwen/Qwen3-TTS-12Hz-{model_size}-{model_type}")
19
 
 
20
  def get_model(model_type: str, model_size: str):
 
21
  global loaded_models
22
  key = (model_type, model_size)
23
  if key not in loaded_models:
24
  from qwen_tts import Qwen3TTSModel
25
  model_path = get_model_path(model_type, model_size)
 
 
26
  device = "cuda" if torch.cuda.is_available() else "cpu"
27
  dtype = torch.bfloat16 if device == "cuda" else torch.float32
28
+ print(f"Đang tải model {model_type} {model_size} trên {device}")
 
 
29
  loaded_models[key] = Qwen3TTSModel.from_pretrained(
30
+ model_path, device_map=device, dtype=dtype, token=HF_TOKEN
 
 
 
31
  )
32
  return loaded_models[key]
33
 
 
34
  def _normalize_audio(wav, eps=1e-12, clip=True):
 
35
  x = np.asarray(wav)
 
36
  if np.issubdtype(x.dtype, np.integer):
37
  info = np.iinfo(x.dtype)
38
  if info.min < 0:
 
46
  if m > 1.0 + 1e-6:
47
  y = y / (m + eps)
48
  else:
49
+ raise TypeError(f"Kiểu dữ liệu không hỗ trợ: {x.dtype}")
 
50
  if clip:
51
  y = np.clip(y, -1.0, 1.0)
 
52
  if y.ndim > 1:
53
  y = np.mean(y, axis=-1).astype(np.float32)
 
54
  return y
55
 
 
56
  def _audio_to_tuple(audio):
 
57
  if audio is None:
58
  return None
 
59
  if isinstance(audio, tuple) and len(audio) == 2 and isinstance(audio[0], int):
60
  sr, wav = audio
61
  wav = _normalize_audio(wav)
62
  return wav, int(sr)
 
63
  if isinstance(audio, dict) and "sampling_rate" in audio and "data" in audio:
64
  sr = int(audio["sampling_rate"])
65
  wav = _normalize_audio(audio["data"])
66
  return wav, sr
 
67
  return None
68
 
69
+ SPEAKERS = ["Aiden", "Dylan", "Eric", "Ono_anna", "Ryan", "Serena", "Sohee", "Uncle_fu", "Vivian"]
 
 
 
 
70
  LANGUAGES = ["Tự động", "Tiếng Trung", "Tiếng Anh", "Tiếng Nhật", "Tiếng Hàn", "Tiếng Pháp", "Tiếng Đức", "Tiếng Tây Ban Nha", "Tiếng Bồ Đào Nha", "Tiếng Nga"]
71
  LANGUAGE_MAP = {
72
+ "Tự động": "Auto", "Tiếng Trung": "Chinese", "Tiếng Anh": "English",
73
+ "Tiếng Nhật": "Japanese", "Tiếng Hàn": "Korean", "Tiếng Pháp": "French",
74
+ "Tiếng Đức": "German", "Tiếng Tây Ban Nha": "Spanish",
75
+ "Tiếng Bồ Đào Nha": "Portuguese", "Tiếng Nga": "Russian"
 
 
 
 
 
 
76
  }
77
 
 
78
  def generate_voice_design(text, language, voice_description):
 
79
  if not text or not text.strip():
80
+ return None, "❌ Vui lòng nhập văn bản"
81
  if not voice_description or not voice_description.strip():
82
+ return None, "❌ Vui lòng nhập mô tả giọng nói"
 
83
  try:
 
84
  tts = get_model("VoiceDesign", "1.7B")
85
  lang_code = LANGUAGE_MAP.get(language, "Auto")
86
  wavs, sr = tts.generate_voice_design(
87
+ text=text.strip(), language=lang_code,
 
88
  instruct=voice_description.strip(),
89
+ non_streaming_mode=True, max_new_tokens=2048
 
90
  )
91
+ return (sr, wavs[0]), "✅ Hoàn thành!"
 
92
  except Exception as e:
93
+ return None, f"❌ Lỗi: {str(e)}"
 
 
 
 
94
 
95
  def generate_voice_clone(ref_audio, ref_text, target_text, language, use_xvector_only, model_size):
 
96
  if not target_text or not target_text.strip():
97
+ return None, "❌ Vui lòng nhập văn bản đích"
 
98
  audio_tuple = _audio_to_tuple(ref_audio)
99
  if audio_tuple is None:
100
+ return None, "❌ Vui lòng tải audio tham chiếu"
 
101
  if not use_xvector_only and (not ref_text or not ref_text.strip()):
102
+ return None, "❌ Vui lòng nhập văn bản tham chiếu"
 
103
  try:
 
104
  tts = get_model("Base", model_size)
105
  lang_code = LANGUAGE_MAP.get(language, "Auto")
106
  wavs, sr = tts.generate_voice_clone(
107
+ text=target_text.strip(), language=lang_code,
 
108
  ref_audio=audio_tuple,
109
  ref_text=ref_text.strip() if ref_text else None,
110
+ x_vector_only_mode=use_xvector_only, max_new_tokens=2048
 
111
  )
112
+ return (sr, wavs[0]), "✅ Hoàn thành!"
 
113
  except Exception as e:
114
+ return None, f"❌ Lỗi: {str(e)}"
 
 
 
 
115
 
116
  def generate_custom_voice(text, language, speaker, instruct, model_size):
 
117
  if not text or not text.strip():
118
+ return None, "❌ Vui lòng nhập văn bản"
119
  if not speaker:
120
+ return None, "❌ Vui lòng chọn giọng nói"
 
121
  try:
 
122
  tts = get_model("CustomVoice", model_size)
123
  lang_code = LANGUAGE_MAP.get(language, "Auto")
124
  wavs, sr = tts.generate_custom_voice(
125
+ text=text.strip(), language=lang_code,
 
126
  speaker=speaker.lower().replace(" ", "_"),
127
  instruct=instruct.strip() if instruct else None,
128
+ non_streaming_mode=True, max_new_tokens=2048
 
129
  )
130
+ return (sr, wavs[0]), "✅ Hoàn thành!"
 
131
  except Exception as e:
132
+ return None, f"❌ Lỗi: {str(e)}"
 
 
 
 
133
 
 
134
  def build_ui():
135
  theme = gr.themes.Soft(
136
+ font=[gr.themes.GoogleFont("Inter"), "sans-serif"],
137
  primary_hue="blue",
138
+ radius_size="md",
139
  )
140
 
141
  css = """
142
+ /* Container chính */
143
  .gradio-container {
144
+ max-width: 100% !important;
145
+ padding: 10px !important;
146
  }
147
+
148
+ /* Tab style */
149
+ .tab-nav button {
150
+ font-size: 14px !important;
151
+ padding: 10px 15px !important;
152
  }
153
+
154
+ /* Button style */
155
+ button.primary {
156
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
157
+ border: none !important;
158
+ font-weight: 600 !important;
159
+ padding: 12px 24px !important;
160
+ border-radius: 8px !important;
161
+ transition: all 0.3s ease !important;
162
  }
163
+
164
+ button.primary:hover {
165
+ transform: translateY(-2px) !important;
166
+ box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3) !important;
167
+ }
168
+
169
+ /* Input fields */
170
+ textarea, input, select {
171
+ border-radius: 8px !important;
172
+ border: 1.5px solid #e0e0e0 !important;
173
+ font-size: 14px !important;
174
  }
175
+
176
+ /* Labels */
177
+ label {
178
+ font-weight: 600 !important;
179
+ color: #374151 !important;
180
+ margin-bottom: 8px !important;
181
  }
182
+
183
+ /* Header */
184
+ .app-header {
185
  text-align: center;
186
+ padding: 20px 10px;
187
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
188
+ color: white;
189
+ border-radius: 12px;
190
+ margin-bottom: 20px;
 
191
  }
192
+
193
+ .app-header h1 {
194
+ margin: 0;
195
+ font-size: clamp(24px, 5vw, 36px);
196
+ font-weight: 700;
197
+ }
198
+
199
+ .app-header p {
200
+ margin: 8px 0 0 0;
201
+ font-size: clamp(12px, 3vw, 16px);
202
+ opacity: 0.95;
203
+ }
204
+
205
+ /* Card style cho sections */
206
+ .input-card {
207
+ background: white;
208
+ padding: 20px;
209
+ border-radius: 12px;
210
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
211
+ margin-bottom: 15px;
212
+ }
213
+
214
+ /* Status message */
215
+ .status-box {
216
+ padding: 12px;
217
+ border-radius: 8px;
218
+ margin-top: 10px;
219
+ font-size: 13px;
220
+ }
221
+
222
+ /* Info boxes */
223
+ .info-box {
224
+ background: #f0f9ff;
225
+ border-left: 4px solid #3b82f6;
226
+ padding: 12px 15px;
227
+ border-radius: 6px;
228
+ margin: 10px 0;
229
+ font-size: 13px;
230
+ }
231
+
232
+ .warning-box {
233
+ background: #fef3c7;
234
+ border-left: 4px solid #f59e0b;
235
+ padding: 12px 15px;
236
+ border-radius: 6px;
237
+ margin: 10px 0;
238
+ font-size: 13px;
239
+ }
240
+
241
+ /* Responsive adjustments */
242
+ @media (max-width: 768px) {
243
+ .gradio-container {
244
+ padding: 5px !important;
245
+ }
246
+
247
+ .app-header {
248
+ padding: 15px 10px;
249
+ margin-bottom: 15px;
250
+ }
251
+
252
+ .input-card {
253
+ padding: 15px;
254
+ }
255
+
256
+ button.primary {
257
+ width: 100%;
258
+ padding: 14px 20px !important;
259
+ }
260
+
261
+ .tab-nav button {
262
+ font-size: 12px !important;
263
+ padding: 8px 10px !important;
264
+ }
265
+ }
266
+
267
+ /* Compact spacing for mobile */
268
+ @media (max-width: 480px) {
269
+ .block {
270
+ margin: 8px 0 !important;
271
+ }
272
+
273
+ textarea {
274
+ font-size: 14px !important;
275
+ }
276
  }
277
  """
278
 
279
+ with gr.Blocks(theme=theme, css=css, title="Qwen3-TTS") as demo:
280
+ # Header
281
+ gr.HTML("""
282
+ <div class="app-header">
283
+ <h1>🎙️ Qwen3-TTS</h1>
284
+ <p>Chuyển đổi Văn bản thành Giọng nói bằng AI</p>
285
+ </div>
286
+ """)
287
 
288
  with gr.Tabs():
289
  # Tab 1: Thiết kế Giọng nói
290
+ with gr.Tab("🎨 Thiết kế Giọng"):
291
+ gr.Markdown("**Tạo giọng nói tùy chỉnh bằng mô tả** (Model 1.7B)")
292
+
293
+ design_text = gr.Textbox(
294
+ label="📝 Văn bản",
295
+ lines=4,
296
+ placeholder="Nhập nội dung cần đọc...",
297
+ value="Xin chào! Đây là giọng nói được tạo bởi AI."
298
  )
299
+
300
+ design_language = gr.Dropdown(
301
+ label="🌍 Ngôn ngữ",
302
+ choices=LANGUAGES,
303
+ value="Tự động"
304
+ )
305
+
306
+ design_instruct = gr.Textbox(
307
+ label="🎭 Mô tả giọng nói",
308
+ lines=3,
309
+ placeholder="VD: Giọng vui vẻ, tràn đầy năng lượng...",
310
+ value="Nói với giọng thân thiện và nhiệt tình"
311
+ )
312
+
313
+ design_btn = gr.Button("🚀 Tạo giọng nói", variant="primary")
314
+ design_audio_out = gr.Audio(label="🔊 Kết quả")
315
+ design_status = gr.Textbox(label="Trạng thái", lines=2, interactive=False)
316
+
317
+ gr.HTML("""
318
+ <div class="info-box">
319
+ <strong>💡 Mẹo:</strong> Mô tả chi tiết cảm xúc, tốc độ, phong cách để có kết quả tốt nhất
320
+ </div>
321
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
322
 
323
  design_btn.click(
324
  generate_voice_design,
325
  inputs=[design_text, design_language, design_instruct],
326
+ outputs=[design_audio_out, design_status]
327
  )
328
 
329
+ # Tab 2: Nhân bản Giọng
330
+ with gr.Tab("🎤 Nhân bản Giọng"):
331
+ gr.Markdown("**Sao chép giọng nói từ audio mẫu**")
332
+
333
+ clone_ref_audio = gr.Audio(
334
+ label="🎵 Audio mẫu",
335
+ type="numpy"
336
  )
337
+
338
+ clone_ref_text = gr.Textbox(
339
+ label="📄 Nội dung audio mẫu",
340
+ lines=2,
341
+ placeholder="Nhập chính xác nội dung trong audio..."
342
+ )
343
+
344
+ clone_xvector = gr.Checkbox(
345
+ label="⚡ Chế độ nhanh (không cần nội dung audio)",
346
+ value=False
347
+ )
348
+
349
+ clone_target_text = gr.Textbox(
350
+ label="✍️ Văn bản cần đọc",
351
+ lines=3,
352
+ placeholder="Nhập nội dung muốn giọng nhân bản đọc..."
353
+ )
354
+
355
  with gr.Row():
356
+ clone_language = gr.Dropdown(
357
+ label="🌍 Ngôn ngữ",
358
+ choices=LANGUAGES,
359
+ value="Tự động",
360
+ scale=1
361
+ )
362
+ clone_model_size = gr.Dropdown(
363
+ label="⚙️ Model",
364
+ choices=MODEL_SIZES,
365
+ value="0.6B",
366
+ scale=1
367
+ )
368
+
369
+ clone_btn = gr.Button("🎬 Nhân bản giọng", variant="primary")
370
+ clone_audio_out = gr.Audio(label="🔊 Kết quả")
371
+ clone_status = gr.Textbox(label="Trạng thái", lines=2, interactive=False)
372
+
373
+ gr.HTML("""
374
+ <div class="info-box">
375
+ <strong>💡 Lưu ý:</strong> Audio mẫu nên rõ ràng, ít nhiễu và độ dài 3-10 giây
376
+ </div>
377
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
  clone_btn.click(
380
  generate_voice_clone,
381
+ inputs=[clone_ref_audio, clone_ref_text, clone_target_text,
382
+ clone_language, clone_xvector, clone_model_size],
383
+ outputs=[clone_audio_out, clone_status]
384
  )
385
 
386
+ # Tab 3: Giọng có sẵn
387
+ with gr.Tab("🗣️ Giọng có sẵn"):
388
+ gr.Markdown("**Sử dụng giọng đọc được huấn luyện sẵn**")
389
+
390
+ tts_text = gr.Textbox(
391
+ label="📝 Văn bản",
392
+ lines=4,
393
+ placeholder="Nhập nội dung cần đọc...",
394
+ value="Xin chào! Chào mừng bạn đến với hệ thống TTS."
395
  )
396
+
397
  with gr.Row():
398
+ tts_language = gr.Dropdown(
399
+ label="🌍 Ngôn ngữ",
400
+ choices=LANGUAGES,
401
+ value="Tiếng Anh",
402
+ scale=1
403
+ )
404
+ tts_speaker = gr.Dropdown(
405
+ label="👤 Giọng đọc",
406
+ choices=SPEAKERS,
407
+ value="Ryan",
408
+ scale=1
409
+ )
410
+
411
+ tts_instruct = gr.Textbox(
412
+ label="🎨 Phong cách (tùy chọn)",
413
+ lines=2,
414
+ placeholder="VD: Nói chậm rãi và rõ ràng"
415
+ )
416
+
417
+ tts_model_size = gr.Dropdown(
418
+ label="⚙️ Kích thước Model",
419
+ choices=MODEL_SIZES,
420
+ value="0.6B"
421
+ )
422
+
423
+ tts_btn = gr.Button("🎵 Tạo giọng nói", variant="primary")
424
+ tts_audio_out = gr.Audio(label="🔊 Kết quả")
425
+ tts_status = gr.Textbox(label="Trạng thái", lines=2, interactive=False)
426
+
427
+ gr.HTML("""
428
+ <div class="info-box">
429
+ <strong>👥 Giọng:</strong> Aiden, Dylan, Eric, Ryan (nam) • Serena, Vivian (nữ) • Ono_anna, Sohee (châu Á)
430
+ </div>
431
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
 
433
  tts_btn.click(
434
  generate_custom_voice,
435
  inputs=[tts_text, tts_language, tts_speaker, tts_instruct, tts_model_size],
436
+ outputs=[tts_audio_out, tts_status]
437
  )
438
 
439
+ # Footer
440
+ gr.HTML("""
441
+ <div class="warning-box">
442
+ <strong>⚠️ Lưu ý CPU:</strong> Thời gian xử lý: 30s - vài phút. Dùng model 0.6B để nhanh hơn. Văn bản ngắn tốt hơn.
443
+ </div>
444
+ """)
445
+
446
+ gr.Markdown("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  ---
448
+ <div style="text-align: center; color: #888; font-size: 13px;">
449
+ Powered by <a href="https://github.com/QwenLM/Qwen3-TTS" target="_blank">Qwen3-TTS</a> • Alibaba Qwen Team
450
+ </div>
451
+ """)
452
 
453
  return demo
454
 
 
455
  if __name__ == "__main__":
456
  demo = build_ui()
457
  demo.launch(
458
  server_name="0.0.0.0",
459
  server_port=7860,
460
+ share=False
 
461
  )