### Description
`extract_release_id` now accepts the native Spotify URI format
(`spotify:album:<id>`, `spotify:track:<id>`) in addition to the already
supported `open.spotify.com` URLs and bare IDs.
Co-authored-by: Marina Pena Malschitzky Crespo <marinacrespo@estudante.ufscar.br>
## Description
This PR adds https://lrcmux.dev
([repo](https://github.com/f1nniboy/lrcmux)) as a new lyrics fetcher
backend. lrcmux aggregates various different lyrics sources and exposes
them via a single API, so it's perfect as a replacement for the
currently defunct Musixmatch fetcher. Users can host their own instance
if they prefer not to share their queries with a central API.
# Description
This PR introduces a regex-based **normalization (alias) system** to
unify variant genre tags and improves the plugin's documentation.
The normalization feature uses an ordered list of regular expression
aliases to map variant spellings or synonyms to a single canonical name.
The mapping keys act as `re.Match.expand()` templates, supporting
`\g<N>` (or shorter, eg. `\1`) back-references to regex capture groups:
```yaml
lastgenre:
aliases:
# Maps 'hip-hop' -> 'hip hop' using back-references
# (regex full-match)
\1hop:
- (hip|trip|jazz)y?[ /-]*hop
# Static normalization examples
drum and bass:
- dnb
- d&b
```
- **Where?**: Integrated `normalize_genre` before `is_ignored` in both
the `LastFmClient` (for clean lookup/filtering) and the core
`_resolve_genres` loop (for uniform processing of existing file tags).
- Updated default whitelist and genre tree files for canonical-name
consistency, and curated the bundled `aliases.yaml` from the top 1,000
Last.fm tags to cover common inconsistencies without over-normalizing.
- Any normalization that occurs is logged (extra debug log level `beet
-vvv ...`)
- **Additional documentation changes**:
- Added a **[Choosing the Right
Tool](file:///Users/jtiefenbacher/git/beets/docs/plugins/lastgenre.rst)**
guide. This is for endusers but note that it might even be _a primary
starting point for maintainer review._
- Added **non-whitelist mode** details to the **Canonicalization**
chapter.
- **Additional unrelated changes**:
- Fixes a few variable name inconsistencies in the recently merged
`ignorelist` features' tests.
- Improved naming of ignorelist related type `IgnorePatternsByArtist`
- Fixes some inconsistencies in spelling in the data files we ship with
the plugin as well as adds some new genres to both files that seem to
be returned often by last.fm.
- For some changes the default aliases feature helps to streamline
inconsistencies that come from last.fm already.
- The default aliases file that ships with beets
- Tests fixes that prove that most of the alias normalization patterns
work as they are supposed to.
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
- Document the genre alias normalization feature and the default aliases
we ship with the plugin (by using rst "literalinclude" referring to
aliases.yaml directly)
- Clarify what canonicalization acutually does when used without a
whitelist enabled.
- New docs chapter "Using the right tool"
- Genre alias normalization feature implementation
- Extend and simplify _filter_valid()
- Remove drop_ignored_genres()
- More smaller ignorelist related refactoring
## Summary
- add a CI job that installs Beets without dependency groups or plugin
extras
- run `beet version` with an isolated configuration directory to verify
the base installation
Fixes#5713
Fixes#2438.
When editing album metadata, `artpath: null` was converted through the
string parsing path and could become a path ending in `None`.
This keeps YAML null values as `None` when dumping and applying edit
data, and adds a regression test for editing album metadata while
`artpath` is missing.
- Added a regression test.
- Added a changelog entry.
Fixes#5354.
`beet splupdate` raises `TypeError: unhashable type: 'list'` whenever a
smartplaylist configuration contains a `playlist:` query. smartplaylist
collects playlists in a `set`, which hashes the query. `playlist:`
parses to `PlaylistQuery`, a subclass of `InQuery` whose `pattern` is a
list, and `InQuery` inherited `FieldQuery.__hash__`, which does
`hash(self.pattern)` and fails on a list.
As @wisp3rwind suggested on the issue, this overrides `__hash__` on
`InQuery` to hash a tuple copy of the pattern. It stays order-sensitive
to match the inherited `FieldQuery.__eq__`, so the hash/eq contract
holds.
Changes:
- `beets/dbcore/query.py`: add `InQuery.__hash__`.
- `test/dbcore/test_query.py`: regression test that an `InQuery` with a
list pattern can be put in a set.
- changelog entry under Bug fixes.
Left the order-insensitive `__eq__`/`__hash__` and the smartplaylist
`set`-vs-`dict` cleanup you also floated out of scope here; happy to
follow up on either if you want them.
InQuery inherited FieldQuery.__hash__, which hashes the pattern directly.
A list pattern raises `unhashable type: 'list'`, which broke the
smartplaylist `splupdate` command whenever a `playlist:` query was configured.
Fixes#3804.
The `missing` plugin ignored `-f`/`--format` (and the `format_album`
config) in album mode. `beet missing -a` always printed a hardcoded
`<artist> - <title>` and never consulted the format string. Now each
missing release group is turned into an `Album` and emitted with
`format_album`, so the format option is respected. With the default
`$albumartist - $album` the output is unchanged, matching the behavior
already documented in `docs/plugins/missing.rst`.
Fixes#5635.
When the Subsonic server returns a non-JSON response, `subsonicupdate`
currently logs the JSON decode error directly. This can produce a
cryptic message like `Expecting value`.
This logs a clearer error with the server URL and HTTP status, and adds
a regression test for an HTML error response.
Found a few more places were it is possible to easily get rid of the
`unittest` setup in favor for `pytest`.
Touches and removes `unittest` from the following files:
- `test_ihate`
- `test_the`
- `test_substitute`
- `test_m3u`
- `test_acousticbrainz`
- `test_advancedwrite`
- `test_tidal`
- `test_inline`
- `test_mpdstats`
- `test_replace`
- `test_rewrite`
related to #5361
Because beets wraps `logging.Logger`, the default `stacklevel=1`
incorrectly reports all log messages being emitted from
`beets/logging.py:163` at the `super()._log()` callsite. Change the
default to `stacklevel=2` so that the reported line numbers in debug
logging are the actual logging callsites.
Part of #6807.
- Refactors `test/plugins/test_playlist.py` to use `pathlib.Path` as the
main path abstraction instead of building paths manually with
`os.path.join`.
- Centralizes playlist test setup by introducing shared helpers like
`write_absolute_playlist()`, `write_relative_playlist()`, and reusable
path attributes such as `c_track_path` and `nonexisting_track_path`.
- Simplifies fixture data creation by using `self.add_album(...)` in a
small loop instead of constructing test items manually.
- Architecturally, this change makes the test base class responsible for
common path definitions, playlist file generation, and initial library
setup. The individual test classes now mostly declare only the
`playlist` configuration they care about.
- High-level impact: the playlist tests become more consistent, less
repetitive, and easier to extend, while keeping behavior the same.
- It also makes path handling more explicit and type-safe in the tests
by treating filesystem paths as `Path` objects and only converting to
`str` where config values require it.
Fixes#6808
- This change makes the path-handling utilities `is_hidden`,
`sorted_walk`, `unique_path`, `remux_mpeglayer3_wav` and `albums_in_dir`
work with both `str` and `bytes` paths instead of assuming `bytes`
everywhere.
- Architecturally, the PR moves these helpers toward a single generic
path flow using `AnyStr`, so callers can stay in their native path type
while the utilities preserve that type through traversal, filtering, and
hidden-file checks.
- In `albums_in_dir`, the multi-disc detection logic was updated to
support both string and byte paths by splitting regex handling into
`str` and `bytes` variants while keeping the existing album-collapsing
behavior the same.
- High-level impact: this reduces path-conversion friction, improves
typing consistency across importer and util code, and makes the
filesystem helpers easier to reuse as the codebase continues moving away
from `bytes`-only assumptions.