Encoder de l’audio WAV en MP3 en Python avec Pydub et FFmpeg
Convertissez un enregistrement ou un lot de fichiers WAV en MP3 avec un script Python qui indique quels fichiers ont été convertis et renvoie un code de sortie d’échec si une conversion échoue. L’exemple ci-dessous utilise Pydub et FFmpeg, ne modifie pas vos fichiers sources et refuse d’écraser les fichiers de sortie existants.
Ce tutoriel utilise Bash sous Linux et des enregistrements WAV locaux en mono ou en stéréo. Chaque fichier de sortie est un MP3 à 192 kbps et 44,1 kHz, avec le même nombre de canaux que sa source. Pydub conserve l’audio décodé en mémoire : traitez donc les fichiers l’un après l’autre et utilisez des enregistrements qui tiennent en RAM. Il s’agit d’un script de conversion local, et non d’un service de téléversement ou d’un encodeur de streaming.
Préparer votre environnement
L’exemple a été testé avec Python 3.14.7, Pydub 0.25.1, audioop-lts 0.2.2 et FFmpeg 9.0.1 sous Linux. Les commandes utilisent explicitement un environnement virtuel : vous n’avez donc pas besoin de l’activer.
Installer FFmpeg
Installez Python avec la prise en charge des environnements virtuels, FFmpeg et ffprobe. Sous Ubuntu :
sudo apt-get update &&
sudo apt-get install -y python3 python3-venv ffmpeg
Les autres distributions Linux fournissent leurs propres paquets ; la
page de téléchargement de FFmpeg renvoie vers les paquets des
distributions. Vérifiez que votre version de FFmpeg inclut
l’encodeur libmp3lame :
ffmpeg -version &&
ffprobe -version &&
ffmpeg -hide_banner -encoders
Recherchez libmp3lame dans la liste des encodeurs. L’installation de Pydub n’installe pas FFmpeg.
Installer Pydub
Créez un nouveau répertoire depuis son répertoire parent. Si audio-encoding existe déjà, choisissez
un autre nom ; la chaîne && interrompt la configuration avant toute installation dans
un projet existant.
mkdir audio-encoding &&
cd audio-encoding &&
python3 -m venv .venv &&
.venv/bin/python -m pip install 'pydub==0.25.1' 'audioop-lts==0.2.2; python_version >= "3.13"'
Restez dans audio-encoding avec ce shell pour les commandes suivantes. Python
a supprimé audioop dans la version 3.13, mais Pydub 0.25.1
l’importe toujours. La dépendance conditionnelle installe
audioop-lts sous Python 3.13 et versions ultérieures ;
Python 3.12 utilise le module de sa bibliothèque standard. Aucun paquet de lecture audio n’est
nécessaire.
Enregistrer le convertisseur
Enregistrez le script complet ci-dessous sous le nom encode_audio.py. WAV est un conteneur : ses
échantillons peuvent être des entiers ou des valeurs à virgule flottante. Le script demande d’abord
à FFmpeg de décoder en PCM signé 16 bits, puis transmet cette représentation connue à Pydub. Cela
évite de dépendre du chemin rapide WAV de Pydub 0.25.1,
qui ne distingue pas tous les types d’échantillons des fichiers WAV extensibles. Les profondeurs de
bits supérieures sont réduites à 16 bits pour ce flux de travail MP3 ; conservez le WAV source
comme fichier maître.
Le MP3 est encodé dans un répertoire temporaire situé à côté de sa destination. Seul un encodage terminé est publié, au moyen d’un lien physique qui échoue si la destination existe déjà. Utilisez un système de fichiers local qui prend en charge les liens physiques. Les fichiers issus des conversions réussies sont conservés lorsqu’une autre entrée échoue.
import argparse
import os
from pathlib import Path
import shutil
import subprocess
import sys
from tempfile import TemporaryDirectory
from pydub import AudioSegment
from pydub.exceptions import CouldntDecodeError, CouldntEncodeError
def convert_one(source, output_dir, ffmpeg, fade_ms):
if source.suffix.lower() != '.wav' or not source.is_file():
raise ValueError('Expected an existing WAV file')
destination = output_dir / (source.stem + '.mp3')
if os.path.lexists(destination):
raise FileExistsError(f'Output already exists: {destination}')
with TemporaryDirectory(prefix='.encode-', dir=output_dir) as work:
decoded = Path(work) / 'decoded.wav'
encoded = Path(work) / 'encoded.mp3'
subprocess.run(
[ffmpeg, '-nostdin', '-hide_banner', '-v', 'error', '-xerror',
'-f', 'wav', '-i', str(source.resolve(strict=True)),
'-map', '0:a:0', '-c:a', 'pcm_s16le', '-ar', '44100', str(decoded)],
check=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
)
audio = AudioSegment.from_wav(decoded)
if audio.channels not in (1, 2) or len(audio) == 0:
raise ValueError('Expected nonempty mono or stereo audio')
if fade_ms > len(audio):
raise ValueError('The fade must not exceed the recording length')
if fade_ms:
audio = audio.fade_in(fade_ms).fade_out(fade_ms)
audio.export(encoded, format='mp3', codec='libmp3lame', bitrate='192k').close()
os.link(encoded, destination)
return destination
def main():
parser = argparse.ArgumentParser(description='Convert WAV recordings to MP3')
parser.add_argument('--out-dir', type=Path, required=True)
parser.add_argument('--fade-ms', type=int, default=0)
parser.add_argument('inputs', type=Path, nargs='+')
args = parser.parse_args()
if args.fade_ms < 0:
parser.error('--fade-ms must be zero or greater')
ffmpeg = shutil.which('ffmpeg')
if ffmpeg is None:
parser.error('ffmpeg is missing from PATH')
AudioSegment.converter = ffmpeg
try:
output_dir = args.out_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
except OSError as error:
parser.exit(2, f'Cannot prepare output directory: {error}\n')
failed = 0
for source in args.inputs:
try:
destination = convert_one(source, output_dir, ffmpeg, args.fade_ms)
except (OSError, ValueError, subprocess.CalledProcessError,
CouldntDecodeError, CouldntEncodeError) as error:
failed += 1
print(f'FAILED {source}: {error}', file=sys.stderr)
else:
print(f'OK {source} -> {destination}')
print(f'{len(args.inputs) - failed} converted, {failed} failed')
return 1 if failed else 0
if __name__ == '__main__':
sys.exit(main())
L’option -xerror de FFmpeg fait échouer la conversion en
cas d’erreur de décodage signalée. Elle permet de détecter une entrée endommagée que FFmpeg
pourrait sinon récupérer en partie. Ce n’est pas une preuve que chaque enregistrement est complet :
écoutez les fichiers de sortie importants et comparez leur durée à la durée attendue de
l’enregistrement.
Convertir un enregistrement
Utilisez votre propre input.wav, ou créez un son de test de trois secondes. L’option -n
refuse d’écraser un fichier existant :
ffmpeg -nostdin -hide_banner -v error -n -f lavfi \
-i 'sine=frequency=440:sample_rate=44100:duration=3' -c:a pcm_s16le input.wav
Exécutez le convertisseur depuis audio-encoding :
.venv/bin/python encode_audio.py --out-dir mp3 -- input.wav
Il affiche une ligne OK et 1 converted, 0 failed, puis se termine avec le code de sortie 0. Le
fichier est mp3/input.mp3. Si vous relancez la commande, elle signale un échec dû à un fichier de
sortie existant, se termine avec le code de sortie 1 et préserve les octets du premier
MP3. Choisissez un autre répertoire de sortie pour un nouvel encodage.
Traiter des fichiers audio par lots
Créez un répertoire wav_files et placez-y vos enregistrements WAV, puis transmettez-les au
même script :
.venv/bin/python encode_audio.py --out-dir mp3-batch -- wav_files/*.wav
Bash développe ce motif glob pour les fichiers situés directement dans le répertoire, sans
récursivité. Il correspond aux noms .wav en minuscules ; transmettez explicitement les
fichiers .WAV en majuscules. S’il n’y a aucune correspondance, le motif littéral parvient au
script et produit un échec pour entrée manquante. Mettez entre guillemets les chemins individuels
contenant des espaces. Le séparateur -- permet aussi d’utiliser des noms d’entrée
commençant par un tiret.
Chaque entrée produit une ligne OK ou FAILED. Un lot mixte conserve les MP3 valides,
continue après un fichier corrompu et se termine avec le code de sortie 1. Deux entrées
telles que a/interview.wav et b/interview.wav correspondent au même nom de sortie : la première
conversion réussie se l’approprie, et la seconde est signalée comme un échec. C’est aussi pour cette
raison que relancer un lot terminé signale des échecs au lieu d’ignorer ou de remplacer
silencieusement des fichiers.
| Code de sortie | Signification |
|---|---|
0 | Toutes les entrées demandées ont été converties. |
1 | Au moins une entrée a échoué, y compris en cas de destination existante. |
2 | Erreur d’argument ou de configuration, comme un exécutable FFmpeg manquant ou un répertoire de sortie inutilisable. |
Ajouter des fondus à bon escient
Pour adoucir les 100 premières et dernières millisecondes, utilisez un nouveau répertoire de sortie :
.venv/bin/python encode_audio.py --out-dir mp3-faded --fade-ms 100 -- input.wav
Sans cette option, le script n’applique aucun fondu, gain, normalisation ni compression de la plage dynamique. Un débit MP3 plus élevé ne restaure pas les informations absentes de la source. Écoutez le résultat avant de décider de modifier les niveaux ; une normalisation des crêtes suivie d’une augmentation du volume peut provoquer de l’écrêtage. Consultez l’API Pydub pour les opérations de gain et de fondu.
Vérifier le résultat
Inspectez le premier MP3 et décodez-le entièrement :
ffprobe -v error -select_streams a:0 \
-show_entries stream=codec_name,sample_rate,channels,bit_rate:format=duration \
-of json mp3/input.mp3 &&
ffmpeg -nostdin -v error -xerror -i mp3/input.mp3 -f null -
Pour le son de test, codec_name doit valoir mp3 et sample_rate doit valoir 44100, avec un
canal et une durée proche de trois secondes. Le remplissage des trames MP3 peut rendre la durée
signalée légèrement plus longue. La commande de décodage doit se terminer correctement, sans aucun
message d’erreur. Les métadonnées et la capacité de décodage sont des vérifications utiles, mais
aucune des deux n’établit la qualité audio perçue.
Résoudre les problèmes courants
No module named 'audioop'ou'pyaudioop': relancez l’installation des paquets aux versions fixées avec.venv/bin/python -m pipdans le projet, y compris la dépendance conditionnelleaudioop-lts.- FFmpeg ou
Unknown encoder 'libmp3lame'manquant : vérifiez l’exécutable et la liste des encodeurs comme lors de la configuration. Le paquet Python et l’encodeur système sont des installations distinctes. - Entrée corrompue ou manquante : lisez la ligne
FAILEDet tout diagnostic FFmpeg qui la précède. Un décodage ou un encodage qui échoue ne publie pas de nouveau MP3 ; une destination qui existait déjà reste intacte. - Erreur de lien physique ou d’autorisation : utilisez un répertoire de sortie local accessible en écriture qui prend en charge les liens physiques. Le script supprime son répertoire de préparation en cas d’échec ordinaire et ne remplace jamais une destination pour contourner une erreur du système de fichiers.
Conservez les enregistrements originaux et utilisez un nouveau répertoire de sortie lorsque vous modifiez les paramètres d’encodage. Si la conversion doit sortir de votre processus Python local, découvrez notre service d’encodage audio.
