Skip to content

Releases: linuxserver/docker-beets

nightly-83017c06-ls267

14 Apr 20:11
1e2aac7

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-83017c06-ls267/index.html

LinuxServer Changes:

Full Changelog: nightly-ff9f93e6-ls266...nightly-83017c06-ls267

Remote Changes:

docs: Added link and description to the beets-aisauce plugin (#6534)

see beetbox/beets#5922
and https://github.com/metasauce/beets-aisauce

nightly-80cd2155-ls268

15 Apr 20:05
f0a0d1a

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-80cd2155-ls268/index.html

LinuxServer Changes:

No changes

Remote Changes:

inline: make the album/item available directly (#5439)

There have been multiple requests, in the past, for the ability to use
plugin fields in inline fields. This has not previously been available.
From what I can tell, it was intentionally left unavailable due to
performance concerns.

The way the item fields are made available to the inline python code
means that all fields are looked up, whether they're actually used by
the code or not. Doing that for all computed fields would be a
performance concern.

I don't believe there's a good way to postpone the field computation, as
python eval and compile requires that globals be a dictionary, not a
mapping. Instead, we can make available the album or item model object
to the code directly, and let the code access the fields it needs via
that object, resulting in postponing the computation of the fields until
they're actually accessed.

This is a simple approach that makes the computed and plugin fields
available to inline python, which allows for more code reuse, as well as
more options for shifting logic out of templates and into python code.
The object is available as db_obj.

Examples:

item_fields:
  test_file_size: db_obj.filesize

album_fields:
  test_album_path: db_obj.path
  # If the missing plugin is enabled
  test_album_missing: db_obj.missing

nightly-37d60dd2-ls268

16 Apr 10:11
f0a0d1a

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-37d60dd2-ls268/index.html

LinuxServer Changes:

No changes

Remote Changes:

Populate extra artists for discogs (#6509)

Discogs: Populate multi-value contributor fields from artist roles

Fixes: #6380

This PR extends the Discogs plugin to properly populate beets'
remixers, lyricists, composers, and arrangers fields from
Discogs artist role credits.

What changed

beetsplug/discogs/states.py — Core logic change in ValidArtist
and ArtistState:

  • The is_feat: bool field on ValidArtist is replaced with role: str, preserving the raw Discogs role string rather than discarding it
    after a single boolean check.
  • A new main_artists property filters to artists with no role or a
    featuring role — this replaces the previous filtering that happened at
    parse time, so valid_artists now includes all artists regardless of
    role.
  • artists, artists_credit, and artists_ids are updated to source
    from main_artists instead of valid_artists, ensuring non-primary
    contributors (e.g. composers, arrangers) no longer appear in artist
    identity fields.
  • New properties — lyricists, arrangers, remixers, composers
    derive their values from valid_artists by substring-matching on the
    role field (e.g. "lyrics", "producer", "remix", "written-by").

beetsplug/discogs/types.pyArtistInfo typed dict gains four
optional fields: arrangers, composers, remixers, lyricists.

Test changes

  • test_anv is refactored from a flat parametrised function into a
    TestAnv class with a shared album_info fixture, splitting the
    assertions across focused test methods per field group.
  • test_parse_featured_artists gains assertions for artists_ids and
    composers.
  • A new test_parse_extraartist_roles test covers role-to-field mapping
    end-to-end for all four contributor types.

Impact

Discogs imports now populate contributor fields that were previously
only filled by the MusicBrainz plugin.

nightly-1d01689a-ls268

14 Apr 23:45
f0a0d1a

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-1d01689a-ls268/index.html

LinuxServer Changes:

Full Changelog: nightly-83017c06-ls267...nightly-1d01689a-ls268

Remote Changes:

chroma: gate candidates on musicbrainz plugin being enabled (#6532)

Fixes #6212.
Fixes #6441.

Split out from #6522 per @semohr's suggestion.

The chroma plugin unconditionally instantiated its own private
MusicBrainzPlugin instance and called album_for_id /
track_for_id on it regardless of the user's plugin configuration.
This meant MusicBrainz-sourced candidates appeared during tagging even
when the user had not enabled the musicbrainz plugin.

This PR replaces the direct instantiation with a lookup through the
metadata-source registry (get_metadata_source("musicbrainz")).
When the musicbrainz plugin is not loaded, both candidates and
item_candidates short-circuit and return empty. Acoustid
fingerprinting itself is unaffected — acoustid_id and
acoustid_fingerprint fields are still populated as before.

Side-effect fix

The previous cached_property mb = MusicBrainzPlugin() pattern
created a second instance outside the plugin registry. This bypassed
any plugin that swaps the musicbrainz instance at runtime (e.g.
mbpseudo). Routing through get_metadata_source fixes this —
chroma now uses the same shared instance as the rest of beets.

Scope

This PR is intentionally scoped to the gating fix only. The richer
cross-reference routing (extracting Spotify/Discogs/etc. IDs from
MusicBrainz url-relations and dispatching to enabled external
plugins) is being developed separately in #6522.

nightly-ff9f93e6-ls266

12 Apr 16:36
518517d

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-ff9f93e6-ls266/index.html

LinuxServer Changes:

Full Changelog: nightly-c6409d2e-ls265...nightly-ff9f93e6-ls266

Remote Changes:

Type musicbrainz (#6329)

Add full type coverage to the MusicBrainz plugin

This PR introduces complete static typing for
beetsplug/musicbrainz.py, beetsplug/mbpseudo.py, and
beetsplug/_utils/musicbrainz.py, and enforces it via mypy strict
mode.

Key changes

New TypedDict schema in beetsplug/_utils/musicbrainz.py

A comprehensive set of TypedDict classes now models the full
MusicBrainz API response surface — Release, Recording, Track,
ReleaseGroup, Artist, ArtistCredit, Work, and all relation
types. Public API methods (get_release, get_recording, etc.) now
return these typed shapes instead of the opaque JSONDict.

Key normalization: dash-to-underscore

The internal _normalize_data method (previously _group_relations)
now also converts all hyphenated keys (e.g. artist-credit,
release-group, sort-name) to underscored equivalents (e.g.
artist_credit, release_group, sort_name) at parse time. This makes
the data structure Python-idiomatic and is what allows the TypedDict
definitions to use clean attribute names. All downstream field accesses,
test fixtures, and JSON test resources are updated accordingly.

mypy strict mode enabled for the three affected modules via
setup.cfg.

Impact

  • No behaviour change for end users — this is purely an internal
    refactor.
  • Code reading the MusicBrainz response now has IDE completion and
    type-checking support.
  • The normalization boundary is clearly established at
    _normalize_data, so callers never see raw hyphenated keys.
  • Test fixtures and resource JSON files are updated to match the new
    normalized shape.

2.9.0-ls324

11 Apr 12:41
1dbdba3

Choose a tag to compare

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/2.9.0-ls324/index.html

LinuxServer Changes:

Full Changelog: 2.8.0-ls323...2.9.0-ls324

Remote Changes:

Updating PIP version of beets to 2.9.0

nightly-c6409d2e-ls265

11 Apr 15:35
fd8b414

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-c6409d2e-ls265/index.html

LinuxServer Changes:

Full Changelog: nightly-26485613-ls264...nightly-c6409d2e-ls265

Remote Changes:

Retry listenbrainz requests for temporary failures

nightly-26485613-ls264

11 Apr 12:40
9dc18aa

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-26485613-ls264/index.html

LinuxServer Changes:

Full Changelog: nightly-0f2bb215-ls263...nightly-26485613-ls264

Remote Changes:

Store relative paths in the DB to make beets library portable (#6460)

Migrate Item & Album Paths to Library-Relative Storage

Fixes: #133

Core Problem Solved

Before: Beets stored absolute file system paths in SQLite (e.g.
/home/user/Music/Artist/album/track.mp3). This made library databases
non-portable — moving the music directory or sharing a database across
machines broke all path references.

After: Paths are stored relative to the music directory (e.g.
Artist/album/track.mp3), and expanded back to absolute paths
transparently on read. The database is now portable.


Architecture Changes

1. Context Variable for Music Directory (beets/context.py)

A new contextvars.ContextVar (_music_dir_var) holds the active music
directory, set once during Library.__init__ via
context.set_music_dir(). This avoids passing library.directory
through every call stack.

Library.__init__()
    └─ context.set_music_dir(self.directory)   ← single write point
           ↓
    PathType.to_sql()  / PathType.from_sql()   ← read at DB layer
    pathutils.normalize_path_for_db()
    pathutils.expand_path_from_db()

2. Path Relativization Moved to the DB Layer (beets/dbcore/)

Previously, path conversion lived in Item._setitem /
Item.__getitem__ — model-specific overrides. It is now pushed down
into PathType.to_sql / PathType.from_sql in dbcore/types.py,
through two helpers in the new beets/dbcore/pathutils.py:

Helper Direction Behaviour
normalize_path_for_db(path) write Strips music dir prefix →
relative path
expand_path_from_db(path) read Prepends music dir → absolute
path

All models using PathType (currently Item and Album) benefit
automatically — no per-model overrides required.

3. PathQuery Updated for Relative Storage

(beets/dbcore/query.py)

Queries like path:/home/user/Music/Artist now normalize the search
term to its relative form before hitting the database, so SQL
comparisons match stored values correctly. Both col_clause (SQL path)
and match (in-memory path) use normalize_path_for_db.

4. One-Time Database Migration (RelativePathMigration)

Existing absolute paths in path and artpath columns are migrated on
startup:

Library startup
    └─ _migrate()
           └─ RelativePathMigration._migrate_data(Item, Album)
                  └─ _migrate_field("path")
                  └─ _migrate_field("artpath")
                         ↓
                  Reads rows where field starts with b"/"
                  Writes os.path.relpath(path, music_dir) in batches

self.directory assignment was moved before super().__init__() in
Library.__init__ so the migration can access the music dir when it
runs.

5. Context Propagation to Background Threads

The music dir context variable must be available in worker threads
(pipeline stages, replaygain pool). Two propagation points were added:

  • beets/util/pipeline.py: Pipeline.run_parallel() snapshots the
    calling context with contextvars.copy_context() and passes a
    per-thread copy to each PipelineThread. Each stage coroutine is
    invoked via ctx.run(...).
  • beetsplug/replaygain.py: Pool workers and their callbacks are
    wrapped in ctx.run(...) so expand_path_from_db works correctly
    inside the process pool.
  • beets/util/__init__.py: par_map similarly propagates context
    into its thread pool workers.

Data Flow: Read & Write Path

item.path = "/home/user/Music/Artist/track.mp3"   ← absolute on write
      ↓
PathType.to_sql()
      ↓
normalize_path_for_db()  →  b"Artist/track.mp3"   ← stored in SQLite

item.path                                           ← absolute on read
      ↑
PathType.from_sql()
      ↑
expand_path_from_db()    ←  b"Artist/track.mp3"   ← fetched from SQLite

Key Invariants

  • Public API unchanged: item.path always returns an absolute
    bytes path.
  • Raw DB value is relative: direct SQL reads return the relative
    form (tests assert both).
  • Paths outside the music dir are stored as-is (e.g. IPFS paths —
    see beetsplug/ipfs.py fix).
  • Migration is idempotent: rows already relative (no leading /)
    are skipped.

nightly-1eff9865-ls263

11 Apr 11:28
987057e

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-1eff9865-ls263/index.html

LinuxServer Changes:

No changes

Remote Changes:

Support for python 3.14 (#6267)

This PR adds testing targets for version 3.14, enabling us to verify
compatibility with Python 3.14.

closes #6232

I would also like to see this addition included here as it will
introduce issues for 3.14 users if not
beetbox/pyacoustid#90

image

nightly-0f2bb215-ls263

11 Apr 01:19
987057e

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-0f2bb215-ls263/index.html

LinuxServer Changes:

Full Changelog: nightly-72b1118a-ls262...nightly-0f2bb215-ls263

Remote Changes:

Slightly simplify lastgenre client (#6495)

Consolidate Last.fm genre fetching into a single fetch method

This PR simplifies the lastgenre client API by replacing three
separate fetch methods (fetch_track_genre, fetch_album_genre,
fetch_artist_genre) with a single unified fetch(kind, obj) method.

What changed

client.py:

  • Introduces a class-level FETCH_METHODS registry (ClassVar dict)
    mapping fetch "kinds" ("track", "album", "artist",
    "album_artist") to a (pylast_method, arg_extractor) tuple.
  • Replaces the three fetch_* methods with a single fetch(kind, obj)
    that dispatches via this registry.
  • Removes a private _tags_for wrapper — its logic is inlined into the
    now-public fetch_genres.
  • Drops the workaround for a pylast.Album.get_top_tags() inconsistency
    fixed in 2014.

__init__.py:

  • All call sites updated to use client.fetch(kind, obj) — the client
    now owns field extraction (e.g. obj.artist, obj.album), removing
    that concern from the plugin layer.

test_lastgenre.py:

  • Test mocking simplified: a single monkeypatch on
    LastFmClient.fetch replaces three separate method patches.

Impact

  • Reduced surface area: one method to mock, test, and reason about
    instead of three.
  • Field extraction centralised: callers no longer need to know which
    fields to pass per entity type.
  • No behaviour change — pure refactor.