Codifique áudio WAV como MP3 em Python com Pydub e FFmpeg
Converta uma gravação ou um lote de arquivos WAV em MP3 com um script Python que informa quais arquivos foram convertidos com sucesso e retorna um código de saída de falha se alguma conversão falhar. O exemplo abaixo usa Pydub e FFmpeg, não altera seus arquivos de origem e se recusa a sobrescrever saídas existentes.
O passo a passo usa Bash no Linux e gravações WAV locais mono ou estéreo. Cada saída é um MP3 de 192 kbps a 44,1 kHz, com o mesmo número de canais da origem. O Pydub mantém o áudio decodificado na memória, então processe os arquivos sequencialmente e use gravações que caibam na RAM. Este é um script de conversão local, não um serviço de upload nem um codificador de streaming.
Configurando o ambiente
O exemplo foi testado com Python 3.14.7, Pydub 0.25.1, audioop-lts 0.2.2 e FFmpeg 9.0.1 no Linux. Os comandos usam um ambiente virtual de forma explícita, então você não precisa ativá-lo.
Instalando o FFmpeg
Instale o Python com suporte a ambientes virtuais, o FFmpeg e o ffprobe. No Ubuntu:
sudo apt-get update &&
sudo apt-get install -y python3 python3-venv ffmpeg
Outras distribuições Linux oferecem seus próprios pacotes; a
página de download do FFmpeg tem links para pacotes de
distribuições. Verifique se a sua build inclui
o codificador libmp3lame:
ffmpeg -version &&
ffprobe -version &&
ffmpeg -hide_banner -encoders
Procure libmp3lame na lista de codificadores. Instalar o Pydub não instala o
FFmpeg.
Instalando o Pydub
Crie um novo diretório a partir do diretório pai. Se audio-encoding já existir,
escolha outro nome; a cadeia && interrompe a configuração antes que ela
possa instalar algo em um projeto existente.
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"'
Mantenha este shell em audio-encoding para os comandos restantes. O Python
removeu audioop na versão 3.13, mas o
Pydub 0.25.1 ainda o importa. A dependência condicional instala
audioop-lts no Python 3.13 e em versões mais
recentes; o Python 3.12 usa o módulo da biblioteca padrão. Nenhum pacote de reprodução de áudio é
necessário.
Salve o conversor
Salve o script completo abaixo como encode_audio.py. WAV é um contêiner: suas
amostras podem ser valores inteiros ou de ponto flutuante. O script primeiro pede ao FFmpeg que
decodifique para PCM de 16 bits com sinal e depois entrega essa representação conhecida ao Pydub.
Isso evita depender do
caminho rápido de WAV do Pydub 0.25.1,
que não distingue todos os tipos de amostra de WAV extensível. Profundidades de bits maiores são
reduzidas para 16 bits neste fluxo de trabalho de MP3; mantenha o WAV de origem como seu arquivo
master.
O MP3 é codificado em um diretório temporário ao lado do destino. Somente uma codificação concluída é publicada, usando um hard link que falha se o destino já existir. Use um sistema de arquivos local que ofereça suporte a hard links. Os arquivos de conversões bem-sucedidas permanecem quando outra entrada falha.
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())
A opção -xerror do FFmpeg faz com que erros
de decodificação relatados causem falha na conversão. Isso detecta entradas danificadas que o FFmpeg
poderia, de outra forma, recuperar parcialmente. Não é uma prova de que toda gravação esteja
completa: ouça as saídas importantes e compare a duração delas com a duração esperada da gravação.
Converta uma gravação
Use o seu próprio input.wav ou crie um tom de teste de três segundos. A flag
-n se recusa a sobrescrever um arquivo existente:
ffmpeg -nostdin -hide_banner -v error -n -f lavfi \
-i 'sine=frequency=440:sample_rate=44100:duration=3' -c:a pcm_s16le input.wav
Execute o conversor a partir de audio-encoding:
.venv/bin/python encode_audio.py --out-dir mp3 -- input.wav
Ele imprime uma linha OK e 1 converted, 0 failed e depois
encerra com código de saída 0. O arquivo é
mp3/input.mp3. Se você executar o comando novamente, ele relata uma falha por saída
já existente, encerra com código de saída 1 e preserva os bytes do
primeiro MP3. Escolha outro diretório de saída para uma nova codificação.
Processamento em lote de arquivos de áudio
Crie um diretório wav_files e coloque nele suas gravações WAV; depois, passe-as
para o mesmo script:
.venv/bin/python encode_audio.py --out-dir mp3-batch -- wav_files/*.wav
O Bash expande esse glob para os arquivos diretamente dentro do diretório, sem recursão. Ele
corresponde a nomes .wav em minúsculas; passe os arquivos
.WAV em maiúsculas explicitamente. Se não houver correspondências, o padrão
literal chega ao script e produz uma falha por entrada ausente. Coloque entre aspas os caminhos
individuais que contêm espaços. O separador -- também permite nomes de
entrada que começam com hífen.
Cada entrada recebe uma linha OK ou FAILED. Um
lote misto mantém os MP3s válidos, continua após um arquivo corrompido e encerra com código de saída
1. Duas entradas como a/interview.wav e
b/interview.wav são mapeadas para o mesmo nome de saída: a primeira conversão
bem-sucedida fica com ele, e a segunda é relatada como falha. É também por isso que executar
novamente um lote concluído relata falhas em vez de ignorar ou substituir arquivos silenciosamente.
| Código de saída | Significado |
|---|---|
0 | Todas as entradas solicitadas foram convertidas. |
1 | Pelo menos uma entrada falhou, incluindo um destino já existente. |
2 | Erro de argumento ou de configuração, como um executável do FFmpeg ausente ou um diretório de saída inutilizável. |
Aplique fades de forma intencional
Para suavizar os primeiros e os últimos 100 milissegundos, use um diretório de saída novo:
.venv/bin/python encode_audio.py --out-dir mp3-faded --fade-ms 100 -- input.wav
Sem essa opção, o script não aplica fades, ganho, normalização nem compressão de faixa dinâmica. Um bitrate de MP3 maior não restaura informações ausentes na origem. Ouça antes de decidir se vai alterar os níveis; normalização de pico seguida de aumento de volume pode causar clipping no áudio. Consulte a API do Pydub para operações de ganho e fade.
Verifique a saída
Inspecione o primeiro MP3 e decodifique-o do início ao fim:
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 -
Para o tom de teste, espere que codec_name seja mp3,
que sample_rate seja 44100, um canal e uma duração
próxima de três segundos. O preenchimento de frames do MP3 pode deixar a duração relatada um pouco
maior. O comando de decodificação deve terminar com sucesso, sem mensagens de erro. Metadados e
capacidade de decodificação são verificações úteis, mas nenhum dos dois comprova a qualidade de
áudio percebida.
Solução de problemas comuns
No module named 'audioop'ou'pyaudioop': execute novamente a instalação dos pacotes com versões fixadas usando.venv/bin/python -m pipdentro do projeto, incluindo a dependência condicionalaudioop-lts.- FFmpeg ausente ou
Unknown encoder 'libmp3lame': verifique o executável e a lista de codificadores da etapa de configuração. O pacote Python e o codificador do sistema são instalações separadas. - Entrada corrompida ou ausente: leia a linha
FAILEDe qualquer diagnóstico do FFmpeg que a preceda. Uma decodificação ou codificação que falha não publica um novo MP3; um destino que já existia permanece intacto. - Erro de hard link ou de permissão: use um diretório de saída local gravável com suporte a hard links. O script remove seu diretório de preparação em falhas comuns e nunca substitui um destino para contornar um erro do sistema de arquivos.
Mantenha as gravações originais e use um novo diretório de saída ao alterar as configurações de codificação. Se a conversão precisar sair do seu processo Python local, conheça nosso serviço de codificação de áudio.
