Skip to content

Releases: linuxserver/docker-beets

2.5.1-ls305

09 Jan 19:03
22c23cb

Choose a tag to compare

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

08 Jan 21:14
9d6db60

Choose a tag to compare

Pre-release

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

07 Jan 23:14
d057350

Choose a tag to compare

Pre-release

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:

https://github.com/beetbox/beets/blob/1a899cc92af57b72a76570fb37d1c4eda0f5e842/beetsplug/play.py#L135

nightly-1a899cc9-ls214

07 Jan 12:13
7117dc6

Choose a tag to compare

Pre-release

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:

  • musicbrainzMusicBrainzAPIMixin
  • listenbrainzMusicBrainzAPIMixin
  • mbcollectionMusicBrainzUserAPIMixin (requires authentication)
  • missingMusicBrainzAPIMixin
  • parentworkMusicBrainzAPIMixin

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_call error wrapper (errors propagate naturally from
    RequestHandler)
  • Consolidated pagination logic into MBCollection.releases property
  • Type hints for Library, ImportSession, ImportTask parameters

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 None

parentwork: 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, workdate

Eliminates 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 core

The 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 NEEDED

Plugins 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_mock

This avoids artificial delays when mocking HTTP responses.

New Plugin Test Suites

Added comprehensive tests for previously untested plugins:

  1. test_listenbrainz.py: Tests recording ID lookup and track info
    fetching
  2. test_mbcollection.py: Tests collection validation, pagination,
    and sync operations
  3. test_missing.py: Tests missing album detection logic
  4. test/plugins/utils/test_musicbrainz.py: Tests group_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

04 Jan 03:14
b7f9656

Choose a tag to compare

Pre-release

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_keywords configuration 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, and vip
    • 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 keywords

    • Song (Remix) ft. ArtistSong ft. Artist (Remix)
  • Song (Live) [Remix] ft. ArtistSong 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. ArtistSong (Arbitrary) ft. Artist
  • Nested brackets: Correctly handles nested brackets of same and
    different types

  • Song (Remix [Extended]) ft. ArtistSong ft. Artist (Remix [Extended])

  • Multiple brackets: Picks the earliest bracket containing keywords

  • Song (Live) (Remix) ft. ArtistSong 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.)

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

02 Jan 19:03
4f7a422

Choose a tag to compare

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

02 Jan 19:04
2de99d5

Choose a tag to compare

Pre-release

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

29 Dec 18:13
9718d51

Choose a tag to compare

Pre-release

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

30 Dec 21:08
9718d51

Choose a tag to compare

Pre-release

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

01 Jan 16:09
9718d51

Choose a tag to compare

Pre-release

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