Pragmaticl commited on
Commit
1a8c186
·
verified ·
1 Parent(s): 9f388ac

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +303 -0
app.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import zipfile
4
+ import tempfile
5
+ import shutil
6
+ from pathlib import Path
7
+ import pandas as pd
8
+ import pyarrow as pa
9
+ import pyarrow.parquet as pq
10
+ from pydub import AudioSegment
11
+ from faster_whisper import WhisperModel
12
+ import numpy as np
13
+ from typing import List, Tuple
14
+ import soundfile as sf
15
+
16
+ class AudioProcessor:
17
+ def __init__(self):
18
+ # Khởi tạo Faster-Whisper với model Large-v3-Turbo
19
+ print("Đang tải Whisper model...")
20
+ self.model = WhisperModel(
21
+ "large-v3-turbo",
22
+ device="cuda", # Dùng "cpu" nếu không có GPU
23
+ compute_type="float16" # Dùng "int8" cho CPU
24
+ )
25
+ self.min_duration = 2.0 # Độ dài tối thiểu mỗi đoạn (giây)
26
+ self.max_duration = 30.0 # Độ dài tối đa mỗi đoạn (giây)
27
+
28
+ def load_audio(self, audio_path: str) -> AudioSegment:
29
+ """Load file âm thanh"""
30
+ return AudioSegment.from_file(audio_path)
31
+
32
+ def transcribe_audio(self, audio_path: str) -> List[dict]:
33
+ """Transcribe âm thanh và lấy timestamps"""
34
+ segments, info = self.model.transcribe(
35
+ audio_path,
36
+ beam_size=5,
37
+ word_timestamps=True,
38
+ vad_filter=True
39
+ )
40
+
41
+ results = []
42
+ for segment in segments:
43
+ results.append({
44
+ 'start': segment.start,
45
+ 'end': segment.end,
46
+ 'text': segment.text.strip()
47
+ })
48
+
49
+ return results
50
+
51
+ def merge_short_segments(self, segments: List[dict]) -> List[dict]:
52
+ """Gộp các câu ngắn lại với nhau"""
53
+ if not segments:
54
+ return []
55
+
56
+ merged = []
57
+ current = segments[0].copy()
58
+
59
+ for i in range(1, len(segments)):
60
+ duration = current['end'] - current['start']
61
+ next_seg = segments[i]
62
+
63
+ # Nếu câu hiện tại quá ngắn hoặc tổng thời lượng chưa quá max
64
+ if duration < self.min_duration or (
65
+ next_seg['end'] - current['start'] < self.max_duration
66
+ ):
67
+ # Gộp với câu tiếp theo
68
+ current['end'] = next_seg['end']
69
+ current['text'] = current['text'] + ' ' + next_seg['text']
70
+ else:
71
+ merged.append(current)
72
+ current = next_seg.copy()
73
+
74
+ merged.append(current)
75
+ return merged
76
+
77
+ def split_audio(self, audio: AudioSegment, segments: List[dict],
78
+ output_dir: str, base_filename: str) -> List[dict]:
79
+ """Cắt âm thanh thành các đoạn nhỏ"""
80
+ audio_dir = os.path.join(output_dir, "audio")
81
+ os.makedirs(audio_dir, exist_ok=True)
82
+
83
+ dataset_rows = []
84
+
85
+ for idx, segment in enumerate(segments, 1):
86
+ start_ms = int(segment['start'] * 1000)
87
+ end_ms = int(segment['end'] * 1000)
88
+
89
+ # Cắt đoạn âm thanh
90
+ audio_chunk = audio[start_ms:end_ms]
91
+
92
+ # Tên file
93
+ filename = f"{base_filename}_{idx:05d}.wav"
94
+ filepath = os.path.join(audio_dir, filename)
95
+
96
+ # Xuất file WAV
97
+ audio_chunk.export(filepath, format="wav")
98
+
99
+ # Đọc lại để lưu vào parquet dưới dạng bytes
100
+ with open(filepath, 'rb') as f:
101
+ audio_bytes = f.read()
102
+
103
+ dataset_rows.append({
104
+ 'audio': audio_bytes,
105
+ 'transcription': segment['text'],
106
+ 'file_name': f"audio/{filename}"
107
+ })
108
+
109
+ return dataset_rows
110
+
111
+ def save_to_parquet(self, data: List[dict], output_dir: str, max_size_mb: int = 500):
112
+ """Lưu dữ liệu vào file parquet, chia nhỏ nếu cần"""
113
+
114
+ # Tạo schema cho parquet
115
+ schema = pa.schema([
116
+ ('audio', pa.binary()),
117
+ ('transcription', pa.string()),
118
+ ('file_name', pa.string())
119
+ ])
120
+
121
+ # Chuyển đổi dữ liệu
122
+ df = pd.DataFrame(data)
123
+
124
+ # Ước tính kích thước và chia nhỏ nếu cần
125
+ max_size_bytes = max_size_mb * 1024 * 1024
126
+ part_num = 0
127
+ current_data = []
128
+ current_size = 0
129
+
130
+ for idx, row in df.iterrows():
131
+ row_size = len(row['audio']) + len(row['transcription'].encode()) + len(row['file_name'].encode())
132
+
133
+ if current_size + row_size > max_size_bytes and current_data:
134
+ # Lưu part hiện tại
135
+ self._write_parquet_part(current_data, output_dir, part_num, schema)
136
+ part_num += 1
137
+ current_data = []
138
+ current_size = 0
139
+
140
+ current_data.append(row.to_dict())
141
+ current_size += row_size
142
+
143
+ # Lưu phần còn lại
144
+ if current_data:
145
+ self._write_parquet_part(current_data, output_dir, part_num, schema)
146
+
147
+ def _write_parquet_part(self, data: List[dict], output_dir: str, part_num: int, schema):
148
+ """Ghi một phần dữ liệu vào file parquet"""
149
+ df = pd.DataFrame(data)
150
+ table = pa.Table.from_pandas(df, schema=schema)
151
+
152
+ if part_num == 0:
153
+ filename = "dataset.parquet"
154
+ else:
155
+ filename = f"dataset_part_{part_num:03d}.parquet"
156
+
157
+ filepath = os.path.join(output_dir, filename)
158
+ pq.write_table(table, filepath, compression='snappy')
159
+ print(f"Đã lưu: {filename}")
160
+
161
+ def process_single_audio(self, audio_path: str, output_dir: str) -> List[dict]:
162
+ """Xử lý một file âm thanh"""
163
+ base_filename = Path(audio_path).stem
164
+
165
+ print(f"Đang xử lý: {base_filename}")
166
+
167
+ # Transcribe
168
+ print(" - Đang transcribe...")
169
+ segments = self.transcribe_audio(audio_path)
170
+
171
+ # Gộp các câu ngắn
172
+ print(" - Đang gộp các câu ngắn...")
173
+ merged_segments = self.merge_short_segments(segments)
174
+
175
+ # Load và cắt âm thanh
176
+ print(" - Đang cắt âm thanh...")
177
+ audio = self.load_audio(audio_path)
178
+ dataset_rows = self.split_audio(audio, merged_segments, output_dir, base_filename)
179
+
180
+ print(f" - Đã tạo {len(dataset_rows)} đoạn âm thanh")
181
+ return dataset_rows
182
+
183
+ def process_audio_files(self, input_path: str) -> str:
184
+ """Xử lý file âm thanh hoặc zip"""
185
+ # Tạo thư mục tạm
186
+ with tempfile.TemporaryDirectory() as temp_dir:
187
+ work_dir = os.path.join(temp_dir, "work")
188
+ output_dir = os.path.join(temp_dir, "output")
189
+ os.makedirs(work_dir, exist_ok=True)
190
+ os.makedirs(output_dir, exist_ok=True)
191
+
192
+ # Xác định các file âm thanh cần xử lý
193
+ audio_files = []
194
+
195
+ if input_path.endswith('.zip'):
196
+ # Giải nén zip
197
+ with zipfile.ZipFile(input_path, 'r') as zip_ref:
198
+ zip_ref.extractall(work_dir)
199
+
200
+ # Tìm tất cả file âm thanh
201
+ for root, dirs, files in os.walk(work_dir):
202
+ for file in files:
203
+ if file.lower().endswith(('.mp3', '.wav', '.flac', '.m4a', '.ogg')):
204
+ audio_files.append(os.path.join(root, file))
205
+ else:
206
+ # File đơn
207
+ audio_files.append(input_path)
208
+
209
+ if not audio_files:
210
+ raise ValueError("Không tìm thấy file âm thanh nào!")
211
+
212
+ print(f"Tìm thấy {len(audio_files)} file âm thanh")
213
+
214
+ # Xử lý từng file
215
+ all_data = []
216
+ for audio_file in audio_files:
217
+ try:
218
+ data = self.process_single_audio(audio_file, output_dir)
219
+ all_data.extend(data)
220
+ except Exception as e:
221
+ print(f"Lỗi khi xử lý {audio_file}: {str(e)}")
222
+
223
+ # Lưu vào parquet
224
+ print("\nĐang lưu vào parquet...")
225
+ self.save_to_parquet(all_data, output_dir)
226
+
227
+ # Tạo file zip kết quả
228
+ output_zip = os.path.join(temp_dir, "result.zip")
229
+ with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
230
+ for root, dirs, files in os.walk(output_dir):
231
+ for file in files:
232
+ file_path = os.path.join(root, file)
233
+ arcname = os.path.relpath(file_path, output_dir)
234
+ zipf.write(file_path, arcname)
235
+
236
+ # Copy file zip ra ngoài temp directory
237
+ final_output = os.path.join(tempfile.gettempdir(), "audio_dataset.zip")
238
+ shutil.copy(output_zip, final_output)
239
+
240
+ print(f"\nHoàn thành! Tổng số đoạn: {len(all_data)}")
241
+ return final_output
242
+
243
+
244
+ # Khởi tạo processor (sẽ load model một lần)
245
+ processor = AudioProcessor()
246
+
247
+ def process_audio_interface(audio_file):
248
+ """Interface function cho Gradio"""
249
+ try:
250
+ if audio_file is None:
251
+ return None, "Vui lòng tải lên file âm thanh hoặc zip!"
252
+
253
+ result_path = processor.process_audio_files(audio_file)
254
+ return result_path, "Xử lý thành công! Tải file zip bên dưới."
255
+
256
+ except Exception as e:
257
+ return None, f"Lỗi: {str(e)}"
258
+
259
+
260
+ # Tạo giao diện Gradio
261
+ with gr.Blocks(title="Audio Dataset Creator") as demo:
262
+ gr.Markdown("""
263
+ # 🎙️ Audio Dataset Creator
264
+
265
+ Công cụ tạo dataset âm thanh tự động sử dụng Whisper Large-v3-Turbo
266
+
267
+ **H��ớng dẫn:**
268
+ 1. Tải lên một file âm thanh (.mp3, .wav, .flac, v.v.) hoặc file .zip chứa nhiều file âm thanh
269
+ 2. Nhấn "Xử lý" và đợi
270
+ 3. Tải file zip kết quả chứa:
271
+ - Folder `audio/`: Các file âm thanh đã được cắt
272
+ - File `dataset.parquet` (hoặc nhiều part nếu > 500MB): Dataset với cột audio, transcription, file_name
273
+ """)
274
+
275
+ with gr.Row():
276
+ with gr.Column():
277
+ input_file = gr.File(
278
+ label="Tải lên file âm thanh hoặc ZIP",
279
+ file_types=[".mp3", ".wav", ".flac", ".m4a", ".ogg", ".zip"]
280
+ )
281
+ process_btn = gr.Button("🚀 Xử lý", variant="primary")
282
+
283
+ with gr.Column():
284
+ status_text = gr.Textbox(label="Trạng thái", lines=3)
285
+ output_file = gr.File(label="Tải xuống kết quả")
286
+
287
+ process_btn.click(
288
+ fn=process_audio_interface,
289
+ inputs=[input_file],
290
+ outputs=[output_file, status_text]
291
+ )
292
+
293
+ gr.Markdown("""
294
+ ---
295
+ **Lưu ý:**
296
+ - Quá trình xử lý có thể mất nhiều thời gian tùy vào kích thước file
297
+ - Model Whisper Large-v3-Turbo sẽ được tải về lần đầu tiên (khoảng 1.5GB)
298
+ - Nên dùng GPU để tăng tốc độ xử lý
299
+ """)
300
+
301
+ # Chạy ứng dụng
302
+ if __name__ == "__main__":
303
+ demo.launch(share=True)