Releases: linuxserver/docker-beets
2.5.1-ls305
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/2.5.1-ls305/index.html
LinuxServer Changes:
Full Changelog: 2.5.1-ls304...2.5.1-ls305
Remote Changes:
Updating PIP version of beets to 2.5.1
nightly-c04fc95e-ls216
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-c04fc95e-ls216/index.html
LinuxServer Changes:
Full Changelog: nightly-ad2ff1f9-ls215...nightly-c04fc95e-ls216
Remote Changes:
Merge #6213 - Fix ftintitle plugin to prioritize explicit featuring tokens
nightly-ad2ff1f9-ls215
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-ad2ff1f9-ls215/index.html
LinuxServer Changes:
Full Changelog: nightly-1a899cc9-ls214...nightly-ad2ff1f9-ls215
Remote Changes:
Fix fetchart colors broken by 67e668d81ff03d7ce14671e68676a7ad9d0ed94a (#6273)
Fetchart colors were broken by 67e668d81ff03d7ce14671e68676a7ad9d0ed94a.
Since that commit fetchart displays this, with escape codes instead of
colorized output:
fetchart: Alasdair Fraser & Natalie Haas - Meridians: �[37mhas album art�[39;49;00m
This fixes it by using ui.print_ instead of Logger. This seems to be
in line with other plugins such as play:
nightly-1a899cc9-ls214
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-1a899cc9-ls214/index.html
LinuxServer Changes:
Full Changelog: nightly-ea2e7bf9-ls213...nightly-1a899cc9-ls214
Remote Changes:
Drop dependency on python-musicbrainzngs (#6234)
Replace python-musicbrainzngs with Custom Lightweight MusicBrainz
Client
Core Problem Solved
Before: Beets depended on the external python-musicbrainzngs
library (v0.7.1) for all MusicBrainz API interactions. This dependency
required separate installation for multiple plugins and introduced an
abstraction layer that obscured direct HTTP semantics.
After: Custom lightweight MusicBrainz client
(beetsplug.utils.musicbrainz) built directly on requests and
requests-ratelimiter, eliminating the external dependency while
maintaining full API compatibility.
Architecture Overview
1. New MusicBrainz Client Foundation
Created beetsplug/utils/musicbrainz.py with three core components:
MusicBrainzAPI (base client)
├─ Configuration-driven initialization (from config['musicbrainz'])
├─ Rate-limited session via LimiterTimeoutSession
├─ Generic entity fetching (get_entity, search_entity)
├─ Specialized methods (get_release, get_recording, get_work)
└─ Recursive relation grouping (group_relations)
MusicBrainzUserAPI (authenticated operations)
├─ Extends MusicBrainzAPI with HTTPDigestAuth
├─ Collection management (get_collections)
└─ User-specific API operations
MBCollection (collection manipulation)
├─ Paginated release fetching
├─ Chunked PUT operations (add_releases)
└─ Chunked DELETE operations (remove_releases)
**2. Config parsing now centralized in
MusicBrainzAPI.__post_init__()**.
And applies to all plugins.
3. Mixin Pattern for Plugin Integration
# OLD: Plugins directly instantiated musicbrainzngs
import musicbrainzngs
musicbrainzngs.auth(user, pass)
resp = musicbrainzngs.get_recording_by_id(id, includes=['releases'])
# NEW: Plugins inherit MusicBrainzAPIMixin
class MyPlugin(MusicBrainzAPIMixin, BeetsPlugin):
def some_method(self):
recording = self.mbapi.get_recording(id, includes=['releases'])Affected plugins:
musicbrainz→MusicBrainzAPIMixinlistenbrainz→MusicBrainzAPIMixinmbcollection→MusicBrainzUserAPIMixin(requires authentication)missing→MusicBrainzAPIMixinparentwork→MusicBrainzAPIMixin
Plugin-Specific Refactoring
mbcollection: From Procedural to Object-Oriented
Before: Module-level functions (submit_albums, mb_call) with
error handling scattered across multiple try-except blocks.
After: MBCollection dataclass encapsulating collection operations:
# OLD approach
collection_id = get_collection()
albums_in_collection = get_albums_in_collection(collection_id)
submit_albums(collection_id, album_ids)
# NEW approach
collection = self.collection # cached property with validation
collection.add_releases(album_ids)
collection.remove_releases(removed_ids)Key improvements:
- Eliminated
mb_callerror wrapper (errors propagate naturally from
RequestHandler) - Consolidated pagination logic into
MBCollection.releasesproperty - Type hints for
Library,ImportSession,ImportTaskparameters
listenbrainz: Search Query Simplification
Before: Called musicbrainzngs.search_recordings() with constructed
query strings.
After: Uses self.mbapi.search_entity() which handles query
formatting:
# OLD
resp = musicbrainzngs.search_recordings(
query=f'track:{track_name} AND release:{album_name}',
strict=True
)
if resp.get('recording-count') > 1:
return resp['recording-list'][0].get('id')
# NEW
for recording in self.mbapi.search_entity('recording',
{'track': track_name,
'release': album_name}):
return recording['id']
return Noneparentwork: Inline Traversal Logic
Before: Module-level functions (direct_parent_id,
work_parent_id, find_parent_work_info) that made sequential API
calls.
After: Single method find_parent_work_info that traverses work
hierarchy inline:
def find_parent_work_info(self, mbworkid: str) -> tuple[dict, str | None]:
workdate = None
parent_id = mbworkid
while parent_id := current_id: # walrus operator
workinfo = self.mbapi.get_work(current_id, includes=['work-rels', 'artist-rels'])
workdate = workdate or extract_composer_date(workinfo)
parent_id = find_parent_in_relations(workinfo)
return workinfo, workdateEliminates three function calls per traversal level.
Dependency & Installation Impact
Package Dependencies
# pyproject.toml
-musicbrainzngs = {version = ">=0.4", optional = true}
# Removed from extras groups
-listenbrainz = ["musicbrainzngs"]
-mbcollection = ["musicbrainzngs"]
-missing = ["musicbrainzngs"]
-parentwork = ["musicbrainzngs"]Result: Four plugin extras no longer require external package
installation beyond requests (already a core dependency).
CI Workflow
# .github/workflows/ci.yaml
-poetry install --extras=parentwork
+poetry install # parentwork now part of coreThe parentwork extra is removed entirely since it had no other
dependencies.
Documentation Updates
Removed "Installation" sections from four plugin docs that previously
required:
pip install beets[listenbrainz] # NO LONGER NEEDED
pip install beets[mbcollection] # NO LONGER NEEDED
pip install beets[missing] # NO LONGER NEEDED
pip install beets[parentwork] # NO LONGER NEEDEDPlugins now work out-of-the-box with just plugins: [listenbrainz, ...] in config.
Testing Improvements
New Shared Fixture: requests_mock
Created test/plugins/conftest.py with fixture that disables rate
limiting during tests:
@pytest.fixture
def requests_mock(requests_mock, monkeypatch):
"""Use plain session wherever MB requests are mocked."""
monkeypatch.setattr(
'beetsplug.utils.musicbrainz.MusicBrainzAPI.session',
requests.Session,
)
return requests_mockThis avoids artificial delays when mocking HTTP responses.
New Plugin Test Suites
Added comprehensive tests for previously untested plugins:
test_listenbrainz.py: Tests recording ID lookup and track info
fetchingtest_mbcollection.py: Tests collection validation, pagination,
and sync operationstest_missing.py: Tests missing album detection logictest/plugins/utils/test_musicbrainz.py: Testsgroup_relations
transformation
Test Migration
Moved test_group_relations from test_musicbrainz.py to
test/plugins/utils/test_musicbrainz.py (84 lines) since
group_relations is now a utility function.
Migration Benefits
| Aspect | Before | After |
| ------------------------- | ------------------------------------ |
------------------------------------------- |
| External dependencies | python-musicbrainzngs (0.7.1) | None
(uses existing requests) |
| Plugin installation | pip install beets[plugin] required | Works
with base install |
| API surface area | ~50 functions in musicbrainzngs | ~10 methods
tailored to Beets |
| Error messages | Generic exceptions with status codes | Full HTTP
response text included |
| Response structure | Raw MusicBrainz JSON | Normalized with
grouped relations |
| Code ownership | External maintenance dependency | Direct control
over API client |
| Test speed | Rate-limited even with mocks | Fixture disables
limits for mocked requests |
| Type safety | Minimal type hints in musicbrainzngs | Full type
hints (JSONDict, list[str]) |
Backward Compatibility
✅ Fully backward compatible:
- All existing plugin APIs unchanged from user perspective
- Configuration keys remain identical (
musicbrainz.user,
musicbrainz.pass, etc.) - MusicBrainz API responses maintain same structure (with additional
normalization) - Test suite passes without modification to integration tests
Breaking changes: None from end-user perspective.
closes #6265
nightly-ea2e7bf9-ls213
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-ea2e7bf9-ls213/index.html
LinuxServer Changes:
Full Changelog: nightly-afc26fa5-ls212...nightly-ea2e7bf9-ls213
Remote Changes:
feat(ftintitle): Insert featured artist before track variant clause (#6159)
Summary
This PR updates the ftintitle plugin to insert featured artist tokens
before brackets containing remix/edit-related keywords (e.g., "Remix",
"Live", "Edit") instead of always appending them at the end of the
title.
Motivation
Previously, the plugin would always append featured artists at the end
of titles, resulting in awkward formatting like:
Threshold (Myselor Remix) ft. Hallucinator
With this change, featured artists are inserted before the first bracket
containing keywords, producing cleaner formatting:
Threshold ft. Hallucinator (Myselor Remix)
Changes
Core Functionality
- Added
find_bracket_position()function that:- Searches for brackets containing remix/edit-related keywords
- Supports multiple bracket types:
(),[],<>,{} - Only matches brackets with matching opening/closing pairs
- Uses case-insensitive word-boundary matching for keywords
- Returns the position of the earliest matching bracket
- Updated
update_metadata()to insert featured artists before brackets
instead of appending
Configuration
- Added new
bracket_keywordsconfiguration option: - Default: List of keywords including:
abridged,acapella,
club,demo,edit,edition,extended,instrumental,live,
mix,radio,release,remaster,remastered,remix,rmx,
unabridged,unreleased,version, andvip- Customizable: Users can override with their own keyword list
- Empty list: Setting to
[]matches any bracket content regardless
of keywords
Example Configuration
ftintitle:
bracket_keywords: ["remix", "live", "edit", "version", "extended"]Behavior
-
Titles with keyword brackets: Featured artists are inserted before
the first bracket containing keywordsSong (Remix) ft. Artist→Song ft. Artist (Remix)
-
Song (Live) [Remix] ft. Artist→Song ft. Artist (Live) [Remix]
(picks first bracket with keyword) -
Titles without keyword brackets: Featured artists are appended at
the end (backward compatible)Song (Arbitrary) ft. Artist→Song (Arbitrary) ft. Artist
-
Nested brackets: Correctly handles nested brackets of same and
different types -
Song (Remix [Extended]) ft. Artist→Song ft. Artist (Remix [Extended]) -
Multiple brackets: Picks the earliest bracket containing keywords
-
Song (Live) (Remix) ft. Artist→Song ft. Artist (Live) (Remix)
(if both contain keywords, picks first)
Testing
- Added comprehensive test coverage for:
- Different bracket types (
(),[],<>,{}) - Nested brackets (same and different types)
- Multiple brackets
- Custom keywords
- Empty keyword list behavior
- Edge cases (unmatched brackets, no brackets, etc.)
- Different bracket types (
All 112 tests pass.
Backward Compatibility
This change is backward compatible:
- Titles without brackets continue to append featured artists at the end
- Titles with brackets that don't contain keywords also append at the
end - Existing configuration files continue to work (uses sensible defaults)
Documentation
- Updated changelog with detailed description of the new feature
- Configuration option is documented in the changelog entry
2.5.1-ls304
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/2.5.1-ls304/index.html
LinuxServer Changes:
Full Changelog: 2.5.1-ls303...2.5.1-ls304
Remote Changes:
Updating PIP version of beets to 2.5.1
nightly-afc26fa5-ls212
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-afc26fa5-ls212/index.html
LinuxServer Changes:
Full Changelog: nightly-1b701c86-ls211...nightly-afc26fa5-ls212
Remote Changes:
Add packaging note about mock dependency removal
nightly-ed566eb1-ls211
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-ed566eb1-ls211/index.html
LinuxServer Changes:
Full Changelog: nightly-21e6a1f7-ls210...nightly-ed566eb1-ls211
Remote Changes:
Fix original release id access for a pseudo release (#6250)
Fixes #6248
nightly-e0489097-ls211
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-e0489097-ls211/index.html
LinuxServer Changes:
No changes
Remote Changes:
lastgenre: Finalize type hints in plugin (#6239)
Description
Add type hints to the few remaining methods and helpers that didn't have
them already.
nightly-afc26fa5-ls211
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-afc26fa5-ls211/index.html
LinuxServer Changes:
No changes
Remote Changes:
Add packaging note about mock dependency removal