DeepLearning101 commited on
Commit
fe392ad
·
verified ·
1 Parent(s): 0c2d490

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -104
app.py CHANGED
@@ -1,102 +1,141 @@
1
  import gradio as gr
2
  import torch
 
3
  import os
4
- import soundfile as sf
5
- import librosa
6
- import logging
7
  import tempfile
 
8
  import traceback
9
  from datetime import datetime
10
- from DPTNet_eval.DPTNet_quant_sep import load_dpt_model, dpt_sep_process
11
 
12
- # 配置日志系统
13
  logging.basicConfig(
14
- filename='app.log',
15
  level=logging.INFO,
16
  format='%(asctime)s - %(levelname)s - %(message)s'
17
  )
18
  logger = logging.getLogger(__name__)
19
 
20
- # 全局模型加载(避免重复加载)
 
 
 
 
21
  try:
22
- logger.info("開始加載語音分離模型...")
 
 
 
 
 
 
 
 
 
23
  model = load_dpt_model()
24
- logger.info("模型加載成功")
25
  except Exception as e:
26
- logger.error(f"模型加載失敗: {str(e)}")
27
- raise RuntimeError("模型初始化失敗") from e
28
 
29
- def separate_audio(input_wav):
30
- """處理音訊分離的主函數"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  process_id = datetime.now().strftime("%Y%m%d%H%M%S%f")
32
  temp_wav = None
33
 
34
  try:
35
- logger.info(f"[{process_id}] 開始處理檔案: {input_wav}")
36
 
37
- # 1. 驗證輸入檔案
38
- if not os.path.exists(input_wav):
39
- raise gr.Error("檔案不存在,請重新上傳")
40
- if os.path.getsize(input_wav) > 50 * 1024 * 1024: # 50MB限制
41
- raise gr.Error("檔案大小超過50MB限制")
42
-
43
- # 2. 讀取並標準化音訊
44
- logger.info(f"[{process_id}] 讀取音訊檔案...")
45
- data, sr = librosa.load(input_wav, sr=None, mono=True)
46
 
47
- # 3. 重採樣處理
48
- if sr != 16000:
49
- logger.info(f"[{process_id}] 重採樣從 {sr}Hz 到 16000Hz...")
50
- data = librosa.resample(data, orig_sr=sr, target_sr=16000)
51
- sr = 16000
52
-
53
- # 4. 創建臨時檔案
54
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file:
55
- temp_wav = tmp_file.name
56
- logger.info(f"[{process_id}] 寫入臨時檔案: {temp_wav}")
57
- sf.write(temp_wav, data, sr, subtype='PCM_16')
58
-
59
- # 5. 執行語音分離
60
- logger.info(f"[{process_id}] 開始語音分離...")
61
- out_dir = tempfile.mkdtemp() # 使用��時目錄存放輸出
62
  outfilename = os.path.join(out_dir, "output.wav")
 
63
 
64
- dpt_sep_process(temp_wav, model=model, outfilename=outfilename)
 
 
65
 
66
- # 6. 獲取輸出檔案
67
- output_files = [
68
- outfilename.replace('.wav', '_sep1.wav'),
69
- outfilename.replace('.wav', '_sep2.wav')
70
- ]
71
- logger.info(f"[{process_id}] 預期輸出檔案: {output_files}")
72
-
73
- # 7. 驗證輸出
74
- if not all(os.path.exists(f) for f in output_files):
75
- missing = [f for f in output_files if not os.path.exists(f)]
76
- raise gr.Error(f"分離失敗,缺失檔案: {missing}")
77
-
78
- logger.info(f"[{process_id}] 處理完成")
79
- return output_files
80
-
81
  except Exception as e:
82
- error_msg = f"[{process_id}] 處理錯誤: {str(e)}\n{traceback.format_exc()}"
83
- logger.error(error_msg)
84
- raise gr.Error(f"處理失敗: {str(e)}") from e
85
-
86
  finally:
87
  # 清理臨時檔案
88
  if temp_wav and os.path.exists(temp_wav):
89
  try:
90
- os.remove(temp_wav)
91
- logger.info(f"[{process_id}] 已清理臨時檔案")
92
  except Exception as clean_err:
93
- logger.warning(f"[{process_id}] 清理失敗: {str(clean_err)}")
94
 
95
- # 🎯 你提供的 description 內容(已轉為 HTML)
96
  description_html = """
97
  <h1 align='center'><a href='https://www.twman.org/AI/ASR/SpeechSeparation' target='_blank'>中文語者分離(分割)</a></h1>
98
- <p align='center'><b>上傳一段混音音檔 (支援 `.mp3`, `.wav`),自動分離出兩個人的聲音</b></p>
99
-
100
  <div align='center'>
101
  <a href='https://www.twman.org' target='_blank'>TonTon Huang Ph.D.</a> |
102
  <a href='https://www.twman.org/AI' target='_blank'> AI </a> |
@@ -105,10 +144,7 @@ description_html = """
105
  <a href='http://deeplearning101.twman.org' target='_blank'>Deep Learning 101</a> |
106
  <a href='https://www.youtube.com/c/DeepLearning101' target='_blank'>YouTube</a>
107
  </div>
108
-
109
  <br>
110
-
111
- ### 📘 相關技術文章:
112
  <ul>
113
  <li><a href='https://blog.twman.org/2025/03/AIAgent.html' target='_blank'>避開 AI Agent 開發陷阱:常見問題、挑戰與解決方案 (那些 AI Agent 實戰踩過的坑)</a>:探討多種 AI Agent 工具的應用經驗與挑戰</li>
114
  <li><a href='https://blog.twman.org/2024/08/LLM.html' target='_blank'>白話文手把手帶你科普 GenAI</a>:淺顯介紹生成式人工智慧核心概念</li>
@@ -123,44 +159,50 @@ description_html = """
123
  <li><a href='https://blog.twman.org/2023/07/wsl.html' target='_blank'>用PPOCRLabel來幫PaddleOCR做OCR的微調和標註</a></li>
124
  <li><a href='https://blog.twman.org/2023/07/HugIE.html' target='_blank'>基於機器閱讀理解和指令微調的統一信息抽取框架之診斷書醫囑資訊擷取分析</a></li>
125
  </ul>
126
-
127
  <br>
128
  """
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  if __name__ == "__main__":
131
- # 完整配置 Gradio 接口
132
- interface = gr.Interface(
133
- fn=separate_audio,
134
- inputs=gr.Audio(
135
- type="filepath",
136
- label="請上傳混音音檔 (支援格式: mp3/wav/ogg)",
137
- sources=["upload", "microphone"],
138
- max_length=180
139
- ),
140
- outputs=[
141
- gr.Audio(label="語音軌道 1", format="wav"),
142
- gr.Audio(label="語音軌道 2", format="wav")
143
- ],
144
- title="🎙️ 語音分離 Demo - Deep Learning 101",
145
- description=description_html, # 直接使用HTML描述
146
- allow_flagging="never",
147
- live=True,
148
- examples=[
149
- ["examples/sample1.wav"],
150
- ["examples/sample2.mp3"]
151
- ],
152
- theme="default"
153
- )
154
-
155
- launch_kwargs = {
156
- "server_name": "0.0.0.0",
157
- "server_port": 7860,
158
- "share": False,
159
- "debug": False,
160
- "auth": None,
161
- "inbrowser": True,
162
- "quiet": False,
163
- "prevent_thread_lock": True
164
- }
165
-
166
- interface.launch(**launch_kwargs)
 
1
  import gradio as gr
2
  import torch
3
+ import torchaudio
4
  import os
 
 
 
5
  import tempfile
6
+ import logging
7
  import traceback
8
  from datetime import datetime
 
9
 
10
+ # 設定日誌系統
11
  logging.basicConfig(
 
12
  level=logging.INFO,
13
  format='%(asctime)s - %(levelname)s - %(message)s'
14
  )
15
  logger = logging.getLogger(__name__)
16
 
17
+ # 檢查 Hugging Face 環境變數
18
+ if not os.getenv("SpeechSeparation"):
19
+ logger.warning("⚠️ 環境變數 SpeechSeparation 未設定!請在 Hugging Face Space 的 Secrets 中設定 HF_TOKEN")
20
+
21
+ # 載入模型模組
22
  try:
23
+ logger.info("🔧 開始載入語音分離模型...")
24
+ from DPTNet_eval.DPTNet_quant_sep import load_dpt_model, dpt_sep_process
25
+ logger.info("✅ 模型模組載入成功")
26
+ except ImportError as e:
27
+ logger.error(f"❌ 模組載入失敗: {str(e)}")
28
+ raise RuntimeError("本地模組路徑配置錯誤") from e
29
+
30
+ # 全域模型初始化
31
+ try:
32
+ logger.info("🔄 初始化模型中...")
33
  model = load_dpt_model()
34
+ logger.info(f"🧠 模型載入完成,運行設備: {'GPU' if torch.cuda.is_available() else 'CPU'}")
35
  except Exception as e:
36
+ logger.error(f"💣 模型初始化失敗: {str(e)}")
37
+ raise RuntimeError("模型載入異常終止") from e
38
 
39
+ def validate_audio(path):
40
+ """驗證音檔格式與內容有效性"""
41
+ try:
42
+ info = torchaudio.info(path)
43
+ logger.info(f"🔊 音檔資訊: 采樣率={info.sample_rate}Hz, 通道數={info.num_channels}")
44
+
45
+ if info.num_channels not in [1, 2]:
46
+ raise gr.Error("❌ 不支援的音檔通道數(僅支援單聲道或立體聲)")
47
+
48
+ if info.sample_rate < 8000 or info.sample_rate > 48000:
49
+ raise gr.Error("❌ 不支援的采樣率(需介於 8kHz~48kHz)")
50
+
51
+ return info.sample_rate
52
+ except Exception as e:
53
+ logger.error(f"⚠️ 音檔驗證失敗: {str(e)}")
54
+ raise gr.Error("❌ 無效的音訊檔案格式")
55
+
56
+ def convert_to_wav(input_path):
57
+ """統一轉換為 16kHz WAV 格式"""
58
+ try:
59
+ # 使用 torchaudio 保持一致性
60
+ waveform, sample_rate = torchaudio.load(input_path)
61
+
62
+ # 單聲道轉換
63
+ if waveform.shape[0] > 1:
64
+ waveform = torch.mean(waveform, dim=0, keepdim=True)
65
+
66
+ # 重采樣至 16kHz
67
+ if sample_rate != 16000:
68
+ resampler = torchaudio.transforms.Resample(orig_freq=sample_rate, new_freq=16000)
69
+ waveform = resampler(waveform)
70
+
71
+ # 建立臨時 WAV 檔案
72
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpfile:
73
+ torchaudio.save(tmpfile.name, waveform, 16000, bits_per_sample=16)
74
+ logger.info(f"📝 已生成標準 WAV 檔案: {tmpfile.name}")
75
+ return tmpfile.name
76
+
77
+ except Exception as e:
78
+ logger.error(f"⚠️ 音檔轉換失敗: {str(e)}")
79
+ raise gr.Error("❌ 音訊格式轉換失敗")
80
+
81
+ def separate_audio(input_audio):
82
+ """主處理函式"""
83
  process_id = datetime.now().strftime("%Y%m%d%H%M%S%f")
84
  temp_wav = None
85
 
86
  try:
87
+ logger.info(f"[{process_id}] 🚀 收到新請求: {input_audio}")
88
 
89
+ # 1️⃣ 檔案驗證與轉換
90
+ if not os.path.exists(input_audio):
91
+ raise gr.Error("檔案不存在,請重新上傳")
 
 
 
 
 
 
92
 
93
+ if os.path.getsize(input_audio) > 50 * 1024 * 1024: # 50MB 限制
94
+ raise gr.Error("❌ 檔案大小超過 50MB 限制")
95
+
96
+ logger.info(f"[{process_id}] 🔁 轉換標準音檔格式...")
97
+ temp_wav = convert_to_wav(input_audio)
98
+ validate_audio(temp_wav)
99
+
100
+ # 2️⃣ 建立輸出目錄
101
+ out_dir = tempfile.mkdtemp()
 
 
 
 
 
 
102
  outfilename = os.path.join(out_dir, "output.wav")
103
+ logger.info(f"[{process_id}] 📁 建立臨時輸出目錄: {out_dir}")
104
 
105
+ # 3️⃣ 執行語音分離
106
+ logger.info(f"[{process_id}] 🧠 開始執行語音分離...")
107
+ sep_files = dpt_sep_process(temp_wav, model=model, outfilename=outfilename)
108
 
109
+ # 4️⃣ 驗證輸出結果
110
+ for f in sep_files:
111
+ if not os.path.exists(f):
112
+ raise gr.Error(f"❌ 缺少輸出檔案: {f}")
113
+ validate_audio(f)
114
+
115
+ logger.info(f"[{process_id}] ✅ 處理成功完成")
116
+ return sep_files
117
+
118
+ except RuntimeError as e:
119
+ if "CUDA out of memory" in str(e):
120
+ logger.error(f"[{process_id}] 💥 CUDA 記憶體不足")
121
+ raise gr.Error("⚠️ 記憶體不足,請上傳較短的音檔") from e
122
+ else:
123
+ raise
124
  except Exception as e:
125
+ logger.error(f"[{process_id}] 處理錯誤: {str(e)}\n{traceback.format_exc()}")
126
+ raise gr.Error(f"⚠️ 處理失敗: {str(e)}") from e
 
 
127
  finally:
128
  # 清理臨時檔案
129
  if temp_wav and os.path.exists(temp_wav):
130
  try:
131
+ os.unlink(temp_wav)
132
+ logger.info(f"[{process_id}] 🧹 臨時檔案已清理")
133
  except Exception as clean_err:
134
+ logger.warning(f"[{process_id}] ⚠️ 清理失敗: {str(clean_err)}")
135
 
136
+ # 🎯 description 內容(轉為 HTML)
137
  description_html = """
138
  <h1 align='center'><a href='https://www.twman.org/AI/ASR/SpeechSeparation' target='_blank'>中文語者分離(分割)</a></h1>
 
 
139
  <div align='center'>
140
  <a href='https://www.twman.org' target='_blank'>TonTon Huang Ph.D.</a> |
141
  <a href='https://www.twman.org/AI' target='_blank'> AI </a> |
 
144
  <a href='http://deeplearning101.twman.org' target='_blank'>Deep Learning 101</a> |
145
  <a href='https://www.youtube.com/c/DeepLearning101' target='_blank'>YouTube</a>
146
  </div>
 
147
  <br>
 
 
148
  <ul>
149
  <li><a href='https://blog.twman.org/2025/03/AIAgent.html' target='_blank'>避開 AI Agent 開發陷阱:常見問題、挑戰與解決方案 (那些 AI Agent 實戰踩過的坑)</a>:探討多種 AI Agent 工具的應用經驗與挑戰</li>
150
  <li><a href='https://blog.twman.org/2024/08/LLM.html' target='_blank'>白話文手把手帶你科普 GenAI</a>:淺顯介紹生成式人工智慧核心概念</li>
 
159
  <li><a href='https://blog.twman.org/2023/07/wsl.html' target='_blank'>用PPOCRLabel來幫PaddleOCR做OCR的微調和標註</a></li>
160
  <li><a href='https://blog.twman.org/2023/07/HugIE.html' target='_blank'>基於機器閱讀理解和指令微調的統一信息抽取框架之診斷書醫囑資訊擷取分析</a></li>
161
  </ul>
 
162
  <br>
163
  """
164
 
165
+ EXAMPLES = [
166
+ ["examples/sample1.wav"],
167
+ ["examples/sample2.mp3"]
168
+ ]
169
+
170
+ AUDIO_INPUT = gr.Audio(
171
+ label="🔊 上傳混合音檔",
172
+ type="filepath",
173
+ sources=["upload", "microphone"],
174
+ show_label=True,
175
+ max_length=180 # 最大 3 分鐘
176
+ )
177
+
178
+ AUDIO_OUTPUTS = [
179
+ gr.Audio(label="🗣️ 語音軌道 1", type="filepath"),
180
+ gr.Audio(label="🗣️ 語音軌道 2", type="filepath")
181
+ ]
182
+
183
+ # 🚀 啟動應用程式
184
+ interface = gr.Interface(
185
+ fn=separate_audio,
186
+ inputs=AUDIO_INPUT,
187
+ outputs=AUDIO_OUTPUTS,
188
+ title="🎙️ 語音分離��上傳一段混音音檔(支援.mp3, .wav),自動分離出兩個人的聲音;Deep Learning 101",
189
+ description=description_html,
190
+ examples=EXAMPLES,
191
+ allow_flagging="never",
192
+ cache_examples=False,
193
+ theme="default"
194
+ )
195
+
196
+ LAUNCH_CONFIG = {
197
+ "server_name": "0.0.0.0",
198
+ "server_port": 7860,
199
+ "share": False,
200
+ "debug": True,
201
+ "auth": None,
202
+ "inbrowser": True,
203
+ "quiet": False
204
+ }
205
+
206
  if __name__ == "__main__":
207
+ logger.info("🚀 啟動 Gradio 服務...")
208
+ interface.launch(**LAUNCH_CONFIG)