Beets now requires Python 3.10 or later since support for EOL Python 3.9 has been dropped.
New features
- Added support for Python 3.13.
- Convert Plugin:
forcecan be passed to override checks like no_convert, never_convert_lossy_files, same format, and max_bitrate - Discogs Plugin: Added support for multi value fields. 🐛 (#6068)
- EmbedArt Plugin: Embedded arts can now be cleared during import with the
clearart_on_importconfig option. Also,beet clearartis only going to update the files matching the query and with an embedded art, leaving untouched the files without. - FetchArt Plugin: Added config setting for a fallback cover art image.
- FetchArt Plugin: Fix colorized output text.
- Fish Plugin: Filenames are now completed in more places, like after import.
- FtInTitle Plugin: Added album template value
album_artist_no_feat. - FtInTitle Plugin: Added argument for custom feat. words in ftintitle.
- FtInTitle Plugin: Added argument to skip the processing of artist and album artist are the same in ftintitle.
- FtInTitle Plugin: Featured artists are now inserted before brackets containing remix/edit-related keywords (e.g., "Remix", "Live", "Edit") instead of being appended at the end. This improves formatting for titles like "Song 1 (Carol Remix) ft. Bob" which becomes "Song 1 ft. Bob (Carol Remix)". A variety of brackets are supported and a new
bracket_keywordsconfiguration option allows customizing the keywords. Settingbracket_keywordsto an empty list matches any bracket content regardless of keywords. - ImportSource Plugin: Added new plugin that tracks original import paths and optionally suggests removing source files when items are removed from the library.
- LastGenre Plugin: For tuning plugin settings
-vvvcan be passed to receive extra verbose logging around last.fm results and how they are resolved. Theextended_debugconfig setting and--debugoption have been removed. - MusicBrainz Plugin: Allow selecting tags or genres to populate the genres tag.
- MusicBrainz Pseudo-Release Plugin: Add a new MusicBrainz Pseudo-Release Plugin plugin to proactively receive MusicBrainz pseudo-releases as recommendations during import.
- Play Plugin: Added $playlist marker to precisely edit the playlist filepath into the command calling the player program.
- Random Plugin: Added
--fieldoption to specify which field to use for equal-chance sampling (default:albumartist). - Spotify Plugin: Added support for multi-artist albums and tracks, saving all contributing artists to the respective fields.
- Titlecase Plugin: Add the Titlecase Plugin plugin to allow users to resolve differences in metadata source styles.
Bug fixes
- Errors in metadata plugins during autotage process will now be logged but won't crash beets anymore. If you want to raise exceptions instead, set the new configuration option
raise_on_errortoyes🐛 (#5903), 🐛 (#4789). - Fix a bug introduced in release 2.4.0 where import from any valid import-log-file always threw a "none of the paths are importable" error.
- Handle potential OSError when unlinking temporary files in ArtResizer. 🐛 (#5615)
- Running beet --config <mypath> config -e now edits <mypath> rather than the default config path. 🐛 (#5652)
- Sanitize log messages by removing control characters preventing terminal rendering issues.
- When hardlinking from a symlink (e.g. importing a symlink with hardlinking enabled), dereference the symlink then hardlink, rather than creating a new (potentially broken) symlink 🐛 (#5676)
- When using FromFilename Plugin together with Edit Plugin, temporary tags extracted from filenames are no longer lost when discarding or cancelling an edit session during import. 🐛 (#6104)
- Command-Line Interface: Fix 'from_scratch' option for singleton imports: delete all (old) metadata when new metadata is applied. 🐛 (#3706)
- Convert Plugin:
auto_keepnow respectsno_convertandnever_convert_lossy_fileswhen deciding whether to copy/transcode items, avoiding extra lossy duplicates. - Discogs Plugin: Fixed unexpected flex attr from the Discogs plugin. 🐛 (#6177)
- FtInTitle Plugin: Fixed artist name splitting to prioritize explicit featuring tokens (feat, ft, featuring) over generic separators (&, and), preventing incorrect splits when both are present.
- Inline Plugin: Fix recursion error when an inline field definition shadows a built-in item field (e.g., redefining
track_no). Inline expressions now skip self-references during evaluation to avoid infinite recursion. 🐛 (#6115) - LastGenre Plugin: Canonicalize genres when
forceandkeep_existingareon, yet no genre info on lastfm could be found. 🐛 (#6303) - LastGenre Plugin: Fix the issue where last.fm doesn't return any result in the artist genre stage because "concatenation" words in the artist name (like "feat.", "+", or "&") prevent it. Using the albumartists list field and fetching a genre for each artist separately improves the chance of receiving valid results in that stage.
- Lyrics Plugin: Accepts strings for lyrics sources (previously only accepted a list of strings). 🐛 (#5962)
- Smart Playlist Plugin: Fixed an issue where multiple queries in a playlist configuration were not preserving their order, causing items to appear in database order rather than the order specified in the config. 🐛 (#6183)
- Spotify Plugin: The plugin now gracefully handles audio-features API deprecation (HTTP 403 errors). When a 403 error is encountered from the audio-features endpoint, the plugin logs a warning once and skips audio features for all remaining tracks in the session, avoiding unnecessary API calls and rate limit exhaustion.
- Spotify Plugin: Updated Spotify API credentials. 🐛 (#6270)
- Web Plugin: repair broken /item/values/… and /albums/values/… endpoints. Previously, due to single-quotes (ie. string literal) in the SQL query, the query eg. GET /item/values/albumartist would return the literal "albumartist" instead of a list of unique album artists.
- update Edit Plugin fix display formatting of field changes to clearly show added and removed flexible fields.
For plugin developers
-
A new plugin event,
album_matched, is sent when an album that is being imported has been matched to its metadata and the corresponding distance has been calculated. -
Added a reusable requests handler which can be used by plugins to make HTTP requests with built-in retry and backoff logic. It uses beets user-agent and configures timeouts. See beetsplug._utils.requests.RequestHandler for documentation.
-
Replaced dependency on
python-musicbrainzngswith a lightweight custom MusicBrainz client implementation and updated relevant plugins accordingly:- ListenBrainz Plugin
- MusicBrainz Collection Plugin
- MusicBrainz Pseudo-Release Plugin
- Missing Plugin
- MusicBrainz Plugin
- ParentWork Plugin
See beetsplug._utils.musicbrainz.MusicBrainzAPI for documentation.
For packagers
- An unused dependency on
mockhas been removed. - The minimum supported Python version is now 3.10.
Other changes
- Finally removed gmusic plugin and all related code/docs as the Google Play Music service was shut down in 2020.
- Moved beets/random.py into beetsplug/random.py to cleanup core module.
- Refactored the
beets/ui/commands.pymonolithic file (2000+ lines) into multiple modules within thebeets/ui/commandsdirectory for better maintainability. - The documentation chapter Handling Paths has been moved to the "For Developers" section and revised to reflect current best practices (pathlib usage).
- Updated color documentation with
bright_*andbg_bright_*entries. - BPD Plugin: Raise ImportError instead of ValueError when GStreamer is unavailable, enabling
importorskipusage in pytest setup. - dbcore: Allow models to declare SQL indices; add an
items.album_idindex to speed upalbum.items()queries. 🐛 (#5809)