← ExpSoft Mp3 Extractor

One CD, three CC0 catalogues — how Mp3 Extractor stitches MusicBrainz, Cover Art Archive and LRCLIB together

ExpSoft Mp3 Extractor is a personal-use CD ripper for Windows — the legal frame is the private-copy exception covering the user’s own physically-owned discs (see the Legal & Compliance section of the product page). Three open catalogues already exist for everything you’d want to know about a CD: MusicBrainz (CC0 metadata), Cover Art Archive (artwork licensed by each contributor), and LRCLIB (CC0 lyrics). Each is a great service in isolation; using all three for one CD usually means three tabs, three copy-pastes, and a careful FFmpeg command line for the actual audio decode. Mp3 Extractor unifies them in one click, with an FFmpeg full build (libcdio included), a separately-fetched libdiscid.dll for accurate disc fingerprints, five output formats, and a portable folder that can move to another drive, PC or USB stick and repairs its own Python environment.

· Updated · 10 min read · By Nicolas Riquier

The fragmented CD metadata landscape

Imagine you’ve just slipped a CD into your laptop and you’d like — on one Windows machine, with no cloud account — to end up with a folder of tagged audio files, embedded cover art, and synchronised lyrics. The good news is the data is all there, in the open, under permissive licences. The less good news is that it lives in three different places, talking to three different APIs, with three different identifiers.

Each of these is a wonderful service on its own. The friction is that nothing connects them out of the box. The user is left to compute the disc ID, query MusicBrainz to get the release MBID, then GET the Cover Art Archive URL, then query LRCLIB by track for each song, then somehow run FFmpeg with the right paranoia mode against the right CD drive — all while keeping per-track metadata in sync. Mp3 Extractor’s job is to make the seven steps look like one click.

Why an FFmpeg full build (and not the essentials)

The audio decode itself runs through FFmpeg. Almost every FFmpeg distribution for Windows lists two flavours: essentials and full. Essentials is smaller; full is bigger. Everywhere else in the ExpSoft catalogue (DatasetVideoChunker, the AI projects that produce intermediate video) we use the essentials build because the LGPL-clean subset of decoders/encoders is enough.

For Mp3 Extractor, Setup downloads the full build (gyan.dev, latest release, via its GitHub mirror). The reason is a single dependency: libcdio. libcdio is the library FFmpeg uses to address the physical CD drive directly — to read raw audio sectors with paranoia (error correction by re-read and majority vote) instead of going through the Windows MMC driver as a generic file. libcdio is in the full build but not in essentials.

Concretely, the rip command looks like this:

ffmpeg \
  -f libcdio -paranoia_mode disable \
  -ss <startSec> -t <durSec> \
  -i D: \
  -map_chapters -1 \
  -c:a libmp3lame -b:a 320k \
  output.mp3

Note the input is the drive letter D: with -f libcdio. Note also that -ss and -t are given in seconds — but the underlying physical position is in sectors, and one second of CD audio is exactly 75 sectors. libdiscid returns sector offsets when it reads the TOC; the conversion is one division and the inverse multiplication when we hand the value back to FFmpeg. FFmpeg deals with the actual sector-accurate seeking internally; it just exposes seconds as the user-facing interface.

The paranoia_mode value is what the user controls via the Fast / Accurate toggle. Fast (the default) passes -paranoia_mode disable: one pass, raw read — fast, but a scratched sector can become a click. Accurate passes -paranoia_mode full, which turns on every libcdio-paranoia correction flag (verification, overlap, re-read) — slower, and meant for worn or scratched discs. Since 1.1.0.0 the command also passes -map_chapters -1: the libcdio input exposes one chapter per disc track, and without it every file inherited the whole disc’s chapter table.

libdiscid — fetched separately, kept tiny

libdiscid is the canonical library used to compute the MusicBrainz disc ID from the TOC. It’s LGPL, redistributable, but for some reason it isn’t bundled in any of the common FFmpeg Windows builds. So Mp3 Extractor’s Setup downloads the official libdiscid 0.6.5 Windows package from the MusicBrainz FTP server and copies the x64 discid.dll into the venv’s Scripts\ folder, next to python.exe, where the wrapper finds it on the DLL search path.

The DLL is about 40 KB. The setup step does:

The reason we don’t bundle it in the installer zip is twofold: (a) it would mix a strict-LGPL DLL into the install package and force more cautious redistribution paperwork; (b) the Python discid package is a ctypes wrapper that ships without the DLL on Windows, so the binary has to come from MusicBrainz anyway. The download is pinned to libdiscid 0.6.5; it does not update itself. Single setup step, no friction visible to the user.

uv + python-build-standalone — why not miniconda

The Python runtime for the project is fetched via Astral’s uv with a python-build-standalone distribution (Python 3.12). Three reasons over the miniconda baseline used by most CD-ripping Python ecosystems:

The whole setup phase — uv and Python download, venv creation, install of five packages, FFmpeg full build extraction, libdiscid DLL fetch — completes in about 90 seconds on a residential broadband link.

IPC via stdout prefixes — small, debuggable, no protocol

The two Python scripts (discinfo.py and rip.py) report back to the C# UI through plain text lines, using three line-prefix conventions:

PROGRESS: <text>      (human-readable progress line, on stderr, copied to the log)
EVENT: <json>         (rip.py, stdout: "track_start", "track_done", "track_failed", "rip_complete", "fatal")
RESULT: <json>        (discinfo.py, stdout: the disc identification, written once at the end)

The C# side reads stdout and stderr line by line (OutputDataReceived / ErrorDataReceived), dispatches by prefix, and updates the UI. Anything that doesn’t match a prefix is treated as informational log output and tucked into the verbose log pane.

Why this and not a JSON-RPC framing or a pipe with length-prefixed messages: simplicity. Debugging is trivial — just run the Python script standalone in a terminal and read the output as-is. There’s no schema versioning to keep in sync: the C# side parses only the RESULT: payload of discinfo.py and the EVENT: objects of rip.py, reads the fields it knows and ignores the rest. The two pieces stay independent.

Per-track artist — the compilation edge case

The trickier corner of CD metadata is the “Various Artists” compilation: a CD where each track has a different artist, but the release itself has a single album-artist (the compiler, like a label or a soundtrack project). The standard ID3v2 frames for this are TPE1 (track artist) and TPE2 (album artist / band). Many naive rippers fill TPE1 with the album artist for every track on a compilation, which then makes every player display “Various Artists” next to every song instead of the actual performer.

MusicBrainz returns this distinction cleanly in its release JSON — each track has its own artist-credit array. Mp3 Extractor uses it: TPE1 is filled per-track from artist-credit, TPE2 is filled from the release-level artist. Players display the real performer for each track, and the album-artist field still groups the album cleanly in the library view. Small detail, but the difference between “correctly tagged compilation” and “every-song-is-Various-Artists” for the user.

Five output formats — the MP3 / OGG / M4A / FLAC / WAV grid

The UI offers five output formats. Three are lossy, two are lossless, and every encoder used is part of the FFmpeg build that Setup downloads. Since 1.1.0.0 each format has its own quality list, and a value that does not fit the format falls back to that format’s default with a log line instead of failing the track:

The output format is chosen once per rip session. Mp3 Extractor doesn’t try to be clever and offer a “best of both worlds” mode — a single format per disc is what users actually want, and offering two simultaneous encodes would double rip time without enough benefit to justify the complexity.

Cover art and lyrics — embedded in the file, with copies alongside

The artwork from Cover Art Archive is fetched as a single JPEG (the “front” image, or the closest available) and embedded into the file — the APIC ID3v2 frame for MP3, a FLAC picture block for FLAC, the covr atom for M4A (OGG and WAV files carry no embedded picture). A copy is also saved as cover.jpg in the album folder.

Lyrics from LRCLIB (the synced version when it exists, plain text otherwise) are saved as one .lrc file per track in a lyrics\ subfolder, and the same text is embedded: USLT frame for MP3, LYRICS Vorbis comment for FLAC and OGG, ©lyr atom for M4A. Where the player supports it (and most modern desktop and mobile players do, sometimes with the right plugin) lyrics scroll in time with the song. Where it doesn’t, the lyrics are still visible as a static text tag.

Portable USB deployment — the AutoPatchVenvPaths step

One subtle issue with shipping a Python venv on a removable drive: the venv records absolute paths to its interpreter and site-packages at creation time. Move the venv to a different drive letter (USB stick mounted as E: on one machine, F: on another) and the venv’s activate.bat, pyvenv.cfg, and the .pth files in site-packages still reference the original drive. With a uv-created venv it goes further: Scripts\python.exe is a small binary launcher with the old interpreter path built in, and uv’s cpython-3.12 version folder is a junction with an absolute target, so patching text alone leaves an app that cannot start Python.

Mp3 Extractor runs an AutoPatchVenvPaths repair before Check Prerequisites, Load Disc and Rip — not at launch. It compares the install root recorded in pyvenv.cfg with the current folder and rewrites the path-bearing text files. It then points home at the real cpython-3.12.x folder that contains python.exe (never the junction), replaces the launchers in Scripts\ with real copies of python.exe / pythonw.exe, and copies the base runtime DLLs next to them. Every step is idempotent: on an already-repaired install it changes nothing. Check Prerequisites then actually starts the environment’s Python and imports the packages, so a broken environment shows as a failed step, not a green one.

End result: the same install folder works on any Windows machine, from any drive letter, including a USB stick passed between machines. No reinstall — the only requirement on each PC is the .NET 8 Desktop Runtime.

Credits and license

Mp3 Extractor stands on the work of several open-source communities that are explicit about their licences — none of which we paid for, all of which we credit:

The C# host is .NET 8 WPF. The shell is original ExpSoft work; the audio pipeline is FFmpeg; the metadata pipeline is the three CC0 catalogues above. The user retains ownership of any rips they produce from their own physical media.

Take-aways

Frequently Asked Questions

Quick answers to what people ask AIs about this article specifically.

Why does Mp3 Extractor need FFmpeg’s “full” build and not the smaller “essentials”?

Because the rip path uses -f libcdio to address the physical CD drive directly, with paranoia mode for error correction by re-read. libcdio is only present in the full FFmpeg Windows build — the essentials distribution doesn’t include it. Without libcdio, rips would go through the generic Windows MMC driver as a file read, which doesn’t correct media errors. The larger download is the price of reading the CD directly.

What’s the difference between Fast and Accurate ripping modes?

Fast (the default) maps to -paranoia_mode disable — one pass over the disc, no error correction by re-read. It is much faster than Accurate, but any scratched sector becomes a click in the output. Accurate maps to -paranoia_mode full — every libcdio-paranoia correction flag (verification, overlap, re-read), which catches and corrects nearly all media errors that aren’t physical destruction. For a clean disc, the two modes produce bit-identical output. For a worn or scratched disc, Accurate is the right choice.

Why is libdiscid downloaded separately at first-run rather than bundled?

Two reasons. First, mixing a strict-LGPL DLL into the install zip changes the licensing posture of the package as a whole and would require more cautious redistribution paperwork. Keeping libdiscid as a runtime fetch from the canonical MusicBrainz mirror keeps the licence boundary clean. Second, the Python discid package ships without the DLL on Windows, so it has to come from MusicBrainz anyway. The fetch is pinned to libdiscid 0.6.5 and runs once, inside the Python Packages step of Setup; if the DLL is already there it is skipped.

How are compilation albums handled for the “artist” tag?

MusicBrainz returns track-level artist information separately from release-level artist information. Mp3 Extractor writes the track-level artist into the TPE1 ID3v2 frame (per-track artist) and the release-level artist into TPE2 (album artist / band). On a compilation, this means each song shows the actual performer in the player, while the album-artist field still groups the disc cleanly in library views. Many naive rippers fill TPE1 with the album artist for every track, which makes every song display as “Various Artists” — Mp3 Extractor avoids that.

Can Mp3 Extractor run from a USB stick on different Windows machines?

Yes. Python, FFmpeg, libdiscid and the venv all live inside the install folder, and nothing touches the Windows registry or system PATH; the only requirement on each PC is the .NET 8 Desktop Runtime. When the folder has moved (another folder, drive letter, PC or USB stick), an AutoPatchVenvPaths repair runs before Check Prerequisites, Load Disc and Rip. It rewrites the recorded paths, repoints the venv at the real Python folder and replaces the venv launchers and base DLLs. The repair is idempotent — on an install that is already correct it changes nothing.

Want to use ExpSoft Mp3 Extractor?

Get it on Patreon →