Commit graph

12644 commits

Author SHA1 Message Date
J0J0 Todos
a434ecfe7b Refactor _get_genre test to parametrized pytest 2025-01-21 17:04:02 +01:00
J0J0 Todos
b0e0f1b048 Fallback to next stage when fetch_ returns None
This was the original behaviour and broke when _combine_and_label helper
was introduced.
2025-01-21 17:04:02 +01:00
J0J0 Todos
462a7a524a _combine_and_label return None not empty str 2025-01-21 17:04:02 +01:00
J0J0 Todos
6866fce364 Fix default for _dedup_genre whitelist arg
when not stated otherwise whitelist_only must be disabled, we assume it
that way in _get_genre calls.
2025-01-21 17:04:02 +01:00
J0J0 Todos
70d641c556 Experiment with test_lastgenre 2025-01-21 17:04:02 +01:00
J0J0 Todos
cbc33e78fc lastgenre: Add comments over groups of methods
trying to get a little order in the chaos. Maybe reordering and/or
moving out of the main plugin logic would be a better idea for some
methods but don't put much more refactoring into this PR to keep it
readable.
2025-01-21 17:04:02 +01:00
J0J0 Todos
2c86af3ce6 Quickfix constant msgpack poetry issue 2025-01-21 17:04:02 +01:00
J0J0 Todos
11f7a98917 Refactor keep/new genres combination
- Handle genre combination logic in a well documented helper function
  that also include type hints.
- Throughout the _get_genre function rename the result variable to
  new_genres to make it clearly descriptive.
- Rewrite thze _get_genre function's docstring.
2025-01-21 17:04:02 +01:00
J0J0 Todos
d935ec869c Implement --force and --keep-allowed behaviours
- Retrieving, filtering and deduplicating present genres of Items/Albums
  via separate methods.
- Implement all four cases of behaviour as described in PR#4982
- Issues:
  - There is quite some unnecessary spliting of genres from strings into
    lists and the other way round happening throughout the plugin.
  - In the case where existing genres get "augmented" with last.fm
    genres, we might end up with _more_ genres than the configured
    limit.
2025-01-21 17:04:02 +01:00
J0J0 Todos
40760a0621 Docs for lastgenre keep_allowed/force
Keep both options' "Configuration" chapter texts as compact as possible,
while linking to a new chapter that describes all 4 possible
combinations in detail.
2025-01-21 17:04:02 +01:00
J0J0 Todos
318c02099a Add lastgenre keep_allowed options (-k/-K)
- Default to False.
- During PR#4982 discussions we came to the conclusion that the
  following behaviour would be a good new default choice:
  - Keep whitelisted existing genres
  - Only Fetch last.fm genres for empty tags.
  - To get this we also have to change the default of the force
    option!!!
  - Resulting in "force: no" and "keep_allowed: yes"; see Case 4 in
    PR#4982 description
- Options are not put to use yet, just defined and defaults set!
2025-01-21 17:04:02 +01:00
J0J0 Todos
4ea8650799 Use provided deduplicate function for keep_allowed
generation, instead of a simple set(). This way we keep the original
order of genres.
2025-01-21 17:04:02 +01:00
J0J0 Todos
fe466f4bb3 Use separator as configured instead of hardcoding
in lastgenre plugin.
2025-01-21 17:04:02 +01:00
J0J0 Todos
517c037c25 Refactor lastgenre keep_allowed to list comprehension 2025-01-21 17:04:02 +01:00
J0J0 Todos
7d6a4046ce Handle dups of existing genres in lastgenre plugin
When handling existing comma-separated genres in the _get_genre method
of the plugin, make sure duplicate genres are removed.
2025-01-21 17:04:02 +01:00
J0J0 Todos
6ab6ae2051 Fix track-level genre handling in lastgenre plugin
When `lastgenre.source: track` is configured,

- `lastgenre -a` _should not_ fall back to the album level genre (by
  making use of the with_album=False kwarg of the Libary's get method).
- `lastgenre -a`, when finally storing the genres of _an album_, should
  _not_ also write the tracks genres (by making use of the inherit=False
  kwarg of the Album's store method.
2025-01-21 17:04:02 +01:00
J0J0 Todos
4ff8c34ed1 Quickfix lastgenre always overwriting multi-genre 2025-01-21 17:04:02 +01:00
Šarūnas Nejus
f709ac14d8
Fix lrclib lyrics (#5406)
Fixes #5102

### LRCLib lyrics backend fixes

#### Bug Fixes
- Fixed fetching lyrics from `lrclib` source. If lyrics for a specific
album, artist, and title combination are not found, the plugin now
searches for the artist and title and picks the most relevant result,
scoring them by
  1. Duration similarity to the target item 
  1. Availability of synced lyrics
- Updated the default `sources` configuration to prioritize `lrclib`
over other sources for faster and more reliable results.

#### Code Improvements
  - Added type annotations to `fetch` method in all backends.
- Introduced `LRCLyrics` and `LRCLibItem` classes to encapsulate lyrics
data and improve code structure.
- Enhanced error handling and logging enchancements to the `LRCLib`
backend. These will be added to the rest of the backends in a separate
PR.
2025-01-20 17:09:40 +00:00
Šarūnas Nejus
bb5f3e0593
lyrics: sort lrclib lyrics by synced field and query search first
I found that the `/get` endpoint often returns incorrect or unsynced
lyrics, while results returned by the `/search` more accurate options.

Thus I reversed the change in the previous commit to prioritize
searching first.
2025-01-20 13:14:37 +00:00
Šarūnas Nejus
33aafdd50b
Remove trailing spaces in synced lyrics lines without text 2025-01-19 18:39:56 +00:00
Šarūnas Nejus
618c3a21a6
Try to GET LRCLib lyrics before searching 2025-01-19 18:39:54 +00:00
Šarūnas Nejus
2fb72c65a5
lyrics/LRCLib: handle instrumental lyrics 2025-01-19 15:19:44 +00:00
Šarūnas Nejus
30379bca38
Update lyrics.sources configuration to prioritize lrclib 2025-01-19 15:19:44 +00:00
Šarūnas Nejus
a398fbe62d
LRCLib: Improve exception handling 2025-01-19 15:19:44 +00:00
Šarūnas Nejus
8d4a569291
Fix fetching lyrics from lrclib
Adjust the base URL to perform a '/search' instead of attempting to
'/get' specific lyrics where we're unlikely to find lyrics for the
specific combination of album, artist, track names and the duration (see
https://lrclib.net/docs).

Since we receive an array of matching lyrics candidates, rank them by
their duration similarity to the item's duration, and whether they
contain synced lyrics.
2025-01-19 15:19:41 +00:00
Šarūnas Nejus
38c820901b
Refactor lyrics tests, do not search for empty metadata (#5452)
## Description

Fixes #2635
Fixes #5133

I realised that #5406 has gotten too big, thus I'm splitting it into
several smaller PRs.

This PR refactors lyrics plugin tests and fixes an empty metadata issue
in the lyrics logic.

#### CI
- Added `--extras=lyrics` to the Poetry install command to include the
lyrics plugin dependencies.
- In the main task which measures coverage, set `LYRICS_UPDATED`
environment variable based on changes detected in the lyrics files.

#### Test setup
- Introduced `ConfigMixin` to centralize configuration setup for tests,
reducing redundancy. This can be used by tests based on `pytest`.

#### Lyrics logic
- Trimmed whitespace from `item.title`, `item.artist`, and
`item.artist_sort` in `search_pairs` function.
- Added checks to avoid searching for lyrics if either the artist or
title is missing.
- Improved `_scrape_strip_cruft` function to remove Google Ads tags and
unnecessary HTML tags.

#### Lyrics tests overhaul
- Migrated lyrics tests to use `pytest` for better isolation and
configuration management.
- Deleted redundant lyrics text files and some unused utils.
- Marked tests that should only run when lyrics source code is updated
(`LYRICS_UPDATED` is set from the CI) using the `on_lyrics_update`
marker.

#### Documentation and Dependencies
- Added `requests-mock` version `1.12.1` to `pyproject.toml` and
`poetry.lock` for mocking HTTP requests in tests.
- Updated `setup.cfg` to include a new marker `on_lyrics_update`.
2025-01-19 01:59:54 +00:00
Šarūnas Nejus
e5c006d99d
Test lyrics texts explicitly
Add explicit checks for lyrics texts fetched from the tested sources.

- Introduced `LyricsPage` class to represent lyrics pages for integrated
  tests.
- Configured expected lyrics for each of the URLs that are being
  fetched.
- Consolidated integrated tests in a new `TestLyricsSources` class.
- Mocked Google Search API to return the lyrics page under test.
2025-01-19 01:54:53 +00:00
Šarūnas Nejus
c250bfa724
Google: test the entire fetch method 2025-01-19 01:48:04 +00:00
Šarūnas Nejus
334bbde826
Make album, duration required for LyricsPlugin.fetch
Since at least one Backend requires album` and `duration` arguments
(`LRCLib`), the caller (`LyricsPlugin.fetch_item_lyrics`) must always
provide them.

Since they need to provided, we need to enforce this by defining them as
positional arguments.

Why is this important? I found that integrated `LRCLib` tests have been
passing, but they called `LRCLib.fetch` with values for `artist` and
`title` fields only, while the actual functionality *always* provides
values for `album` and `duration` fields too.

When I adjusted the test to provide values for the missing fields,
I found that it failed. This makes sense: Lib `album` and `duration`
filters are strict on LRCLib, so I was not surprised the lyrics could
not be found.

Thus I adjusted `LRCLib` backend implementation to only filter by each
of these fields when their values are truthy.
2025-01-19 01:48:04 +00:00
Šarūnas Nejus
0a12d07a94
Do not attempt to fetch lyrics with empty data
Modified `search_pairs` function in `lyrics.py` to:

* Firstly strip each of `artist`, `artist_sort` and `title` fields
* Only generate alternatives if both `artist` and `title` are not empty
* Ensure that `artist_sort` is not empty and not equal to artist (ignoring
  case) before appending it to the artists

Extended tests to cover the changes.
2025-01-19 01:48:04 +00:00
Šarūnas Nejus
767a83fbe6
Refactor utils test cases to use pytest.mark.parametrize 2025-01-19 01:48:04 +00:00
Šarūnas Nejus
f674d65a65
Refactor search_pairs tests to use pytest parametrize
- Consolidated multiple test cases into parameterized tests for better
  readability and maintainability.
- Simplified assertions by comparing lists of actual and expected
  artists/titles.
- Added `unexpected_empty_artist` marker to handle cases which
  unexpectedly return an empty artist. This seems to be happen when
  `artist_sort` field is empty.
2025-01-19 01:48:04 +00:00
Šarūnas Nejus
14fd151f80
Refactor test_slug to pytest 2025-01-19 01:48:04 +00:00
Šarūnas Nejus
67e0af526c
Remove outdated GeniusLyrics test
The test for GeniusLyrics was heavily patched and no longer provided
useful coverage. It has been removed to clean up the test suite.
2025-01-19 01:48:04 +00:00
Šarūnas Nejus
35dcfe508a
Configure integrated lyrics tests to only run on lyrics code changes 2025-01-19 01:48:03 +00:00
Šarūnas Nejus
fc49902f3a
Refactor lyrics backend tests to use pytest fixtures
- Replaced unittest.mock with pytest fixtures for better test isolation and readability.
- Simplified test cases by using parameterized tests.
- Added `requests-mock` dependency to `pyproject.toml` and `poetry.lock`.
- Removed redundant helper functions and classes.
2025-01-19 01:33:15 +00:00
Šarūnas Nejus
b9bc2cbc04
lyrics: isolate test configuration
(#5102) Refactor lyrics tests which depended on local developer beets
configuration.
2025-01-19 01:33:14 +00:00
Šarūnas Nejus
29a3dd5084
Remove redundant lyrics test files 2025-01-19 01:32:17 +00:00
Šarūnas Nejus
3b73a26002
Address failing google sources tests
Two google sources failed to return the expected output. I looked into
each case why parsing failed:

- lyrics on musica.com contain <aside> Google Ads
- each lyrics line on lacoccinelle.net is wrapped within alternating
  <em> and <strong> tags

Thus remove these tags as part of the HTML cleanup logic.
2025-01-19 01:32:17 +00:00
Šarūnas Nejus
e99d457c9d
Rewrite lyrics integration tests 2025-01-19 01:32:17 +00:00
Šarūnas Nejus
bd3043935c
Handle ambiguous column names in queries involving 'any' field and a relation field (#5541)
## Description

Fixes a `sqlite3.OperationalError` that occurs when querying *any* field
and a field that only exists in the relation table. For exaple, trying
to list albums that contain **keyword** and contain a track with **foo**
in its title:
```
beet list -a keyword title:foo
```

## Root Cause
SQLite fails when JOINs contain ambiguous column references. This
happened because:
- *any* Album field search looks at `album`, `albumartist` and `genre`
fields.
- The second part of the query `title:foo` queries a field in the
`items` table, which got
  JOINed with `albums`
- Some fields (like `album`) exist in both `items` and `albums` tables,
thus SQLite couldn't resolve which table's column to use

## Changes
- Centralize query construction in `LibModel` with consistent table
qualification
- Add methods:
  - `field_query()` - Creates table-qualified field queries
  - `any_field_query()` - Creates multi-field OR queries
- `any_writable_media_field_query()` - Similar to the above but for BPD
/ media files
  - `match_all_query()` - Creates multi-field AND queries
- Remove `AnyFieldQuery` in favor of composed `OrQuery`
- Add tests for shared field querying
2025-01-19 01:18:52 +00:00
Šarūnas Nejus
a8ad7df064
Use Item.field_query for queries that receive user input 2025-01-19 01:09:11 +00:00
Šarūnas Nejus
4650f6513b
Add Item.any_writable_media_field_query method for BPD search 2025-01-19 01:09:11 +00:00
Šarūnas Nejus
d22c497dc0
Use table-qualified names in any field query
In order to include the table name for fields in this query, use the
`field_query` method.

Since `AnyFieldQuery` is just an `OrQuery` under the hood, remove it and
construct `OrQuery` explicitly instead.
2025-01-19 01:09:11 +00:00
Šarūnas Nejus
69faa58bab
Unify query construction logic
Unify query creation logic from
- queryparse.py:construct_query_part,
- Model.field_query,
- DefaultTemplateFunctions._tmpl_unique

to a single implementation under `LibModel.field_query` class method.
This method should be used for query resolution for model fields.
2025-01-19 01:09:11 +00:00
Šarūnas Nejus
f4097410eb
Fix Sphinx configuration post-update (#5588)
Adjust Sphinx configuration and release script

Changes:

- Update `source_suffix` in docs/conf.py to use dict format as required
by newer Sphinx
  versions
- Fix typo in theme config: `pygment_light_style` ->
`pygments_light_style`
- Improve release script to handle Sphinx intersphinx output formatting:
  - Replace tabs with spaces for consistent parsing
  - Add reference to sphinx.ext.intersphinx command in docstring
  - Update line parsing to match space-indented format
- Restructure CI workflow to ensure relevant dependencies are installed
and changelog formatting is tested.

These changes ensure compatibility with newer Sphinx versions and
improve the robustness
of the release script's reference parsing.

Edit:

While working on this PR GitHub decided to upgrade their linux/ubuntu
runners with `Ubuntu 24.04`. Ubuntu upgrades are the worst: imagine not
updating your system for 3 years and then suddenly upgrading
**everything** to the most recent versions. At which point you start
realizing that the final **S** in their **LTS** means __Suffering__
instead of __Support__.

We use just a few system dependencies in our builds and as expected,
things broke:
* `libcairo2-dev` now needs to be installed for pygobject.
* `pandoc` `rst` -> `markdown` conversion output has changed
* Completion tests are unhappy about `bash` / `bash-completion` upgrade,
and I could not figure out why so I'm just `xfail`ing that test in CI.
2025-01-19 01:06:00 +00:00
Šarūnas Nejus
a1e85727f5
Enable colors in the docs 2025-01-19 00:30:47 +00:00
Šarūnas Nejus
3bb8af819e
Ignore this test 2025-01-13 22:48:14 +00:00
Šarūnas Nejus
044712b796
Add now missing libcairo dependency
Thanks GitHub for breaking workflows out of thin air.
2025-01-13 06:58:30 +00:00
Šarūnas Nejus
0a53a930aa
Fix release formatting for new versions of pandoc
Ubuntu version in GitHub Actions has recently been upgraded to 24.04:
  https://github.com/actions/runner-images/issues/10636)

This meant that pandoc was upgraded and it changed the way markdown is
formatted by default.
2025-01-13 05:21:01 +00:00