Skip to content

Beets

Developer Productivity

A media library management system for organizing and enhancing music collections

Python Latest v2.11.0 · 28d ago Security brief →

Features

  • Automatically catalogues and improves metadata using MusicBrainz, Discogs, Beatport or acoustic fingerprinting
  • Fetches album art, lyrics, genres, ReplayGain levels, tempos and acoustic fingerprints via plugins
  • Detects duplicate tracks/albums and identifies missing tracks in collections

Recent releases

View all 9 releases →
v2.11.0 Breaking risk
⚠ Upgrade required
  • `--pretend-paths` flag removed from Smart Playlist Plugin; use `--format='$path'` instead.
  • Library path migration now handles manually edited database rows with SQLite TEXT paths for seamless upgrade to portable-path storage format (fix #6561).
Notable features
  • [Smart Playlist Plugin] Restructured `splupdate` output with track count, per-track details on `-v`, `--pretend` reports "would be updated", added `--format` for custom line format.
  • [import command] Automatically removes source archives after successful move imports; preserves archives when any file is skipped or import aborts.
  • [FromFilename Plugin] Added support for parsing track numbers prefixed with `track` (e.g., `track01.m4a`).
Full changelog

New features

  • Smart Playlist Plugin: The splupdate command output is restructured. The per-playlist summary now includes a track count. Per-track details are shown only when -v flag is provided (beet -v splupdate). The --pretend flag produces the same output but reports "N playlists would be updated" instead of "N playlists updated". The --format option allows customizing the track line format. The --pretend-paths option was removed (use --format='$path' instead). :bug: (#6183)

  • import command: When importing an archive (zip, tar, rar, or 7z) with move: yes, the source archive is now removed after a successful import. Archives are preserved if any file in the archive was not imported (e.g. skipped as a duplicate, or the import was aborted), and in non-move import modes.

  • FromFilename Plugin: Support track prefix when parsing the track number from the filename (e.g., track01.m4a).

  • Tidal plugin: Introduces a new plugin for fetching metadata from Tidal. It supports album and track lookups by ID, including batch operations via albums_for_ids and tracks_for_ids. It also enables search by query as well as identifier-based retrieval, with support for ISRC codes (tracks) and barcode/EANs (albums).

    This is an initial, relatively minimal implementation, but already fully usable for common metadata workflows. We welcome feedback, improvement ideas, and community contributions to further extend its capabilities.

    See Tidal Plugin for more information.

  • Add support for adding or modifying a subtitle (ID3 tag TIT3) field

Bug fixes

  • Bad Files Plugin: Respect quiet mode (the --quiet flag or import.quiet: yes config) during import so the corrupt-file prompt is suppressed in non-interactive imports. :bug: (#4736)
  • Discogs Plugin: Prevent duplicate featured artists in track artist fields when the same artist is credited both in artists (for example with Feat. join text) and extraartists as Featuring. :bug: (#6166)
  • import command: Fix duplicate album art files (e.g. cover.2.jpg) being created when re-importing albums with the FetchArt Plugin plugin enabled. Old album art is now properly removed when replacing duplicate albums during import. :bug: (#1264) :bug: (#6205)
  • import command: Metadata source plugin ID lookups now correctly call each plugin's own lookup method when running in parallel. :bug: (#6583)
  • import command: Multi-disc album detection now recognizes cassette, digital media, and vinyl as disc markers (e.g. vinyl 1, 12 vinyl 2), in addition to the existing disc, disk, and cd markers.
  • import command: Tags with a zero distance penalty are no longer shown as differences in the match display. Previously, custom distance_weights could cause fields with no actual mismatch to appear in the line.
  • import command: With original_date: yes, album-level year, month, and day now use the original release date. :bug: (#6577)
  • Lyrics Plugin: Fix apostrophe handling in the musixmatch backend slug. :bug: (#4759)
  • MusicBrainz Plugin: Correctly handle release dates where leading or intermediate components are missing, e.g. 2008-??-02
  • Improve DBAccessError messages to help users diagnose database permission issues more easily. The error message now mentions directory missing and file permissions as potential causes. :bug: (#1676)
  • Library path migration now also handles manually edited database rows where item or album-art paths were stored as SQLite TEXT values instead of bytes, so upgrading to the portable-path storage format no longer fails for those libraries. :bug: (#6561)

Other changes

  • Spotify Plugin: Batch spotifysync track and audio-features API requests and deduplicate repeated Spotify track IDs within a run.
v2.10.0 Breaking risk patches GHSA-3gxm-wfjx-m847
⚠ Upgrade required
  • Run `beet version` (or any other command) at least once after upgrading to trigger the library‑path migration before relocating the library.
Breaking changes
  • Library item and album‑art paths stored relative to the library root; run any `beet` command after upgrade to trigger migration before moving the library.
Security fixes
  • Web plugin: fixed stored XSS by using escaped interpolation (`<%-`) for metadata fields (artist, album, title, comments, lyrics).
Notable features
  • Inline Plugin now exposes `db_obj` (album or item) in inline fields.
  • Discogs Plugin imports remixer, lyricist, composer, and arranger credits into multi‑value fields.
  • Lyrics Plugin adds `keep_synced` option/CLI flag to skip re‑fetching synced lyrics.
Full changelog

New features

  • Beets library is now made portable: item and album-art paths are now stored relative to the library root in the database while remaining absolute in the rest of beets. Path queries continue matching both library-relative paths and absolute paths under the currently configured music directory under the new storage model. The existing paths in the database are migrated automatically the first time you run any beet command after the update. :bug: (#133)

[!WARNING]
make sure you run beet version (or any other command) at least once after upgrading to trigger the migration. Only then you can safely move the library to a new location.

  • Inline Plugin: Add access to the album or item object as db_obj in inline fields.
  • Discogs Plugin: Import Discogs remixer, lyricist, composer, and arranger credits into the multi-value remixers, lyricists, composers, and arrangers fields. :bug: (#6380)
  • Lyrics Plugin: Add keep_synced config option and --keep-synced CLI flag to skip re-fetching lyrics for tracks that already have synced lyrics, even when force is enabled. :bug: (#5249)
  • MusicBrainz Plugin: Use aliases for artist credit.
  • Metadata source plugin searches and lookups are now executed concurrently, speeding up lookups when multiple plugins (e.g. MusicBrainz and Spotify) are enabled.

Bug fixes

  • Chromaprint/Acoustid Plugin: Do not produce MusicBrainz-sourced autotagger candidates when the MusicBrainz Plugin plugin is not enabled. The chroma plugin now looks up the musicbrainz plugin through the metadata-source registry instead of unconditionally instantiating its own private instance, which also restores compatibility with MusicBrainz Pseudo-Release Plugin for chroma-triggered lookups. :bug: (#6212) :bug: (#6441)
  • import command Automatically remux WAV files containing MP3 streams (WAVE_FORMAT_MPEGLAYER3) to proper MP3 files during import, instead of silently importing them with incorrect metadata. :bug: (#6455)
  • import command Remove clutter from imported album folders. :bug: (#5016)
  • ListenBrainz Plugin: Retry listenbrainz requests for temporary failures.
  • Web Plugin: Fix a stored XSS vulnerability where unescaped metadata fields (artist, album, title, comments, lyrics) could execute arbitrary JavaScript in the browser. Template tags now use <%- (escaped interpolation) instead of <%= (raw interpolation).

For plugin developers

  • Consumers of beetsplug._utils.musicbrainz.MusicBrainzAPI now receive normalized MusicBrainz payloads with underscore-separated field names (for example artist_credit and release_group) and grouped relation lists such as work_relations, release_relations, and url_relations. The API responses are also now fully typed with concrete TypedDict models for releases, recordings, works, and relations. Update direct access to raw MusicBrainz response keys if needed.
v2.9.0 Breaking risk
Notable features
  • [AutoBPM Plugin] Add `force` config/CLI option and deprecate `overwrite`
  • [AutoBPM Plugin] Hide "BPM already exists" log with `--quiet` flag
  • [Chromaprint/Acoustid Plugin] New `chromasearch` command to search library by fingerprint
Full changelog

Beets now officially supports Python 3.14.

New features

  • AutoBPM Plugin: Add force configuration and CLI option and deprecate overwrite.
  • AutoBPM Plugin: The "BPM already exists for item" log message can now be hidden with the --quiet flag.
  • Chromaprint/Acoustid Plugin: Add new command chromasearch to search the local library by chromaprint fingerprint.
  • FetchArt Plugin: Add support for WebP images.
  • import command Use ffprobe to recognize format of any import music file that has no extension. If the file cannot be recognized as a music file, leave it alone. :bug: (#4881)
  • LastGenre Plugin: Add support for a user-configurable ignorelist to exclude unwanted or incorrect Last.fm (or existing) genres, either per artist or globally :bug: (#6449)
  • MusicBrainz Plugin: Store MBIDs for remixers, lyricists, composers, and arrangers in the new multi-valued fields remixers_mbid, lyricists_mbid, composers_mbid, and arrangers_mbid. :bug: (#5698)
  • ReplayGain Plugin: Conflicting replay gain tags are now removed on write. RG* tags are removed when setting R128* and vice versa.
  • Smart Playlist Plugin: The list of available playlists shown when an unknown playlist name is passed as an argument is now sorted alphabetically and printed space-delimited and POSIX shell-quoted when required. This makes it easier to copy and paste multiple playlists for further use in the shell.
  • Query: Add has_cover_art computed field to query items by embedded cover art presence. Users can now search for tracks with or without embedded artwork using beet list has_cover_art:true or beet list has_cover_art:false.
  • Store track remixers, lyricists, composers, and arrangers in the multi-valued remixers, lyricists, composers, and arrangers fields instead of the legacy single-value remixer, lyricist, composer, and arranger fields. Existing libraries are migrated automatically, and MusicBrainz Plugin now preserves each MusicBrainz remixer, lyricist, composer, and arranger relation as a separate value.

Bug fixes

  • Deezer Plugin: Fix a regression in 2.8.0 where selecting a Deezer match during import could crash with AttributeError: 'AlbumInfo' object has no attribute 'raw_data' when Deezer returned numeric artist IDs. :bug: (#6503)
  • Deezer Plugin: Fix Various Artists albums being tagged with a localized string instead of the configured va_name. Detection now uses Deezer's artist ID rather than the artist name string. :bug: (#4956)
  • Discogs Plugin: Store specific Discogs styles in beets genres and broader Discogs genres in the style field. When append_style_genre is enabled, the broader Discogs genres are also appended to the genres list. :bug: (#6390)
  • FetchArt Plugin: Error when a configured source does not exist or sources configuration is empty. :bug: (#6336)
  • import command Fix albumartists_sort (and related fields) incorrectly prepending the full combined artist credit as the first element for multi-artist releases. :bug: (#6470)
  • ListenBrainz Plugin: Fix lbimport crashing when ListenBrainz tracks are processed through Last.fm-specific play-count import logic. Play-count imports now use source-specific fields so ListenBrainz Plugin, LastImport Plugin, and MPDStats Plugin do not clash. :bug: (#6469)
  • ListenBrainz Plugin: Paginate through all ListenBrainz listens instead of fetching only 25, aggregate individual listen events into correct play counts, use recording_mbid from the ListenBrainz mapping when available, and avoid per-listen MusicBrainz API lookups that caused imports to hang on large listen histories. :bug: (#6469)
  • modify command accepts legacy singular field names such as genre, composer, lyricist, remixer, and arranger in assignments, rewrites them to the corresponding multi-valued fields, and warns users to switch to the plural field names. list command, and query expressions, accept the same legacy singular field names and warn users to switch to the plural field names. :bug: (#6483)
  • Rewrite Plugin Advanced Rewrite Plugin: Fix rewriting multi-valued fields such as genres by applying rules to each matching list entry. Additionally, apply rewrite rules in config order, so that multiple rules can be applied to the same field. :bug: (#6515)
  • Correctly handle semicolon-delimited genre values from externally-tagged files. :bug: (#6450)

For plugin developers

  • If you maintain a metadata source plugin that populates any of arranger, composer, lyricist, remixer fields, update it to populate the respective multi-valued fields instead (arrangers, composers, lyricists, remixers).
v2.8.0 Breaking risk
⚠ Upgrade required
  • Deprecation warning: Beatport and BPSync plugins are deprecated due to retired API.
  • [Missing Plugin] Now uses stored `data_source` for ID lookups with fallback to MusicBrainz.
  • [MBSync Plugin] Same behavior change as Missing plugin regarding data source usage.
Breaking changes
  • LastImport plugin: `play_count` flexible field renamed to `lastfm_play_count`; manual migration required because automatic migration is impossible due to name clash with MPDStats plugin.
Notable features
  • [Discogs Plugin] Added `extra_tags` option for barcode, catalognum, country, label, media, and year in Discogs search queries.
  • [Lyrics Plugin] Introduced `auto_ignore` config to skip lyric fetching for items matching a beets query during auto import.
  • [Missing Plugin] Added `--release-type` flag to filter missing album mode by MusicBrainz release types; default changed to show only type `album`.
Full changelog

New features

  • Discogs Plugin: Add extra_tags option to use additional tags (such as barcode, catalognum, country, label, media, and year) in Discogs search queries.
  • Lyrics Plugin: Add auto_ignore configuration option to skip fetching lyrics for items matching a beets query during auto import.
  • Missing Plugin: When running in missing album mode, allows users to specify MusicBrainz release types to show using the --release-type flag. The default behavior is also changed to just show releases of type album. :bug: (#2661)
  • Play Plugin: Added -R/--randomize flag to shuffle the playlist order before passing it to the player.
  • Smart Playlist Plugin: Add new configuration option dest_regen to regenerate items' path in the generated playlist instead of using those in the library. This is useful when items have been imported in don't copy-move (-C -M) mode in the library but are later passed through the Convert Plugin plugin which will regenerate new paths according to the Beets path format.

Bug fixes

  • Beatport Plugin: Use va_name config for the album artist on VA releases instead of hardcoded "Various Artists". :bug: (#6316)
  • config command on Windows now uses cmd /c start "" for the default editor fallback so beet config -e works when VISUAL and EDITOR are unset. :bug: (#6436)
  • Fish Plugin: Fix AttributeError. :bug: (#6340)
  • import command Autotagging by explicit release or recording IDs now keeps candidates from all enabled metadata sources instead of dropping matches when different providers share the same ID. :bug: (#6178) :bug: (#6181)
  • import command Simplify autotag metadata application for albums and singletons, fixing null-overwrite handling and keeping singular/plural artist metadata fields in sync during tagging.
  • LastImport Plugin: Rename flexible field play_count to lastfm_play_count to avoid conflicts with MPDStats Plugin. Migration: This cannot be migrated automatically because of the field clash. If you use LastImport Plugin without MPDStats Plugin, migrate manually with beet modify lastfm_play_count='$play_count'.
  • MBSync Plugin and Missing Plugin now use each item's stored data_source for ID lookups, with a fallback to MusicBrainz.
  • Missing Plugin: Fix --album mode incorrectly reporting albums already in the library as missing. The comparison now correctly uses mb_releasegroupid.
  • MusicBrainz Plugin: Use va_name config for albumartist_sort, albumartists_sort, albumartist_credit, albumartists_credit, and albumartists on VA releases instead of hardcoded "Various Artists". :bug: (#6316)
  • replace: Made drive_sep_replace regex logic more precise to prevent edge-case mismatches (e.g., a song titled "1:00 AM" would incorrectly be considered a Windows drive path).

For plugin developers

  • beets.metadata_plugins.album_for_id and beets.metadata_plugins.track_for_id now require a data_source argument and query only that provider.
  • Colorisation, diff and layout utility helpers previously imported from beets.ui now live in beets.util.color, beets.util.diff, and beets.util.layout. Update external imports accordingly.
  • The lastgenre tunelog helper was generalized into beets.logging.BeetsLogger.extra_debug, which emits DEBUG messages only at verbosity level 3 or higher (for example -vvv). Plugin authors can use it via self._log.extra_debug(...).

Other changes

  • Contributing: Update pipx installation guide link
  • Getting Started: Update quick installation section to reflect current installation guide structure.
  • Installation: Remove redundant macOS section from the installation guide. :bug: (#5993)
  • Installation: Update installation guide to document plugin management with pipx and move package manager instructions to the FAQ.
  • Installation: Update pipx installation guide link
  • API-backed metadata source plugins can now use SearchApiMetadataSourcePlugin for shared search orchestration. Implement provider behavior in ~beets.metadata_plugins.SearchApiMetadataSourcePlugin.get_search_query_with_filters and ~beets.metadata_plugins.SearchApiMetadataSourcePlugin.get_search_response.
  • Deprecate the Beatport Plugin and BPSync Plugin plugins. Beatport has retired the API these plugins rely on, making them non-functional. :bug: (#3862)
v2.7.1 Bug fix

Fixed tests depending on the optional langdetect package to be skipped when unavailable.

Full changelog

Bug fixes

  • Tests that depend on the optional langdetect package are now skipped when the package is not installed. :bug: (#6421)

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

About

Stars
15,215
Forks
2,028
Languages
Python JavaScript Shell

Install & Platforms

Install via
pip

Community & Support

Open source alternatives

Beta — feedback welcome: [email protected]