- Route both album and track preview output through one show_change path
with polymorphic Change helpers.
- Removes duplicate branching in session prompts and keeps preview
rendering behavior centralized.
Introduce Source class to encapsulate metadata extraction logic,
replacing scattered `get_most_common_tags` calls throughout codebase.
- Add Source class in importer.tasks with artist, name, and metadata
- Simplify `distance()` to accept Source.data instead of items list
- Update display functions to use Source objects
- Cache Source creation with @cached_property on ImportTask
This centralizes metadata handling and reduces coupling between
autotag and library modules.
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.
During interactive import, the `edit` plugin only exposed item-level
fields — the `albumfields` configuration was completely ignored in
`importer_edit()`. Users who wanted to modify album-level fields such as
`album` or `year` had to complete the import, then run `beet edit -a
QUERY` as a separate step.
### Solution
Updated `importer_edit()` to support editing album-level fields during
import. Album fields (configured via `albumfields`) are presented as a
YAML header section prepended to the per-track documents. Changes to the
header are applied to all items in the album, making album-level edits
work in both the `eDit` and `edit Candidates` choices.
Change `config["verbose"]` from `1` to `2` so that DEBUG-level logging
from plugin import tasks and event hooks are emitted. (See
`beets.plugins._set_log_level_and_params`.)
`config["verbose"] == 2` does not appear to affect logging anywhere else
in the codebase, while `config["verbose"] == 3` would enable output of
messages logged via `BeetsLogger.extra_debug()`.
Fixes#3461.
Sorting by a field whose declared type is nullable (its null value is
`None`, e.g. `NullInteger`/`NullFloat`) crashed with `TypeError: '<' not
supported between instances of ...` when the field was present on some
objects but missing on others, because `FieldSort.sort` compared `None`
against real values.
The sort key now groups missing values together so `None` is never
compared against a present value: missing values are ordered first when
sorting ascending and last when descending, matching SQLite's default
ordering of NULLs already used by the fast `FixedFieldSort` (SQL) path.
Sorting by a field whose declared type is nullable (its null value is
`None`, e.g. `NullInteger`/`NullFloat`) raised `TypeError: '<' not
supported between instances of ...` when the field was present on some
objects but missing on others, because `FieldSort.sort` compared `None`
against real values.
Group missing values together in the sort key so they are never compared
against present ones: they are ordered first when sorting ascending and
last when descending, matching SQLite's default ordering of NULLs used by
the fast `FixedFieldSort` path.
Fixes#3461.
Co-Authored-By: Claude <noreply@anthropic.com>
## Description
The `@NEEDS_FFPROBE` decorator was incorrectly skipping tests when
`ffprobe` is available, because it was calling `ffprobe --version`,
while the valid option is `ffprobe -version` (with a single dash).
## Description
`requests_mock` was configured with a fallback matcher to return an
exception on any request. The intent was to have tests fail if any
requests are attempted in tests without matching mocks. This is the
default behavior of `requests_mock`, so no fallback matcher is
necessary. The side-effect of the fallback matcher, removed by this
commit, was that 1) the `NoMockAddress` exception was raised to the
function under test rather than to the test harness and 2) the
`NoMockAddress` exception was not able to be constructed because the
`exc` parameter to `requests_mock.register_uri` does not support
exceptions that require arguments. This behavior is visible in the
following log snippet, taken from an incorrectly-passing test:
```
DEBUG beets.fetchart:logging.py:163 downloading image: https://images.amazon.com/images/P/xxxx.01.LZZZZZZZ.jpg
DEBUG requests_mock.adapter:adapter.py:256 GET https://images.amazon.com/images/P/xxxx.01.LZZZZZZZ.jpg 200
DEBUG beets.fetchart:logging.py:163 not a supported image: text/html
DEBUG beets.fetchart:logging.py:163 downloading image: https://images.amazon.com/images/P/xxxx.02.LZZZZZZZ.jpg
DEBUG beets.fetchart:logging.py:163 error fetching art: NoMockAddress.__init__() missing 1 required positional argument: 'request'
```
The test that emitted these logs passed because the `FetchArt` plugin is
written to handle exceptions raised by art sources, skipping and logging
an error. For the exception to cause a failing test, the default
behavior of `requests_mock` of raising an exception to the test harness
when no matching mock requests can be found must be allowed.
`requests_mock` was configured with a fallback matcher to return an
exception on any request. The intent was to have tests fail if any
requests are attempted in tests without matching mocks. This is the
default behavior of `requests_mock`, so no fallback matcher is
necessary. The side-effect of the fallback matcher, removed by this
commit, was that 1) the `NoMockAddress` exception was raised to the
function under test rather than to the test harness and 2) the
`NoMockAddress` exception was not able to be constructed because the
`exc` parameter to `requests_mock.register_uri` does not support
exceptions that require arguments. This behavior is visible in the
following log snippet, taken from an incorrectly-passing test:
```
DEBUG beets.fetchart:logging.py:163 downloading image: https://images.amazon.com/images/P/xxxx.01.LZZZZZZZ.jpg
DEBUG requests_mock.adapter:adapter.py:256 GET https://images.amazon.com/images/P/xxxx.01.LZZZZZZZ.jpg 200
DEBUG beets.fetchart:logging.py:163 not a supported image: text/html
DEBUG beets.fetchart:logging.py:163 downloading image: https://images.amazon.com/images/P/xxxx.02.LZZZZZZZ.jpg
DEBUG beets.fetchart:logging.py:163 error fetching art: NoMockAddress.__init__() missing 1 required positional argument: 'request'
```
The test that emitted these logs passed because the `FetchArt` plugin is
written to handle exceptions raised by art sources, skipping and logging
an error. For the exception to cause a failing test, the default
behavior of `requests_mock` of raising an exception to the test harness
when no matching mock requests can be found must be allowed.
Signed-off-by: Ross Williams <ross@ross-williams.net>
- This change adds a small CLI override to `beetsplug/lyrics.py`:
`--no-keep-synced`. It is the inverse of `--keep-synced`, and lets one
manual `lyrics` run ignore the configured `keep_synced: yes`.
Fixes#6719
- This change shifts instrumental-track handling in the lyrics flow from
storing the literal `"[Instrumental]"` in `item.lyrics` to storing that
state as structured metadata in `lyrics_instrumental`.
- At the architecture level, the source of truth is now the `Lyrics`
value object in `beets/util/lyrics.py`: it normalizes instrumental
results early, clears the lyrics text, and carries an `instrumental`
flag through the rest of the pipeline.
- The `lyrics` plugin then persists that flag as the flexible field
`lyrics_instrumental`, alongside existing metadata like `lyrics_backend`
and `lyrics_url`. This keeps canonical lyrics text clean while
preserving whether the track was identified as instrumental.
- A new library migration, `InstrumentalLyricsInFlexFieldMigration`,
updates existing databases by moving legacy `"[Instrumental]"` values
out of the main `lyrics` column and into the flexible attribute. This
makes old data match the new model automatically.
- Docs and tests were updated to reflect the new behavior, so reviewers
can think of this as a small data-model cleanup with backward-compatible
migration: cleaner `lyrics` content, explicit instrumental metadata, and
automatic upgrade for existing libraries.
Change `config["verbose"]` from `1` to `2` so that DEBUG-level logging
from plugin import tasks and event hooks are emitted.
(See `beets.plugins._set_log_level_and_params`.)
`config["verbose"] == 2` does not appear to affect logging anywhere else
in the codebase, while `config["verbose"] == 3` would enable output of
messages logged via `BeetsLogger.extra_debug()`.
Signed-off-by: Ross Williams <ross@ross-williams.net>
The `@NEEDS_FFPROBE` decorator was incorrectly skipping tests when
`ffprobe` is available, because it was calling `ffprobe --version`,
while the valid option is `ffprobe -version` (with a single dash).
Signed-off-by: Ross Williams <ross@ross-williams.net>
Add `fetch_for_asis` setting to FetchArt plugin
## Description
Enable fetching art for imports even when as-is is selected as the
metadata source. Allows for the case when files with good metadata but
without album art are being imported (e.g. digital download store).
## To Do
<!--
- If you believe one of below checkpoints is not required for the change
you
are submitting, cross it out and check the box nonetheless to let us
know.
For example: - [x] ~Changelog~
- Regarding the changelog, often it makes sense to add your entry only
once
reviewing is finished. That way you might prevent conflicts from other
PR's in
that file, as well as keep the chance high your description fits with
the
latest revision of your feature/fix.
- Regarding documentation, bugfixes often don't require additions to the
docs.
- Please remove the descriptive sentences in braces from the enumeration
below,
which helps to unclutter your PR description.
-->
- [x] Documentation.
- [x] Changelog.
- [x] Tests.
Test whether fetchart is run during the import phase and, specifically,
whether the `fetch_for_asis` setting is honored. Mock boundary is
between `FetchArtPlugin` and `ArtSource`, an already-existing internal
API boundary.
Enable fetching art for imports even when as-is is selected as the
metadata source. Allows for the case when files with good metadata
but without album art are being imported (e.g. digital download store).
Signed-off-by: Ross Williams <ross@ross-williams.net>
Depends on https://github.com/beetbox/beets/pull/6732.
## Summary
This PR integrates `ftintitle` with metadata fetched through beets’
metadata-received events, so commands like `mbsync` apply
already-normalized metadata when `ftintitle` is enabled.
Addresses https://github.com/beetbox/beets/issues/1153.
## Changes
- Register `ftintitle` listeners for `trackinfo_received` and
`albuminfo_received` when `ftintitle.auto` is enabled.
- Rewrite fetched `TrackInfo` metadata before `mbsync` applies it,
avoiding unnecessary re-sync churn.
- Preserve existing manual command and import behavior while sharing the
same rewrite logic.
- Handle `artist_credit`, cached `Info` properties, empty titles, and
no-op configurations consistently.
- Update `ftintitle` docs for the expanded `auto` behavior.