Update app.py
Browse files
app.py
CHANGED
@@ -32,12 +32,20 @@ class AudioAnalyzer:
|
|
32 |
"""Initialize with a temporary directory for file storage."""
|
33 |
self.temp_dir = Path(temp_dir or tempfile.mkdtemp())
|
34 |
self.temp_dir.mkdir(exist_ok=True)
|
|
|
35 |
logger.info(f"Initialized temporary directory: {self.temp_dir}")
|
36 |
|
37 |
def cleanup(self) -> None:
|
38 |
-
"""Remove temporary directory and
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
if self.temp_dir.exists():
|
40 |
-
shutil.rmtree(self.temp_dir)
|
41 |
logger.info(f"Cleaned up temporary directory: {self.temp_dir}")
|
42 |
|
43 |
def download_youtube_audio(self, video_url: str, progress=gr.Progress()) -> Tuple[Optional[str], str]:
|
@@ -73,6 +81,25 @@ class AudioAnalyzer:
|
|
73 |
logger.error(f"Unexpected error during download: {str(e)}")
|
74 |
return None, f"Error: {str(e)}"
|
75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
def extract_basic_features(self, audio_path: str, sr: int = 16000, max_duration: float = 60.0,
|
77 |
progress=gr.Progress()) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
78 |
"""Extract basic audio features and generate visualizations."""
|
@@ -100,7 +127,7 @@ class AudioAnalyzer:
|
|
100 |
'zero_crossing_rate': librosa.feature.zero_crossing_rate(y)[0]
|
101 |
}
|
102 |
|
103 |
-
progress(0.5, desc="Computing
|
104 |
hop_length = 512
|
105 |
S_mel = librosa.feature.melspectrogram(y=y, sr=sr, hop_length=hop_length, n_mels=80)
|
106 |
S_dB = librosa.power_to_db(S_mel, ref=np.max)
|
@@ -129,9 +156,9 @@ class AudioAnalyzer:
|
|
129 |
axes[1, 1].legend()
|
130 |
|
131 |
plt.tight_layout()
|
132 |
-
plot_path = self.
|
133 |
-
|
134 |
-
|
135 |
|
136 |
# Validate feature shapes
|
137 |
for key in ['mfcc', 'spectral_centroid', 'spectral_rolloff', 'zero_crossing_rate']:
|
@@ -154,7 +181,7 @@ class AudioAnalyzer:
|
|
154 |
"""
|
155 |
|
156 |
progress(1.0, desc="Analysis complete!")
|
157 |
-
return
|
158 |
|
159 |
except Exception as e:
|
160 |
logger.error(f"Error processing audio: {str(e)}")
|
@@ -177,9 +204,9 @@ class AudioAnalyzer:
|
|
177 |
y_harm = librosa.effects.harmonic(y=y, margin=8)
|
178 |
chroma_harm = librosa.feature.chroma_cqt(y=y_harm, sr=sr)
|
179 |
chroma_filter = np.minimum(chroma_harm,
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
chroma_smooth = scipy.ndimage.median_filter(chroma_filter, size=(1, 9))
|
184 |
chroma_stft = librosa.feature.chroma_stft(y=y, sr=sr)
|
185 |
chroma_cens = librosa.feature.chroma_cens(y=y, sr=sr)
|
@@ -200,13 +227,13 @@ class AudioAnalyzer:
|
|
200 |
axes[i].set_title(title)
|
201 |
|
202 |
plt.tight_layout()
|
203 |
-
plot_path = self.
|
204 |
-
|
205 |
-
|
206 |
|
207 |
summary = "Chroma feature analysis complete! Visualizations show different chroma extraction methods for harmonic analysis."
|
208 |
progress(1.0, desc="Chroma analysis complete!")
|
209 |
-
return
|
210 |
|
211 |
except Exception as e:
|
212 |
logger.error(f"Error processing chroma features: {str(e)}")
|
@@ -222,7 +249,7 @@ class AudioAnalyzer:
|
|
222 |
progress(0.1, desc="Loading audio...")
|
223 |
y, sr = librosa.load(audio_path, sr=sr)
|
224 |
|
225 |
-
progress(0.3, desc="Computing
|
226 |
hop_length = 512
|
227 |
S_mel = librosa.feature.melspectrogram(y=y, sr=sr, hop_length=hop_length, n_mels=80)
|
228 |
S_dB = librosa.power_to_db(S_mel, ref=np.max)
|
@@ -239,16 +266,16 @@ class AudioAnalyzer:
|
|
239 |
|
240 |
for i in range(num_patches_to_show):
|
241 |
librosa.display.specshow(patches[..., i], y_axis='mel', x_axis='time',
|
242 |
-
|
243 |
axes[i].set_title(f'Patch {i+1}')
|
244 |
|
245 |
for i in range(num_patches_to_show, len(axes)):
|
246 |
axes[i].set_visible(False)
|
247 |
|
248 |
plt.tight_layout()
|
249 |
-
plot_path = self.
|
250 |
-
|
251 |
-
|
252 |
|
253 |
summary = f"""
|
254 |
**Patch Generation Summary:**
|
@@ -260,7 +287,7 @@ class AudioAnalyzer:
|
|
260 |
"""
|
261 |
|
262 |
progress(1.0, desc="Patch generation complete!")
|
263 |
-
return
|
264 |
|
265 |
except Exception as e:
|
266 |
logger.error(f"Error generating patches: {str(e)}")
|
@@ -334,11 +361,11 @@ def create_gradio_interface() -> gr.Blocks:
|
|
334 |
|
335 |
gr.Markdown("""
|
336 |
### ℹ️ Usage Tips
|
337 |
-
- **Processing Limits**: 60s for basic features, 30s for chroma features
|
338 |
- **YouTube Downloads**: Ensure URLs are valid and respect YouTube's terms of service
|
339 |
-
- **Visualizations**: High-quality, suitable for research and
|
340 |
-
- **Storage**: Temporary files are
|
341 |
-
- **Support**: For issues, check the [GitHub repository](https://github.com/your-repo)
|
342 |
""")
|
343 |
|
344 |
# Event handlers
|
|
|
32 |
"""Initialize with a temporary directory for file storage."""
|
33 |
self.temp_dir = Path(temp_dir or tempfile.mkdtemp())
|
34 |
self.temp_dir.mkdir(exist_ok=True)
|
35 |
+
self.plot_files = [] # Track plot files for cleanup
|
36 |
logger.info(f"Initialized temporary directory: {self.temp_dir}")
|
37 |
|
38 |
def cleanup(self) -> None:
|
39 |
+
"""Remove temporary directory and plot files."""
|
40 |
+
for plot_file in self.plot_files:
|
41 |
+
if Path(plot_file).exists():
|
42 |
+
try:
|
43 |
+
Path(plot_file).unlink()
|
44 |
+
logger.info(f"Removed plot file: {plot_file}")
|
45 |
+
except Exception as e:
|
46 |
+
logger.warning(f"Failed to remove plot file {plot_file}: {str(e)}")
|
47 |
if self.temp_dir.exists():
|
48 |
+
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
49 |
logger.info(f"Cleaned up temporary directory: {self.temp_dir}")
|
50 |
|
51 |
def download_youtube_audio(self, video_url: str, progress=gr.Progress()) -> Tuple[Optional[str], str]:
|
|
|
81 |
logger.error(f"Unexpected error during download: {str(e)}")
|
82 |
return None, f"Error: {str(e)}"
|
83 |
|
84 |
+
def save_plot(self, fig, filename: str) -> Optional[str]:
|
85 |
+
"""Save matplotlib figure to a temporary file and verify existence."""
|
86 |
+
try:
|
87 |
+
# Use NamedTemporaryFile to ensure persistence
|
88 |
+
with tempfile.NamedTemporaryFile(suffix='.png', delete=False, dir=self.temp_dir) as tmp_file:
|
89 |
+
plot_path = tmp_file.name
|
90 |
+
fig.savefig(plot_path, dpi=300, bbox_inches='tight', format='png')
|
91 |
+
plt.close(fig)
|
92 |
+
if not Path(plot_path).exists():
|
93 |
+
logger.error(f"Plot file not created: {plot_path}")
|
94 |
+
return None
|
95 |
+
self.plot_files.append(plot_path)
|
96 |
+
logger.info(f"Saved plot: {plot_path}")
|
97 |
+
return str(plot_path)
|
98 |
+
except Exception as e:
|
99 |
+
logger.error(f"Error saving plot {filename}: {str(e)}")
|
100 |
+
plt.close(fig)
|
101 |
+
return None
|
102 |
+
|
103 |
def extract_basic_features(self, audio_path: str, sr: int = 16000, max_duration: float = 60.0,
|
104 |
progress=gr.Progress()) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
105 |
"""Extract basic audio features and generate visualizations."""
|
|
|
127 |
'zero_crossing_rate': librosa.feature.zero_crossing_rate(y)[0]
|
128 |
}
|
129 |
|
130 |
+
progress(0.5, desc="Computing mel spectrogram...")
|
131 |
hop_length = 512
|
132 |
S_mel = librosa.feature.melspectrogram(y=y, sr=sr, hop_length=hop_length, n_mels=80)
|
133 |
S_dB = librosa.power_to_db(S_mel, ref=np.max)
|
|
|
156 |
axes[1, 1].legend()
|
157 |
|
158 |
plt.tight_layout()
|
159 |
+
plot_path = self.save_plot(fig, "basic_features")
|
160 |
+
if not plot_path:
|
161 |
+
return None, None, "Failed to save feature visualizations"
|
162 |
|
163 |
# Validate feature shapes
|
164 |
for key in ['mfcc', 'spectral_centroid', 'spectral_rolloff', 'zero_crossing_rate']:
|
|
|
181 |
"""
|
182 |
|
183 |
progress(1.0, desc="Analysis complete!")
|
184 |
+
return plot_path, summary, None
|
185 |
|
186 |
except Exception as e:
|
187 |
logger.error(f"Error processing audio: {str(e)}")
|
|
|
204 |
y_harm = librosa.effects.harmonic(y=y, margin=8)
|
205 |
chroma_harm = librosa.feature.chroma_cqt(y=y_harm, sr=sr)
|
206 |
chroma_filter = np.minimum(chroma_harm,
|
207 |
+
librosa.decompose.nn_filter(chroma_harm,
|
208 |
+
aggregate=np.median,
|
209 |
+
metric='cosine'))
|
210 |
chroma_smooth = scipy.ndimage.median_filter(chroma_filter, size=(1, 9))
|
211 |
chroma_stft = librosa.feature.chroma_stft(y=y, sr=sr)
|
212 |
chroma_cens = librosa.feature.chroma_cens(y=y, sr=sr)
|
|
|
227 |
axes[i].set_title(title)
|
228 |
|
229 |
plt.tight_layout()
|
230 |
+
plot_path = self.save_plot(fig, "chroma_features")
|
231 |
+
if not plot_path:
|
232 |
+
return None, None, "Failed to save chroma visualizations"
|
233 |
|
234 |
summary = "Chroma feature analysis complete! Visualizations show different chroma extraction methods for harmonic analysis."
|
235 |
progress(1.0, desc="Chroma analysis complete!")
|
236 |
+
return plot_path, summary, None
|
237 |
|
238 |
except Exception as e:
|
239 |
logger.error(f"Error processing chroma features: {str(e)}")
|
|
|
249 |
progress(0.1, desc="Loading audio...")
|
250 |
y, sr = librosa.load(audio_path, sr=sr)
|
251 |
|
252 |
+
progress(0.3, desc="Computing mel spectrogram...")
|
253 |
hop_length = 512
|
254 |
S_mel = librosa.feature.melspectrogram(y=y, sr=sr, hop_length=hop_length, n_mels=80)
|
255 |
S_dB = librosa.power_to_db(S_mel, ref=np.max)
|
|
|
266 |
|
267 |
for i in range(num_patches_to_show):
|
268 |
librosa.display.specshow(patches[..., i], y_axis='mel', x_axis='time',
|
269 |
+
ax=axes[i], sr=sr, hop_length=hop_length)
|
270 |
axes[i].set_title(f'Patch {i+1}')
|
271 |
|
272 |
for i in range(num_patches_to_show, len(axes)):
|
273 |
axes[i].set_visible(False)
|
274 |
|
275 |
plt.tight_layout()
|
276 |
+
plot_path = self.save_plot(fig, "patches")
|
277 |
+
if not plot_path:
|
278 |
+
return None, None, "Failed to save patch visualizations"
|
279 |
|
280 |
summary = f"""
|
281 |
**Patch Generation Summary:**
|
|
|
287 |
"""
|
288 |
|
289 |
progress(1.0, desc="Patch generation complete!")
|
290 |
+
return plot_path, summary, None
|
291 |
|
292 |
except Exception as e:
|
293 |
logger.error(f"Error generating patches: {str(e)}")
|
|
|
361 |
|
362 |
gr.Markdown("""
|
363 |
### ℹ️ Usage Tips
|
364 |
+
- **Processing Limits**: 60s for basic features, 30s for chroma features for fast response
|
365 |
- **YouTube Downloads**: Ensure URLs are valid and respect YouTube's terms of service
|
366 |
+
- **Visualizations**: High-quality, suitable for research and education
|
367 |
+
- **Storage**: Temporary files are cleaned up when the interface closes
|
368 |
+
- **Support**: For issues, check the [GitHub repository](https://github.com/your-repo)
|
369 |
""")
|
370 |
|
371 |
# Event handlers
|