WAV is uncompressed (typically 10-15MB per minute of audio); MP3 is lossy-compressed (~1MB per minute at 128kbps). The conversion is one line in any reasonable library. The interesting choices are bitrate (quality vs. file size), VBR vs. CBR, and ID3 tag handling.

Method 1: pydub (the easy default)

pydub wraps FFmpeg and exposes a clean API. Best for any audio work where you don't need precise control over encoding internals.

pip install pydub
apt install ffmpeg  # or brew install ffmpeg
from pydub import AudioSegment

def wav_to_mp3(in_path: str, out_path: str, bitrate: str = "192k") -> None:
    audio = AudioSegment.from_wav(in_path)
    audio.export(out_path, format="mp3", bitrate=bitrate)

wav_to_mp3("recording.wav", "recording.mp3")
wav_to_mp3("podcast.wav", "podcast.mp3", bitrate="128k")  # smaller file

Bitrate guide:

  • 320k — transparent quality, indistinguishable from source for most listeners. ~2.5MB/min.
  • 192k — high quality, the typical default. ~1.5MB/min.
  • 128k — good quality for spoken word. ~1MB/min. Audible artifacts on complex music.
  • 96k — voice-only, podcast-tier. ~700KB/min.

To embed ID3 tags:

audio.export("out.mp3", format="mp3", bitrate="192k", tags={
    "title": "My Track",
    "artist": "My Artist",
    "album": "My Album",
    "year": "2026",
})

Method 2: ffmpeg-python (full control)

For VBR encoding, custom sample rates, or specific codecs (libmp3lame, libshine), ffmpeg-python gives you direct access.

pip install ffmpeg-python
import ffmpeg

def wav_to_mp3(in_path: str, out_path: str, bitrate: str = "192k", vbr: bool = False) -> None:
    stream = ffmpeg.input(in_path)
    if vbr:
        # VBR with quality scale: -q:a 2 ≈ 190kbps avg, 0 = best, 9 = worst
        stream = stream.output(out_path, acodec="libmp3lame", **{"q:a": 2})
    else:
        # CBR
        stream = stream.output(out_path, acodec="libmp3lame", audio_bitrate=bitrate)
    stream.overwrite_output().run(quiet=True)

wav_to_mp3("recording.wav", "recording.mp3", bitrate="320k")
wav_to_mp3("recording.wav", "recording_vbr.mp3", vbr=True)

VBR (variable bitrate) typically produces smaller files at equivalent perceptual quality. For most use cases, CBR 192k or VBR -q:a 2 are the right defaults.

To resample (e.g., 96kHz studio recording to 44.1kHz for MP3):

stream = ffmpeg.input(in_path).output(out_path, ar=44100, ac=2, audio_bitrate="192k", acodec="libmp3lame")
stream.overwrite_output().run(quiet=True)

Method 3: ChangeThisFile API (no FFmpeg install)

FFmpeg is ~150MB. If you don't want it on every machine that needs MP3 conversion, the API does it server-side. Get a free API key.

import requests

API_KEY = "ctf_sk_your_key_here"

def wav_to_mp3(in_path: str, out_path: str) -> None:
    with open(in_path, "rb") as f:
        response = requests.post(
            "https://changethisfile.com/v1/convert",
            headers={"Authorization": f"Bearer {API_KEY}"},
            files={"file": f},
            data={"source": "wav", "target": "mp3"},
            timeout=120,
        )
    response.raise_for_status()
    with open(out_path, "wb") as out:
        out.write(response.content)

wav_to_mp3("recording.wav", "recording.mp3")

The API uses 192kbps CBR by default — a good universal choice. To customize, pass bitrate=320k or vbr=true in the form data.

When to use each

ApproachBest forTradeoff
pydubMost projects, batch scripts, easy ID3 tag handlingSlightly slower than raw FFmpeg, opinionated defaults
ffmpeg-pythonVBR encoding, resampling, custom codec configsMore verbose, need FFmpeg installed
ChangeThisFile APINo FFmpeg dependency, multi-language teams, edge runtimesPer-call cost, file size limit (25MB free)

CLI alternative: ffmpeg or lame

For one-off conversions:

# FFmpeg (recommended):
ffmpeg -i recording.wav -c:a libmp3lame -b:a 192k recording.mp3

# Or LAME directly (slightly higher quality, slower):
apt install lame
lame -V 2 recording.wav recording.mp3   # VBR ~190kbps
lame -b 192 recording.wav recording.mp3 # CBR 192k

LAME is the underlying encoder both FFmpeg and the Python libraries call. Use it directly for absolute peak quality at a given bitrate; use FFmpeg/pydub for everything else.

Production tips

  • For voice content, 128k mono is the right default. Mono cuts file size in half. 128k is plenty for spoken word — the artifacts only show up on complex music.
  • For music, 192k VBR is the sweet spot. Smaller than 320k CBR with no audible difference for most listeners.
  • Resample 96kHz studio to 44.1kHz before encoding. MP3's frequency limits make 96kHz pointless. Resampling to 44.1kHz cuts file size and processing time without affecting perceived quality.
  • Strip metadata if privacy matters. WAV files often contain recording timestamps and software signatures. ffmpeg -map_metadata -1 strips it; pydub does this by default unless you pass tags=.
  • For batch jobs, parallelize. Audio encoding is CPU-bound. Use concurrent.futures.ProcessPoolExecutor — threading is slower because of the GIL.

For batch scripts: pydub. For peak quality: LAME or ffmpeg-python with VBR. To skip the FFmpeg install: the API. Free tier is 100 conversions/month, no card.