import librosa # Monkeypatch for pyloudnorm which uses the deprecated scipy.signal.hann import scipy.signal import scipy.signal.windows if not hasattr(scipy.signal, 'hann'): scipy.signal.hann = scipy.signal.windows.hann import pyloudnorm as pyln import numpy as np def analyze_audio(file_path: str) -> dict: """Analyzes an audio file and returns metadata.""" try: # Load audio (mono, original sample rate to preserve frequency content) y, sr = librosa.load(file_path, sr=None, mono=True) # BPM tempo, _ = librosa.beat.beat_track(y=y, sr=sr) if isinstance(tempo, np.ndarray): bpm = float(tempo[0]) else: bpm = float(tempo) # Key extraction using chroma (simplified root note extraction) chroma = librosa.feature.chroma_cqt(y=y, sr=sr) key_idx = np.argmax(np.sum(chroma, axis=1)) keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] key = keys[key_idx] # Energy (RMS) rms = librosa.feature.rms(y=y) energy = float(np.mean(rms)) # Spectral Centroid (Brightness) cent = librosa.feature.spectral_centroid(y=y, sr=sr) spectral_centroid = float(np.mean(cent)) # Onset Density (Danceability/Rhythm) onset_env = librosa.onset.onset_strength(y=y, sr=sr) onsets = librosa.onset.onset_detect(onset_envelope=onset_env, sr=sr) duration_sec = len(y) / sr onset_density = len(onsets) / duration_sec if duration_sec > 0 else 0 # LUFS meter = pyln.Meter(sr) # create BS.1770 meter lufs = meter.integrated_loudness(y) return { "bpm": bpm, "key": key, "energy": energy, "spectral_centroid": spectral_centroid, "onset_density": float(onset_density), "lufs": float(lufs) } except Exception as e: print(f"Error analyzing {file_path}: {e}") return None