Commit graph

13751 commits

Author SHA1 Message Date
Jack
59b636ac8c CI: install discogs extra so discogs tests run; remove skip logic 2026-03-13 00:25:39 +00:00
Jack
da32d64599 Fix module-level skip: use pytest.skip(allow_module_level=True) for discogs 2026-03-13 00:25:39 +00:00
Jack
04e5d0083d CI: skip discogs tests when discogs_client missing, guard empty items, reset config in test 2026-03-13 00:25:39 +00:00
jdoe29103
99eec9ec25 Update changelog 2026-03-13 00:25:39 +00:00
jdoe29103
809744e8f4 Fix test class structure: move DGSearchQueryTest after DGAlbumInfoTest 2026-03-13 00:25:39 +00:00
jdoe29103
0d8d3bfadf Add discogs.extra_tags and updated documentation 2026-03-13 00:25:39 +00:00
Šarūnas Nejus
6af84bcfb6
Ensure changelog entries are under Unreleased section (#6432)
## Enforce Changelog Entries Under 'Unreleased' Section

I've had enough checking this manually 😆. Adds a CI lint step
that prevents contributors from accidentally adding changelog entries
under an already-released version header in `docs/changelog.rst`.

### How it works

The check runs `git diff --word-diff=plain -U1000` against the base
branch, then pipes through `awk` to scan the diff for new list entries
(`{+- ...`) that appear after any versioned release header (e.g. `1.2.3
(`). If such an entry is found, the step fails with a human-readable
error pointing to the offending line which GitHub should show in the
diff view.

* `--word-diff=plain` is required to match _truly new_ changelog entries
instead of some formatting adjustments in the middle of the line.
* `-U1000` should ensure that we grab the first 1000 lines in the
changelog to reliably match the headers.
2026-03-11 06:55:16 +00:00
Šarūnas Nejus
f969084a2a
Ensure changelog entries are under Unreleased section 2026-03-10 16:48:33 +00:00
Šarūnas Nejus
c89b94a467
docs(installation): Remove MacOS section from installation guide (#6427)
Fixes #5993

The section is outdated. MacOS (or HomeBrew's python package?) by
default disallows using `pip3 install` even with `--user` option.

I'd actually propose to add more information around how to use `pipx` in
this installation guide to install beets and its plugins (both provided
as extras and third-party), maybe even make it the main focus of this
page as a recommended way regardless of which OS and Linux distro is
used. I can create a separate issue/discussion to discuss and align on
this if it's fine by you.
2026-03-10 14:52:49 +00:00
Andrey M.
079b0276b8
Update docs/changelog.rst
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2026-03-10 12:16:59 +00:00
Andrejs Mivreniks
c019d790c5 docs(installation): Remove redundant MacOS section from installation guide 2026-03-10 13:06:16 +02:00
Šarūnas Nejus
3a8e65c515
Use aliases for tracks, releases and release groups (#6231)
> This PR add support for aliases to releases, release-groups and
recordings.

> This PR is a must have (IMO at least) for people that listen to
Japanese, Chinese and other songs that has other symbols for letters.
With this, not only the artist name will use the alias if available, but
now the album and track name will also use aliases.

Follow up from https://github.com/beetbox/beets/pull/5277 based on the
recent new mechanism to query musicbrainz
(https://github.com/beetbox/beets/pull/6052).

## API examples

- Aliases for releases, recordings and release groups:
http://musicbrainz.org/ws/2/release/59211ea4-ffd2-4ad9-9a4e-941d3148024a?inc=recordings+aliases+release-groups&fmt=json
- Aliases for recordings:
https://musicbrainz.org/ws/2/recording/b9ad642e-b012-41c7-b72a-42cf4911f9ff?inc=aliases&fmt=json
2026-03-10 08:24:08 +00:00
Eric Masseran
548bd9bab6 Merge remote-tracking branch 'official/master' into use-aliases-for-track-album
* official/master: (54 commits)
  Require data_source in album_for_id and track_for_id functions
  Invoke album_matched hook from AlbumMatch.__post_init__
  Refactor match_by_id
  Take data source into account when deciding duplicate candidates
  Return album candidates from multiple sources when matching by IDs
  Add a test to reproduce the issue
  Move assignment tests to test/autotag/test_match.py
  Pulled latest changelog and added my entry to 'Unreleased > Bug fixes' section.
  Moved changelog note to top, under Unreleased.
  This PR improves the regex detection used for the drive_sep_replace default.
  This PR improves the regex detection used for the drive_sep_replace default.
  refactor: Use deprecate_for_user for beatport/bpsync deprecation warnings
  Fix docs: use single-line deprecated directive compatible with docstrfmt
  Fix docs formatting for beatport and bpsync rst files
  Deprecate beatport and bpsync plugins
  Update changelog.rst
  try to fix fish plugin
  Make get_search_query_with_filters abstract
  Document new methods
  Document shared metadata search plugin workflow
  ...
2026-03-10 08:52:31 +01:00
Eric Masseran
efd2b090b5 Copilot feedback 2026-03-10 08:52:08 +01:00
Šarūnas Nejus
abd77b35ac
Return candidates from all data sources on id search (#6184)
Closes #6178 (multiple metadata source results per ID) and #6181
(duplicate/overwrite of candidates).

- (#6178) Replace `album_for_id` / `track_for_id` with `albums_for_ids`
/ `tracks_for_ids` in `metadata_plugins` that yield candidates from all
metadata sources
- (#6181) Use `Info.identifier` (`(data_source, id)`) as candidate keys
to avoid cross-source ID collisions.
- Add tests (`test/autotag/test_match.py`) for assignment logic and
multi-source ID matching
- Simplify `match_by_id`
- Dedupe `album_matched` event emission by moving it to
`AlbumMatch.__post_init__` (and convert `AlbumMatch` / `TrackMatch` to
dataclasses)
2026-03-10 01:02:56 +00:00
Šarūnas Nejus
35361a63b6
Require data_source in album_for_id and track_for_id functions
These functions now accept both an ID and data_source parameter,
enabling plugins like mbsync and missing to retrieve metadata from the
correct source.

Update mbsync and missing plugins to use the restored functions with
explicit data_source parameters. Add data_source validation to prevent
lookups when the source is not specified.

Add get_metadata_source helper function to retrieve plugins by their
data_source name, cached for performance.
2026-03-10 00:56:35 +00:00
Šarūnas Nejus
fcfa8ddfac
Invoke album_matched hook from AlbumMatch.__post_init__ 2026-03-10 00:56:35 +00:00
Šarūnas Nejus
de25d7623b
Refactor match_by_id 2026-03-10 00:56:14 +00:00
Šarūnas Nejus
85aa1d7a9c
Take data source into account when deciding duplicate candidates 2026-03-10 00:56:13 +00:00
Šarūnas Nejus
094d5bfb9d
Return album candidates from multiple sources when matching by IDs 2026-03-10 00:55:38 +00:00
Šarūnas Nejus
a64bde86bf
Add a test to reproduce the issue 2026-03-10 00:55:38 +00:00
Šarūnas Nejus
2f28fbc6e7
Move assignment tests to test/autotag/test_match.py 2026-03-10 00:55:38 +00:00
Šarūnas Nejus
44dc3cd4e9
Improve regex detection for the drive_sep_replace default (#6417)
I imported an album where a track had the name `1:00 AM - Clear` and
another track named `12:00 AM - Clear` (just two examples).

See: [Animal Crossing: New Horizons
OST](https://musicbrainz.org/release/263f7ed3-60c2-4251-ac7d-6da3f8691256)

After import, the former was renamed `1_00 AM - Clear`, and the latter
`12;00 AM - Clear`. Notice the inconsistency of how the `:` was
replaced.

I did not make use of the (hidden) `drive_sep_replace` setting. These
were my `replace` settings:
```
replace:                        # prevent file name incompatibiliy
  '[\s]'                        : ' '   # standardize whitespace
  '["`‘’“”]'                    : "'"   # standardize quotes
  '[\u002D\u2010-\u2015\u2E3A]' : '-'   # standardize dashes
  '[\u2E3B\uFE58\uFE63\uFF0D]'  : '-'   # standardize dashes
  '[\xAD]'                      : '-'   # standardize dashes
  '[\\\|\/]'                    : ' '   # slashes, pipe > space
  '[:]'                         : ';'   # colon > semicolon
  '[<>]'                        : '-'   # chevrons > dashes
  '[\?\*]'                      : ''    # remove restricted characters
  '[\x00-\x1F\x7F]'             : ''    # remove basic control characters
  '[\x80-\x9F]'                 : ''    # remove extra control characters
  '^\.'                         : ''    # remove leading period
  '\.$'                         : ''    # remove trailing period
  '^\s+'                        : ''    # remove leading space
  '\s+$'                        : ''    # remove trailing space
```

I found the issue to be too generic regex for drive separator detection.
I'm on macOS, so this is irrelevant to me anyway (and I got around it by
adding `drive_sep_replace: ';'` to my settings), but regardless, I think
this could be improved.

This PR improves the regex to detect drive separators. Instead of merely
looking for any first character followed by a colon (`^\w:`), we look
for a letter, followed by a colon, followed by a backslash instead
(`^[a-zA-Z]:\\`).

The regex logic is solid, but I am not able to test this on a real
Windows environment.

~Still have to add an entry to the changelog, will do so soon.~

# Update

Initially this commit failed the
`MoveTest.test_move_file_with_colon_alt_separator` test because it
checks the logic using a `C:DOS` path. So I had to make the logic less
restrictive again, not checking for a backslash (`^[a-zA-Z]:`). I would
argue the test itself should be amended (test with `C:\DOS` instead),
but that's not up to me.

As a result, my case of "1:00 AM" being replaced incorrectly is still
resolved, but other hypothetical cases like "a:b" would still not be
covered due to an arguably incorrect test limiting a more precise regex.
2026-03-10 00:16:56 +00:00
spaceage64
fbe9495bdd Pulled latest changelog and added my entry to 'Unreleased > Bug fixes' section. 2026-03-10 00:25:02 +01:00
spaceage64
b8823dddde Merge remote-tracking branch 'origin/drive-separator-fix' 2026-03-10 00:14:25 +01:00
spaceage64
031c2a8aeb Moved changelog note to top, under Unreleased. 2026-03-10 00:09:32 +01:00
spaceage64
e14191ed05 This PR improves the regex detection used for the drive_sep_replace default. 2026-03-09 20:19:17 +00:00
spaceage64
7b26733e04 This PR improves the regex detection used for the drive_sep_replace default. 2026-03-09 11:07:37 +01:00
Šarūnas Nejus
80d08ed11b
beatport: Deprecate beatport and bpsync plugins (#6426)
As agreed in the comments:
https://github.com/beetbox/beets/pull/4477#issuecomment-3990774420

Deprecation notices reference those for additional context:
- https://github.com/beetbox/beets/issues/3862
- https://github.com/beetbox/beets/pull/4477

Beatport retired the API (v3) that the built-in `beatport` plugin relies
on, making it non-functional. This PR soft-deprecates both the
`beatport` and `bpsync` plugins following the same pattern used for
`acousticbrainz` — adding deprecation warnings in `__init__()` while
keeping all existing code intact.

The `bpsync` plugin is deprecated alongside `beatport` since it imports
and depends on `BeatportPlugin` directly.

### Changes

- **`beetsplug/beatport.py`**: Add deprecation warning in
`BeatportPlugin.__init__()`
- **`beetsplug/bpsync.py`**: Add deprecation warning in
`BPSyncPlugin.__init__()`
- **`docs/plugins/beatport.rst`**: Add `.. deprecated::` directive
- **`docs/plugins/bpsync.rst`**: Add `.. deprecated::` directive
- **`docs/changelog.rst`**: Add changelog entry under Unreleased → Other
changes

### What's NOT changed (and why)

- **`pyproject.toml`** (beatport extra): Kept so the plugin loads and
shows the deprecation warning instead of crashing with an `ImportError`
- **`test/plugins/test_beatport.py`**: Tests cover data models which
still exist — all 15 tests pass
- **`docs/plugins/index.rst`**: `beatport` stays in the toctree since
the page still exists
- **`beets/util/id_extractors.py`**: Core utility, not plugin-specific
2026-03-08 17:17:45 +00:00
Szymon Tarasiński
b008488b84 refactor: Use deprecate_for_user for beatport/bpsync deprecation warnings 2026-03-08 17:10:29 +00:00
Szymon Tarasiński
bdb039c760 Fix docs: use single-line deprecated directive compatible with docstrfmt 2026-03-08 17:10:29 +00:00
Szymon Tarasiński
740db4d500 Fix docs formatting for beatport and bpsync rst files 2026-03-08 17:10:29 +00:00
Szymon Tarasiński
efa1f370b7 Deprecate beatport and bpsync plugins
Beatport has retired the API these plugins rely on, making them
non-functional. Add deprecation warnings and update documentation
to reflect the current state.

Fixes #3862
2026-03-08 17:10:29 +00:00
Šarūnas Nejus
f1509a40f9
Fix fish plugin (#6428)
Fixes #6340
2026-03-08 13:24:01 +00:00
Konstantin
d2705fef4e Update changelog.rst 2026-03-08 14:16:49 +01:00
Konstantin
b64bda86ff try to fix fish plugin 2026-03-08 13:57:42 +01:00
Šarūnas Nejus
3c48b0c6e6
Centralise common autotagger search functionality (#5982)
Add a unified search abstraction across metadata source plugins.

Summary:
- Introduces `SearchApiMetadataSourcePlugin` with `SearchParams`,
`get_search_query_with_filters`, and `get_search_response` hooks to
standardize album/track searches.
- Replaces ad-hoc `_search_api` and query construction logic in Deezer,
Spotify, MusicBrainz, and Discogs plugins with the new shared
implementation.
- Refactors Discogs and MusicBrainz plugins to use the new abstraction
and move provider-specific criteria/query construction into hook
methods.
- Centralizes error handling and logging in the shared search flow;
Spotify now retries authentication once on `401`, and failures cleanly
fall back to empty results at the shared layer.
2026-03-08 09:17:48 +00:00
Šarūnas Nejus
46aa0a4ba5
Make get_search_query_with_filters abstract 2026-03-08 09:06:00 +00:00
Šarūnas Nejus
0670611d7a
Document new methods 2026-03-08 09:00:14 +00:00
Šarūnas Nejus
4cccb70921
Document shared metadata search plugin workflow
Add developer guidance for SearchApiMetadataSourcePlugin and its hooks.

Clarify migration advice for API-backed metadata source plugins.
2026-03-08 09:00:14 +00:00
Šarūnas Nejus
9855d46901
Bound Spotify auth retry during search
Replace recursive 401 handling with a single retry loop.

This prevents unbounded recursion when authentication keeps failing.
2026-03-08 09:00:14 +00:00
Šarūnas Nejus
9b63985989
Migrate MusicBrainz to shared search hooks
Move MusicBrainzPlugin to SearchApiMetadataSourcePlugin hooks.

Keep entity mapping and criteria in provider-specific hooks.

Update typing and tests for the candidate search path.
2026-03-08 09:00:14 +00:00
Šarūnas Nejus
27bb34411c
Refactor shared search API flow
Move search orchestration into SearchApiMetadataSourcePlugin.

Migrate Deezer, Spotify, and Discogs to provider hooks.

Keep query handling, logging, and limits centralized.
2026-03-08 09:00:14 +00:00
Šarūnas Nejus
9f6a4b4147
Clarify %if template behavior (#6399)
Fixes #4991.

I don't know if there's a single, good workaround for this, so I opted
to not add an example solution.
2026-03-08 08:57:55 +00:00
Emil Hammarberg
1f2beb6d02 Format 2026-03-08 08:51:42 +00:00
Emil Hammarberg
763dc7ccb1 Fix empty literal error 2026-03-08 08:51:42 +00:00
Emil Hammarberg
8a57472224 Reword 2026-03-08 08:51:42 +00:00
Emil Hammarberg
654b76190c Clarify %if template behavior
Closes #4991
2026-03-08 08:51:42 +00:00
Šarūnas Nejus
6ef2a8cb0d
docs(CONTRIBUTING): Fix typo (#6423) 2026-03-08 08:50:12 +00:00
Pierre Ayoub
af42992cd9 docs(CONTRIBUTING): Fix typo 2026-03-08 08:45:09 +00:00