Releases: linuxserver/docker-beets
2.10.0-ls326
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/2.10.0-ls326/index.html
LinuxServer Changes:
Full Changelog: 2.9.0-ls325...2.10.0-ls326
Remote Changes:
Updating PIP version of beets to 2.10.0
nightly-5f6b2d35-ls272
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-5f6b2d35-ls272/index.html
LinuxServer Changes:
Full Changelog: nightly-06f5e14b-ls271...nightly-5f6b2d35-ls272
Remote Changes:
Increment version to 2.10.0
nightly-e3e8793d-ls271
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-e3e8793d-ls271/index.html
LinuxServer Changes:
No changes
Remote Changes:
Centralise field type defs (#6550)
Summary
This PR eliminates duplicated field-to-type mappings across Album and
Item model classes. Previously, each model class maintained its own
_fields: dict[str, types.Type] — a verbose, hand-written dictionary
that encoded both which fields exist and what type each one has.
This duplication made it easy for the two definitions to drift out of
sync.
Architecture Change
Before: Each model class owned a full inline _fields dict mapping
field names to type instances.
Album._fields = {"id": types.PRIMARY_ID, "artpath": types.NullPathType(), "added": types.DATE, ...} # ~40 entries
Item._fields = {"id": types.PRIMARY_ID, "path": types.PathType(), "album_id": types.FOREIGN_ID, ...} # ~80 entries
After: Field names are declared as a set[str], and the type
mapping is derived lazily from a single centralised TYPE_BY_FIELD
registry in beets.dbcore.fields.
Album._field_names = {"id", "artpath", "added", ...} # plain set of names
Item._field_names = Album._field_names - {"artpath"} | {...} # extends Album's set
LibModel._fields (cached_classproperty) = {f: TYPE_BY_FIELD[f] for f in cls._field_names}
This creates a clean separation of concerns:
- What fields belong to a model → declared in the model
(_field_names) - What type a field has → declared once in
dbcore.fields.TYPE_BY_FIELD
Key Impacts
| Area | Before | After |
|---|---|---|
| Type definitions | Inline per model | Single registry in |
dbcore.fields |
||
Item._fields |
Fully independent dict | Derived from |
Album._field_names |
||
Album.item_keys |
Hard-coded list of 40+ strings | `_field_names - |
| {"artpath", "id"}` | ||
_media_fields / _media_tag_fields |
Intersected with | |
_fields.keys() |
Intersected with _field_names directly |
|
| Coverage measurement | --cov=beets --cov=beetsplug |
--cov=. |
(fixes missing beetsplug/musicbrainz.py) |
Risk Areas to Review
TYPE_BY_FIELDcompleteness: all field names in_field_names
must have a corresponding entry in the registry — a missing key will
raise at class definition time viacached_classproperty.Album.item_keyssemantics: changed fromlisttosetand is
now derived automatically. Any code that relied on ordering or specific
list membership should be checked.Item._field_namesinheritance expression:Album._field_names - {"artpath"} | {...}— operator precedence here is `(Album._field_names- {"artpath"}) | {...}`, which is the intended behaviour, but worth a
close read.
nightly-be33782f-ls271
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-be33782f-ls271/index.html
LinuxServer Changes:
No changes
Remote Changes:
Use aliases for artist credit (#6552)
The *_credit fields for album and track are also aliased.
Follow up of beetbox/beets#6231. I wanted to
mbsync my library but then I realized that *_credit fields were
reverted to non-alias if already aliased.
I don't how I got those aliased in the first place as the code is not
using them since years. Also, I had 2 albums added the same day and one
was with aliased credit and the other one not.
Discussed here:
beetbox/beets#6358 (comment)
nightly-82384dab-ls271
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-82384dab-ls271/index.html
LinuxServer Changes:
No changes
Remote Changes:
Parallelize candidate lookup for metadata plugins. (#6546)
This pull request introduces concurrent execution of metadata source
plugin searches and lookups, which significantly improves performance
when multiple plugins are enabled. Instead of running each plugin
sequentially, the code now uses threads to perform plugin lookups in
parallel, reducing overall wait time for I/O-bound operations.
For me this improved lookup times by up to 5s each! The improvement
should roughly scale linear with the number of enabled metadata plugins.
nightly-2f3efaea-ls271
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-2f3efaea-ls271/index.html
LinuxServer Changes:
No changes
Remote Changes:
Refactor musicbrainz to reduce cognitive complexity (#6530)
Summary
A broad internal refactor of beetsplug/musicbrainz.py and its test
suite. No user-facing behaviour changes are intended. The goal is to
make parsing logic easier to understand, test, and maintain.
This PR halves the cognitive complexity in musicbrainz plugin:
Before
$ complexipy beetsplug/musicbrainz.py
🧠 Total Cognitive Complexity: 181
1 file analyzed in 0.0116 seconds
After
$ complexipy beetsplug/musicbrainz.py
🧠 Total Cognitive Complexity: 93
1 file analyzed in 0.0097 seconds
Production Code Changes
Parsing Decomposition
The monolithic album_info method is broken into focused
@staticmethod helpers, each returning a typed TypedDict:
| Method | Responsibility |
|---|---|
_parse_artist_credits |
Replaces _flatten_artist_credit / |
_multi_artist_credit |
|
_parse_release_group |
albumtype, original_year, etc. |
_parse_label_infos |
label, catalognum |
_parse_genres |
Genre list |
_parse_external_ids |
Third-party IDs from URL relations |
_parse_work_relations |
Composer / lyricist credits |
_parse_artist_relations |
Arranger / remixer credits |
get_tracks_from_medium |
Per-medium track iteration |
Each helper returns a typed TypedDict, making the data contract
explicit and the code easier to compose with **kwargs unpacking into
AlbumInfo / TrackInfo.
Other Simplifications
_set_date_str(mutable, side-effecting) is replaced by_get_date,
a pure function returning(year, month, day)._preferred_release_eventand_preferred_aliasare simplified.- Three
@cached_propertyentries (ignored_media,
ignore_data_tracks,ignore_video_tracks) are moved up to sit with
other class-level properties.
Test Infrastructure Changes
Factory Layer (test/plugins/factories/musicbrainz.py)
factory-boy / pytest-factoryboy are added as test dependencies. A
new factory module introduces composable, deterministic factories:
AliasFactory,ArtistFactory,ArtistCreditFactoryRecordingFactory,TrackFactory,MediumFactoryReleaseGroupFactory,ReleaseFactory
Factories use a shared _IdFactory base class that generates stable,
predictable UUIDs (00000000-0000-0000-0000-000000001001, etc.) based
on an id_base + index pair. This makes assertions on IDs readable
and deterministic without hard-coded magic strings.
Before:
recording = {
"title": "foo",
"id": "bar",
"length": 42,
"artist_credit": [{"artist": {"name": "some-artist", "id": "some-id", ...}, ...}],
...
}After:
recording = recording_factory(title="foo", length=42)Test Consolidation
Many small single-field assertion tests that each constructed an
identical release are collapsed into a single test_parse_release
snapshot assertion, covering all AlbumInfo fields at once. This
reduces redundant fixture setup and makes it immediately obvious what
the full output of album_info looks like for a default release.
The hand-rolled _make_release / _make_recording helpers are removed
entirely, replaced by composable factory calls using factory-boy's __
double-underscore traversal syntax:
# Before
release = self._make_release(date="1987-03-31")
# After
release = release_factory(release_group__first_release_date="1987-03-31")nightly-06f5e14b-ls271
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-06f5e14b-ls271/index.html
LinuxServer Changes:
Full Changelog: nightly-342a7c0e-ls270...nightly-06f5e14b-ls271
Remote Changes:
Call remux_mpeglayer3_wav proactively in read_item (#6544)
Follow-up to #6526. This change moves the remux_mpeglayer3_wav call to
before library.Item.from_path in read_item, so beets no longer
depends on mediafile raising FileTypeError for MPEGLAYER3 WAV files.
This is related to beetbox/mediafile#107.
2.9.0-ls325
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/2.9.0-ls325/index.html
LinuxServer Changes:
Full Changelog: 2.9.0-ls324...2.9.0-ls325
Remote Changes:
Updating PIP version of beets to 2.9.0
nightly-bacebe26-ls270
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-bacebe26-ls270/index.html
LinuxServer Changes:
Full Changelog: nightly-bacebe26-ls269...nightly-bacebe26-ls270
Remote Changes:
Add keep_synced option to lyrics plugin (#6538)
lyrics: Add keep_synced option to protect synced lyrics from
being overwritten
Adds a keep_synced config option and --keep-synced CLI flag to the
lyrics plugin. When enabled, tracks that already have synced lyrics are
skipped during fetching — even if force is set.
Motivation: Libraries with a mix of synced and plain lyrics can use
force to fill gaps without risking overwriting the higher-quality
synced entries.
Fixes: #5249
This is another widely requested issue with many reactions from users.
nightly-bacebe26-ls269
CI Report:
https://ci-tests.linuxserver.io/linuxserver/beets/nightly-bacebe26-ls269/index.html
LinuxServer Changes:
Full Changelog: nightly-1d01689a-ls268...nightly-bacebe26-ls269
Remote Changes:
Add keep_synced option to lyrics plugin (#6538)
lyrics: Add keep_synced option to protect synced lyrics from
being overwritten
Adds a keep_synced config option and --keep-synced CLI flag to the
lyrics plugin. When enabled, tracks that already have synced lyrics are
skipped during fetching — even if force is set.
Motivation: Libraries with a mix of synced and plain lyrics can use
force to fill gaps without risking overwriting the higher-quality
synced entries.
Fixes: #5249
This is another widely requested issue with many reactions from users.