"""Audio Standardization Pipeline for Bangla Speech Emotion Recognition. Converts all raw audio files non-destructively: - Downmixes 2-channel stereo to 1-channel mono ((L+R)/2) - Resamples varying sample rates (48kHz, 44.1kHz) to standard 16,000 Hz - Normalizes peak amplitude to -1.0 dBFS (0.891) - Caps extreme duration outliers (> 10.0 seconds) to 10.0 seconds maximum - Saves clean, standardized 16-bit PCM WAV files to dataset/processed// - Preserves the original raw dataset in dataset/BASE/ completely intact. """ import os from pathlib import Path from concurrent.futures import ProcessPoolExecutor import numpy as np import soundfile as sf import librosa from tqdm import tqdm from src.utils.config import ( RAW_DATA_DIR, PROCESSED_DATA_DIR, TARGET_SAMPLE_RATE, MAX_DURATION_SECONDS, EMOTION_CLASSES ) def process_single_audio(args): raw_path, out_path = args try: if os.path.exists(out_path): return True, raw_path, None # Read raw audio audio, sr = sf.read(raw_path, dtype='float32') # 1. Downmix stereo to mono if needed if audio.ndim > 1: audio = np.mean(audio, axis=1) # 2. Resample to 16,000 Hz if needed if sr != TARGET_SAMPLE_RATE: audio = librosa.resample(audio, orig_sr=sr, target_sr=TARGET_SAMPLE_RATE, res_type="soxr_hq") # 3. Handle extreme durations (cap at MAX_DURATION_SECONDS = 10.0s) max_samples = int(MAX_DURATION_SECONDS * TARGET_SAMPLE_RATE) if len(audio) > max_samples: # Keep the first 10 seconds of speech audio = audio[:max_samples] # 4. Normalize peak amplitude to -1.0 dBFS (~0.891) peak = np.max(np.abs(audio)) if peak > 0: target_peak = 10 ** (-1.0 / 20.0) # ~0.89125 audio = audio * (target_peak / peak) # 5. Save standardized audio as 16-bit PCM WAV os.makedirs(os.path.dirname(out_path), exist_ok=True) sf.write(out_path, audio, TARGET_SAMPLE_RATE, subtype='PCM_16') return True, raw_path, None except Exception as e: return False, raw_path, str(e) def run_audio_standardization(max_workers: int = 4): print("=" * 70) print("STARTING AUDIO STANDARDIZATION PIPELINE") print(f"Target format: Mono, {TARGET_SAMPLE_RATE} Hz, 16-bit PCM WAV") print(f"Target duration cap: {MAX_DURATION_SECONDS} seconds") print(f"Source Directory: {RAW_DATA_DIR}") print(f"Destination Directory: {PROCESSED_DATA_DIR}") print("=" * 70) tasks = [] for emotion in EMOTION_CLASSES: raw_emotion_dir = RAW_DATA_DIR / emotion processed_emotion_dir = PROCESSED_DATA_DIR / emotion processed_emotion_dir.mkdir(parents=True, exist_ok=True) for root, _, files in os.walk(raw_emotion_dir): for f in files: if f.lower().endswith(".wav"): raw_path = os.path.join(root, f) out_path = os.path.join(processed_emotion_dir, f) tasks.append((raw_path, out_path)) print(f"Total files queued for standardization: {len(tasks)}") failed = [] success_count = 0 with ProcessPoolExecutor(max_workers=max_workers) as executor: results = list(tqdm(executor.map(process_single_audio, tasks), total=len(tasks), desc="Standardizing Audio")) for success, raw_path, error_msg in results: if success: success_count += 1 else: failed.append((raw_path, error_msg)) print(f"\nStandardization Complete: {success_count}/{len(tasks)} files processed.") if failed: print(f"WARNING: {len(failed)} files failed:") for fp, err in failed[:5]: print(f" - {fp}: {err}") else: print("All 5,000 files standardized with ZERO errors.") print("=" * 70) if __name__ == "__main__": run_audio_standardization()