pnnbao-ump commited on
Commit
863b7f0
·
1 Parent(s): 65f2e4f

add more examples

Browse files
app.py CHANGED
@@ -6,229 +6,317 @@ import soundfile as sf
6
  import tempfile
7
  import torch
8
  from vieneu_tts import VieNeuTTS
 
 
9
 
10
  print("⏳ Đang khởi động VieNeu-TTS...")
11
 
12
- # Khởi tạo model
13
  print("📦 Đang tải model...")
14
  device = "cuda" if torch.cuda.is_available() else "cpu"
15
  print(f"🖥️ Sử dụng thiết bị: {device.upper()}")
16
 
17
- tts = VieNeuTTS(
18
- backbone_repo="pnnbao-ump/VieNeu-TTS-1000h",
19
- backbone_device=device,
20
- codec_repo="neuphonic/neucodec",
21
- codec_device=device
22
- )
23
- print("✅ Model đã tải xong!")
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- # Danh sách giọng mẫu
26
  VOICE_SAMPLES = {
27
- "Nam 1": {
28
- "audio": "./sample/id_0001.wav",
29
- "text": "./sample/id_0001.txt"
30
- },
31
- "Nữ 1": {
32
- "audio": "./sample/id_0002.wav",
33
- "text": "./sample/id_0002.txt"
34
- },
35
- "Nam 2": {
36
- "audio": "./sample/id_0003.wav",
37
- "text": "./sample/id_0003.txt"
38
- },
39
- "Nữ 2": {
40
- "audio": "./sample/id_0004.wav",
41
- "text": "./sample/id_0004.txt"
42
- },
43
- "Nam 3": {
44
- "audio": "./sample/id_0005.wav",
45
- "text": "./sample/id_0005.txt"
46
- },
47
- "Nam 4": {
48
- "audio": "./sample/id_0007.wav",
49
- "text": "./sample/id_0007.txt"
50
- }
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  @spaces.GPU(duration=120)
54
- def synthesize_speech(text, voice_choice, custom_audio=None, custom_text=None):
55
- """Tổng hợp giọng nói từ văn bản"""
56
  try:
57
  if not text or text.strip() == "":
58
- return None, " Vui lòng nhập văn bản cần tổng hợp"
59
 
 
60
  if len(text) > 250:
61
- return None, "❌ Văn bản quá dài! Vui lòng nhập tối đa 250 ký tự. Để tổng hợp văn bản dài hơn, vui lòng tham khảo examples/infer_long_text.py"
62
-
63
- # Xác định reference audio và text
64
- if custom_audio is not None and custom_text:
 
 
65
  ref_audio_path = custom_audio
66
  ref_text_raw = custom_text
67
- print("🎨 Sử dụng giọng tùy chỉnh")
68
- elif voice_choice in VOICE_SAMPLES:
 
 
69
  ref_audio_path = VOICE_SAMPLES[voice_choice]["audio"]
70
  ref_text_path = VOICE_SAMPLES[voice_choice]["text"]
 
 
 
 
71
  with open(ref_text_path, "r", encoding="utf-8") as f:
72
  ref_text_raw = f.read()
73
- print(f"🎤 Sử dụng giọng: {voice_choice}")
74
- else:
75
- return None, "❌ Vui lòng chọn giọng hoặc tải lên audio tùy chỉnh"
 
76
 
77
- # Encode tổng hợp
78
- print(f"📝 Đang xử lý: {text[:50]}...")
79
- ref_codes = tts.encode_reference(ref_audio_path)
80
 
81
- print(f"🎵 Đang tổng hợp giọng nói trên {device.upper()}...")
82
  wav = tts.infer(text, ref_codes, ref_text_raw)
83
 
84
- # Lưu file
 
 
 
85
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
86
  sf.write(tmp_file.name, wav, 24000)
87
  output_path = tmp_file.name
88
 
89
- print("✅ Hoàn thành!")
90
- return output_path, f"✅ Tổng hợp thành công"
91
-
92
  except Exception as e:
93
- print(f"❌ Lỗi: {str(e)}")
94
  import traceback
95
  traceback.print_exc()
96
- return None, f"❌ Lỗi: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- # Custom CSS - tối giản
99
- custom_css = """
100
- .gradio-container {
101
- max-width: 900px !important;
102
- margin: 0 auto !important;
 
 
 
 
 
 
103
  }
104
- .warning-box {
105
- background-color: #fef3c7;
106
- border-left: 4px solid #f59e0b;
107
- padding: 12px 16px;
108
- border-radius: 6px;
109
- margin: 10px 0;
110
- color: #000000;
 
111
  }
112
- """
113
-
114
- # Tạo giao diện
115
- with gr.Blocks(title="VieNeu-TTS", css=custom_css, theme=gr.themes.Soft()) as demo:
116
- gr.Markdown("""
117
-
118
-
119
- # VieNeu-TTS
 
 
 
 
 
120
 
121
- Hệ thống tổng hợp tiếng nói tiếng Việt sử dụng Large Language Model
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- **Phiên bản:** VieNeu-TTS-1000h (model mới nhất, train trên 1000 giờ dữ liệu)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- [GitHub](https://github.com/pnnbao97/VieNeu-TTS) [Model Card](https://huggingface.co/pnnbao-ump/VieNeu-TTS) • [Finetune Guide](https://github.com/pnnbao-ump/VieNeuTTS/blob/main/finetune.ipynb)
 
126
 
127
- """)
128
- # Main interface
129
- with gr.Row():
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- with gr.Column(scale=1):
133
-
 
134
  text_input = gr.Textbox(
135
- label="Văn bản",
136
- placeholder="Nhập văn bản tiếng Việt (khuyến cáo dưới 250 ký tự)...",
137
- lines=5,
138
- value="Trí tuệ nhân tạo đang cách mạng hóa nhiều lĩnh vực, từ y tế, giáo dục đến giao thông vận tải, mang lại những giải pháp thông minhhiệu quả."
 
139
  )
140
 
141
- char_count = gr.Markdown("**142 / 250 ký tự**")
142
-
143
- voice_select = gr.Radio(
144
- choices=list(VOICE_SAMPLES.keys()),
145
- label="Chọn giọng",
146
- value="Nam 1"
147
- )
148
 
149
- submit_btn = gr.Button("Tổng hợp", variant="primary", size="lg")
150
-
151
- with gr.Column(scale=1):
152
- audio_output = gr.Audio(label="Kết quả", type="filepath")
153
- status_output = gr.Textbox(label="Trạng thái", interactive=False, show_label=False)
154
-
155
- with gr.Accordion("Giọng tùy chỉnh", open=False):
156
- gr.Markdown("""
157
- Tải lên file audio và nhập nội dung tương ứng. Để có kết quả tốt nhất, nên finetune model trên giọng của bạn.
158
- """)
159
- custom_audio = gr.Audio(label="File audio (.wav)", type="filepath")
160
- custom_text = gr.Textbox(
161
- label="Nội dung audio",
162
- placeholder="Nhập chính xác nội dung...",
163
- lines=2
164
- )
165
- gr.HTML("""
166
- <div class="warning-box" style="color: #000000;">
167
- ⚠️ Chúng tôi khuyến cáo sử dụng đoạn văn bản <250 ký tự để đảm bảo chất lượng tốt nhất.
168
- Nếu muốn tổng hợp văn bản dài hơn, vui lòng tham khảo code trong examples/infer_long_text.py
169
- </div>
170
- """)
171
-
172
- # Examples
173
- with gr.Row():
174
- gr.Examples(
175
- examples=[
176
- ["Trí tuệ nhân tạo đang cách mạng hóa nhiều lĩnh vực, từ y tế, giáo dục đến giao thông vận tải, mang lại những giải pháp thông minh và hiệu quả.", "Nam 1"],
177
- ["Trên bầu trời xanh thẳm, những đám mây trắng lửng lờ trôi như những chiếc thuyền nhỏ đang lướt nhẹ theo dòng gió. Dưới mặt đất, cánh đồng lúa vàng rực trải dài tới tận chân trời, những bông lúa nghiêng mình theo từng làn gió.", "Nữ 2"],
178
- ["Legacy là một bộ phim đột phá về mặt âm nhạc, quay phim, hiệu ứng đặc biệt, và tôi rất mừng vì cuối cùng nó cũng được cả giới phê bình lẫn người hâm mộ đánh giá lại. Chúng ta đã quá bất công với bộ phim này vào năm 2010.", "Nam 4"],
179
- ["Thật đáng ngạc nhiên! Mặc dù con đường này rất xa và khó đi, nhưng với sự kiên trì và sự đồng lòng của tất cả mọi người, chúng ta đã hoàn thành được công việc sửa chữa trước 3 ngày so với kế hoạch ban đầu, bạn có tin không?", "Nữ 1"],
180
- ["Các bác sĩ đang nghiên cứu một loại vaccine mới chống lại virus cúm mùa. Thí nghiệm lâm sàng cho thấy phản ứng miễn dịch mạnh mẽ và ít tác dụng phụ.", "Nam 2"],
181
- ],
182
- inputs=[text_input, voice_select],
183
- outputs=[audio_output, status_output],
184
- fn=synthesize_speech,
185
- cache_examples=False
186
- )
187
-
188
- # Footer info
189
- gr.Markdown("""
190
- ---
191
-
192
- **Tác giả:** Phạm Nguyễn Ngọc Bảo • **Model:** VieNeu-TTS-1000h
193
 
194
- **Lưu ý:** Nếu muốn sử dụng model cũ VieNeu-TTS-140h, hãy thay đổi `backbone_repo` trong mã nguồn
 
 
 
195
 
196
- ---
 
197
 
198
- ### Ủng hộ dự án
 
 
 
 
 
199
 
200
- VieNeu-TTS dự án miễn phí và mã nguồn mở. Tuy nhiên, việc train model TTS chất lượng cao trên 1000+ giờ dữ liệu đòi hỏi nguồn lực tính toán đáng kể.
 
 
 
 
201
 
202
- Nếu bạn thấy dự án này hữu ích, hãy cân nhắc ủng hộ:
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
- [Buy Me a Coffee](https://buymeacoffee.com/pnnbao)
205
 
206
- """)
207
-
208
- # Update character count
209
- def update_char_count(text):
210
- count = len(text) if text else 0
211
- color = "#dc2626" if count > 250 else "#374151"
212
- return f"<span style='color: {color}; font-weight: 500'>{count} / 250 ký tự</span>"
213
 
214
- text_input.change(
215
- fn=update_char_count,
216
- inputs=[text_input],
217
- outputs=[char_count]
218
- )
 
219
 
220
- # Event handler
221
- submit_btn.click(
 
 
 
222
  fn=synthesize_speech,
223
- inputs=[text_input, voice_select, custom_audio, custom_text],
224
  outputs=[audio_output, status_output]
225
  )
226
 
227
- # Launch
228
  if __name__ == "__main__":
229
- demo.queue(max_size=20)
230
- demo.launch(
231
- server_name="0.0.0.0",
232
- server_port=7860,
233
- show_error=True
234
  )
 
6
  import tempfile
7
  import torch
8
  from vieneu_tts import VieNeuTTS
9
+ import os
10
+ import time
11
 
12
  print("⏳ Đang khởi động VieNeu-TTS...")
13
 
14
+ # --- 1. SETUP MODEL ---
15
  print("📦 Đang tải model...")
16
  device = "cuda" if torch.cuda.is_available() else "cpu"
17
  print(f"🖥️ Sử dụng thiết bị: {device.upper()}")
18
 
19
+ try:
20
+ tts = VieNeuTTS(
21
+ backbone_repo="pnnbao-ump/VieNeu-TTS",
22
+ backbone_device=device,
23
+ codec_repo="neuphonic/neucodec",
24
+ codec_device=device
25
+ )
26
+ print("✅ Model đã tải xong!")
27
+ except Exception as e:
28
+ print(f"⚠️ Không thể tải model (Chế độ UI Demo): {e}")
29
+ class MockTTS:
30
+ def encode_reference(self, path): return None
31
+ def infer(self, text, ref, ref_text):
32
+ import numpy as np
33
+ # Giả lập độ trễ để test tính năng đo thời gian
34
+ time.sleep(1.5)
35
+ return np.random.uniform(-0.5, 0.5, 24000*3)
36
+ tts = MockTTS()
37
 
38
+ # --- 2. DATA ---
39
  VOICE_SAMPLES = {
40
+ "Bình (nam miền Bắc)": {"audio": "./sample/Bình (nam miền Bắc).wav", "text": "./sample/Bình (nam miền Bắc).txt"},
41
+ "Vĩnh (nam miền Nam)": {"audio": "./sample/Vĩnh (nam miền Nam).wav", "text": "./sample/Vĩnh (nam miền Nam).txt"},
42
+ "Tuyên (nam miền Bắc)": {"audio": "./sample/Tuyên (nam miền Bắc).wav", "text": "./sample/Tuyên (nam miền Bắc).txt"},
43
+ "Nguyên (nam miền Nam)": {"audio": "./sample/Nguyên (nam miền Nam).wav", "text": "./sample/Nguyên (nam miền Nam).txt"},
44
+ "Sơn (nam miền Nam)": {"audio": "./sample/Sơn (nam miền Nam).wav", "text": "./sample/Sơn (nam miền Nam).txt"},
45
+ "Hương (nữ miền Bắc)": {"audio": "./sample/Hương (nữ miền Bắc).wav", "text": "./sample/Hương (nữ miền Bắc).txt"},
46
+ "Ly (nữ miền Bắc)": {"audio": "./sample/Ly (nữ miền Bắc).wav", "text": "./sample/Ly (nữ miền Bắc).txt"},
47
+ "Ngọc (nữ miền Bắc)": {"audio": "./sample/Ngọc (nữ miền Bắc).wav", "text": "./sample/Ngọc (nữ miền Bắc).txt"},
48
+ "Đoan (nữ miền Nam)": {"audio": "./sample/Đoan (nữ miền Nam).wav", "text": "./sample/Đoan (nữ miền Nam).txt"},
49
+ "Dung (nữ miền Nam)": {"audio": "./sample/Dung (nữ miền Nam).wav", "text": "./sample/Dung (nữ miền Nam).txt"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
 
52
+ # --- 3. HELPER FUNCTIONS ---
53
+ def load_reference_info(voice_choice):
54
+ if voice_choice in VOICE_SAMPLES:
55
+ audio_path = VOICE_SAMPLES[voice_choice]["audio"]
56
+ text_path = VOICE_SAMPLES[voice_choice]["text"]
57
+ try:
58
+ if os.path.exists(text_path):
59
+ with open(text_path, "r", encoding="utf-8") as f:
60
+ ref_text = f.read()
61
+ return audio_path, ref_text
62
+ else:
63
+ return audio_path, "⚠️ Không tìm thấy file text mẫu."
64
+ except Exception as e:
65
+ return None, f"❌ Lỗi: {str(e)}"
66
+ return None, ""
67
+
68
  @spaces.GPU(duration=120)
69
+ def synthesize_speech(text, voice_choice, custom_audio, custom_text, mode_tab):
 
70
  try:
71
  if not text or text.strip() == "":
72
+ return None, "⚠️ Vui lòng nhập văn bản cần tổng hợp!"
73
 
74
+ # --- LOGIC CHECK LIMIT 250 ---
75
  if len(text) > 250:
76
+ return None, f"❌ Văn bản quá dài ({len(text)}/250 ký tự)! Vui lòng cắt ngắn lại để đảm bảo chất lượng."
77
+
78
+ # Logic chọn Reference
79
+ if mode_tab == "custom_mode":
80
+ if custom_audio is None or not custom_text:
81
+ return None, "⚠️ Vui lòng tải lên Audio v�� nhập nội dung Audio đó."
82
  ref_audio_path = custom_audio
83
  ref_text_raw = custom_text
84
+ print("🎨 Mode: Custom Voice")
85
+ else: # Preset
86
+ if voice_choice not in VOICE_SAMPLES:
87
+ return None, "⚠️ Vui lòng chọn một giọng mẫu."
88
  ref_audio_path = VOICE_SAMPLES[voice_choice]["audio"]
89
  ref_text_path = VOICE_SAMPLES[voice_choice]["text"]
90
+
91
+ if not os.path.exists(ref_audio_path):
92
+ return None, f"❌ Không tìm thấy file audio: {ref_audio_path}"
93
+
94
  with open(ref_text_path, "r", encoding="utf-8") as f:
95
  ref_text_raw = f.read()
96
+ print(f"🎤 Mode: Preset Voice ({voice_choice})")
97
+
98
+ # Inference & Đo thời gian
99
+ print(f"📝 Text: {text[:50]}...")
100
 
101
+ start_time = time.time() # <--- Bắt đầu bấm giờ
 
 
102
 
103
+ ref_codes = tts.encode_reference(ref_audio_path)
104
  wav = tts.infer(text, ref_codes, ref_text_raw)
105
 
106
+ end_time = time.time() # <--- Kết thúc bấm giờ
107
+ process_time = end_time - start_time # <--- Tính thời gian xử lý
108
+
109
+ # Save
110
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
111
  sf.write(tmp_file.name, wav, 24000)
112
  output_path = tmp_file.name
113
 
114
+ # <--- Cập nhật thông báo kết quả
115
+ return output_path, f"✅ Thành công! (Mất {process_time:.2f} giây để tạo)"
116
+
117
  except Exception as e:
 
118
  import traceback
119
  traceback.print_exc()
120
+ return None, f"❌ Lỗi hệ thống: {str(e)}"
121
+
122
+ # --- 4. UI SETUP ---
123
+ theme = gr.themes.Ocean(
124
+ primary_hue="indigo",
125
+ secondary_hue="cyan",
126
+ neutral_hue="slate",
127
+ font=[gr.themes.GoogleFont('Inter'), 'ui-sans-serif', 'system-ui'],
128
+ ).set(
129
+ button_primary_background_fill="linear-gradient(90deg, #6366f1 0%, #0ea5e9 100%)",
130
+ button_primary_background_fill_hover="linear-gradient(90deg, #4f46e5 0%, #0284c7 100%)",
131
+ block_shadow="0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
132
+ )
133
 
134
+ # <--- CSS ĐÃ SỬA (Background xanh đen + Chữ sáng)
135
+ css = """
136
+ .container { max-width: 1200px; margin: auto; }
137
+ .header-box {
138
+ text-align: center;
139
+ margin-bottom: 25px;
140
+ padding: 25px;
141
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); /* Xanh đen (Slate 900 -> 800) */
142
+ border-radius: 12px;
143
+ border: 1px solid #334155;
144
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
145
  }
146
+ .header-title {
147
+ font-size: 2.5rem;
148
+ font-weight: 800;
149
+ color: white; /* Chữ trắng */
150
+ background: -webkit-linear-gradient(45deg, #60A5FA, #22D3EE); /* Gradient xanh sáng cho chữ nổi bật */
151
+ -webkit-background-clip: text;
152
+ -webkit-text-fill-color: transparent;
153
+ margin-bottom: 10px;
154
  }
155
+ .header-desc {
156
+ font-size: 1.1rem;
157
+ color: #cbd5e1; /* Màu xám sáng (Slate-300) */
158
+ margin-bottom: 15px;
159
+ }
160
+ .link-group a {
161
+ text-decoration: none;
162
+ margin: 0 10px;
163
+ font-weight: 600;
164
+ color: #94a3b8; /* Màu link sáng hơn chút */
165
+ transition: color 0.2s;
166
+ }
167
+ .link-group a:hover { color: #38bdf8; text-shadow: 0 0 5px rgba(56, 189, 248, 0.5); }
168
 
169
+ .status-box { font-weight: bold; text-align: center; border: none; background: transparent; }
170
+ .warning-note {
171
+ background-color: #fff7ed;
172
+ border-left: 4px solid #f97316;
173
+ padding: 12px;
174
+ color: #9a3412;
175
+ font-size: 0.9rem;
176
+ border-radius: 4px;
177
+ margin-top: 10px;
178
+ margin-bottom: 10px;
179
+ }
180
+ """
181
 
182
+ EXAMPLES_LIST = [
183
+ # Nam Miền Nam
184
+ ["Về miền Tây không chỉ để ngắm nhìn sông nước hữu tình, mà còn để cảm nhận tấm chân tình của người dân nơi đây. Cùng ngồi xuồng ba lá len lỏi qua rặng dừa nước, nghe câu vọng cổ ngọt ngào thì còn gì bằng.", "Vĩnh (nam miền Nam)"],
185
+
186
+ # Nam Miền Bắc
187
+ ["Hà Nội những ngày vào thu mang một vẻ đẹp trầm mặc và cổ kính đến lạ thường. Đi dạo quanh Hồ Gươm vào sáng sớm, hít hà mùi hoa sữa nồng nàn và thưởng thức chút cốm làng Vòng là trải nghiệm khó quên.", "Bình (nam miền Bắc)"],
188
+
189
+ # Nam Miền Bắc
190
+ ["Sự bùng nổ của trí tuệ nhân tạo đang định hình lại cách chúng ta làm việc và sinh sống. Từ xe tự lái đến trợ lý ảo thông minh, công nghệ đang dần xóa nhòa ranh giới giữa thực tại và những bộ phim viễn tưởng.", "Tuyên (nam miền Bắc)"],
191
+
192
+ # Nam Miền Nam
193
+ ["Sài Gòn hối hả là thế, nhưng chỉ cần tấp vào một quán cà phê ven đường, gọi ly bạc xỉu đá và ngắm nhìn dòng người qua lại, bạn sẽ thấy thành phố này cũng có những khoảng lặng thật bình yên và đáng yêu.", "Nguyên (nam miền Nam)"],
194
+
195
+ # Nam Miền Nam
196
+ ["Để đảm bảo tiến độ dự án quan trọng này, chúng ta cần tập trung tối đa nguồn lực và phối hợp chặt chẽ giữa các phòng ban. Mọi khó khăn phát sinh cần được báo cáo ngay lập tức để ban lãnh đạo xử lý kịp thời.", "Sơn (nam miền Nam)"],
197
+
198
+ # Nữ Miền Nam
199
+ ["Dạ em chào anh chị, hiện tại bên em đang có chương trình ưu đãi đặc biệt cho căn hộ hướng sông này. Với thiết kế hiện đại và không gian xanh mát, đây chắc chắn là tổ ấm lý tưởng mà gia đình mình đang tìm kiếm.", "Đoan (nữ miền Nam)"],
200
+
201
+ # Nữ Miền Bắc
202
+ ["Dưới cơn mưa phùn lất phất của những ngày cuối đông, em khẽ nép vào vai anh, cảm nhận hơi ấm lan tỏa. Những khoảnh khắc bình dị như thế này khiến em nhận ra rằng, hạnh phúc đôi khi chỉ đơn giản là được ở bên nhau.", "Ngọc (nữ miền Bắc)"],
203
 
204
+ # Nữ Miền Bắc
205
+ ["Thay mặt phi hành đoàn, xin chào mừng quý khách đến với chuyến bay vi en 2024. Quý khách vui lòng thắt dây an toàn, dựng thẳng lưng ghế và gập bàn ăn phía trước để chuẩn bị cho máy bay cất cánh trong ít phút nữa.", "Hương (nữ miền Bắc)"],
206
 
207
+ # Nữ Miền Bắc
208
+ ["Ngày xửa ngày xưa, ở một ngôi làng nọ có cô Tấm xinh đẹp, nết na nhưng sớm mồ côi mẹ. Dù bị mẹ kế và Cám hãm hại đủ đường, Tấm vẫn giữ được tấm lòng lương thiện và cuối cùng tìm được hạnh phúc xứng đáng.", "Ly (nữ miền Bắc)"],
209
+ ]
210
 
211
+ with gr.Blocks(theme=theme, css=css, title="VieNeu-TTS Studio") as demo:
212
+
213
+ with gr.Column(elem_classes="container"):
214
+ # Header - Cập nhật class cho HTML
215
+ gr.HTML("""
216
+ <div class="header-box">
217
+ <div class="header-title">🎙️ VieNeu-TTS Studio</div>
218
+ <div class="header-desc">
219
+ Phiên bản: VieNeu-TTS-1000h (model mới nhất, train trên 1000 giờ dữ liệu)
220
+ </div>
221
+ <div class="link-group">
222
+ <a href="https://huggingface.co/pnnbao-ump/VieNeu-TTS" target="_blank">🤗 Model Card</a> •
223
+ <a href="https://huggingface.co/datasets/pnnbao-ump/VieNeu-TTS-1000h" target="_blank">📖 Dataset 1000h</a> •
224
+ <a href="https://github.com/pnnbao97/VieNeu-TTS" target="_blank">🦜 GitHub</a>
225
+ </div>
226
+ </div>
227
+ """)
228
+
229
+ with gr.Row(elem_classes="container", equal_height=False):
230
 
231
+ # --- LEFT: INPUT ---
232
+ with gr.Column(scale=3, variant="panel"):
233
+ gr.Markdown("### 📝 Văn bản đầu vào")
234
  text_input = gr.Textbox(
235
+ label="Nhập văn bản",
236
+ placeholder="Nhập nội dung tiếng Việt cần chuyển thành giọng nói...",
237
+ lines=4,
238
+ value=" Nội những ngày vào thu mang một vẻ đẹp trầm mặc cổ kính đến lạ thường. Đi dạo quanh Hồ Gươm vào sáng sớm, hít mùi hoa sữa nồng nànthưởng thức chút cốm làng Vòng là trải nghiệm khó quên.",
239
+ show_label=False
240
  )
241
 
242
+ # Counter + Warning
243
+ with gr.Row():
244
+ char_count = gr.HTML("<div style='text-align: right; color: #64748B; font-size: 0.8rem;'>0 / 250 ký tự</div>")
 
 
 
 
245
 
246
+ gr.Markdown("### 🗣️ Chọn giọng đọc")
247
+ with gr.Tabs() as tabs:
248
+ with gr.TabItem("👤 Giọng có sẵn (Preset)", id="preset_mode"):
249
+ voice_select = gr.Dropdown(
250
+ choices=list(VOICE_SAMPLES.keys()),
251
+ value="Bình (nam miền Bắc)",
252
+ label="Danh sách giọng",
253
+ interactive=True
254
+ )
255
+ with gr.Accordion("Thông tin giọng mẫu", open=False):
256
+ ref_audio_preview = gr.Audio(label="Audio mẫu", interactive=False, type="filepath")
257
+ ref_text_preview = gr.Markdown("...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
+ with gr.TabItem("🎙️ Giọng tùy chỉnh (Custom)", id="custom_mode"):
260
+ gr.Markdown("Tải lên giọng của bạn (Zero-shot Cloning)")
261
+ custom_audio = gr.Audio(label="File ghi âm (.wav)", type="filepath")
262
+ custom_text = gr.Textbox(label="Nội dung ghi âm", placeholder="Nhập chính xác lời thoại...")
263
 
264
+ current_mode = gr.Textbox(visible=False, value="preset_mode")
265
+ btn_generate = gr.Button("Tổng hợp giọng nói", variant="primary", size="lg")
266
 
267
+ # --- RIGHT: OUTPUT ---
268
+ with gr.Column(scale=2):
269
+ gr.Markdown("### 🎧 Kết quả")
270
+ with gr.Group():
271
+ audio_output = gr.Audio(label="Audio đầu ra", type="filepath", show_download_button=True, autoplay=True)
272
+ status_output = gr.Textbox(label="Trạng thái", show_label=False, elem_classes="status-box", placeholder="Sẵn sàng...")
273
 
274
+ # --- EXAMPLES ---
275
+ with gr.Row(elem_classes="container"):
276
+ with gr.Column():
277
+ gr.Markdown("### 📚 Ví dụ mẫu")
278
+ gr.Examples(examples=EXAMPLES_LIST, inputs=[text_input, voice_select], label="Thử nghiệm nhanh")
279
 
280
+ # --- LOGIC ---
281
+ def update_count(text):
282
+ l = len(text)
283
+ if l > 250:
284
+ color = "#dc2626" # Red
285
+ msg = f"⚠️ <b>{l} / 250</b> - Quá giới hạn!"
286
+ elif l > 200:
287
+ color = "#ea580c" # Orange
288
+ msg = f"{l} / 250"
289
+ else:
290
+ color = "#64748B" # Gray
291
+ msg = f"{l} / 250 ký tự"
292
+ return f"<div style='text-align: right; color: {color}; font-size: 0.8rem; font-weight: bold'>{msg}</div>"
293
 
294
+ text_input.change(update_count, text_input, char_count)
295
 
296
+ def update_ref_preview(voice):
297
+ audio, text = load_reference_info(voice)
298
+ return audio, f"> *\"{text}\"*"
 
 
 
 
299
 
300
+ voice_select.change(update_ref_preview, voice_select, [ref_audio_preview, ref_text_preview])
301
+ demo.load(update_ref_preview, voice_select, [ref_audio_preview, ref_text_preview])
302
+
303
+ # Tab handling - FIXED WITH *ARGS
304
+ tab_preset = tabs.children[0]
305
+ tab_custom = tabs.children[1]
306
 
307
+ # Dùng *args để nhận bất kỳ số lượng tham số nào (0 hoặc 1), tránh lỗi Warning
308
+ tab_preset.select(fn=lambda *args: "preset_mode", inputs=None, outputs=current_mode)
309
+ tab_custom.select(fn=lambda *args: "custom_mode", inputs=None, outputs=current_mode)
310
+
311
+ btn_generate.click(
312
  fn=synthesize_speech,
313
+ inputs=[text_input, voice_select, custom_audio, custom_text, current_mode],
314
  outputs=[audio_output, status_output]
315
  )
316
 
 
317
  if __name__ == "__main__":
318
+ demo.queue().launch(
319
+ server_name="127.0.0.1",
320
+ server_port=7860,
321
+ share=False
 
322
  )
sample/Bình (nam miền Bắc).txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Anh chỉ muốn được nhìn nhận như là một huấn luyện viên.
sample/{id_0002.wav → Bình (nam miền Bắc).wav} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:8195d3fa6bfc1a049f2b61f2d4052e2d8caf69556b206d2d5eff48314c5ee907
3
- size 435788
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:135f087ced48606c4d406b770a11e344d4d9aa6bd7adfb3e5c26f69cd9cc6df1
3
+ size 127054
sample/Dung (nữ miền Nam).txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Tục ngữ có câu, sai một li, đi một dặm.
sample/{id_0007.wav → Dung (nữ miền Nam).wav} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:090480cf6a7751a11107470971cfcca64af06768552036497aa16426d14f4cb7
3
- size 161468
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:56e42039d0c96ad19e9f78ecb7218853202022b2a8460010d34ffb7879b17409
3
+ size 143438
sample/Hương (nữ miền Bắc).txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Tuy nhiên, lúc này có một vấn đề khó khăn nảy sinh.
sample/Hương (nữ miền Bắc).wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c064b4ec64df44ea1306e87b25b84905e540d0fe29885629d2fe8bc8a5e53bc
3
+ size 155756
sample/Ly (nữ miền Bắc).txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Chúng ta có thể áp dụng logic tương tự với người khác.
sample/Ly (nữ miền Bắc).wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0d4e47cfa5ed0b753c2bed07c58e26da89ee2977ca5e941244a6bbafd8869d5e
3
+ size 147534
sample/{id_0003.txt → Nguyên (nam miền Nam).txt} RENAMED
File without changes
sample/{id_0003.wav → Nguyên (nam miền Nam).wav} RENAMED
File without changes
sample/{id_0004.txt → Ngọc (nữ miền Bắc).txt} RENAMED
File without changes
sample/{id_0004.wav → Ngọc (nữ miền Bắc).wav} RENAMED
File without changes
sample/{id_0005.txt → Sơn (nam miền Nam).txt} RENAMED
File without changes
sample/{id_0005.wav → Sơn (nam miền Nam).wav} RENAMED
File without changes
sample/Tuyên (nam miền Bắc).txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Bạn cầm khúc cây, và ném vào bãi cỏ xanh tươi rậm rạp ở đằng xa.
sample/Tuyên (nam miền Bắc).wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f6b7ac2605db0a2cf634ce0f3a55a87a89f4c2e3bc06f83433e6af583c1f3692
3
+ size 217166
sample/{id_0001.txt → Vĩnh (nam miền Nam).txt} RENAMED
File without changes
sample/{id_0001.wav → Vĩnh (nam miền Nam).wav} RENAMED
File without changes
sample/id_0002.txt DELETED
@@ -1 +0,0 @@
1
- Từ nhiều nguồn tài liệu lịch sử, có thể thấy nuôi con theo phong cách Do Thái không chỉ tốt cho đứa trẻ mà còn tốt cho cả các bậc cha mẹ.
 
 
sample/id_0007.txt DELETED
@@ -1 +0,0 @@
1
- Sau bữa sáng, tôi tìm thấy xe của mình trong bãi và lái ra cổng.
 
 
sample/Đoan (nữ miền Nam).txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Nuôi con theo phong cách Do Thái, không chỉ tốt cho đứa trẻ, mà còn tốt cho cả các bậc cha mẹ.
sample/Đoan (nữ miền Nam).wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3e319ed45dd2a1458a52edfe43a83a36eff813f19399ac2e59ee3f93cace74be
3
+ size 294830