"""Chargement, validation et prétraitement audio partagé par toutes les interfaces.""" from __future__ import annotations from pathlib import Path # Rend les chemins fiables sous Windows, Linux et macOS. # librosa décode WAV/MP3 et réalise le rééchantillonnage demandé par le modèle. import librosa # NumPy représente le signal audio sous forme de tableau de nombres réels. import numpy as np from src.errors import AudioValidationError # Wav2Vec2 français attend précisément 16 000 valeurs (échantillons) par seconde. TARGET_SAMPLE_RATE = 16_000 # La limite de l'énoncé protège l'API contre un traitement trop long ou trop coûteux. MAX_DURATION_SECONDS = 5 * 60 # Seuls les deux formats explicitement autorisés par l'énoncé sont acceptés. SUPPORTED_EXTENSIONS = {".wav", ".mp3"} def load_and_preprocess_audio(audio_path: str | Path) -> np.ndarray: """Retourne un signal mono normalisé à 16 kHz prêt pour Wav2Vec2. Wav2Vec2 a été entraîné sur des signaux mono à 16 kHz : imposer ce format évite les incohérences entre les fichiers WAV/MP3 envoyés par les clients. """ # Convertir la chaîne en Path permet ensuite de vérifier extension, existence et taille. path = Path(audio_path) if path.suffix.lower() not in SUPPORTED_EXTENSIONS: raise AudioValidationError("Format non supporté : utilisez un fichier .wav ou .mp3.") # Un fichier vide est rejeté avant de demander à librosa de le décoder. if not path.is_file() or path.stat().st_size == 0: raise AudioValidationError("Le fichier audio est introuvable ou vide.") try: # mono=True mélange les canaux ; sr=16000 effectue le rééchantillonnage. samples, sample_rate = librosa.load(path, sr=TARGET_SAMPLE_RATE, mono=True) except Exception as exc: raise AudioValidationError("Impossible de décoder ce fichier audio.") from exc # Après le chargement, la durée est le nombre d'échantillons divisé par la fréquence. duration = len(samples) / sample_rate if duration == 0: raise AudioValidationError("Le fichier audio ne contient aucun échantillon.") if duration > MAX_DURATION_SECONDS: raise AudioValidationError("La durée maximale autorisée est de 5 minutes.") # Le pic mesure l'amplitude la plus forte ; il sert à détecter le silence puis normaliser. peak = float(np.max(np.abs(samples))) # Un seuil non nul rejette les enregistrements silencieux/bruités sans écraser # leur amplitude lors de la normalisation. if peak < 1e-4: raise AudioValidationError("Audio silencieux : aucune voix exploitable n'a été détectée.") # La normalisation ramène le pic à 1.0 sans modifier le contenu relatif du signal. # float32 est le type attendu par PyTorch et réduit l'occupation mémoire par rapport à float64. return (samples / peak).astype(np.float32)