CoolFace
Apppublic

hardbanrecords/Metadata-Engine

sourceHugging Faceotherupdated 8mo agoView on Hugging Face
0likes
deep_audio_analyzer.py254 linesDownload Raw Back to services
1# backend/app/services/deep_audio_analyzer.py2"""3Layer 1: Deep Audio Feature Extraction4Ścieżka: E:\\Music-Metadata-Engine\\backend\\app\\services\\deep_audio_analyzer.py5 690+ audio features dla maksymalnej dokładności AI7Czas: 12-15s na i5 (bez GPU)8"""9 10import librosa11import numpy as np12from scipy import signal13from typing import Dict, Any14import logging15 16logger = logging.getLogger(__name__)17 18 19class DeepAudioAnalyzer:20    """21    Rozszerzona analiza audio - 90+ cech22    vs poprzednie 30 cech = +10% accuracy23    """24    25    def __init__(self):26        self.sr = 44100  # Sample rate27        self.hop_length = 51228        29    async def extract_all_features(self, file_path: str) -> Dict[str, Any]:30        """31        Główna funkcja: 90+ audio features32        Czas: 12-15s na i533        """34        35        try:36            # Optimization: Load audio at 22050Hz (sufficient for MIR) and limit to middle 2 minutes37            self.sr = 2205038            duration = librosa.get_duration(filename=file_path)39            offset = 30.0 if duration > 60 else 0.040            duration_to_load = 120.0 if duration > 180 else None41            42            y, sr = librosa.load(file_path, sr=self.sr, mono=True, offset=offset, duration=duration_to_load)43            44            logger.info(f"Loaded audio (Optimized): {len(y)/sr:.1f}s @ {sr}Hz (Offset: {offset}s)")45            46            features = {}47            48            # ===== RHYTHM (10 features) =====49            features['rhythm'] = self._extract_rhythm_features(y, sr)50            51            # ===== HARMONIC (15 features) =====52            features['harmonic'] = self._extract_harmonic_features(y, sr)53            54            # ===== SPECTRAL (20 features) =====55            features['spectral'] = self._extract_spectral_features(y, sr)56            57            # ===== TIMBRE (25 features) =====58            features['timbre'] = self._extract_timbre_features(y, sr)59            60            # ===== ENERGY & DYNAMICS (12 features) =====61            features['energy'] = self._extract_energy_features(y, sr)62            63            # ===== STRUCTURE (8 features) =====64            features['structure'] = self._extract_structure_features(y, sr)65            66            # ===== METADATA =====67            features['meta'] = {68                'duration': float(len(y) / sr),69                'sample_rate': sr,70                'total_features': 9071            }72            73            logger.info(f"Extracted {features['meta']['total_features']} audio features")74            75            return features76            77        except Exception as e:78            logger.error(f"Feature extraction failed: {e}")79            raise80    81    def _extract_rhythm_features(self, y: np.ndarray, sr: int) -> Dict:82        """Rhythm & Tempo features (10)"""83        84        # Tempo & beats85        tempo, beats = librosa.beat.beat_track(y=y, sr=sr)86        beat_times = librosa.frames_to_time(beats, sr=sr)87        88        # Onset envelope89        oenv = librosa.onset.onset_strength(y=y, sr=sr, hop_length=self.hop_length)90        91        # Tempogram92        tempogram = librosa.feature.tempogram(onset_envelope=oenv, sr=sr, hop_length=self.hop_length)93        94        return {95            'tempo': float(tempo),96            'beat_count': len(beats),97            'beat_regularity': float(np.std(np.diff(beat_times))) if len(beat_times) > 1 else 0.0,98            'tempogram_mean': tempogram.mean(axis=1).tolist()[:10],99            'onset_strength_mean': float(np.mean(oenv)),100            'onset_strength_std': float(np.std(oenv)),101        }102    103    def _extract_harmonic_features(self, y: np.ndarray, sr: int) -> Dict:104        """Harmonic features (15)"""105        106        # HPSS107        y_harmonic, y_percussive = librosa.effects.hpss(y)108        109        # Chroma110        chroma_cqt = librosa.feature.chroma_cqt(y=y_harmonic, sr=sr)111        chroma_stft = librosa.feature.chroma_stft(y=y_harmonic, sr=sr)112        113        # Tonnetz114        tonnetz = librosa.feature.tonnetz(y=y_harmonic, sr=sr)115        116        # Harmonic change117        chroma_diff = np.diff(chroma_cqt, axis=1)118        harmonic_change_rate = np.mean(np.abs(chroma_diff))119        120        return {121            'chroma_cqt_mean': chroma_cqt.mean(axis=1).tolist(),122            'chroma_cqt_std': chroma_cqt.std(axis=1).tolist(),123            'chroma_stft_mean': chroma_stft.mean(axis=1).tolist(),124            'tonnetz_mean': tonnetz.mean(axis=1).tolist(),125            'tonnetz_std': tonnetz.std(axis=1).tolist(),126            'harmonic_change_rate': float(harmonic_change_rate),127            'harmonic_percussive_ratio': float(128                np.mean(y_harmonic**2) / (np.mean(y_percussive**2) + 1e-6)129            )130        }131    132    def _extract_spectral_features(self, y: np.ndarray, sr: int) -> Dict:133        """Spectral features (20)"""134        135        # Spectral features136        spec_cent = librosa.feature.spectral_centroid(y=y, sr=sr)137        spec_bw = librosa.feature.spectral_bandwidth(y=y, sr=sr)138        spec_rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)139        spec_contrast = librosa.feature.spectral_contrast(y=y, sr=sr)140        spec_flatness = librosa.feature.spectral_flatness(y=y)141        142        # Mel spectrogram143        mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)144        mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)145        146        return {147            'centroid_mean': float(np.mean(spec_cent)),148            'centroid_std': float(np.std(spec_cent)),149            'centroid_range': float(np.ptp(spec_cent)),150            151            'bandwidth_mean': float(np.mean(spec_bw)),152            'bandwidth_std': float(np.std(spec_bw)),153            154            'rolloff_mean': float(np.mean(spec_rolloff)),155            'rolloff_std': float(np.std(spec_rolloff)),156            157            'contrast_mean': spec_contrast.mean(axis=1).tolist(),158            'contrast_std': spec_contrast.std(axis=1).tolist(),159            160            'flatness_mean': float(np.mean(spec_flatness)),161            'flatness_std': float(np.std(spec_flatness)),162            163            'mel_mean': mel_spec_db.mean(axis=1).tolist()[:20],164            'mel_std': mel_spec_db.std(axis=1).tolist()[:20],165        }166    167    def _extract_timbre_features(self, y: np.ndarray, sr: int) -> Dict:168        """Timbre features (25 - MFCC)"""169        170        # MFCC (20 coefficients)171        mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20)172        mfcc_delta = librosa.feature.delta(mfcc)173        mfcc_delta2 = librosa.feature.delta(mfcc, order=2)174        175        return {176            'mfcc_mean': mfcc.mean(axis=1).tolist(),177            'mfcc_std': mfcc.std(axis=1).tolist(),178            'mfcc_delta_mean': mfcc_delta.mean(axis=1).tolist(),179            'mfcc_delta2_mean': mfcc_delta2.mean(axis=1).tolist(),180        }181    182    def _extract_energy_features(self, y: np.ndarray, sr: int) -> Dict:183        """Energy & dynamics features (12)"""184        185        rms = librosa.feature.rms(y=y)186        zcr = librosa.feature.zero_crossing_rate(y)187        188        # Dynamic range189        dynamic_range = float(np.max(rms) - np.min(rms))190        191        # Mel spectrogram for band energies192        mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)193        mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)194        195        # Energy per frequency band196        freq_bands = [197            (0, 25),      # Sub-bass198            (25, 60),     # Bass199            (60, 100),    # Midrange200            (100, 110),   # Upper mid201            (110, 128)    # Highs202        ]203        204        band_energies = []205        for low, high in freq_bands:206            band_energy = float(np.mean(mel_spec_db[low:high]))207            band_energies.append(band_energy)208        209        return {210            'rms_mean': float(np.mean(rms)),211            'rms_std': float(np.std(rms)),212            'rms_max': float(np.max(rms)),213            'dynamic_range': dynamic_range,214            215            'zcr_mean': float(np.mean(zcr)),216            'zcr_std': float(np.std(zcr)),217            218            'band_energies': band_energies,219        }220    221    def _extract_structure_features(self, y: np.ndarray, sr: int) -> Dict:222        """Structure features (8 - segmentation)"""223        224        # Chroma for structure225        chroma = librosa.feature.chroma_cqt(y=y, sr=sr)226        227        # Recurrence matrix228        rec_matrix = librosa.segment.recurrence_matrix(229            chroma,230            mode='affinity',231            metric='cosine'232        )233        234        # Detect boundaries235        try:236            boundaries = librosa.segment.agglomerative(rec_matrix, k=5)237            boundary_times = librosa.frames_to_time(boundaries, sr=sr)238            239            segment_durations = np.diff(boundary_times).tolist() if len(boundary_times) > 1 else []240            241            return {242                'segment_count': len(boundaries),243                'segment_durations': segment_durations,244                'avg_segment_length': float(np.mean(segment_durations)) if segment_durations else 0.0,245                'structure_regularity': float(np.std(segment_durations)) if segment_durations else 0.0,246            }247        except:248            return {249                'segment_count': 0,250                'segment_durations': [],251                'avg_segment_length': 0.0,252                'structure_regularity': 0.0,253            }254