## 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.
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.
* 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
...
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)
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.
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.
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
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
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.
Move MusicBrainzPlugin to SearchApiMetadataSourcePlugin hooks.
Keep entity mapping and criteria in provider-specific hooks.
Update typing and tests for the candidate search path.
Move search orchestration into SearchApiMetadataSourcePlugin.
Migrate Deezer, Spotify, and Discogs to provider hooks.
Keep query handling, logging, and limits centralized.