CVNSS commited on
Commit
320f794
·
verified ·
1 Parent(s): d0acdd5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -123
app.py CHANGED
@@ -2,10 +2,12 @@
2
  # -*- coding: utf-8 -*-
3
 
4
  """
5
- 💎 CVNSS4.0 Vietnamese TTS Studio - Công nghệ giọng nói
6
- - Compatibility: Valtec Source Structure
7
- - Author: Long Ngo, 2026 | Phiên bản 1.0.1
8
- - Advisor: Thầy Trần Tư Bình
 
 
9
  """
10
 
11
  import os
@@ -15,32 +17,66 @@ import time
15
  import re
16
  import logging
17
  import tempfile
18
- import shutil
 
19
  from pathlib import Path
20
  from typing import Optional, List
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  import torch
23
  import numpy as np
24
  import soundfile as sf
25
  import gradio as gr
26
- from huggingface_hub import hf_hub_download
27
 
28
- # --- 1. ROBUST LOGGING & PATH SETUP ---
29
  logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)s | %(message)s')
30
  logger = logging.getLogger("CVNSS_Studio")
31
 
32
- # Định vị thư mục gốc chính xác
33
  ROOT_DIR = Path(__file__).resolve().parent
34
  if str(ROOT_DIR) not in sys.path:
35
  sys.path.insert(0, str(ROOT_DIR))
36
 
37
- # --- 2. IMPORT HANDLER (CRITICAL FIX) ---
38
- # Chúng ta sẽ thử import, nếu thiếu src sẽ báo lỗi rõ ràng
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  try:
40
- # Kiểm tra xem folder src có tồn tại không
41
- if not (ROOT_DIR / "src").exists():
42
- raise ImportError("Thư mục 'src' không tồn tại. Vui lòng upload folder src từ repo gốc!")
43
 
 
 
44
  from src.vietnamese.text_processor import process_vietnamese_text
45
  from src.vietnamese.phonemizer import text_to_phonemes, VIPHONEME_AVAILABLE
46
  from src.models.synthesizer import SynthesizerTrn
@@ -49,15 +85,15 @@ try:
49
  from src.nn import commons
50
  CORE_LOADED = True
51
  IMPORT_ERROR_MSG = ""
52
- except Exception as e:
53
- logger.error(f"❌ Core load failed: {e}")
54
  CORE_LOADED = False
55
  IMPORT_ERROR_MSG = str(e)
56
  VIPHONEME_AVAILABLE = False
57
  symbols = []
58
 
59
  # =========================================================
60
- # 3. ELEGANT CSS (AZURE HORIZON)
61
  # =========================================================
62
  ELEGANT_CSS = r"""
63
  @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;800&family=Roboto+Mono:wght@400;500&display=swap');
@@ -65,10 +101,8 @@ ELEGANT_CSS = r"""
65
  :root {
66
  --primary-blue: #3b82f6;
67
  --text-dark: #1e293b;
68
- --text-gray: #64748b;
69
  --surface-white: #ffffff;
70
  --bg-gradient: linear-gradient(135deg, #f8fafc 0%, #e0f2fe 100%);
71
- --radius-xl: 20px;
72
  }
73
 
74
  body, .gradio-container {
@@ -79,16 +113,14 @@ body, .gradio-container {
79
 
80
  .elegant-card {
81
  background: var(--surface-white);
82
- border-radius: var(--radius-xl);
83
  border: 1px solid rgba(255, 255, 255, 0.8);
84
  box-shadow: 0 10px 30px -10px rgba(59, 130, 246, 0.15);
85
  padding: 24px;
86
  }
87
 
88
  .header-title {
89
- font-weight: 800;
90
- font-size: 2rem;
91
- color: #0f172a;
92
  letter-spacing: -0.03em;
93
  }
94
 
@@ -98,6 +130,7 @@ button.primary-btn {
98
  border-radius: 12px !important;
99
  border: none !important;
100
  font-weight: 600 !important;
 
101
  transition: 0.2s !important;
102
  }
103
  button.primary-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px -5px rgba(59, 130, 246, 0.4); }
@@ -105,11 +138,10 @@ button.primary-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px
105
  .badge { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 99px; font-size: 0.8rem; font-weight: 600; margin-right: 5px;}
106
  .badge-success { background: #dcfce7; color: #15803d; }
107
  .badge-error { background: #fee2e2; color: #b91c1c; }
108
- .badge-warn { background: #fef9c3; color: #854d0e; }
109
  """
110
 
111
  # =========================================================
112
- # 4. UTILITIES & LOGIC
113
  # =========================================================
114
  def split_text_smart(text: str, max_chars: int = 300) -> List[str]:
115
  if not text: return []
@@ -138,7 +170,7 @@ def split_text_smart(text: str, max_chars: int = 300) -> List[str]:
138
  return chunks if chunks else [text]
139
 
140
  # =========================================================
141
- # 5. ENGINE CORE (Auto-Downloading)
142
  # =========================================================
143
  class TTSManager:
144
  def __init__(self):
@@ -146,52 +178,34 @@ class TTSManager:
146
  self.net_g = None
147
  self.hps = None
148
  self.ready = False
149
- self.status_msg = "Khởi tạo..."
150
-
151
- # Tự động load ngay khi init
152
  self._initialize_model()
153
 
154
- def _download_file_if_missing(self, repo_id, filename, local_dir):
155
- target_path = local_dir / filename
156
- if not target_path.exists():
157
- logger.info(f"⬇️ Đang tải {filename}...")
158
- try:
159
- # Tải về file tạm rồi move vào đúng chỗ để tránh lỗi cache
160
- file_path = hf_hub_download(repo_id=repo_id, filename=filename, local_dir=local_dir)
161
- return Path(file_path)
162
- except Exception as e:
163
- logger.error(f"Không tải được {filename}: {e}")
164
- return None
165
- return target_path
166
-
167
  def _initialize_model(self):
168
  try:
169
- # 1. Định nghĩa thư mục chứa model cục bộ (để kiểm soát chắc chắn)
170
  model_dir = ROOT_DIR / "model_cache"
171
  model_dir.mkdir(exist_ok=True)
172
 
173
- repo_id = "valtecAI-team/valtec-vietnamese-tts" # Repo gốc bạn cung cấp
 
174
 
175
- # 2. Tải Config
176
- cfg_path = self._download_file_if_missing(repo_id, "config.json", model_dir)
177
 
178
- # 3. Tải Model (G_100000.pth hoặc file G mới nhất)
179
- # Ở đây ta hardcode file G_100000.pth vì repo valtec thường dùng tên này hoặc tương tự
180
- # Bạn thể đổi tên file nếu repo update
181
- ckpt_path = self._download_file_if_missing(repo_id, "G_100000.pth", model_dir)
182
-
183
- if not cfg_path or not ckpt_path:
184
- self.status_msg = "❌ Không tải được file model. Kiểm tra kết nối mạng."
185
- return
186
 
187
- # 4. Load Config
188
  with open(cfg_path, "r", encoding="utf-8") as f:
189
  self.hps = json.load(f)
190
 
191
  self.spk2id = self.hps["data"]["spk2id"]
192
  self.speakers = sorted(list(self.spk2id.keys()))
193
 
194
- # 5. Load Network
195
  if CORE_LOADED:
196
  self.net_g = SynthesizerTrn(
197
  len(symbols),
@@ -206,19 +220,17 @@ class TTSManager:
206
  self.net_g.eval()
207
  self.ready = True
208
  self.status_msg = f"✅ Sẵn sàng ({self.device})"
209
- logger.info("Engine Ready!")
210
  else:
211
- self.status_msg = "❌ Lỗi Import Core (src folder missing)"
212
 
213
  except Exception as e:
214
  self.ready = False
215
- self.status_msg = f"❌ Lỗi Init: {str(e)}"
216
  logger.error(self.status_msg)
217
 
218
  def infer(self, text, spk, speed, ns, nsw, sdp):
219
- if not self.ready:
220
- raise RuntimeError(f"Engine chưa sẵn sàng: {self.status_msg}")
221
-
222
  if self.device.type == 'cuda': torch.cuda.empty_cache()
223
 
224
  text_norm = process_vietnamese_text(text)
@@ -232,106 +244,79 @@ class TTSManager:
232
  lang = torch.LongTensor(commons.intersperse(lang_ids, 0)).unsqueeze(0).to(self.device)
233
  sid = torch.LongTensor([self.spk2id.get(spk, 0)]).to(self.device)
234
 
235
- outputs = self.net_g.infer(x, x_len, sid, tone, lang,
236
- noise_scale=ns, noise_scale_w=nsw,
237
- length_scale=speed, sdp_ratio=sdp)
238
-
239
- audio = outputs[0][0, 0].data.cpu().float().numpy()
240
- return audio, self.hps["data"]["sampling_rate"]
241
 
242
  # =========================================================
243
- # 6. UI CONSTRUCTION
244
  # =========================================================
245
  def build_interface():
246
- # Khởi tạo Manager
247
  manager = TTSManager()
248
 
249
  def run_inference(text, spk, speed, ns, nsw, sdp, is_long, chunk_size, pause, progress=gr.Progress()):
250
  if not manager.ready:
251
- return None, f"<span class='badge badge-error'>{manager.status_msg}</span><br><small>{IMPORT_ERROR_MSG}</small>"
 
 
 
252
 
253
  if not text: return None, "⚠️ Chưa nhập nội dung"
254
 
255
- start_time = time.time()
256
  try:
257
  full_audio = None
258
  sr = 0
259
-
260
  if not is_long:
261
  full_audio, sr = manager.infer(text, spk, speed, ns, nsw, sdp)
262
  else:
263
  chunks = split_text_smart(text, chunk_size)
264
  segments = []
265
- # Dummy sr, will be updated
266
- sr = 22050
267
-
268
  for i, chunk in enumerate(chunks):
269
  progress((i)/len(chunks), desc=f"Đoạn {i+1}/{len(chunks)}")
270
  a, r = manager.infer(chunk, spk, speed, ns, nsw, sdp)
271
  sr = r
272
  segments.append(a)
273
- if pause > 0:
274
- segments.append(np.zeros(int(sr * pause / 1000)))
275
-
276
- if segments:
277
- full_audio = np.concatenate(segments)
278
 
279
- if full_audio is None: return None, "❌ Lỗi tạo âm thanh"
280
-
281
- # Export
282
- proc_time = time.time() - start_time
283
- dur = len(full_audio) / sr
284
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as fp:
285
  sf.write(fp.name, full_audio, sr)
 
286
  return fp.name, f"<span class='badge badge-success'>Hoàn thành: {dur:.1f}s</span>"
287
-
288
  except Exception as e:
289
  return None, f"<span class='badge badge-error'>Lỗi: {str(e)}</span>"
290
 
291
- # --- LAYOUT ---
292
- speaker_list = manager.speakers if manager.ready else ["Chưa tải model"]
293
 
294
- with gr.Blocks(theme=gr.themes.Soft(), css=ELEGANT_CSS, title="CVNSS4.0 Auto-Fix") as app:
295
- with gr.Row():
296
- with gr.Column():
297
- gr.HTML(f"""
298
- <div style="margin-bottom: 20px;">
299
- <div class="header-title">CVNSS4.0 Studio</div>
300
- <div style="color: #64748b; font-size: 0.9rem;">
301
- Long Ngo • Trần Tư Bình • Valtec TTS Core<br>
302
- Trạng thái Engine: <b>{manager.status_msg}</b>
303
- </div>
304
- </div>
305
- """)
306
- if not CORE_LOADED:
307
- gr.HTML(f"""
308
- <div style="background: #fee2e2; color: #b91c1c; padding: 10px; border-radius: 8px; margin-bottom: 10px;">
309
- <b>⚠️ CẢNH BÁO QUAN TRỌNG:</b><br>
310
- Không tìm thấy thư mục <code>src</code>. Engine không thể chạy.<br>
311
- Vui lòng đảm bảo bạn đã upload thư mục <code>src</code> từ repo Valtec lên tab Files của Space.
312
- <br><i>Chi tiết lỗi: {IMPORT_ERROR_MSG}</i>
313
- </div>
314
- """)
315
 
316
  with gr.Tabs():
317
- # Tab Nhanh
318
  with gr.Tab("⚡ Chế độ Nhanh"):
319
  with gr.Row():
320
  with gr.Column(scale=3, elem_classes="elegant-card"):
321
- txt_input = gr.Textbox(label="Văn bản", placeholder="Nhập đó đi...", lines=3)
322
  with gr.Row():
323
- spk_drp = gr.Dropdown(speaker_list, value=speaker_list[0] if speaker_list else None, label="Giọng")
324
  spd_sld = gr.Slider(0.5, 2.0, 1.0, label="Tốc độ")
325
  btn_run = gr.Button("🔊 Đọc Ngay", elem_classes="primary-btn")
326
-
327
  with gr.Column(scale=2, elem_classes="elegant-card"):
328
  out_aud = gr.Audio(label="Kết quả", type="filepath")
329
  out_html = gr.HTML()
330
-
331
- btn_run.click(lambda t, s, sp: run_inference(t, s, sp, 0.667, 0.8, 0.2, False, 0, 0),
332
- [txt_input, spk_drp, spd_sld], [out_aud, out_html])
333
 
334
- # Tab Chuyên sâu
335
  with gr.Tab("💎 Chế độ Dài"):
336
  with gr.Row():
337
  with gr.Column(scale=3, elem_classes="elegant-card"):
@@ -340,17 +325,13 @@ def build_interface():
340
  ns = gr.Slider(0.1, 1.5, 0.667, label="Noise Scale")
341
  nsw = gr.Slider(0.1, 1.5, 0.8, label="Noise Width")
342
  sdp = gr.Slider(0, 1, 0.2, label="SDP")
343
- chunk = gr.Slider(100, 1000, 300, label="Ngắt câu (ký tự)")
344
  pause = gr.Slider(0, 1000, 250, label="Nghỉ (ms)")
345
  btn_long = gr.Button("🚀 Xử lý", elem_classes="primary-btn")
346
-
347
  with gr.Column(scale=2, elem_classes="elegant-card"):
348
  out_long = gr.Audio(label="Audio", type="filepath")
349
  out_html_long = gr.HTML()
350
-
351
- btn_long.click(lambda t, s, sp, n, nw, sd, c, p: run_inference(t, s, sp, n, nw, sd, True, c, p),
352
- [txt_long, spk_drp, spd_sld, ns, nsw, sdp, chunk, pause],
353
- [out_long, out_html_long])
354
 
355
  return app
356
 
 
2
  # -*- coding: utf-8 -*-
3
 
4
  """
5
+ 💎 CVNSS4.0 Vietnamese TTS Studio - Final Repair Edition
6
+ - Fix: 'No module named imp' (Python 3.12+ Compatibility Patch)
7
+ - Fix: Auto-download 'src' folder if missing
8
+ - Fix: Auto-download Model checkpoints
9
+ - Design: Azure Horizon (Ceramic White & Soft Blue)
10
+ - Author: Refactored by 100-Year AI Expert
11
  """
12
 
13
  import os
 
17
  import re
18
  import logging
19
  import tempfile
20
+ import importlib
21
+ import types
22
  from pathlib import Path
23
  from typing import Optional, List
24
 
25
+ # --- 0. CRITICAL PATCH: FIX 'No module named imp' ---
26
+ # Mã nguồn cũ dùng 'imp' (đã bị xóa ở Python 3.12). Ta tạo module giả để đánh lừa nó.
27
+ try:
28
+ import imp
29
+ except ImportError:
30
+ import types
31
+ # Tạo module giả
32
+ imp = types.ModuleType("imp")
33
+ # Map các hàm quan trọng từ importlib sang imp
34
+ imp.new_module = types.ModuleType
35
+ imp.reload = importlib.reload
36
+ sys.modules["imp"] = imp
37
+ print("🔧 Đã vá lỗi 'imp' module cho Python 3.12+")
38
+
39
  import torch
40
  import numpy as np
41
  import soundfile as sf
42
  import gradio as gr
43
+ from huggingface_hub import hf_hub_download, snapshot_download
44
 
45
+ # --- 1. SETUP LOGGING & PATH ---
46
  logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)s | %(message)s')
47
  logger = logging.getLogger("CVNSS_Studio")
48
 
 
49
  ROOT_DIR = Path(__file__).resolve().parent
50
  if str(ROOT_DIR) not in sys.path:
51
  sys.path.insert(0, str(ROOT_DIR))
52
 
53
+ # --- 2. AUTO-HEALING: TẢI SOURCE CODE NẾU THIẾU ---
54
+ def ensure_source_code():
55
+ src_path = ROOT_DIR / "src"
56
+ if not src_path.exists():
57
+ logger.warning("⚠️ Không thấy thư mục 'src'. Đang tự động tải từ kho gốc Valtec...")
58
+ try:
59
+ # Tải folder src từ repo gốc về thư mục hiện tại
60
+ snapshot_download(
61
+ repo_id="valtecAI-team/valtec-vietnamese-tts",
62
+ repo_type="space", # Repo gốc là Space
63
+ allow_patterns=["src/*", "src/**/*"],
64
+ local_dir=str(ROOT_DIR),
65
+ token=None # Public repo không cần token
66
+ )
67
+ logger.info("✅ Đã tải xong mã nguồn 'src'.")
68
+ except Exception as e:
69
+ logger.error(f"❌ Không thể tải mã nguồn: {e}")
70
+ raise RuntimeError("Không thể tải 'src'. Vui lòng kiểm tra kết nối mạng.")
71
+
72
+ # Chạy hàm kiểm tra ngay lập tức
73
  try:
74
+ ensure_source_code()
75
+ except Exception:
76
+ pass # Sẽ xử lỗi phần import bên dưới
77
 
78
+ # --- 3. IMPORT CORE MODULES ---
79
+ try:
80
  from src.vietnamese.text_processor import process_vietnamese_text
81
  from src.vietnamese.phonemizer import text_to_phonemes, VIPHONEME_AVAILABLE
82
  from src.models.synthesizer import SynthesizerTrn
 
85
  from src.nn import commons
86
  CORE_LOADED = True
87
  IMPORT_ERROR_MSG = ""
88
+ except ImportError as e:
89
+ logger.error(f"❌ Lỗi Import Core: {e}")
90
  CORE_LOADED = False
91
  IMPORT_ERROR_MSG = str(e)
92
  VIPHONEME_AVAILABLE = False
93
  symbols = []
94
 
95
  # =========================================================
96
+ # 4. ELEGANT CSS (AZURE HORIZON)
97
  # =========================================================
98
  ELEGANT_CSS = r"""
99
  @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;800&family=Roboto+Mono:wght@400;500&display=swap');
 
101
  :root {
102
  --primary-blue: #3b82f6;
103
  --text-dark: #1e293b;
 
104
  --surface-white: #ffffff;
105
  --bg-gradient: linear-gradient(135deg, #f8fafc 0%, #e0f2fe 100%);
 
106
  }
107
 
108
  body, .gradio-container {
 
113
 
114
  .elegant-card {
115
  background: var(--surface-white);
116
+ border-radius: 20px;
117
  border: 1px solid rgba(255, 255, 255, 0.8);
118
  box-shadow: 0 10px 30px -10px rgba(59, 130, 246, 0.15);
119
  padding: 24px;
120
  }
121
 
122
  .header-title {
123
+ font-weight: 800; font-size: 2rem; color: #0f172a;
 
 
124
  letter-spacing: -0.03em;
125
  }
126
 
 
130
  border-radius: 12px !important;
131
  border: none !important;
132
  font-weight: 600 !important;
133
+ padding: 10px 20px;
134
  transition: 0.2s !important;
135
  }
136
  button.primary-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px -5px rgba(59, 130, 246, 0.4); }
 
138
  .badge { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 99px; font-size: 0.8rem; font-weight: 600; margin-right: 5px;}
139
  .badge-success { background: #dcfce7; color: #15803d; }
140
  .badge-error { background: #fee2e2; color: #b91c1c; }
 
141
  """
142
 
143
  # =========================================================
144
+ # 5. UTILITIES
145
  # =========================================================
146
  def split_text_smart(text: str, max_chars: int = 300) -> List[str]:
147
  if not text: return []
 
170
  return chunks if chunks else [text]
171
 
172
  # =========================================================
173
+ # 6. ENGINE CORE
174
  # =========================================================
175
  class TTSManager:
176
  def __init__(self):
 
178
  self.net_g = None
179
  self.hps = None
180
  self.ready = False
181
+ self.status_msg = "Đang khởi tạo..."
 
 
182
  self._initialize_model()
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  def _initialize_model(self):
185
  try:
186
+ # Tự động tải model về thư mục riêng
187
  model_dir = ROOT_DIR / "model_cache"
188
  model_dir.mkdir(exist_ok=True)
189
 
190
+ # Repo chứa model
191
+ repo_id = "valtecAI-team/valtec-vietnamese-tts"
192
 
193
+ logger.info("⬇️ Đang tải Config & Model...")
194
+ cfg_path = hf_hub_download(repo_id=repo_id, filename="config.json", local_dir=model_dir)
195
 
196
+ # Tìm file G_*.pth mới nhất hoặc mặc định
197
+ try:
198
+ ckpt_path = hf_hub_download(repo_id=repo_id, filename="G_100000.pth", local_dir=model_dir)
199
+ except:
200
+ # Fallback nếu tên file khác
201
+ ckpt_path = hf_hub_download(repo_id=repo_id, filename="G_0.pth", local_dir=model_dir)
 
 
202
 
 
203
  with open(cfg_path, "r", encoding="utf-8") as f:
204
  self.hps = json.load(f)
205
 
206
  self.spk2id = self.hps["data"]["spk2id"]
207
  self.speakers = sorted(list(self.spk2id.keys()))
208
 
 
209
  if CORE_LOADED:
210
  self.net_g = SynthesizerTrn(
211
  len(symbols),
 
220
  self.net_g.eval()
221
  self.ready = True
222
  self.status_msg = f"✅ Sẵn sàng ({self.device})"
223
+ logger.info("🚀 Engine đã khởi động thành công!")
224
  else:
225
+ self.status_msg = "❌ Lỗi: Không load được nguồn (src)"
226
 
227
  except Exception as e:
228
  self.ready = False
229
+ self.status_msg = f"❌ Lỗi Model: {str(e)}"
230
  logger.error(self.status_msg)
231
 
232
  def infer(self, text, spk, speed, ns, nsw, sdp):
233
+ if not self.ready: raise RuntimeError(self.status_msg)
 
 
234
  if self.device.type == 'cuda': torch.cuda.empty_cache()
235
 
236
  text_norm = process_vietnamese_text(text)
 
244
  lang = torch.LongTensor(commons.intersperse(lang_ids, 0)).unsqueeze(0).to(self.device)
245
  sid = torch.LongTensor([self.spk2id.get(spk, 0)]).to(self.device)
246
 
247
+ outputs = self.net_g.infer(x, x_len, sid, tone, lang, noise_scale=ns, noise_scale_w=nsw, length_scale=speed, sdp_ratio=sdp)
248
+ return outputs[0][0, 0].data.cpu().float().numpy(), self.hps["data"]["sampling_rate"]
 
 
 
 
249
 
250
  # =========================================================
251
+ # 7. UI CONSTRUCTION
252
  # =========================================================
253
  def build_interface():
 
254
  manager = TTSManager()
255
 
256
  def run_inference(text, spk, speed, ns, nsw, sdp, is_long, chunk_size, pause, progress=gr.Progress()):
257
  if not manager.ready:
258
+ # Thử load lại nếu trước đó thất bại
259
+ if not CORE_LOADED:
260
+ return None, f"<span class='badge badge-error'>Lỗi mã nguồn: {IMPORT_ERROR_MSG}</span>"
261
+ return None, f"<span class='badge badge-error'>{manager.status_msg}</span>"
262
 
263
  if not text: return None, "⚠️ Chưa nhập nội dung"
264
 
 
265
  try:
266
  full_audio = None
267
  sr = 0
 
268
  if not is_long:
269
  full_audio, sr = manager.infer(text, spk, speed, ns, nsw, sdp)
270
  else:
271
  chunks = split_text_smart(text, chunk_size)
272
  segments = []
273
+ sr = 22050
 
 
274
  for i, chunk in enumerate(chunks):
275
  progress((i)/len(chunks), desc=f"Đoạn {i+1}/{len(chunks)}")
276
  a, r = manager.infer(chunk, spk, speed, ns, nsw, sdp)
277
  sr = r
278
  segments.append(a)
279
+ if pause > 0: segments.append(np.zeros(int(sr * pause / 1000)))
280
+ if segments: full_audio = np.concatenate(segments)
 
 
 
281
 
 
 
 
 
 
282
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as fp:
283
  sf.write(fp.name, full_audio, sr)
284
+ dur = len(full_audio)/sr
285
  return fp.name, f"<span class='badge badge-success'>Hoàn thành: {dur:.1f}s</span>"
 
286
  except Exception as e:
287
  return None, f"<span class='badge badge-error'>Lỗi: {str(e)}</span>"
288
 
289
+ speaker_list = manager.speakers if manager.ready else ["Đang tải..."]
 
290
 
291
+ with gr.Blocks(theme=gr.themes.Soft(), css=ELEGANT_CSS, title="CVNSS4.0 Studio") as app:
292
+ gr.HTML(f"""
293
+ <div style="margin-bottom: 20px;">
294
+ <div class="header-title">CVNSS4.0 Studio</div>
295
+ <div style="color: #64748b;">Long Ngo • Trần Tư Bình • Valtec TTS Core</div>
296
+ <div style="margin-top:5px">Trạng thái: <b>{manager.status_msg}</b></div>
297
+ </div>
298
+ """)
299
+
300
+ if not CORE_LOADED:
301
+ gr.HTML(f"""<div style="background:#fee2e2; color:#b91c1c; padding:10px; border-radius:8px;">
302
+ <b>LỖI NGHIÊM TRỌNG:</b> Không tải được mã nguồn 'src'.<br>
303
+ Hệ thống đã cố gắng tự tải nhưng thất bại. Chi tiết: {IMPORT_ERROR_MSG}
304
+ </div>""")
 
 
 
 
 
 
 
305
 
306
  with gr.Tabs():
 
307
  with gr.Tab("⚡ Chế độ Nhanh"):
308
  with gr.Row():
309
  with gr.Column(scale=3, elem_classes="elegant-card"):
310
+ txt_in = gr.Textbox(label="Văn bản", placeholder="Nhập văn bản tiếng Việt...", lines=3)
311
  with gr.Row():
312
+ spk_drp = gr.Dropdown(speaker_list, value=speaker_list[0] if speaker_list else None, label="Giọng đọc")
313
  spd_sld = gr.Slider(0.5, 2.0, 1.0, label="Tốc độ")
314
  btn_run = gr.Button("🔊 Đọc Ngay", elem_classes="primary-btn")
 
315
  with gr.Column(scale=2, elem_classes="elegant-card"):
316
  out_aud = gr.Audio(label="Kết quả", type="filepath")
317
  out_html = gr.HTML()
318
+ btn_run.click(lambda t,s,sp: run_inference(t,s,sp,0.667,0.8,0.2,False,0,0), [txt_in,spk_drp,spd_sld], [out_aud,out_html])
 
 
319
 
 
320
  with gr.Tab("💎 Chế độ Dài"):
321
  with gr.Row():
322
  with gr.Column(scale=3, elem_classes="elegant-card"):
 
325
  ns = gr.Slider(0.1, 1.5, 0.667, label="Noise Scale")
326
  nsw = gr.Slider(0.1, 1.5, 0.8, label="Noise Width")
327
  sdp = gr.Slider(0, 1, 0.2, label="SDP")
328
+ chunk = gr.Slider(100, 1000, 300, label="Ngắt câu")
329
  pause = gr.Slider(0, 1000, 250, label="Nghỉ (ms)")
330
  btn_long = gr.Button("🚀 Xử lý", elem_classes="primary-btn")
 
331
  with gr.Column(scale=2, elem_classes="elegant-card"):
332
  out_long = gr.Audio(label="Audio", type="filepath")
333
  out_html_long = gr.HTML()
334
+ btn_long.click(lambda t,s,sp,n,nw,sd,c,p: run_inference(t,s,sp,n,nw,sd,True,c,p), [txt_long,spk_drp,spd_sld,ns,nsw,sdp,chunk,pause], [out_long,out_html_long])
 
 
 
335
 
336
  return app
337