Skip to content

fetchart: handle sources config given as plain string#6437

Open
wavebyrd wants to merge 9033 commits intobeetbox:masterfrom
wavebyrd:fix-fetchart-filesystem
Open

fetchart: handle sources config given as plain string#6437
wavebyrd wants to merge 9033 commits intobeetbox:masterfrom
wavebyrd:fix-fetchart-filesystem

Conversation

@wavebyrd
Copy link

Summary

  • Fix fetchart failing with "no art found" when sources is configured as a plain string (e.g. sources: filesystem) instead of a list (sources: [filesystem]).
  • In confuse 2.2.0, the Pairs template no longer inherits StrSeq's string-to-list normalization, so a bare string gets iterated character by character. This normalizes the value to a list before passing it to as_pairs().
  • Adds tests for both string and list forms of the sources config.

Fixes #6336
Related: #5962

Test plan

  • Existing fetchart tests pass
  • New tests verify sources: filesystem (string), sources: [filesystem] (list), and sources: filesystem coverart (space-separated string) all produce the correct source objects

snejus and others added 30 commits January 30, 2026 01:06
…x#5926)

Creating indexes turned out to be relatively straightforward!
While we can’t remove them yet, that doesn’t seem necessary for now.
Interestingly, much of the infrastructure for database additions was
already in place.

- This PR introduces a new type, `Index`, which can be used to create an
index on any defined table. 🎉
- The `items` table now automatically registers an index on `album_id`

Closes beetbox#5809
Fixes a bug where existing tags were set to None, if they weren't whitelisted, but an whitelisted canonicalized parent existed up the tree.

In all other cases, the original genres are canonicalized and considered for the final genre, except in the keep_existing logic branch.
This PR fixes the issue and results in the expected behavior for this combination of options.

For the bug to trigger several conditions had to be met:

- Canonicalization is enabled and a whitelist is specified.
- `force` and `keep_existing` are set. Meaning, that Lastfm is queried for a genre, but the existing genres are still left around when none are found online.
- A release with a non-whitelisted genre exists, but that genre has a whitelisted genre parent up the tree.
- That very release has no genre on lastfm.

This is rather convoluted, but stay with me :D
What would happen is the following:

- `keep_genres` is set to the existing genres, as `force` and `keep_existing` is set.
- Genres for `track`/`album`/`artist` aren't found for this release, as they don't exist in lastfm.
- Then the `keep_existing` logic is entered.
  - The old logic only checks if the existing genres have an **exact** match for the whitelist. In contrast to all other code branches, we don't do the `_try_resolve_stage` in case there's no direct match, resulting in no match.
- We continue to the fallback logic, which returns the fallback (`None` in my case)

This patch results in one last try to resolve the existing genres when `keep_existing` is set, which includes canonicalization (if enabled).
Found a couple of additional historical commits that added noise to git
blame and which should be ignored.
- Update the MusicBrainz integration to read the release group's
canonical `'primary-type'` field instead of the legacy `'type'` field
when deriving `info.albumtype`.

Noticed that `albumtype` was missing from the metadata when I did some
imports in my library.
When a metadata plugin raises an exception during the auto-tagger
process, the entire operation crashes. This behavior is not desirable,
since metadata lookups can legitimately fail for various reasons (e.g.,
temporary API downtime, network issues, or offline usage).

This PR introduces a safeguard by adding general exception handling
around metadata plugin calls. Instead of causing the whole process to
fail, exceptions from individual plugins are now caught and logged. This
ensures that the auto-tagger continues to function with the remaining
available metadata sources. I used a proxy pattern here as this
seems like an elegant solution to me.

This replaces the efforts from beetbox#5910
Fixes beetbox#6291

I think it does not hurt if the imported_items() method returned an
empty list instead of an throwing an exception if there is nothing in
it!
Fixes beetbox#6332 

- Promotes `packaging` from a release-only dependency to a required
runtime dependency by moving it into `pyproject.toml`'s main
dependencies.

- Updates `poetry.lock` to pick up patched versions of vulnerable
libraries, notably `brotli` (`1.1.0` → `1.2.0`), `urllib3` (`2.5.0` →
`2.6.3`), `werkzeug` (`3.1.3` → `3.1.5`), and `filelock` (`3.20.2` →
`3.20.3`).

<img width="442" height="169" alt="image"
src="https://github.com/user-attachments/assets/fe70475e-0163-4d45-b1e4-362008669c00"
/>
Add a helper to lower/strip and escape Lucene query syntax.
Use it when building search queries and add unit tests.
snejus and others added 24 commits March 10, 2026 00:56
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.
Closes beetbox#6178 (multiple metadata source results per ID) and beetbox#6181
(duplicate/overwrite of candidates).

- (beetbox#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
- (beetbox#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)
…rack-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
  ...
> 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 beetbox#5277 based on the
recent new mechanism to query musicbrainz
(beetbox#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
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
…tbox#6427)

Fixes beetbox#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.
## 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.
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
Formatting fix
Fixes beetbox#6412.

This is my first time submitting a PR for an open source project so
please point out any mistakes!

## Summary

- Add `discogs.extra_tags` configuration option to narrow Discogs search
queries using existing tag values.
- Map supported tags (`barcode`, `catalognum`, `country`, `label`,
`media`, `year`) to corresponding Discogs search parameters.
- Update Discogs plugin documentation and tests to cover the new
behavior.

## Details

The Discogs plugin now mirrors `musicbrainz.extra_tags` by allowing
users to specify additional tags that should be used when building
Discogs search filters.

- New config option: `discogs.extra_tags` (default: `[]`).
- Supported tags and their Discogs search parameters:
  - `barcode` → `barcode`
  - `catalognum` → `catno` (whitespace removed)
  - `country` → `country`
  - `label` → `label`
  - `media` → `format`
  - `year` → `year`
- Tags `alias` and `tracks` are recognized but intentionally ignored for
Discogs, since the Discogs API does not provide direct equivalents for
these MusicBrainz-specific fields.

When `extra_tags` are configured, the plugin uses `beets.util.plurality`
over the items in the import session to select the most common value for
each configured tag and adds the corresponding Discogs filter.

## Testing

- Added unit tests in `test/plugins/test_discogs.py` to verify:
- Default search filters remain unchanged when `extra_tags` is not set.
- `discogs.extra_tags: [label, catalognum]` results in `label` and
`catno` filters populated from library items (with catalog number
whitespace stripped).
- Ran:
  - `pytest test/plugins/test_discogs.py`
  - `pytest test/plugins/test_musicbrainz.py`
When the 'sources' config is a plain string (e.g. "sources: filesystem"
rather than "sources: [filesystem]"), confuse 2.2.0's Pairs template
iterates over individual characters instead of treating it as a single
source name. This is because Pairs no longer inherits StrSeq's
string-to-list normalization.

Normalize the config value to a list before calling as_pairs() so both
forms work correctly.

Fixes beetbox#6336
@wavebyrd wavebyrd requested a review from a team as a code owner March 13, 2026 02:57
if isinstance(raw_sources, str):
self.config["sources"].set(raw_sources.split())
sources = sanitize_pairs(
self.config["sources"].as_pairs(default_value="*"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to simply

Suggested change
self.config["sources"].as_pairs(default_value="*"),
self.config["sources"].as_str_seq(),

?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fetchart: "no art found" when filesystem is only source