Skip to content

Releases: linuxserver/docker-beets

nightly-db9e0441-ls315

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 15 Jul 20:13
6895718

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-db9e0441-ls315/index.html

LinuxServer Changes:

Full Changelog: nightly-35e48114-ls314...nightly-db9e0441-ls315

Remote Changes:

typing: add types to beets.dbcore (#6820)

What changed

  • This PR is mainly a typing pass over beets.dbcore, especially in
    db.py, query.py, sort.py, and types.py.
  • The main architecture change is in db.py: model materialization is
    now centralized in Database._make_model(), while Results only
    handles lazy row iteration and uses a materializer callback.
  • Reloading is also cleaner now: Model.get_fresh_from_db() goes
    through Database._reload(), which keeps database lookup logic in the
    database layer and preserves the model's concrete type.
  • Along the way, a few runtime mismatches found by typing were corrected
    instead of just adjusting annotations.

Why

  • The goal is to make the typed API match the real behavior of dbcore.
  • This improves separation of responsibilities: querying and result
    iteration stay in Results, while row-to-model construction and reload
    behavior live in Database.

High-level impact

  • dbcore has a clearer internal structure and a more explicit API
    surface.
  • Type information is more accurate across queries, sorting, models, and
    field types, which should make future refactors safer.
  • A few small correctness fixes are included:
    • DurationType.format() now always returns str.
  • PathType.from_sql() only expands paths when the normalized value is
    actually bytes.
  • Several __eq__ implementations now use explicit isinstance(...)
    checks.
  • Reload behavior now has regression coverage in
    test/dbcore/test_db.py.
  • Overall, this is a low-risk cleanup: mostly typing and API-shape
    improvements, with a few small behavior fixes discovered during the
    work.

nightly-86f0f0a7-ls315

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 16 Jul 17:09
6895718

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-86f0f0a7-ls315/index.html

LinuxServer Changes:

No changes

Remote Changes:

Ignore empty deprecated genres (#6848)

While testing my custom out of date soundcloud autotagger which still
sets genre instead of genres, I realised that empty (genre = '')
is converted to genres = [''] list by
Info._get_list_from_string_value.

nightly-4e3884a6-ls315

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 17 Jul 09:44
6895718

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-4e3884a6-ls315/index.html

LinuxServer Changes:

No changes

Remote Changes:

Document art_set event (#6851)

Document art_set event

Summary

Documents the missing art_set plugin event in the developer event
reference. The entry records that the event fires after album cover art
is copied or moved into place and that handlers receive the affected
album.

Fixes #6845

AI assistance disclosure

This contribution was produced by an autonomous AI coding agent (Claude
Code) that @Dodothereal operates and monitors. @Dodothereal is
accountable for it, will address review feedback promptly, and will
close this PR immediately if this kind of contribution is unwelcome in
this project. Commits carry an Assisted-by: Claude Code trailer.

nightly-311662bd-ls315

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 16 Jul 13:16
6895718

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-311662bd-ls315/index.html

LinuxServer Changes:

No changes

Remote Changes:

typing: type beets.library (#6825)

  • This PR finishes a broad typing pass across beets.library, mainly in
    beets/library/models.py and beets/library/library.py, and tightens a
    few related plugin and dbcore interfaces.

  • Architecturally, the change makes the Library, Item, Album,
    query parsing, and template helper layers agree on clearer contracts:
    nullable values are explicit, collection and query return types are
    defined, and cross-layer APIs now use more precise types.

  • The main behavioral surface is still the same, but several weak spots
    are now modeled directly in the code, such as optional albums on items,
    optional art paths, library-backed template memoization, and query
    objects flowing through Library._fetch().

  • The PR also cleans up how template uniqueness helpers work by giving
    _memotable and related keys a stable shape, and by tightening the
    logic around album-aware formatting and disambiguation.

  • A small architectural rename in dbcore changes Database._fetch()
    to Database.get_results(), which makes the lower-level database API
    clearer and avoids confusion with Library._fetch().

  • High-level impact: better static type safety, clearer boundaries
    between database, library, model, and templating code, and less
    ambiguity for future refactors without intentionally changing
    user-facing behavior.

nightly-2cf20aa6-ls315

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 16 Jul 11:39
6895718

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-2cf20aa6-ls315/index.html

LinuxServer Changes:

No changes

Remote Changes:

discogs: refactor _coalesce_tracks around more specific track types (#6818)

  • beetsplug/discogs/types.py now models Discogs tracklist entries as
    distinct shapes: AudioTrack, IndexTrack, and HeadingTrack. This
    shifts the plugin from "guessing by position" to handling each entry
    by its declared type_.

  • beetsplug/discogs/__init__.py refactors _coalesce_tracks() into
    smaller helpers for flat subtracks and index tracks. The normalization
    flow is now type-driven, which makes the rules for "one physical track"
    vs "many physical tracks" more explicit.

  • beetsplug/discogs/states.py now advances track state only for real
    audio entries (type_ == "track"), so structural entries no longer
    affect track counting through indirect position checks.

  • Tests now use dedicated Discogs track factories and add coverage for
    logical index tracks. This gives the refactor a clearer test model and
    protects the new coalescing behavior.

  • High-level impact: Discogs imports should now treat headings, index
    containers, and subtracks more consistently, especially for releases
    where one displayed work contains multiple logical parts but maps to a
    single playable track.

This change is based on the following research (thanks, Codex!):

Discogs track coalescing

DiscogsPlugin._coalesce_tracks() handles three distinct notions all
called
"subtracks." This is the main source of its complexity.

Discogs distinguishes:

  • track: an audio entry.
  • index: a titled container whose sub_tracks are parts or movements.
  • heading: descriptive text applying to following tracks.

That distinction is documented in the current
Discogs tracklisting
guidelines
.
Positions such as 3.1, 3.2 or 3a, 3b separately indicate
multiple
musical pieces inside one physical track.

Observed input shapes

Case API shape Current result Example
Normal track type_="track", nonempty position Passed through
Most releases
Flat virtual subtracks Multiple top-level track entries with
1.1, 1.2 or 22a, 22b Merged into a copy of the first track
This Is Not, [Eclectic Beatz
8](https://www.discogs.com/release/2885582)
Nested logical subtracks index with nested children sharing one
physical position Parent index becomes the physical track; children
discarded [Defqon.1 Weekend
Festival](https://www.discogs.com/release/7168134)
Nested physical subtracks index with children numbered as
independent tracks Index discarded; children promoted [Classical
Hollywood III](https://www.discogs.com/release/3647530)
Heading heading, empty position, no sub_tracks Passed through
as structural metadata Defqon.1 CD headings
Ambiguous/nonstandard index index with positions the regex cannot
classify Usually promoted incorrectly as separate tracks [King
Crimson - Lizard](https://www.discogs.com/release/1156598)

Flat virtual subtracks

This Is Not returns nine top-level entries:

{"type_": "track", "position": "1.1", "title": "Intro", "duration": "1:49"}
{"type_": "track", "position": "1.2", "title": "Untitled", "duration": "6:31"}
# ...
{"type_": "track", "position": "1.9", "title": "2 Bad ...", "duration": "10:18"}

There is no index container. _coalesce_tracks() detects the suffixes
and
_add_merged_subtracks() produces:

{
    "type_": "track",
    "position": "1.1",
    "title": "Intro / Untitled / ... / 2 Bad ...",
    "duration": "1:49",
    "artists": ["Unknown Artist"],
}

It copies all metadata from the first entry and only combines titles.
This
explains the incorrect duration and artist.

Eclectic Beatz 8 has the same flat shape:

{"type_": "track", "position": "22a", "title": "Amplifier", "artists": ["Fedde Le Grand"]}
{"type_": "track", "position": "22b", "title": "Autograph ...", "artists": ["Hatiras", "MC Flipside"]}

Those become one track, retaining only the first artist.

Nested logical subtracks

Defqon.1 contains:

{
    "type_": "index",
    "position": "",
    "title": "Eat This / God! What The Hell!",
    "duration": "3:06",
    "sub_tracks": [
        {"type_": "track", "position": "2-20A", "title": "Eat This"},
        {"type_": "track", "position": "2-20B", "title": "God! What The Hell!"},
    ],
}

Because 2-20A has a parsed subindex, the helper changes the parent to:

{
    "type_": "index",
    "position": "2-20",
    "title": "Eat This / God! What The Hell!",
    "duration": "3:06",
}

The children disappear. Downstream code treats this as audio solely
because
its position is now nonempty. It never checks that type_ remains
"index".

Nested physical subtracks

Classical Hollywood III contains index containers such as:

{
    "type_": "index",
    "position": "",
    "title": "Auld Lang Syne Variations For Piano Quartet",
    "sub_tracks": [
        {"type_": "track", "position": "1", "title": "Eine Kleine Nichtmusik ..."},
        {"type_": "track", "position": "2", "title": "L.V.B. - Adagio"},
        {"type_": "track", "position": "3", "title": "Chaconne ..."},
        {"type_": "track", "position": "4", "title": "Homage ..."},
    ],
}

Because position 1 has no subindex:

  • the index container is removed;
  • its children become top-level physical tracks;
  • parent artists are copied to children missing artists;
  • when index_tracks is enabled, the parent title prefixes every child
    title.

This behavior was added for
issue #2318 by
PR #2355.

Headings

Defqon.1 also contains:

{
    "type_": "heading",
    "position": "",
    "title": "CD2 ⚡ Mixed By Partyraiser",
    "duration": "",
}

This is not coalesced. It passes through, but TracklistState
subsequently
treats every positionless object as structural metadata without checking
type_.

The code therefore cannot distinguish:

position == "" and type_ == "heading"
position == "" and type_ == "index"

Worse, _add_merged_subtracks() identifies a parent using only:

tracklist and not tracklist[-1]["position"]

A heading can therefore theoretically be mistaken for an index parent.

History

  • Issue #1543 reported
    flat
    positions such as 22a and 22b representing one CD track.
  • PR #2222 added both flat
    and
    nested subtrack support. Its author explicitly noted that type_ might
    identify index tracks robustly, but avoided the larger refactor.
  • Issue #2318
    demonstrated that
    nested sub_tracks can instead be independent physical tracks.
  • PR #2355 added the current heuristic:
    • parsed child subindex means logical fragments of one physical track;
    • no parsed child subindex means independent physical tracks.
  • That PR documented the heuristic as imperfect. Lizard, with Ba,
    Bb,
    Bc, and related positions, was already a known failure.

Test data does not match the API shapes

The current test helper always creates:

"type_": "track"

Consequently, test inputs such as:

_track("TRACK GROUP TITLE", sub_tracks=[...])

are not realistic. Real API data uses:

"type_": "index"

Likewise, test medium titles are manufactured as positionless track
objects
rather than heading or index objects.

Recommended direction

Before refactoring production code, introduce accurate input types and
factories:

TrackEntry = AudioTrack | IndexTrack | HeadingTrack

With approximate shapes:

class AudioTrack(TypedDict):
    type_: Literal["track"]
    position: str
    title: str
    duration: str
    artists: NotRequired[list[Artist]]


class IndexTrack(TypedDict):
    type_: Literal["index"]
    position: Literal[""]
    title: str
    duration: str
    sub_tracks: list[AudioTrack]
    artists: NotRequired[list[Artist]]


class HeadingTrack(TypedDict):
    type_: Literal["heading"]
    position: Literal[""]
    title: str
    duration: str

Then test using _track(), _index(), and _heading() factories.

The eventual normalization can dispatch structurally:

  1. track without subindex: pass through.
  2. Flat subindexed track group: apply the flat-fragment policy.
  3. index: normalize its children as logical or physical.
  4. heading: preserve as structural metadata.
  5. Never infer an index merely from position == "".

This removes the most dangerous ambiguity while preserving the
positional
heuristic where Discogs genuinely requires it.

nightly-26b26c4b-ls315

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 16 Jul 07:22
6895718

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-26b26c4b-ls315/index.html

LinuxServer Changes:

No changes

Remote Changes:

lastgenre: Fix whitelist/tree some disco/funk genres (#6841)

Description

Fixes two issues in whitelist and genre tree.

nightly-17d67980-ls315

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 15 Jul 21:56
6895718

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-17d67980-ls315/index.html

LinuxServer Changes:

No changes

Remote Changes:

replaygain: add metaflac backend (#6800)

Fixes #1203.

The replaygain plugin can't compute ReplayGain when metaflac is the only
tool available. The existing backends (command, gstreamer, audiotools,
ffmpeg) each need something heavier installed, which doesn't work on
minimal setups like a NAS where those won't install. Issue #1203 asks
for a metaflac based option so FLAC users on those setups can still tag
ReplayGain.

This adds a metaflac backend for that case. It runs metaflac --add-replay-gain to compute the gain and reads the values back with
metaflac --show-tag. I modeled it on the existing CommandBackend
since both wrap an external tool. It only handles FLAC, skips other
formats, and shifts the gain to the configured targetlevel like the
other backends.

One limitation: metaflac scans a whole album in one pass, so the files
of an album need the same sample rate and channel layout.

Before, selecting the metaflac backend failed:

$ beet replaygain
UserError: Selected ReplayGain backend metaflac is not supported.

After, the backend works and the plugin tests pass:

$ python -m pytest test/plugins/test_replaygain.py
7 passed, 4 skipped

The 4 skipped are the Opus R128 cases, which don't apply to the metaflac
path.

nightly-a85068be-ls313

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 14 Jul 17:07
f800680

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-a85068be-ls313/index.html

LinuxServer Changes:

No changes

Remote Changes:

Removed a few more unittests (#6822)

Found a few more places were it is possible to easily get rid of the
unittest setup in favor for pytest.

Touches and removes unittest from the following files:

  • test_ihate
  • test_the
  • test_substitute
  • test_m3u
  • test_acousticbrainz
  • test_advancedwrite
  • test_tidal
  • test_inline
  • test_mpdstats
  • test_replace
  • test_rewrite

related to #5361

nightly-95abdfb5-ls314

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 14 Jul 21:20
cd69e79

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-95abdfb5-ls314/index.html

LinuxServer Changes:

Full Changelog: nightly-15949c86-ls313...nightly-95abdfb5-ls314

Remote Changes:

Improve subsonicupdate error for non-JSON responses (#6837)

Fixes #5635.

When the Subsonic server returns a non-JSON response, subsonicupdate
currently logs the JSON decode error directly. This can produce a
cryptic message like Expecting value.

This logs a clearer error with the server URL and HTTP status, and adds
a regression test for an HTML error response.

nightly-4775599c-ls314

Pre-release

Choose a tag to compare

@LinuxServer-CI LinuxServer-CI released this 15 Jul 09:53
cd69e79

CI Report:

https://ci-tests.linuxserver.io/linuxserver/beets/nightly-4775599c-ls314/index.html

LinuxServer Changes:

No changes

Remote Changes:

Make InQuery hashable when its pattern is a list (#6838)

Fixes #5354.

beet splupdate raises TypeError: unhashable type: 'list' whenever a
smartplaylist configuration contains a playlist: query. smartplaylist
collects playlists in a set, which hashes the query. playlist:
parses to PlaylistQuery, a subclass of InQuery whose pattern is a
list, and InQuery inherited FieldQuery.__hash__, which does
hash(self.pattern) and fails on a list.

As @wisp3rwind suggested on the issue, this overrides __hash__ on
InQuery to hash a tuple copy of the pattern. It stays order-sensitive
to match the inherited FieldQuery.__eq__, so the hash/eq contract
holds.

Changes:

  • beets/dbcore/query.py: add InQuery.__hash__.
  • test/dbcore/test_query.py: regression test that an InQuery with a
    list pattern can be put in a set.
  • changelog entry under Bug fixes.

Left the order-insensitive __eq__/__hash__ and the smartplaylist
set-vs-dict cleanup you also floated out of scope here; happy to
follow up on either if you want them.