""" Audio Processing Module (CNN Version) - Audio conversion (mp3 / wav) - MFCC extraction for CNN """ import io import tempfile import os from typing import Tuple import numpy as np import librosa from pydub import AudioSegment class AudioProcessor: # cnn parameters SAMPLE_RATE = 16000 N_MFCC = 40 N_FFT = 1024 HOP_LENGTH = 160 # 10 ms hop MAX_LEN = 300 # time frames (~3 sec) MIN_AUDIO_SEC = 0.5 # Audio Conversion def convert_audio_to_samples(self, audio_bytes: bytes) -> Tuple[np.ndarray, int]: temp_path = None try: is_wav = audio_bytes[:4] in [b"RIFF", b"riff"] suffix = ".wav" if is_wav else ".mp3" with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as f: f.write(audio_bytes) temp_path = f.name # Try librosa try: audio, sr = librosa.load( temp_path, sr=self.SAMPLE_RATE, mono=True ) return audio.astype(np.float32), sr except Exception: pass # Try soundfile try: import soundfile as sf audio, sr = sf.read(temp_path) if audio.ndim > 1: audio = audio.mean(axis=1) if sr != self.SAMPLE_RATE: audio = librosa.resample(audio, orig_sr=sr, target_sr=self.SAMPLE_RATE) return audio.astype(np.float32), self.SAMPLE_RATE except Exception: pass # Fallback: pydub (needs ffmpeg) audio = AudioSegment.from_file(temp_path) buf = io.BytesIO() audio.export(buf, format="wav") buf.seek(0) audio, sr = librosa.load(buf, sr=self.SAMPLE_RATE, mono=True) return audio.astype(np.float32), sr except Exception as e: raise ValueError(f"Audio conversion failed: {e}") finally: if temp_path and os.path.exists(temp_path): os.remove(temp_path) # CNN MFCC def extract_mfcc_cnn(self, audio: np.ndarray, sr: int) -> np.ndarray: min_len = int(self.MIN_AUDIO_SEC * sr) if len(audio) < min_len: raise ValueError("Audio too short for detection") mfcc = librosa.feature.mfcc( y=audio, sr=sr, n_mfcc=self.N_MFCC, n_fft=self.N_FFT, hop_length=self.HOP_LENGTH ) # NORMALIZATION (MANDATORY) mfcc = (mfcc - np.mean(mfcc)) / (np.std(mfcc) + 1e-6) # Pad / trim time axis if mfcc.shape[1] < self.MAX_LEN: pad = self.MAX_LEN - mfcc.shape[1] mfcc = np.pad(mfcc, ((0, 0), (0, pad)), mode="constant") else: mfcc = mfcc[:, :self.MAX_LEN] # Shape → (1, 40, T) return mfcc[np.newaxis, :, :].astype(np.float32) def process_audio_file(self, file_bytes: bytes, filename: str) -> Tuple[dict, np.ndarray, int]: """Process raw audio file bytes (mp3/wav) directly — no base64 needed.""" audio, sr = self.convert_audio_to_samples(file_bytes) features = { 'pitch_std': 50.0, 'pitch_range': 200.0, 'spectral_centroid_std': 500.0, 'rms_std': 0.05, 'zcr_std': 0.05, 'voiced_ratio': 0.5, 'mfcc_0_std': 100.0, 'delta_mfcc_0_std': 1.0, 'hf_energy_ratio': 0.1, } return features, audio, sr # Singleton (use everywhere) audio_processor = AudioProcessor()