Skip to content

Releases: linuxserver/docker-beets

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.

2.8.0-ls323

10 Apr 19:31
add00f4

Choose a tag to compare

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/2.8.0-ls323/index.html

LinuxServer Changes:

Full Changelog: 2.8.0-ls322...2.8.0-ls323

Remote Changes:

Updating PIP version of beets to 2.8.0

nightly-72b1118a-ls262

10 Apr 18:51
bbecae4

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-72b1118a-ls262/index.html

LinuxServer Changes:

Full Changelog: nightly-fd586ef6-ls261...nightly-72b1118a-ls262

Remote Changes:

Support multi-valued fields in rewrite and advancedrewrite plugins (#6518)

Fix rewriting of multi-valued fields (rewrite / advancedrewrite

plugins)

Bug: Both rewrite and advancedrewrite plugins assumed all field
values are scalars, so list-type fields (e.g. genres) were not
rewritten correctly. Additionally, only the first matching rule was ever
applied to a field.


What changed

Core logic (beetsplug/rewrite.py):

  • Introduced a rewrite_value singledispatch function to handle both
    str and list[str] values. For lists, each element is rewritten
    individually.
  • Extracted apply_rewrite_rules as a shared utility — now applies
    all matching rules in config order (previously stopped at the first
    match).

advancedrewrite plugin:

  • Replaced its own inline rule-matching loop with a call to the shared
    apply_rewrite_rules, fixing list field support there too.

Behaviour change — rule application order:

Previously, only the first matching rule was applied. Now, all rules run
in config order, allowing chained rewrites. For example:

rewrite:
    artist .*hendrix.*: hendrix catalog
    artist .*catalog.*: Experience catalog

This now produces "Experience catalog" instead of "hendrix catalog".

nightly-fd586ef6-ls261

09 Apr 21:48
2924767

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-fd586ef6-ls261/index.html

LinuxServer Changes:

Full Changelog: nightly-2334b222-ls260...nightly-fd586ef6-ls261

Remote Changes:

lastgenre: Genre ignorelist (#6449)

Description

Adds a global and artist-specific genre ignorelist to lastgenre.
Ignorelist entries can use regex patterns or literal genre names and are
configurable per artist or globally. For config examples see submitted
docs and _load_ignorelist() docstring.

Additional minor refactoring

  • Fixed condition in "keep original fallback stage" to use config view
    object directly via .get().
  • Deduplicate finding the correct artist/albumartist attribute with a
    helper _artist_for_helper

nightly-2334b222-ls260

09 Apr 16:28
78b100a

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-2334b222-ls260/index.html

LinuxServer Changes:

Full Changelog: nightly-06512c9b-ls259...nightly-2334b222-ls260

Remote Changes:

Import MusicBrainz composer/lyricist/arranger ids (#5847)

Updates the MusicBrainz plugin to also import MBIDs for
composers/lyricists/arrangers, and adds them as multi-valued fields.

Closes #5698.

nightly-e7c7bfca-ls259

06 Apr 20:36
fae15d1

Choose a tag to compare

Pre-release

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-e7c7bfca-ls259/index.html

LinuxServer Changes:

No changes

Remote Changes:

Swap Discogs genres and styles (#6478)

discogs: Swap genre and style field mapping

Fixes #6390

Fixes the semantic mismatch between Discogs' taxonomy and beets' fields.
In Discogs, genres are broad (e.g. "Electronic") and styles are
specific
(e.g. "Techno"). Previously, beets stored them the wrong way
around.

What changed

In get_album_info, the source fields are swapped:

  • genres (beets) now reads from Discogs styles — the specific
    values
  • style (beets) now reads from Discogs genres — the broader
    values

The append_style_genre config option retains its original intent:
append broader style values to genres when enabled. Only the data
source for each field changed, not the logic.