File size: 2,918 Bytes
c6b0fdb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
"""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)