Sarthak-gupta-cpp
changes
4dc7304
Raw History Blame Contribute Delete
4.33 kB
import os
import pandas as pd
import librosa
import requests
import io
import soundfile as sf
import numpy as np
from tqdm import tqdm
class AudioPreprocessor:
def __init__(self, target_sr=16000, chunk_duration=3, stride_duration=1):
self.target_sr = target_sr
self.chunk_duration = chunk_duration
self.stride_duration = stride_duration
self.samples_per_chunk = int(target_sr * chunk_duration)
self.samples_per_stride = int(target_sr * stride_duration)
def download_audio(self, url):
"""Downloads audio from URL into memory."""
try:
response = requests.get(url, timeout=15)
response.raise_for_status()
return io.BytesIO(response.content)
except Exception as e:
print(f"Failed to download {url}: {e}")
return None
def process_and_split(self, audio_bytes, trim_silence=True, peak_norm=True):
"""Converts to mono, resamples, optionally trims silence, and splits using a rolling window."""
try:
audio_bytes.seek(0)
y, sr = librosa.load(audio_bytes, sr=self.target_sr, mono=True)
if len(y) == 0:
print("Warning: Downloaded audio is empty.")
return []
# Trim leading/trailing silence (gentle threshold to avoid cutting speech)
if trim_silence and len(y) > self.samples_per_chunk:
y_trim, _ = librosa.effects.trim(
y, top_db=25, frame_length=2048, hop_length=512
)
if len(y_trim) >= self.samples_per_chunk:
y = y_trim
chunks = []
# OVERLAPPING WINDOW: Step forward by samples_per_stride (1 second) instead of full chunk
for i in range(0, len(y), self.samples_per_stride):
chunk = y[i : i + self.samples_per_chunk].copy()
# Keep chunks that are at least 2.5 seconds long.
# (Shorter trailing pieces are safely dropped because the overlap caught them earlier)
min_required_samples = int(self.target_sr * 2.5)
if len(chunk) < min_required_samples:
continue
# Peak-normalize chunk so level is consistent across chunks
if peak_norm:
peak = np.abs(chunk).max()
if peak > 1e-8:
chunk = chunk / peak
chunks.append(chunk)
return chunks
except Exception as e:
print(f"Error processing audio: {e}")
return []
def run_pipeline(self, csv_path, output_dir):
"""Main execution loop."""
df = pd.read_csv(csv_path)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
processed_data = []
print(f"Starting processing of {len(df)} files...")
for _, row in tqdm(df.iterrows(), total=len(df)):
audio_io = self.download_audio(row["audio_url"])
if audio_io:
chunks = self.process_and_split(audio_io)
for idx, chunk in enumerate(chunks):
chunk_filename = f"{row['dp_id']}_chunk{idx}.wav"
chunk_path = os.path.join(output_dir, chunk_filename)
# Save chunk as .wav for the training pipeline
sf.write(chunk_path, chunk, self.target_sr)
processed_data.append(
{
"chunk_path": chunk_path,
"original_id": row["dp_id"],
"label": row["nativity_status"],
"language": row["language"],
}
)
# Save a new manifest for the ML model
manifest_df = pd.DataFrame(processed_data)
manifest_df.to_csv("manifests/processed_manifest.csv", index=False)
print(f"Done! Saved {len(manifest_df)} chunks to {output_dir}")
if __name__ == "__main__":
# Usage
preprocessor = AudioPreprocessor(chunk_duration=3, stride_duration=1)
# Ensure your csv has columns: 'audio_url', 'dp_id', 'nativity_status', 'language'
preprocessor.run_pipeline("manifests/input_data.csv", "data/processed_audio_chunks")