Quarter-Peach commited on
Commit
6eae822
·
1 Parent(s): d3a4010
Files changed (1) hide show
  1. audio_visualizer.py +209 -162
audio_visualizer.py CHANGED
@@ -5,218 +5,265 @@ import librosa
5
  import librosa.display
6
  import matplotlib.pyplot as plt
7
  import numpy as np
 
 
8
 
9
  # 设置页面配置
10
- st.set_page_config(page_title="音频对比与频谱可视化工具 (v2.0)", layout="wide")
11
 
12
  # --- 辅助函数 ---
13
 
14
- # 定义一个可以清理文件名的函数,以便提取通用ID
15
- def clean_filename_for_id(filename_base, prefixes):
16
- """从文件名基础部分剥离指定前缀,获取通用ID"""
17
- cleaned_name = filename_base
18
- for prefix in prefixes:
19
- if cleaned_name.startswith(prefix):
20
- cleaned_name = cleaned_name[len(prefix):]
21
- return cleaned_name
22
-
23
- def get_audio_files_v2(folder_path, prefixes, extensions=['.wav', '.mp3', '.flac']):
24
- """获取文件夹内所有指定扩展名的音频文件的通用ID (通过剥离前缀)"""
25
  if not os.path.isdir(folder_path):
26
- return {} # 返回 ID 到 完整文件名基础 (base name) 的映射
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- file_ids = {}
29
- for filename in os.listdir(folder_path):
30
- base, ext = os.path.splitext(filename)
31
- if ext.lower() in extensions:
32
- # 获取通用 ID
33
- file_id = clean_filename_for_id(base, prefixes)
34
- # 存储通用 ID 到 文件的基础名称 (例如: '2' -> 'mixture_2')
35
- file_ids[file_id] = base
36
- return file_ids
37
 
38
  @st.cache_data(show_spinner="正在加载音频并生成频谱图...")
39
  def generate_spectrogram(audio_path, title):
40
- """加载音频并生成梅尔频谱图,返回Matplotlib Figure对象"""
41
  try:
42
  y, sr = librosa.load(audio_path, sr=None)
43
  S = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=2048, hop_length=512)
44
  S_dB = librosa.power_to_db(S, ref=np.max)
45
 
46
  fig, ax = plt.subplots(figsize=(10, 4))
47
- # 使用'viridis'作为默认色图,如果'magma'在某些环境下不适用
48
  img = librosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel', ax=ax, cmap='viridis')
49
- ax.set(title=f'Mel-spectrogram: {title}')
 
50
 
51
  return fig
52
  except FileNotFoundError:
53
  return None
54
- except Exception:
 
55
  return None
56
 
57
  # --- Streamlit 状态初始化 ---
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- if 'mixture_path' not in st.session_state:
60
- st.session_state.mixture_path = "/inspire/hdd/global_user/chenxie-25019/HaoQiu/music-source-restoration/msr_test_set/Bass" # 预设您的路径
61
- if 'target_path' not in st.session_state:
62
- st.session_state.target_path = "/inspire/hdd/global_user/chenxie-25019/HaoQiu/music-source-restoration/Result/Bass_gan_35k" # 预设您的路径
63
- if 'mix_prefixes' not in st.session_state:
64
- st.session_state.mix_prefixes = "mixture_,source_" # 混合文件常见前缀
65
- if 'tar_prefixes' not in st.session_state:
66
- st.session_state.tar_prefixes = "restored_,pred_,target_" # 目标文件常见前缀
67
- if 'matched_ids' not in st.session_state:
68
- st.session_state.matched_ids = {} # 存储匹配的通用ID -> (mix_base, tar_base)
69
- if 'available_keys' not in st.session_state:
70
- st.session_state.available_keys = [] # 存储通用ID列表
71
- if 'selected_key' not in st.session_state:
72
- st.session_state.selected_key = None
73
 
74
  # --- 主体 UI ---
75
 
76
- st.title("🎼 音频对比与频谱可视化工具 (v2.0)")
77
- st.markdown("此版本包含**智能文件名匹配**功能,用于音源分离/恢复数据的可视化。")
78
 
79
  # 1. 文件夹输入
80
  st.header("1. 输入文件夹路径")
81
- col_mix, col_tar = st.columns(2)
82
- with col_mix:
83
- mixture_path_input = st.text_input("输入 **Mixture/原始音频** 文件夹路径", st.session_state.mixture_path)
84
- with col_tar:
85
- target_path_input = st.text_input("输入 **Target/结果音频** 文件夹路径", st.session_state.target_path)
86
- st.session_state.mixture_path = mixture_path_input
87
- st.session_state.target_path = target_path_input
88
-
89
-
90
- # 2. 前缀配置
91
- st.header("2. 配置文件名匹配前缀")
92
- st.markdown("请配置文件名中需要被**剥离**的前缀,以获得通用ID进行匹配。")
93
- col_mix_p, col_tar_p, col_btn = st.columns([1, 1, 0.5])
94
-
95
- with col_mix_p:
96
- mix_prefixes_input = st.text_input("Mixture 文件前缀 (逗号分隔)", st.session_state.mix_prefixes, key="mix_p_input")
97
-
98
- with col_tar_p:
99
- tar_prefixes_input = st.text_input("Target 文件前缀 (逗号分隔)", st.session_state.tar_prefixes, key="tar_p_input")
100
-
101
- with col_btn:
102
- st.write(" ") # 占位
103
- if st.button("加载/刷新文件列表", help="清除缓存,根据前缀重新匹配音频对"):
104
- st.session_state.mix_prefixes = mix_prefixes_input
105
- st.session_state.tar_prefixes = tar_prefixes_input
 
 
 
 
 
 
 
 
 
 
 
 
106
  st.cache_data.clear()
107
- # 重置选择,触发后续匹配逻辑
108
- st.session_state.selected_key = None
109
  st.rerun()
110
 
111
- # 3. 文件列表加载逻辑
112
- if st.session_state.mixture_path and st.session_state.target_path:
113
-
114
- # 将逗号分隔的前缀字符串转换为列表
115
- mix_prefixes_list = [p.strip() for p in st.session_state.mix_prefixes.split(',') if p.strip()]
116
- tar_prefixes_list = [p.strip() for p in st.session_state.tar_prefixes.split(',') if p.strip()]
117
 
118
- # 获取两个文件夹的 ID -> 文件基础名称 映射
119
- mix_id_to_base = get_audio_files_v2(st.session_state.mixture_path, mix_prefixes_list)
120
- tar_id_to_base = get_audio_files_v2(st.session_state.target_path, tar_prefixes_list)
121
 
122
- # 找到共同存在的通 ID
123
- matched_ids = mix_id_to_base.keys() & tar_id_to_base.keys()
 
 
 
 
124
 
125
- st.session_state.matched_ids = {
126
- file_id: (mix_id_to_base[file_id], tar_id_to_base[file_id])
127
- for file_id in matched_ids
128
- }
129
- st.session_state.available_keys = sorted(list(matched_ids))
130
-
131
- if not st.session_state.available_keys:
132
- st.warning("在两个文件夹中未找到匹配的音频对。请检查路径、文件格式或**前缀配置**。")
133
  else:
134
- st.success(f"成功找到 {len(st.session_state.available_keys)} 匹配的音频文件ID。")
135
- # 确保选中的key仍然可用
136
- if st.session_state.selected_key not in st.session_state.available_keys:
137
- st.session_state.selected_key = st.session_state.available_keys[0] if st.session_state.available_keys else None
138
 
139
 
140
- # 4. 选择音频对
141
- st.header("3. 选择音频对")
142
- if st.session_state.available_keys:
143
-
144
  col_select, col_random = st.columns([3, 1])
145
 
146
  with col_select:
147
- # 手动选择
148
- new_selected_key = st.selectbox(
149
- "手动选择一个通用音频ID",
150
- st.session_state.available_keys,
151
- index=st.session_state.available_keys.index(st.session_state.selected_key) if st.session_state.selected_key in st.session_state.available_keys else 0
152
  )
153
- # 如果新选择的 key 存在,则更新
154
- if new_selected_key and new_selected_key != st.session_state.selected_key:
155
- st.session_state.selected_key = new_selected_key
156
 
157
  with col_random:
158
- # 随机抽取
159
- st.write(" ") # 占位
160
- if st.button("随机抽取"):
161
- st.session_state.selected_key = random.choice(st.session_state.available_keys)
162
  st.rerun()
163
 
164
- # 5. 展示结果
165
- if st.session_state.selected_key and st.session_state.matched_ids:
166
- selected_id = st.session_state.selected_key
 
 
 
167
 
168
- if selected_id in st.session_state.matched_ids:
169
- mix_base, tar_base = st.session_state.matched_ids[selected_id]
170
-
171
- st.header(f"4. 展示结果:通用 ID - {selected_id}")
172
- st.markdown(f"**Mixture 文件基础名:** `{mix_base}` | **Target 文件基础名:** `{tar_base}`")
173
-
174
- # 路径查找函数
175
- def get_full_path(folder, base_name):
176
- """尝试查找常用扩展名,返回完整路径"""
177
- for ext in ['.wav', '.mp3', '.flac']:
178
- full_path = os.path.join(folder, base_name + ext)
179
- if os.path.exists(full_path):
180
- return full_path
181
- return None
182
-
183
- mix_file_path = get_full_path(st.session_state.mixture_path, mix_base)
184
- tar_file_path = get_full_path(st.session_state.target_path, tar_base)
185
-
186
- if mix_file_path and tar_file_path:
187
-
188
- col_mix, col_tar = st.columns(2)
189
-
190
- # --- Mixture 音频展示 ---
191
- with col_mix:
192
- st.subheader("Mixture (原始/输入)")
193
- st.markdown(f"**路径:** `{mix_file_path}`")
194
-
195
- try:
196
- st.audio(mix_file_path, format='audio/wav') # 尝试指定格式
197
- except Exception as e:
198
- st.error(f"播放混合音频失败: {str(e)}")
199
-
200
- fig_mix = generate_spectrogram(mix_file_path, f"Mixture ({mix_base})")
201
- if fig_mix:
202
- st.pyplot(fig_mix)
203
- plt.close(fig_mix) # 避免内存泄漏
204
-
205
- # --- Target 音频展示 ---
206
- with col_tar:
207
- st.subheader("Target (恢复/结果)")
208
- st.markdown(f"**路径:** `{tar_file_path}`")
209
 
210
  try:
211
- st.audio(tar_file_path, format='audio/wav') # 尝试指定格式
 
 
212
  except Exception as e:
213
- st.error(f"播放目标音频失败: {str(e)}")
214
 
215
- fig_tar = generate_spectrogram(tar_file_path, f"Target ({tar_base})")
216
- if fig_tar:
217
- st.pyplot(fig_tar)
218
- plt.close(fig_tar) # 避免内存泄漏
219
- else:
220
- st.error("无法找到选定音频对的完整文件路径,请检查文件扩展名或路径权限。")
221
- else:
222
- st.error(f"内部错误:通用 ID '{selected_id}' 未在匹配表中找到。请刷新��面。")
 
 
 
 
 
 
 
 
5
  import librosa.display
6
  import matplotlib.pyplot as plt
7
  import numpy as np
8
+ import re
9
+ from collections import defaultdict
10
 
11
  # 设置页面配置
12
+ st.set_page_config(page_title="音频对比与频谱可视化工具 (v5.2 - 最终稳定版)", layout="wide")
13
 
14
  # --- 辅助函数 ---
15
 
16
+ def get_safe_prefix(prefix_input):
17
+ """从逗号分隔的字符串中安全地获取第一个有效前缀,否则返回空字符串。"""
18
+ prefix_list = [p.strip() for p in prefix_input.split(',') if p.strip()]
19
+ return prefix_list[0] if prefix_list else ""
20
+
21
+ def get_prefix_list(prefix_input):
22
+ """从逗号分隔的字符串中获取所有有效前缀的列表。"""
23
+ return [p.strip() for p in prefix_input.split(',') if p.strip()]
24
+
25
+ def get_universal_ids_regex(folder_path, prefix_list, regex_pattern, extensions=['.wav', '.mp3', '.flac']):
26
+ # ... (此函数体保持不变) ...
27
  if not os.path.isdir(folder_path):
28
+ return set()
29
+
30
+ ids = set()
31
+ try:
32
+ compiled_regex = re.compile(regex_pattern)
33
+
34
+ for filename in os.listdir(folder_path):
35
+ base, ext = os.path.splitext(filename)
36
+ if ext.lower() in extensions:
37
+
38
+ current_base = base
39
+ for prefix in prefix_list:
40
+ if current_base.startswith(prefix):
41
+ current_base = current_base[len(prefix):]
42
+ break
43
+
44
+ match = compiled_regex.match(current_base)
45
+
46
+ if match and 'x' in match.groupdict():
47
+ file_id_x = match.group('x')
48
+ if file_id_x:
49
+ ids.add(file_id_x)
50
+
51
+ except re.error as e:
52
+ return set()
53
+ except Exception:
54
+ pass
55
+
56
+ return ids
57
+
58
+ @st.cache_data(show_spinner="正在扫描文件并匹配通用 ID (x)...")
59
+ def find_matched_ids(output_path, mix_path, tar_path, out_pfx_str, mix_pfx_str, tar_pfx_str, out_pat, mix_pat, tar_pat):
60
+ """获取三个文件夹中所有通用 ID (x) 的交集,并缓存结果。"""
61
+
62
+ out_prefixes_list = get_prefix_list(out_pfx_str)
63
+ mix_prefixes_list = get_prefix_list(mix_pfx_str)
64
+ tar_prefixes_list = get_prefix_list(tar_pfx_str)
65
+
66
+ # 获取三个集合的通用 ID (x)
67
+ out_ids = get_universal_ids_regex(output_path, out_prefixes_list, out_pat)
68
+ mix_ids = get_universal_ids_regex(mix_path, mix_prefixes_list, mix_pat)
69
+ tar_ids = get_universal_ids_regex(tar_path, tar_prefixes_list, tar_pat)
70
 
71
+ # 找到三个集合的交集
72
+ matched_x_ids = sorted(list(out_ids & mix_ids & tar_ids))
73
+ return matched_x_ids
74
+
 
 
 
 
 
75
 
76
  @st.cache_data(show_spinner="正在加载音频并生成频谱图...")
77
  def generate_spectrogram(audio_path, title):
78
+ # --- 修复点 2: 移除中文标题 (中文乱码问题) ---
79
  try:
80
  y, sr = librosa.load(audio_path, sr=None)
81
  S = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=2048, hop_length=512)
82
  S_dB = librosa.power_to_db(S, ref=np.max)
83
 
84
  fig, ax = plt.subplots(figsize=(10, 4))
 
85
  img = librosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel', ax=ax, cmap='viridis')
86
+ ax.set(title=f'Mel Spectrogram: {title}', xlabel='Time', ylabel='Mel Freq') # <-- 标题改为英文
87
+ ax.tick_params(labelsize=8)
88
 
89
  return fig
90
  except FileNotFoundError:
91
  return None
92
+ except Exception as e:
93
+ st.error(f"处理音频文件失败: {str(e)}")
94
  return None
95
 
96
  # --- Streamlit 状态初始化 ---
97
+ if 'output_path' not in st.session_state: st.session_state.output_path = ""
98
+ if 'mixture_path' not in st.session_state: st.session_state.mixture_path = ""
99
+ if 'target_path' not in st.session_state: st.session_state.target_path = ""
100
+ if 'output_pattern' not in st.session_state: st.session_state.output_pattern = r"(?P<x>\d+)_DT(?P<y>\d+)"
101
+ if 'mix_pattern' not in st.session_state: st.session_state.mix_pattern = r"(?P<x>\d+)_DT(?P<y>\d+)"
102
+ if 'tar_pattern' not in st.session_state: st.session_state.tar_pattern = r"(?P<x>\d+)"
103
+ if 'output_prefixes' not in st.session_state: st.session_state.output_prefixes = ""
104
+ if 'mix_prefixes' not in st.session_state: st.session_state.mix_prefixes = ""
105
+ if 'tar_prefixes' not in st.session_state: st.session_state.tar_prefixes = ""
106
+ if 'separator' not in st.session_state: st.session_state.separator = "_DT" # <--- 新增状态
107
+ if 'selected_y' not in st.session_state: st.session_state.selected_y = "0"
108
+ if 'available_x_ids' not in st.session_state: st.session_state.available_x_ids = []
109
+ if 'selected_x_id' not in st.session_state: st.session_state.selected_x_id = None
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  # --- 主体 UI ---
113
 
114
+ st.title("🎼 音频对比与频谱可视化工具 (v5.2 - 最终稳定版)")
 
115
 
116
  # 1. 文件夹输入
117
  st.header("1. 输入文件夹路径")
118
+ col_out, col_mix, col_tar = st.columns(3)
119
+ with col_out: st.session_state.output_path = st.text_input("Output 文件夹路径", st.session_state.output_path)
120
+ with col_mix: st.session_state.mixture_path = st.text_input("Mixture 文件夹路径", st.session_state.mixture_path)
121
+ with col_tar: st.session_state.target_path = st.text_input("Target 文件夹路径", st.session_state.target_path)
122
+
123
+
124
+ # 2. 模式和前缀配置
125
+ st.header("2. 配置文件名匹配模式")
126
+ st.markdown("请为每种文件类型配置**独立**的模式和前缀。模式必须包含 `(?P<x>...)`。")
127
+
128
+ # --- Output 配置 ---
129
+ col_out_p, col_out_r = st.columns(2)
130
+ with col_out_p: st.session_state.output_prefixes = st.text_input("Output 前缀", st.session_state.output_prefixes)
131
+ with col_out_r: st.session_state.output_pattern = st.text_input("Output 模式 (提取 x)", st.session_state.output_pattern, key='out_pat', help="例如: `(?P<x>\d+)_DT\d+`")
132
+
133
+ # --- Mixture 配置 ---
134
+ col_mix_p, col_mix_r = st.columns(2)
135
+ with col_mix_p: st.session_state.mix_prefixes = st.text_input("Mixture 前缀", st.session_state.mix_prefixes)
136
+ with col_mix_r: st.session_state.mix_pattern = st.text_input("Mixture 模式 (提取 x)", st.session_state.mix_pattern, key='mix_pat', help="例如: `(?P<x>\d+)_DT\d+`")
137
+
138
+ # --- Target 配置 (修复点 1: 确保 Target 模式在这里配置) ---
139
+ col_tar_p, col_tar_r = st.columns(2)
140
+ with col_tar_p: st.session_state.tar_prefixes = st.text_input("Target 前缀", st.session_state.tar_prefixes)
141
+ with col_tar_r: st.session_state.tar_pattern = st.text_input("Target 模式 (提取 x)", st.session_state.tar_pattern, key='tar_pat', help="例如: `(?P<x>\d+)`")
142
+
143
+
144
+ # 3. 核心版本号配置 (UI 结构调整,避免重复标题)
145
+ st.header("3. 核心版本号与 ID 加载")
146
+ col_sep, col_y, col_btn_y = st.columns([1, 1, 2])
147
+ with col_sep:
148
+ st.session_state.separator = st.text_input("X和Y之间的分隔符", st.session_state.separator, help="例如:`_DT`,`_V_` 等。**注意:修改此项后必须相应修改第2部分的模式!**")
149
+ with col_y:
150
+ st.session_state.selected_y = st.text_input("目标小版本号 (y)", st.session_state.selected_y)
151
+
152
+ with col_btn_y:
153
+ st.write(" ")
154
+ if st.button("加载/刷新通用ID列表 (x)", help="清除缓存,根据新的模式和前缀重新匹配通用大序号 (x)"):
155
  st.cache_data.clear()
156
+ st.session_state.selected_x_id = None
 
157
  st.rerun()
158
 
 
 
 
 
 
 
159
 
160
+ # 4. 文件列表加载逻辑 (通用 ID x)
161
+ if st.session_state.output_path and st.session_state.mixture_path and st.session_state.target_path:
 
162
 
163
+ # 核心:调缓存函数,如果参数不变,它会立即返回结果
164
+ matched_x_ids = find_matched_ids(
165
+ st.session_state.output_path, st.session_state.mixture_path, st.session_state.target_path,
166
+ st.session_state.output_prefixes, st.session_state.mix_prefixes, st.session_state.tar_prefixes,
167
+ st.session_state.output_pattern, st.session_state.mix_pattern, st.session_state.tar_pattern
168
+ )
169
 
170
+ st.session_state.available_x_ids = matched_x_ids
171
+
172
+ if not st.session_state.available_x_ids:
173
+ st.warning("在三个文件夹中未找到匹配的通用音频 ID (x)。请检查路径、文件格式或正则表达式模式。")
 
 
 
 
174
  else:
175
+ st.success(f"成功找到 {len(st.session_state.available_x_ids)} 匹配的通用 ID (x)。")
176
+ if st.session_state.selected_x_id not in st.session_state.available_x_ids:
177
+ st.session_state.selected_x_id = st.session_state.available_x_ids[0] if st.session_state.available_x_ids else None
 
178
 
179
 
180
+ # 5. 选择音频对 (通用 ID x)
181
+ if st.session_state.available_x_ids:
182
+ st.header("4. 选择音频 ID (x)")
 
183
  col_select, col_random = st.columns([3, 1])
184
 
185
  with col_select:
186
+ new_selected_x_id = st.selectbox(
187
+ "手动选择一个通用音频 ID (x)",
188
+ st.session_state.available_x_ids,
189
+ index=st.session_state.available_x_ids.index(st.session_state.selected_x_id) if st.session_state.selected_x_id in st.session_state.available_x_ids else 0
 
190
  )
191
+ if new_selected_x_id and new_selected_x_id != st.session_state.selected_x_id:
192
+ st.session_state.selected_x_id = new_selected_x_id
193
+ st.rerun()
194
 
195
  with col_random:
196
+ st.write(" ")
197
+ if st.button("随机抽取 ID (x)"):
198
+ st.session_state.selected_x_id = random.choice(st.session_state.available_x_ids)
 
199
  st.rerun()
200
 
201
+
202
+ # 6. 展示结果
203
+ if st.session_state.selected_x_id:
204
+ selected_x_id = st.session_state.selected_x_id
205
+ selected_y = st.session_state.selected_y
206
+ separator = st.session_state.separator
207
 
208
+ st.header(f"5. 展示结果:ID(x) - {selected_x_id}, 版本(y) - {selected_y}")
209
+
210
+ # --- 文件路径构造逻辑 ---
211
+
212
+ def find_full_path(folder, base_name):
213
+ """尝试查找常用扩展名,返回完整路径"""
214
+ for ext in ['.wav', '.flac', '.mp3']:
215
+ full_path = os.path.join(folder, base_name + ext)
216
+ if os.path.exists(full_path):
217
+ return full_path
218
+ return None
219
+
220
+ # ✅ 安全获取前缀
221
+ out_prefix = get_safe_prefix(st.session_state.output_prefixes)
222
+ mix_prefix = get_safe_prefix(st.session_state.mix_prefixes)
223
+ tar_prefix = get_safe_prefix(st.session_state.tar_prefixes)
224
+
225
+
226
+ # 1. Output 文件:[Output Prefix]x[SEPARATOR]y
227
+ output_base_name = f"{out_prefix}{selected_x_id}{separator}{selected_y}"
228
+ output_file_path = find_full_path(st.session_state.output_path, output_base_name)
229
+
230
+ # 2. Mixture 文件:[Mixture Prefix]x[SEPARATOR]y
231
+ mixture_base_name = f"{mix_prefix}{selected_x_id}{separator}{selected_y}"
232
+ mixture_file_path = find_full_path(st.session_state.mixture_path, mixture_base_name)
233
+
234
+ # 3. Target 文件:[Target Prefix]x (简化的命名)
235
+ target_base_name = f"{tar_prefix}{selected_x_id}"
236
+ target_file_path = find_full_path(st.session_state.target_path, target_base_name)
237
+
238
+
239
+ # --- 三列展示 ---
240
+ col_out, col_mix, col_tar = st.columns(3)
241
+
242
+ def display_audio_col(col, title, file_path, base_name):
243
+ with col:
244
+ st.subheader(title)
245
+ if file_path:
246
+ st.markdown(f"**File:** `{os.path.basename(file_path)}`")
 
 
247
 
248
  try:
249
+ # 尝试用 flac 格式播放,如果文件是 flac
250
+ ext = os.path.splitext(file_path)[1].lower()
251
+ st.audio(file_path, format=f'audio/{ext.strip(".")}' if ext else 'audio/wav')
252
  except Exception as e:
253
+ st.error(f"Playback failed for {title}: {str(e)}")
254
 
255
+ fig = generate_spectrogram(file_path, title)
256
+ if fig:
257
+ st.pyplot(fig)
258
+ plt.close(fig)
259
+ else:
260
+ st.warning(f"File not found. Attempted base name: `{base_name}`")
261
+
262
+ # 1. Output
263
+ display_audio_col(col_out, "Output (Result)", output_file_path, output_base_name)
264
+
265
+ # 2. Mixture 列
266
+ display_audio_col(col_mix, "Mixture (Original)", mixture_file_path, mixture_base_name)
267
+
268
+ # 3. Target 列
269
+ display_audio_col(col_tar, "Target (Ground Truth)", target_file_path, target_base_name)