60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
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
|