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
|