hoanglinhn0 commited on
Commit
5c2299f
·
verified ·
1 Parent(s): 56669a4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -171
app.py CHANGED
@@ -1,5 +1,5 @@
1
  #!/usr/bin/env python3
2
- # app.py - Modified for Piper Dataset Creation (Smart Split) with Beautiful UI
3
 
4
  import logging
5
  import os
@@ -12,7 +12,7 @@ from pathlib import Path
12
  import gradio as gr
13
  import torch
14
  import torchaudio
15
- from pydub import AudioSegment, silence, effects
16
 
17
  # Import từ file model của bạn (giữ nguyên cấu trúc cũ)
18
  from examples import examples
@@ -30,26 +30,41 @@ def MyPrint(s):
30
  now = datetime.now()
31
  date_time = now.strftime("%Y-%m-%d %H:%M:%S")
32
  print(f"{date_time}: {s}")
33
- return f"{date_time}: {s}"
34
 
35
- # --- CÁC HÀM XỬ LÝ AUDIO THÔNG MINH (GIỮ NGUYÊN) ---
36
 
37
  def smart_split_audio(
38
  in_filename: str,
39
  output_dir: str,
40
  base_name: str,
41
- min_silence_len=500,
42
- silence_thresh=-40,
43
- max_len_sec=12,
44
- keep_silence=300
45
  ):
 
 
 
 
 
 
 
46
  try:
 
47
  sound = AudioSegment.from_file(in_filename)
48
  sound = sound.set_frame_rate(16000).set_channels(1)
 
 
49
  sound = effects.normalize(sound)
50
 
 
 
 
 
51
  MyPrint(f"Đang phân tích audio: {in_filename} | Độ dài: {len(sound)/1000}s")
52
 
 
 
53
  nonsilent_ranges = silence.detect_nonsilent(
54
  sound,
55
  min_silence_len=min_silence_len,
@@ -61,24 +76,41 @@ def smart_split_audio(
61
  MyPrint(f"⚠️ Không tìm thấy giọng nói trong file {in_filename}. Kiểm tra lại ngưỡng dB.")
62
  return []
63
 
 
 
 
 
 
64
  output_files = []
65
  chunk_count = 0
66
 
 
67
  for start_i, end_i in nonsilent_ranges:
 
 
 
 
68
  adj_start = max(0, start_i - keep_silence)
69
  adj_end = min(len(sound), end_i + keep_silence)
70
 
71
  chunk_duration = (adj_end - adj_start) / 1000.0
72
 
 
73
  if chunk_duration < 0.3:
74
  continue
75
 
 
 
 
76
  if chunk_duration > max_len_sec:
77
- MyPrint(f"⚠️ Chunk {chunk_count} hơi dài: {chunk_duration:.1f}s")
78
 
79
  chunk = sound[adj_start:adj_end]
 
 
80
  chunk = chunk.fade_in(10).fade_out(10)
81
 
 
82
  out_name = f"{base_name}_{chunk_count:04d}.wav"
83
  out_path = os.path.join(output_dir, out_name)
84
 
@@ -105,21 +137,20 @@ def process_batch_files(
105
  progress=gr.Progress()
106
  ):
107
  if not uploaded_files:
108
- return None, "⚠️ Vui lòng chọn ít nhất một file audio."
109
 
110
- log_buffer = []
111
- def log(msg):
112
- print(msg)
113
- log_buffer.append(msg)
114
-
115
- log(f"Bắt đầu xử lý: {len(uploaded_files)} files gốc.")
116
- log(f"Cấu hình cắt: Thresh={silence_thresh}dB | Min Gap={min_silence_len}ms")
117
 
118
  tmp_dir = tempfile.mkdtemp()
119
  wavs_dir = os.path.join(tmp_dir, "wavs")
120
  os.makedirs(wavs_dir, exist_ok=True)
 
121
  csv_path = os.path.join(tmp_dir, "metadata.csv")
122
 
 
123
  try:
124
  recognizer = get_pretrained_model(
125
  repo_id,
@@ -127,7 +158,7 @@ def process_batch_files(
127
  num_active_paths=num_active_paths,
128
  )
129
  except Exception as e:
130
- return None, f"Lỗi tải model: {str(e)}"
131
 
132
  results_metadata = []
133
  total_chunks = 0
@@ -136,13 +167,14 @@ def process_batch_files(
136
  in_path = file_obj.name
137
  base_name = Path(in_path).stem
138
 
 
139
  chunk_paths = smart_split_audio(
140
  in_path,
141
  wavs_dir,
142
  base_name,
143
- min_silence_len=min_silence_len,
144
- silence_thresh=silence_thresh,
145
- keep_silence=300
146
  )
147
 
148
  for chunk_path in chunk_paths:
@@ -150,29 +182,41 @@ def process_batch_files(
150
  text = decode(recognizer, chunk_path)
151
  text = text.strip()
152
 
 
 
153
  if len(text) > 1:
154
  wav_filename = os.path.basename(chunk_path)
 
155
  line = f"{wav_filename}|{text}"
156
  results_metadata.append(line)
157
  total_chunks += 1
158
  else:
 
159
  os.remove(chunk_path)
160
 
161
  except Exception as e:
162
- log(f"Lỗi decode {chunk_path}: {e}")
163
 
 
164
  with open(csv_path, "w", encoding="utf-8") as f:
165
  for line in results_metadata:
166
  f.write(line + "\n")
167
 
168
- log(f"Hoàn tất! Tổng số mẫu: {total_chunks}")
169
 
 
170
  zip_filename = f"piper_dataset_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
171
  zip_path = os.path.join(tempfile.gettempdir(), zip_filename)
172
  shutil.make_archive(zip_path.replace('.zip', ''), 'zip', tmp_dir)
173
 
174
- final_log = "\n".join(log_buffer)
175
- return zip_path, final_log
 
 
 
 
 
 
176
 
177
  def update_model_dropdown(language: str):
178
  if language in language_to_models:
@@ -180,161 +224,62 @@ def update_model_dropdown(language: str):
180
  return gr.Dropdown(choices=choices, value=choices[0], interactive=True)
181
  raise ValueError(f"Unsupported language: {language}")
182
 
183
- # --- GIAO DIỆN GRADIO ĐẸP MẮT ---
184
-
185
- # CSS Tùy chỉnh: Tạo khối, bo góc, màu sắc dịu nhẹ
186
- custom_css = """
187
- /* Font và nền chung */
188
- body { font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
189
-
190
- /* Header Gradient */
191
- .header-box {
192
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
193
- color: white !important;
194
- padding: 20px;
195
- border-radius: 15px;
196
- text-align: center;
197
- margin-bottom: 20px;
198
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
199
- }
200
- .header-box h1 { color: white !important; margin: 0; font-size: 2em; }
201
- .header-box p { color: #e0e0e0 !important; font-size: 1.1em; }
202
-
203
- /* Block chung */
204
- .custom-group {
205
- background-color: white;
206
- border: 1px solid #e5e7eb;
207
- border-radius: 12px;
208
- padding: 20px !important;
209
- margin-bottom: 15px;
210
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
211
- transition: transform 0.2s;
212
- }
213
- .custom-group:hover {
214
- transform: translateY(-2px);
215
- box-shadow: 0 6px 12px rgba(0,0,0,0.1);
216
- }
217
- .dark .custom-group { background-color: #1f2937; border-color: #374151; }
218
-
219
- /* Màu sắc chỉ dẫn cho từng khối (Border Top) */
220
- .blue-border { border-top: 5px solid #6366f1; } /* Khối Model */
221
- .teal-border { border-top: 5px solid #14b8a6; } /* Khối Audio */
222
- .orange-border { border-top: 5px solid #f97316; } /* Khối Upload */
223
-
224
- /* Nút bấm */
225
- .primary-btn {
226
- background: linear-gradient(90deg, #f97316 0%, #ea580c 100%) !important;
227
- border: none !important;
228
- color: white !important;
229
- font-weight: bold;
230
- font-size: 1.1em;
231
- }
232
-
233
- /* Text output */
234
- .log-box textarea {
235
- font-family: 'Consolas', monospace;
236
- font-size: 0.9em;
237
- background-color: #f3f4f6;
238
- border: none;
239
- }
240
- .dark .log-box textarea { background-color: #111827; color: #d1d5db; }
241
- """
242
 
243
- # Sử dụng theme Soft làm nền tảng cho sự mềm mại
244
- theme = gr.themes.Soft(
245
- primary_hue="indigo",
246
- secondary_hue="teal",
247
- neutral_hue="slate",
248
- radius_size=gr.themes.sizes.RADIUS_LG,
249
- ).set(
250
- button_primary_background_fill="*primary_500",
251
- button_primary_background_fill_hover="*primary_600",
252
- )
253
 
254
- with gr.Blocks(css=custom_css, theme=theme, title="Auto Piper Dataset Maker") as demo:
255
-
256
- # --- HEADER ---
257
- with gr.Row(elem_classes=["header-box"]):
258
- gr.Markdown(
259
- """
260
- # 🎧 Auto Piper Dataset Maker
261
- ### Cắt thông minh • Chuẩn hóa âm lượng • Tự động nhận dạng (ASR)
262
- """
263
- )
264
 
265
- # --- MAIN CONTENT ---
266
  with gr.Row():
267
-
268
- # CỘT TRÁI: CẤU HÌNH (Chia làm 2 khối dọc)
269
  with gr.Column(scale=1):
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
- # Khối 1: Cấu hình Model ASR (Màu Tím/Xanh)
272
- with gr.Group(elem_classes=["custom-group", "blue-border"]):
273
- gr.Markdown("### 🤖 1. Cấu hình Nhận Dạng (ASR)")
274
- language_choices = list(language_to_models.keys())
275
-
276
- language_radio = gr.Radio(
277
- label="Ngôn ngữ",
278
- choices=language_choices,
279
- value=language_choices[0],
280
- interactive=True
281
- )
282
- model_dropdown = gr.Dropdown(
283
- choices=language_to_models[language_choices[0]],
284
- label="Chọn Model Sherpa-ONNX",
285
- value=language_to_models[language_choices[0]][0],
286
- interactive=True
287
- )
288
- # Event change
289
- language_radio.change(update_model_dropdown, inputs=language_radio, outputs=model_dropdown)
290
-
291
- # Khối 2: Cấu hình Cắt Audio (Màu Xanh Ngọc)
292
- with gr.Group(elem_classes=["custom-group", "teal-border"]):
293
- gr.Markdown("### ✂️ 2. Tinh chỉnh Cắt Audio")
294
- gr.Markdown("<small><i>*Đã kích hoạt: Normalize & Smart Padding</i></small>")
295
-
296
- silence_thresh_slider = gr.Slider(
297
- minimum=-60, maximum=-20, value=-45, step=1,
298
- label="Ngưỡng khoảng lặng (dB)",
299
- info="Càng nhỏ (-60) càng nhạy. Mặc định -45dB là tối ưu cho audio sạch."
300
- )
301
- min_silence_slider = gr.Slider(
302
- minimum=200, maximum=2000, value=700, step=100,
303
- label="Độ dài ngắt câu (ms)",
304
- info="Khoảng lặng tối thiểu để AI hiểu là hết câu."
305
- )
306
-
307
- # CỘT PHẢI: UPLOAD & KẾT QUẢ (Khối Màu Cam)
308
  with gr.Column(scale=2):
309
- with gr.Group(elem_classes=["custom-group", "orange-border"]):
310
- gr.Markdown("### 🚀 3. Upload & Xử lý")
311
-
312
- files_input = gr.File(
313
- label="Kéo thả hoặc chọn file Audio gốc",
314
- file_count="multiple",
315
- type="filepath",
316
- height=100
317
- )
318
-
319
- batch_btn = gr.Button(" Bắt đầu Cắt & Tạo Dataset", variant="primary", elem_classes=["primary-btn"], size="lg")
320
-
321
- gr.Markdown("---") # Đường kẻ phân cách
322
-
323
- with gr.Row():
324
- status_output = gr.Textbox(
325
- label="Nhật ký Xử lý (Logs)",
326
- lines=8,
327
- placeholder="Trạng thái sẽ hiện ở đây...",
328
- elem_classes=["log-box"],
329
- show_copy_button=True
330
- )
331
- file_output = gr.File(label="📦 Tải xuống Dataset (.zip)")
332
-
333
- # States ẩn
334
  decoding_method_state = gr.State("modified_beam_search")
335
  num_active_paths_state = gr.State(4)
336
 
337
- # Event click
338
  batch_btn.click(
339
  process_batch_files,
340
  inputs=[
@@ -349,7 +294,6 @@ with gr.Blocks(css=custom_css, theme=theme, title="Auto Piper Dataset Maker") as
349
  outputs=[file_output, status_output],
350
  )
351
 
352
- # Tối ưu luồng
353
  torch.set_num_threads(1)
354
  torch.set_num_interop_threads(1)
355
 
 
1
  #!/usr/bin/env python3
2
+ # app.py - Modified for Piper Dataset Creation (Smart Split)
3
 
4
  import logging
5
  import os
 
12
  import gradio as gr
13
  import torch
14
  import torchaudio
15
+ from pydub import AudioSegment, silence, effects # Thêm effects để normalize
16
 
17
  # Import từ file model của bạn (giữ nguyên cấu trúc cũ)
18
  from examples import examples
 
30
  now = datetime.now()
31
  date_time = now.strftime("%Y-%m-%d %H:%M:%S")
32
  print(f"{date_time}: {s}")
 
33
 
34
+ # --- CÁC HÀM XỬ LÝ AUDIO THÔNG MINH ---
35
 
36
  def smart_split_audio(
37
  in_filename: str,
38
  output_dir: str,
39
  base_name: str,
40
+ min_silence_len=500, # Độ dài khoảng lặng để xác định là "ngắt câu"
41
+ silence_thresh=-40, # Ngưỡng dB
42
+ max_len_sec=12, # Độ dài tối đa cho phép của 1 đoạn (Piper tốt nhất < 15s)
43
+ keep_silence=300 # Giữ lại bao nhiêu ms khoảng lặng ở đầu/cuối (Padding)
44
  ):
45
+ """
46
+ Cắt audio thông minh:
47
+ 1. Normalize audio để ngưỡng dB hoạt động đúng.
48
+ 2. Dùng detect_nonsilent để tìm các đoạn CÓ TIẾNG.
49
+ 3. Gộp các đoạn tiếng gần nhau (cách nhau < min_silence_len) thành 1 câu.
50
+ 4. Thêm padding (keep_silence) để không bị cụt chữ.
51
+ """
52
  try:
53
+ # 1. Load và Normalize Audio
54
  sound = AudioSegment.from_file(in_filename)
55
  sound = sound.set_frame_rate(16000).set_channels(1)
56
+
57
+ # Normalize giúp âm lượng đồng đều, tránh việc đoạn nhỏ bị cắt nhầm
58
  sound = effects.normalize(sound)
59
 
60
+ # Vì đã normalize, ngưỡng silence_thresh nên chỉnh lại tương đối
61
+ # Tuy nhiên để người dùng dễ chỉnh, ta dùng tham số truyền vào nhưng tính toán lại một chút
62
+ # Nếu audio đã normalize, max volume là 0dBFS. Ngưỡng cắt nên khoảng -40 đến -50.
63
+
64
  MyPrint(f"Đang phân tích audio: {in_filename} | Độ dài: {len(sound)/1000}s")
65
 
66
+ # 2. Phát hiện các đoạn có tiếng (Non-silent chunks)
67
+ # seek_step=10: bước nhảy 10ms giúp xử lý nhanh hơn
68
  nonsilent_ranges = silence.detect_nonsilent(
69
  sound,
70
  min_silence_len=min_silence_len,
 
76
  MyPrint(f"⚠️ Không tìm thấy giọng nói trong file {in_filename}. Kiểm tra lại ngưỡng dB.")
77
  return []
78
 
79
+ # 3. Thuật toán Gộp đoạn (Merging Logic)
80
+ # Mục tiêu: Không để các từ bị rời rạc. Nếu khoảng cách giữa 2 từ < min_silence_len, gộp chúng lại.
81
+ # Ở đây detect_nonsilent đã làm việc đó dựa trên min_silence_len.
82
+ # Tuy nhiên, ta cần kiểm tra độ dài tổng để đảm bảo không quá ngắn hoặc quá dài.
83
+
84
  output_files = []
85
  chunk_count = 0
86
 
87
+ # Xử lý từng khoảng thời gian tìm được
88
  for start_i, end_i in nonsilent_ranges:
89
+
90
+ # Tính toán padding để âm thanh nghe "tròn" hơn
91
+ # Lùi điểm đầu lại một chút (start - keep_silence)
92
+ # Kéo điểm cuối ra một chút (end + keep_silence)
93
  adj_start = max(0, start_i - keep_silence)
94
  adj_end = min(len(sound), end_i + keep_silence)
95
 
96
  chunk_duration = (adj_end - adj_start) / 1000.0
97
 
98
+ # Lọc rác: Bỏ qua đoạn quá ngắn (< 0.3s) - thường là tiếng click chuột hoặc noise
99
  if chunk_duration < 0.3:
100
  continue
101
 
102
+ # Xử lý đoạn quá dài: Nếu dài hơn max_len_sec, Piper sẽ khó học.
103
+ # Với script đơn giản này, ta chấp nhận lưu, nhưng cảnh báo.
104
+ # (Giải pháp nâng cao là dùng VAD AI để cắt nhỏ hơn, nhưng ở đây dùng pydub cho nhẹ)
105
  if chunk_duration > max_len_sec:
106
+ MyPrint(f"⚠️ Chunk {chunk_count} hơi dài: {chunk_duration:.1f}s (Khuyên dùng < {max_len_sec}s)")
107
 
108
  chunk = sound[adj_start:adj_end]
109
+
110
+ # Fade in/out cực nhẹ (10ms) để tránh tiếng "bụp" ở đầu/cuối file
111
  chunk = chunk.fade_in(10).fade_out(10)
112
 
113
+ # Xuất file
114
  out_name = f"{base_name}_{chunk_count:04d}.wav"
115
  out_path = os.path.join(output_dir, out_name)
116
 
 
137
  progress=gr.Progress()
138
  ):
139
  if not uploaded_files:
140
+ return None, "Vui lòng chọn ít nhất một file audio."
141
 
142
+ MyPrint(f"Bắt đầu xử lý: {len(uploaded_files)} files gốc.")
143
+ # Lưu ý: threshold càng âm thì càng nhạy (ví dụ -50 nhạy hơn -30).
144
+ # Nhưng vì ta đã normalize audio, nên threshold -40 đến -50 là chuẩn.
145
+ MyPrint(f"Cấu hình cắt: Thresh={silence_thresh}dB | Min Gap={min_silence_len}ms")
 
 
 
146
 
147
  tmp_dir = tempfile.mkdtemp()
148
  wavs_dir = os.path.join(tmp_dir, "wavs")
149
  os.makedirs(wavs_dir, exist_ok=True)
150
+
151
  csv_path = os.path.join(tmp_dir, "metadata.csv")
152
 
153
+ # Load Model Sherpa
154
  try:
155
  recognizer = get_pretrained_model(
156
  repo_id,
 
158
  num_active_paths=num_active_paths,
159
  )
160
  except Exception as e:
161
+ return None, f"Lỗi tải model: {str(e)}"
162
 
163
  results_metadata = []
164
  total_chunks = 0
 
167
  in_path = file_obj.name
168
  base_name = Path(in_path).stem
169
 
170
+ # --- SỬ DỤNG HÀM CẮT MỚI ---
171
  chunk_paths = smart_split_audio(
172
  in_path,
173
  wavs_dir,
174
  base_name,
175
+ min_silence_len=min_silence_len, # Khoảng lặng tối thiểu để tính là hết câu
176
+ silence_thresh=silence_thresh, # Ngưỡng ồn
177
+ keep_silence=300 # Thêm 300ms đệm đầu/cuối để TRÒN CHỮ
178
  )
179
 
180
  for chunk_path in chunk_paths:
 
182
  text = decode(recognizer, chunk_path)
183
  text = text.strip()
184
 
185
+ # Logic làm sạch text
186
+ # Bỏ qua các đoạn text quá ngắn (thường là halluncination/ảo giác của AI)
187
  if len(text) > 1:
188
  wav_filename = os.path.basename(chunk_path)
189
+ # Piper format: filename|transcript
190
  line = f"{wav_filename}|{text}"
191
  results_metadata.append(line)
192
  total_chunks += 1
193
  else:
194
+ # Xóa file rác (âm thanh tiếng thở, click chuột mà AI không dịch ra chữ)
195
  os.remove(chunk_path)
196
 
197
  except Exception as e:
198
+ MyPrint(f"Lỗi decode {chunk_path}: {e}")
199
 
200
+ # Ghi file metadata.csv
201
  with open(csv_path, "w", encoding="utf-8") as f:
202
  for line in results_metadata:
203
  f.write(line + "\n")
204
 
205
+ MyPrint(f"Hoàn tất! Tổng số mẫu: {total_chunks}")
206
 
207
+ # Nén zip
208
  zip_filename = f"piper_dataset_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
209
  zip_path = os.path.join(tempfile.gettempdir(), zip_filename)
210
  shutil.make_archive(zip_path.replace('.zip', ''), 'zip', tmp_dir)
211
 
212
+ info_text = (
213
+ f"✅ Xử lý hoàn tất!\n"
214
+ f"- Tổng số câu (segments): {total_chunks}\n"
215
+ f"- File đã được Normalize và thêm Padding (đệm) để tròn chữ.\n"
216
+ f"- Tải file .zip bên dưới để train Piper."
217
+ )
218
+
219
+ return zip_path, info_text
220
 
221
  def update_model_dropdown(language: str):
222
  if language in language_to_models:
 
224
  return gr.Dropdown(choices=choices, value=choices[0], interactive=True)
225
  raise ValueError(f"Unsupported language: {language}")
226
 
227
+ # --- GIAO DIỆN GRADIO ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
+ css = """
230
+ .result {display:flex;flex-direction:column}
231
+ """
 
 
 
 
 
 
 
232
 
233
+ with gr.Blocks(css=css, title="Auto Piper Dataset Maker (Smart Split)") as demo:
234
+ gr.Markdown("# ✂️ Auto Piper Dataset Maker (Smart Split)")
235
+ gr.Markdown("Tự động cắt audio, chuẩn hóa âm lượng và thêm vùng đệm để **không bị mất chữ**.")
 
 
 
 
 
 
 
236
 
 
237
  with gr.Row():
 
 
238
  with gr.Column(scale=1):
239
+ gr.Markdown("### 1. Cấu hình Model ASR")
240
+ language_choices = list(language_to_models.keys())
241
+ language_radio = gr.Radio(
242
+ label="Ngôn ngữ nhận dạng",
243
+ choices=language_choices,
244
+ value=language_choices[0],
245
+ )
246
+ model_dropdown = gr.Dropdown(
247
+ choices=language_to_models[language_choices[0]],
248
+ label="Chọn Model Sherpa-ONNX",
249
+ value=language_to_models[language_choices[0]][0],
250
+ )
251
+ language_radio.change(update_model_dropdown, inputs=language_radio, outputs=model_dropdown)
252
 
253
+ gr.Markdown("---")
254
+ gr.Markdown("### 2. Cấu hình Cắt Audio")
255
+ gr.Markdown("*(Đã bật chế độ Normalize Smart Padding)*")
256
+ silence_thresh_slider = gr.Slider(
257
+ minimum=-60, maximum=-20, value=-45, step=1,
258
+ label="Ngưỡng khoảng lặng (dB)",
259
+ info="Mặc định -45dB. Nếu file bị cắt vụn quá nhiều câu ngắn, hãy giảm xuống -50 hoặc -55."
260
+ )
261
+ min_silence_slider = gr.Slider(
262
+ minimum=200, maximum=2000, value=700, step=100,
263
+ label="Độ dài ngắt câu (ms)",
264
+ info="Khoảng lặng phải dài hơn số này mới được tính là hết câu. Tăng lên (800-1000) để câu dài hơn."
265
+ )
266
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  with gr.Column(scale=2):
268
+ gr.Markdown("### 3. Upload & Xử lý")
269
+ files_input = gr.File(
270
+ label="Upload Audio gốc",
271
+ file_count="multiple",
272
+ type="filepath"
273
+ )
274
+
275
+ batch_btn = gr.Button("🚀 Cắt Audio & Tạo Dataset", variant="primary")
276
+
277
+ status_output = gr.Textbox(label="Log Kết quả", lines=5)
278
+ file_output = gr.File(label="Download Dataset (.zip)")
279
+
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  decoding_method_state = gr.State("modified_beam_search")
281
  num_active_paths_state = gr.State(4)
282
 
 
283
  batch_btn.click(
284
  process_batch_files,
285
  inputs=[
 
294
  outputs=[file_output, status_output],
295
  )
296
 
 
297
  torch.set_num_threads(1)
298
  torch.set_num_interop_threads(1)
299