Commit graph

13627 commits

Author SHA1 Message Date
Šarūnas Nejus
a8b8aa9d89
Move test_autotag tests under test/autotag 2026-03-03 07:48:52 +00:00
Šarūnas Nejus
1838482c7a
Keep missing multi-value fields as None instead of empty list 2026-03-03 07:48:51 +00:00
Šarūnas Nejus
0f7bce1bfe
Show that album genres are not applied to tracks 2026-03-03 07:46:27 +00:00
Šarūnas Nejus
cf043df13d
autotag: refactor autotag tests to use single comprehensive test
Consolidate multiple granular test methods in ApplyTest into a single
comprehensive test that validates all applied metadata at once. This
improves test maintainability and clarity by:

- Replacing ~20 individual test methods with one data-driven test
- Using expected data dictionaries to validate all fields together
- Removing ApplyCompilationTest class (covered by va=True in main test)
- Keeping focused tests for edge cases (artist_credit, date handling)
- Switching from BeetsTestCase to standard TestCase for speed
- Adding operator import for efficient data extraction

The new approach makes it easier to validate all applied metadata at once.
2026-03-03 07:46:27 +00:00
Šarūnas Nejus
86fac4bf82
fix(lastgenre): Reset plugin config in fixtured tests (#6386)
Fixes a bug in the lastgenre plugin, where test state bled into the
following fixtures.

Each plugin has a view to the global persisted beets.config field. As a
result, config variables that aren't explicitly overwritten are
persisted in that global config view.

This commit exposes the lastgenre default config as a static method and
uses that default config to reset the state in between fixture calls.

There were 3 tests that depended on `count: 10` being set on previous
test fixtures, which I adjusted accordingly.

Discovered and discussed in #6317 , see
https://github.com/beetbox/beets/pull/6317#issuecomment-3935462408
2026-03-02 23:48:17 +00:00
Arne Beer
723b4bbfe9
fix(lastgenre): Reset plugin config in fixtured tests 2026-03-03 00:43:05 +01:00
Šarūnas Nejus
31bbd1fad8
fix(fetchart): prevent deletion of configured fallback cover art (#6283)
When `import.delete` or `import.move` is enabled, the `assign_art`
method calls `task.prune(candidate.path)` unconditionally. This
incorrectly deletes the configured `fetchart.fallback` file. Add
explicit check to skip pruning when the candidate path matches the
configured fallback.
2026-03-02 23:09:58 +00:00
Danny Trunk
974d917df4 fix(fetchart): prevent deletion of configured fallback cover art
When `import.delete` or `import.move` is enabled, the `assign_art` method calls `task.prune(candidate.path)` unconditionally.
This incorrectly deletes the configured `fetchart.fallback` file.
Add explicit check to skip pruning when the candidate path matches the configured fallback.
2026-03-02 18:10:19 +01:00
Šarūnas Nejus
30d5157ba3
zero plugin zeroes disctotal if single disc (#6306)
When `omit_single_disc` is set, `disctotal` is now also zeroed alongside
`disc`. These tags work together ("Disc 2 of 3") so keeping one without
the other is inconsistent.

Previously, only the `disc` tag was zeroed. This follows from #6015
which made it into v2.5.1 and added the `omit_single_disc` option.

I've added tests and have used this locally for some time.
2026-03-02 17:02:22 +00:00
Šarūnas Nejus
5c3ba8e006
Move changelog note under unreleased section 2026-03-02 16:38:02 +00:00
Šarūnas Nejus
91fae7c879
Merge branch 'master' into zero-total-discs 2026-03-02 16:37:43 +00:00
Šarūnas Nejus
6089ab08fa
fix: ftintitle can handle a list of ampersanded artists (#6375)
This was inspired by real life events:
https://musicbrainz.org/release/7c4d7a15-6b30-4bef-8b20-af200186fbdb by
the artist Danny L Harle has a a track with a featuring list that
contains "Danny L Harle, Oklou & MNEK".

Before:
```
artist = Danny L Harle, Oklou
track = Crystallise My Tears feat. MNEK
```

After:
```
artist = Danny L Harle
track = Crystallise My Tears feat. Oklou & MNEK
```
2026-03-02 15:00:05 +00:00
Šarūnas Nejus
b17305aa1e
Update changelog note 2026-03-02 14:54:34 +00:00
Fredrik Möllerstrand
bfd95f47d0 fix: ftintitle can handle a list of ampersanded artists
This was inspired by real life events:
https://musicbrainz.org/release/7c4d7a15-6b30-4bef-8b20-af200186fbdb
by the artist Danny L Harle has a a track with a featuring list
that contains "Danny L Harle, Oklou & MNEK".
2026-03-02 14:53:23 +00:00
Šarūnas Nejus
43a2c69aa6
Fix symlink tests for macOS (#6374)
👋🏻 I was trying to set up a local dev environment and noticed a couple
of tests were failing. On macOS, temporary files are created under
`/var`, which is itself a symlink to `/private/var`. This PR resolves
the `assert`s against temp file paths in tests.
2026-03-02 09:33:16 +00:00
Dmitri Vassilenko
df7db24871 Fix symlink tests for macOS 2026-03-02 09:24:25 +00:00
Šarūnas Nejus
d2faa5efe9
Update my teams page entry (#6394) 2026-03-02 09:23:17 +00:00
J0J0 Todos
532b0dabb1 Update my teams page entry 2026-03-02 08:31:44 +01:00
Šarūnas Nejus
53119fc581
docs: Document match.distance_weights in autotagger config (#6398)
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
2026-03-01 12:37:42 +00:00
Serene
aa81232336 Use proper syntax highlighting for code block
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2026-03-01 11:46:44 +00:00
Serene-Arc
dd1bda4bd0 Format docs 2026-03-01 11:46:44 +00:00
edvatar
a40bd7ca3c docs: Document match.distance_weights in autotagger docs
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>
2026-03-01 11:46:44 +00:00
Šarūnas Nejus
573dca687a
Fix add multiple genres (#6401)
Only set new fields when we create a new table.
2026-02-28 10:12:11 +00:00
Šarūnas Nejus
085ff1267b
Only add fields when we create new table 2026-02-28 10:06:31 +00:00
Šarūnas Nejus
16be1df940
Add multiple genres (#6367)
## 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
2026-02-27 18:42:46 +00:00
Šarūnas Nejus
a540a8174a
Clarify tests 2026-02-27 18:36:04 +00:00
Šarūnas Nejus
10d13992e6
Dedupe genres parsing in beatport 2026-02-27 18:36:04 +00:00
Šarūnas Nejus
2c63fe77ce
Remove test case indices from test_lastgenre.py 2026-02-27 18:36:04 +00:00
Šarūnas Nejus
62e232983a
Document ordering of the genre split separator 2026-02-27 18:36:04 +00:00
Šarūnas Nejus
67cf15b0bd
Remove lastgenre separator config 2026-02-27 18:36:04 +00:00
Šarūnas Nejus
6f886682ea
Update changelog note 2026-02-27 18:36:04 +00:00
Šarūnas Nejus
52375472e8
Replace genre: with genres: in docs 2026-02-27 18:34:26 +00:00
Šarūnas Nejus
a8d53f78de
Fix the rest of the tests 2026-02-27 18:34:26 +00:00
Šarūnas Nejus
5d7fb4e158
Remove genre field 2026-02-27 18:34:26 +00:00
Šarūnas Nejus
b8f1b9d174
Stop overwriting this test file name 2026-02-27 18:34:26 +00:00
Šarūnas Nejus
cf36ed0754
Only handle multiple genres in discogs 2026-02-27 18:34:26 +00:00
Šarūnas Nejus
4dda8e3e49
Fix deprecation warning 2026-02-27 18:24:54 +00:00
Šarūnas Nejus
2ecbe59f48
Add migration for multi-value genres field
* 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.
2026-02-27 18:24:54 +00:00
Šarūnas Nejus
8edd0fc966
Add generic Migration implementation 2026-02-27 18:24:54 +00:00
Šarūnas Nejus
eac9e1fd97
Add support for migrations 2026-02-27 18:24:54 +00:00
dunkla
0191ecf576
Fix mypy incompatible return type in lastgenre 2026-02-27 18:24:54 +00:00
dunkla
10d197e242
Update lastgenre docstring and remove misleading comment (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-02-27 18:24:54 +00:00
dunkla
9003107ee7
Remove noisy comments from beatport tests (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-02-27 18:24:54 +00:00
dunkla
67ce53d2c6
Remove conditional logic from lastgenre tests (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-02-27 18:24:54 +00:00
dunkla
9224c9c960
Use compact generator expression in Beatport (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-02-27 18:24:54 +00:00
dunkla
21fb5a561d
Fix lastgenre migration separator logic (ref https://github.com/beetbox/beets/pull/6169#issuecomment-3716893013) 2026-02-27 18:24:54 +00:00
dunkla
76c4eeedbb
Remove manual migrate command
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.
2026-02-27 18:24:54 +00:00
dunkla
e99d3ca061
Simplify MusicBrainz genres assignment
Remove intermediate variable and assign directly to info.genres.
Addresses PR review comment.
2026-02-27 18:24:54 +00:00
dunkla
36a30b3c65
Implement automatic database-level genre migration
- 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__()
2026-02-27 18:24:54 +00:00
dunkla
99831906c2
simplify check for fallback in beetsplug/lastgenre/__init__.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2026-02-23 05:11:37 +00:00