bangla-ser-api / src /preprocessing /standardize.py
SAJIDIUBAT's picture
Upload folder using huggingface_hub
15ff271 verified
Raw History Blame Contribute Delete
3.97 kB
"""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/<emotion>/
- 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()