Beets
Music library organizer and metadata management tool
Beets is a command-line music library manager that catalogs your collection and improves its metadata using MusicBrainz. With its web plugin it also serves a browsable, streamable interface for your music.
Key features
- MusicBrainz metadata tagging
- Powerful plugin system
- Web interface plugin
- Flexible querying
Pros & cons
Strengths
- Excellent metadata accuracy
- Highly scriptable
Trade-offs
- Command-line focused
- Steep learning curve
Beets replaces
Last reviewed Aug 26, 2026 · 840 words
The first time I pointed Beets at a 40,000-track collection that had been through three ripping programs and two failed iTunes migrations, it took about 2 evenings of answering prompts and it produced the first correctly tagged library I had ever owned. Every album art, every disc number, every "Various Artists" compilation filed under the right name. That is what Beets is: a command-line librarian that matches your files against MusicBrainz, rewrites the tags, and moves the files into a folder structure you define. It is not a music server, and the way a self-hoster uses it is as the step before one.
Where it sits: before the server, not instead of it
Navidrome and Jellyfin read whatever tags are in your files and build their databases from them. Garbage tags in, garbage browsing out: "Unknown Artist" folders, an album split in three because the album-artist field differs per track, 15 copies of the same Beatles compilation. Beets fixes the files once, on disk, and then every server you ever point at that folder inherits the fix. The reason to run it on your server rather than a laptop is that the import runs where the files are and the library database (a single SQLite file) stays next to them. For the streaming side of the question, my Navidrome setup picks up where this guide leaves off.
The 12-line config that does most of the work
Beets reads ~/.config/beets/config.yaml. This is a working starting point:
directory: /music/library
library: /music/beets-library.db
import:
move: yes
write: yes
log: /music/beets-import.log
paths:
default: $albumartist/$album%aunique{}/$track $title
singleton: Singles/$artist - $title
comp: Compilations/$album%aunique{}/$track $title
plugins: fetchart embedart lastgenre chroma duplicates web
move: yes relocates files from the incoming folder into the library; use copy: yes on the first run if you want to keep the originals until you trust it. The paths block is the folder layout, and %aunique{} appends a disambiguator only when two albums by the same artist share a name, which is the detail that keeps remasters from colliding. write: yes puts the corrected tags into the files themselves, which is the whole point.
The import loop, and the two flags that save your evening
pipx install beets
beet import /music/incoming
Beets scans each album folder, queries MusicBrainz, and shows a candidate match with a similarity percentage. Above the auto-accept threshold it just applies the match; below it, you get a prompt to accept, skip, search by ID, or treat the tracks as singletons. For a large messy collection, two flags turn this from a week into an evening: -q (quiet) auto-accepts good matches and skips everything uncertain so you can do the ambiguous ones later with beet import -L against the log, and -i (incremental) remembers folders it has seen so you can stop and resume. MusicBrainz rate-limits to about 1 request per second, so a 3,000-album import is bounded by that, not your CPU.
The plugins worth enabling on day one
chroma does acoustic fingerprinting through Chromaprint and AcoustID, which rescues files with no tags at all; it needs the fpcalc binary and a free AcoustID key. fetchart and embedart pull cover art and write it into the files, which is what makes servers display it. lastgenre fills the genre field from Last.fm tags, since MusicBrainz is weak on genre. duplicates finds the 15 Beatles compilations. replaygain calculates loudness normalisation so the server does not have to. web starts a small Flask interface on port 8337 for browsing and playing the library; it is useful for checking a match and not a replacement for a real server. Every plugin is one word in the plugins list.
Beets and Lidarr overlap, and that is a choice
Lidarr automates finding and downloading music and renames files too, so people ask whether both are needed. Lidarr's tagging is adequate and MusicBrainz-driven; Beets' is better, especially for compilations, classical, and anything you ripped yourself. The pattern that works is Lidarr for acquisition into an incoming folder and Beets as the import step into the library, with Lidarr's own renaming turned off. Running both against the same folder with both renaming is how you get files that move every night.
What I'd do
Install Beets with pipx on the box that holds the music, config as above, copy: yes for the first import, and run it in a tmux session over a couple of evenings with -q -i. Review the skipped albums with beet import -L. Once you trust it, switch to move: yes, point Navidrome at /music/library, and make beet import /music/incoming the only way music enters the collection. Budget 256 MB of RAM, a MusicBrainz account you never need to log into, and a tolerance for a learning curve that pays off once and then stays paid.
Compare Beets
3 head-to-head comparisons.
Similar media servers apps
Jellyfin
Media ServersFree software media system for streaming your library
Replaces Plex, Emby
qBittorrent
Media ServersBittorrent client that aims to be a good alternative to all other
Replaces µTorrent
SRS
Media ServersA simple, high efficiency and real-time video server, supports RTMP,
Navidrome
Media ServersModern music streaming server for your collection
Replaces Spotify
Kodi
Media ServersOpen-source home theater and media center
Replaces Plex
Koel
Media ServersPersonal music streaming server with an elegant interface
Replaces Spotify