Add documentation for the `match.distance_weights` configuration option
in the autotagger matching section of the reference docs.
The section includes:
- Description of what distance weights control
- Complete list of all available fields with their default values
- Example showing how to customize a specific weight
- Note that only overridden fields need to be specified
Closes#6081
Add documentation for the distance_weights configuration option in
the autotagger matching section. This includes all available fields
with their default values and an example of how to customize them.
Closes#6081
Signed-off-by: edvatar <88481784+toroleapinc@users.noreply.github.com>
## Add support for a multi-valued `genres` field
- Update metadata source plugins to populates `genres` instead of
`genre`: `musicbrainz`, `beatport`, `discogs`.
- Remove now redundant `separator` configuration from `lastgenre`.
### Context
We previously had multiple issues with maintaining both _singular_ and
_plural_ fields:
1. Since both fields write and read the same field in music files, the
values in both
fields must be carefully synchronised, otherwise we see these fields
being repeatedly
retagged / rewritten using commands such as `beet write`. See [related
issues](https://github.com/beetbox/beets/issues?q=label%3A"multi%20tags%22)
2. Fixes to sync logic required users manually retagging their
libraries, while music
imported _as-is_ could not be fixed. See #5540, for example.
Therefore, this PR replaces a singular `genre` field by plural `genres`
_for good_:
1. We migrate `genre` -> `genres` immediately on the first `beets`
invocation
2. `genre` field is removed and `genres` is added
3. The old `genre` column in the database is left in place - these
values will be ignored
by beets.
- If someone migrates and later decides to switch back to using an older
version of
beets, their `genre` values are still in place.
### Migration
- This PR creates a new DB table `migrations(name TEXT, table TEXT)`
- We add an entry when a migration has been fully performed on a
specific table
- Thus we only perform the migration if we don't have an entry for that
table
- Entry is only added when the migration has been performed **fully**:
if someone hits
CTRL-C during the migration, the migration will continue on the next
beets invocation,
see:
```py
def migrate_table(self, table: str, *args, **kwargs) -> None:
"""Migrate a specific table."""
if not self.db.migration_exists(self.name, table):
self._migrate_data(table, *args, **kwargs)
self.db.record_migration(self.name, table)
```
- Implemented using SQL due to:
1. Significant speed difference: migrating my 9000 tracks / 2000 albums
library:
- Using our Python implementation: over 11 minutes
- Using SQL: 2 seconds
2. Beets seeing only `genres` field: `genre` field is only accessible by
querying the
database directly.
Supersedes: #6169
* Move genre-to-genres migration into a dedicated Migration class and
wire it into Library._migrations for items and albums.
* Add batched SQL updates via mutate_many and share the multi-value
delimiter as a constant.
* Cover migration behavior with new tests.
I initially attempted to migrate using our model infrastructure
/ Model.store(), see the comparison below:
Durations migrating my library of ~9000 items and ~2300 albums:
1. Using our Python logic: 11 minutes
2. Using SQL directly: 4 seconds
That's why I've gone ahead with option 2.
Migration now happens automatically when the database schema is
updated (in Library._make_table()), so the manual 'beet migrate'
command is no longer needed.
Addresses PR review comment.
- Add Library._make_table() override to automatically migrate genres when database schema is updated
- Migration splits comma/semicolon/slash-separated genre strings into genres list
- Writes changes to both database and media files with progress reporting
- Remove lazy migration from correct_list_fields() - now handled at database level
- Remove migration-specific tests (migration is now automatic, not lazy)
- Update changelog to reflect automatic migration behavior
Related PR review comment changes:
- Replace _is_valid with _filter_valid method in lastgenre plugin
- Use unique_list and remove genre field from Beatport plugin
- Simplify LastGenre tests - remove separator logic
- Document separator deprecation in lastgenre plugin
- Add deprecation warning for genre parameter in Info.__init__()
Simplify multi-genre implementation based on maintainer feedback (PR #6169).
Changes:
- Remove multi_value_genres and genre_separator config options
- Replace complex sync_genre_fields() with ensure_first_value('genre', 'genres')
- Update all plugins (Beatport, MusicBrainz, LastGenre) to always write genres as lists
- Add automatic migration for comma/semicolon/slash-separated genre strings
- Add 'beet migrate genres' command for explicit batch migration with --pretend flag
- Update all tests to reflect simplified approach (44 tests passing)
- Update documentation
Implementation aligns with maintainer vision of always using multi-value genres
internally with automatic backward-compatible sync to the genre field via
ensure_first_value(), eliminating configuration complexity.
Migration strategy avoids problems from #5540:
- Automatic lazy migration on item access (no reimport/mbsync needed)
- Optional batch migration command for user control
- No endless rewrite loops due to proper field synchronization
While working on #6367 I noticed that users are currently required to
use our internal separator `\␀` in order to edit multi-valued fields,
for example `beet modify artists='a\␀b'`.
Similarly, this separator is used in output, for example, reporting of
field changes:
```
$ beet modify path::aaa artists='a\␀b'
Modifying 8 items.
54898 | 2022 / RAVE SLUTZ: Fallen Shrine & dj Christian NXC - DEEEJAAAY
artists: Fallen Shrine\␀dj Christian NXC -> a\␀b
```
This PR replaces `\␀` separator with `; ` for input and formats changes
in multi-valued fields clearly:
```
$ beet modify path::aaa artists='a; b'
54898 | 2022 / RAVE SLUTZ: Fallen Shrine & dj Christian NXC - DEEEJAAAY
artists:
- Fallen Shrine
- dj Christian NXC
+ a
+ b
```
<img width="539" height="142" alt="image"
src="https://github.com/user-attachments/assets/72299db1-d0f8-4f8f-9f30-65caaac85d9e"
/>
### Architecture-level changes
- `DelimitedString` now separates concerns between:
- database serialization via `db_delimiter` (`to_sql`)
- user-facing/template formatting via a fixed `'; '` delimiter
(`format`)
- parsing that accepts both DB and user-facing separators (`parse`)
- Field diff rendering now has a dedicated path for list fields:
- `_field_diff` detects list values
- `_multi_value_diff` computes set-based added/removed entries and
renders per-item diff lines
- Coloring responsibilities were streamlined:
- raw ANSI application moved to `_colorize`
- `colorize` is now only the feature-flag/environment gate
- `colordiff` is reduced to string diff highlighting logic, with
redundant wrapper logic removed
### High-level impact
- Multi-valued fields behave consistently between DB storage and
CLI/template usage (`'; '` for user input/output, DB delimiter
internally).
- Diff output for list fields is much more readable, showing explicit
`+`/`-` item-level changes instead of generic string diffs.
- Docs and tests were updated to reflect the new multi-value behavior,
including `%first` usage and `beet modify` examples.
- Use '\␀' as the DB delimiter while formatting lists with '; ' for
templates.
- Update DelimitedString parsing to accept both separators:
* '\␀' for the values from the DB
* '; ' for the rest of parsed values (for example `beet modify genres="eletronic; jazz"`)
- Refresh %first docs and tests to reflect multi-value field behavior.
Fixes#6339
This change moves MusicBrainz lookup defaults into
`beetsplug/_utils/musicbrainz.py` so `MusicBrainzAPI` now owns the
default `includes` for both `get_release` and `get_recording`.
#### Architecture impact
- Added centralized defaults: `RELEASE_INCLUDES` and
`RECORDING_INCLUDES` in `MusicBrainzAPI`.
- Updated `get_release` and `get_recording` to apply these defaults via
`kwargs.setdefault("includes", ...)`.
- Removed duplicated include lists from `beetsplug/musicbrainz.py` and
simplified call sites to `self.mb_api.get_release(...)` /
`self.mb_api.get_recording(...)` without passing includes explicitly.
#### High-level result
- Ensures consistent MusicBrainz payload shape across callers.
- Fixes the `mbpseudo` failure caused by missing `artist_credit` data in
some responses.
- Reduces coupling and duplication by making include policy an API-layer
concern.