mirror of
https://github.com/beetbox/beets.git
synced 2026-04-01 11:16:20 +02:00
copied more generic implementation from snejus, and updated the tests and docs accordingly
This commit is contained in:
parent
d4dc46591d
commit
55b6fbe77d
4 changed files with 71 additions and 146 deletions
|
|
@ -24,7 +24,7 @@ from contextlib import contextmanager, suppress
|
|||
from dataclasses import dataclass
|
||||
from functools import cached_property, partial, total_ordering
|
||||
from html import unescape
|
||||
from itertools import groupby
|
||||
from itertools import groupby, filterfalse
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, ClassVar, NamedTuple
|
||||
from urllib.parse import quote, quote_plus, urlencode, urlparse
|
||||
|
|
@ -38,6 +38,8 @@ from beets.autotag.distance import string_dist
|
|||
from beets.dbcore import types
|
||||
from beets.util.config import sanitize_choices
|
||||
from beets.util.lyrics import INSTRUMENTAL_LYRICS, Lyrics
|
||||
from beets.dbcore.query import FalseQuery
|
||||
from beets.library import Item, parse_query_string
|
||||
|
||||
from ._utils.requests import HTTPNotFoundError, RequestHandler
|
||||
|
||||
|
|
@ -47,7 +49,7 @@ if TYPE_CHECKING:
|
|||
import confuse
|
||||
|
||||
from beets.importer import ImportTask
|
||||
from beets.library import Item, Library
|
||||
from beets.library import Library
|
||||
from beets.logging import BeetsLogger as Logger
|
||||
|
||||
from ._typing import (
|
||||
|
|
@ -978,8 +980,7 @@ class LyricsPlugin(LyricsRequestHandler, plugins.BeetsPlugin):
|
|||
self.config.add(
|
||||
{
|
||||
"auto": True,
|
||||
"exclude_albums": [],
|
||||
"exclude_songs": [],
|
||||
"auto_ignore": None,
|
||||
"translate": {
|
||||
"api_key": None,
|
||||
"from_languages": [],
|
||||
|
|
@ -1065,13 +1066,14 @@ class LyricsPlugin(LyricsRequestHandler, plugins.BeetsPlugin):
|
|||
cmd.func = func
|
||||
return [cmd]
|
||||
|
||||
def imported(self, _, task: ImportTask) -> None:
|
||||
"""Import hook for fetching lyrics automatically."""
|
||||
for item in task.imported_items():
|
||||
if self.is_excluded(item):
|
||||
self.info("Skipping excluded item: {}", item)
|
||||
continue
|
||||
self.add_item_lyrics(item, False)
|
||||
def imported(self, _, task: ImportTask) -> None:
|
||||
if query_str := self.config["auto_ignore"].get():
|
||||
query, _ = parse_query_string(query_str, Item)
|
||||
else:
|
||||
query = FalseQuery() # matches nothing, so all items proceed normally
|
||||
|
||||
for item in filterfalse(query.match, task.imported_items()):
|
||||
self.add_item_lyrics(item, False)
|
||||
|
||||
def find_lyrics(self, item: Item) -> Lyrics | None:
|
||||
"""Return the first lyrics match from the configured source search."""
|
||||
|
|
@ -1137,17 +1139,3 @@ class LyricsPlugin(LyricsRequestHandler, plugins.BeetsPlugin):
|
|||
|
||||
return None
|
||||
|
||||
def is_excluded(self, item: Item) -> bool:
|
||||
"""Return True if the item matches an exclusion rule."""
|
||||
exclude_albums = {
|
||||
a.lower() for a in self.config["exclude_albums"].as_str_seq()
|
||||
}
|
||||
exclude_songs = {
|
||||
s.lower() for s in self.config["exclude_songs"].as_str_seq()
|
||||
}
|
||||
|
||||
if item.album and item.album.lower() in exclude_albums:
|
||||
return True
|
||||
if item.title and item.title.lower() in exclude_songs:
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -26,9 +26,8 @@ New features
|
|||
:bug:`2661`
|
||||
- :doc:`plugins/play`: Added ``-R``/``--randomize`` flag to shuffle the playlist
|
||||
order before passing it to the player.
|
||||
- :doc:`plugins/lyrics`: Add ``exclude_albums`` and ``exclude_songs``
|
||||
configuration options to skip fetching lyrics for specific albums or songs
|
||||
during auto import.
|
||||
- :doc:`plugins/lyrics`: Add ``auto_ignore`` configuration option to skip
|
||||
fetching lyrics for items matching a beets query during auto import.
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@ Default configuration:
|
|||
|
||||
lyrics:
|
||||
auto: yes
|
||||
exclude_albums: []
|
||||
exclude_songs: []
|
||||
auto_ignore: null
|
||||
translate:
|
||||
api_key:
|
||||
from_languages: []
|
||||
|
|
@ -65,12 +64,20 @@ Default configuration:
|
|||
The available options are:
|
||||
|
||||
- **auto**: Fetch lyrics automatically during import.
|
||||
- **exclude_albums**: A list of album titles to skip when fetching lyrics during
|
||||
auto import. Matching is case-insensitive. Default: ``[]`` (no albums
|
||||
excluded).
|
||||
- **exclude_songs**: A list of song titles to skip when fetching lyrics during
|
||||
auto import. Matching is case-insensitive. Default: ``[]`` (no songs
|
||||
excluded).
|
||||
- **auto_ignore**: A beets query string of items to skip when fetching lyrics
|
||||
during auto import. For example, to skip tracks from Bandcamp or with a
|
||||
Techno genre:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
lyrics:
|
||||
auto_ignore: |
|
||||
data_source:bandcamp
|
||||
,
|
||||
genre:techno
|
||||
|
||||
Default: ``null`` (nothing is ignored). See :doc:`/reference/query` for
|
||||
the query syntax.
|
||||
- **translate**:
|
||||
|
||||
- **api_key**: Api key to access your Azure Translator resource. (see
|
||||
|
|
|
|||
|
|
@ -368,149 +368,80 @@ class TestLyricsPlugin(LyricsPluginMixin):
|
|||
item.lyrics_translation_language
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config_key, exclusions, item, expected",
|
||||
"auto_ignore, items, expected_titles",
|
||||
[
|
||||
pytest.param(
|
||||
None,
|
||||
[],
|
||||
Item(title="Come Together", album="Abbey Road"),
|
||||
False,
|
||||
id="default",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_albums",
|
||||
["Greatest Hits"],
|
||||
Item(title="Come Together", album="Greatest Hits"),
|
||||
True,
|
||||
id="exact-album",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
[Item(title="Come Together", album="Abbey Road", genre="Rock")],
|
||||
["Come Together"],
|
||||
Item(title="Come Together", album="Abbey Road"),
|
||||
True,
|
||||
id="exact-song",
|
||||
id="fetch-when-no-ignore",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_albums",
|
||||
["greatest hits"],
|
||||
Item(title="Come Together", album="Greatest Hits"),
|
||||
True,
|
||||
id="album-case-insensitive",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
["COME TOGETHER"],
|
||||
Item(title="Come Together", album="Abbey Road"),
|
||||
True,
|
||||
id="song-case-insensitive",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_albums",
|
||||
["Greatest"],
|
||||
Item(title="Come Together", album="Greatest Hits"),
|
||||
False,
|
||||
id="album-no-partial-match",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
["Come"],
|
||||
Item(title="Come Together", album="Abbey Road"),
|
||||
False,
|
||||
id="song-no-partial-match",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
["Come Together", "Let It Be"],
|
||||
Item(title="Come Together"),
|
||||
True,
|
||||
id="first-of-many",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
["Come Together", "Let It Be"],
|
||||
Item(title="Let It Be"),
|
||||
True,
|
||||
id="second-of-many",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
["Come Together", "Let It Be"],
|
||||
Item(title="Hey Jude"),
|
||||
False,
|
||||
id="not-in-many",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_is_excluded(
|
||||
self, lyrics_plugin, config_key, exclusions, item, expected
|
||||
):
|
||||
if config_key:
|
||||
lyrics_plugin.config[config_key].set(exclusions)
|
||||
|
||||
assert lyrics_plugin.is_excluded(item) is expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config_key, exclusions, items, expected_titles",
|
||||
[
|
||||
pytest.param(
|
||||
None,
|
||||
"album:Greatest Hits",
|
||||
[Item(title="Come Together", album="Greatest Hits", genre="Rock")],
|
||||
[],
|
||||
[Item(title="Come Together", album="Abbey Road")],
|
||||
["Come Together"],
|
||||
id="fetch-normal-item",
|
||||
id="skip-matching-album",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_albums",
|
||||
["Greatest Hits"],
|
||||
[Item(title="Come Together", album="Greatest Hits")],
|
||||
"album:Greatest Hits",
|
||||
[Item(title="Come Together", album="Abbey Road", genre="Rock")],
|
||||
["Come Together"],
|
||||
id="fetch-non-matching-album",
|
||||
),
|
||||
pytest.param(
|
||||
"genre:rock",
|
||||
[Item(title="Come Together", album="Abbey Road", genre="Rock")],
|
||||
[],
|
||||
id="skip-excluded-album",
|
||||
id="query-case-insensitive",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
["Come Together"],
|
||||
[Item(title="Come Together", album="Abbey Road")],
|
||||
[],
|
||||
id="skip-excluded-song",
|
||||
),
|
||||
pytest.param(
|
||||
"exclude_songs",
|
||||
["Come Together"],
|
||||
"genre:Techno",
|
||||
[
|
||||
Item(title="Hey Jude", album="Abbey Road"),
|
||||
Item(title="Come Together", album="Abbey Road"),
|
||||
Item(title="Hey Jude", album="Abbey Road", genre="Rock"),
|
||||
Item(title="Techno Song", album="Club Hits", genre="Techno"),
|
||||
],
|
||||
["Hey Jude"],
|
||||
id="mixed-task",
|
||||
),
|
||||
pytest.param(None, [], [], [], id="empty-task"),
|
||||
pytest.param(
|
||||
"album:Greatest Hits , genre:Techno",
|
||||
[
|
||||
Item(title="Old Song", album="Greatest Hits", genre="Rock"),
|
||||
Item(title="Techno Song", album="Club Hits", genre="Techno"),
|
||||
Item(title="Come Together", album="Abbey Road", genre="Rock"),
|
||||
],
|
||||
["Come Together"],
|
||||
id="multiple-queries",
|
||||
),
|
||||
pytest.param(
|
||||
"album:Greatest Hits",
|
||||
[],
|
||||
[],
|
||||
id="empty-task",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_imported(
|
||||
self,
|
||||
lyrics_plugin,
|
||||
monkeypatch,
|
||||
config_key,
|
||||
exclusions,
|
||||
auto_ignore,
|
||||
items,
|
||||
expected_titles,
|
||||
):
|
||||
if config_key:
|
||||
lyrics_plugin.config[config_key].set(exclusions)
|
||||
|
||||
if auto_ignore:
|
||||
lyrics_plugin.config["auto_ignore"].set(auto_ignore)
|
||||
|
||||
calls = []
|
||||
monkeypatch.setattr(
|
||||
lyrics_plugin,
|
||||
"add_item_lyrics",
|
||||
lambda current_item, write: calls.append(
|
||||
(current_item.title, write)
|
||||
),
|
||||
lambda current_item, write: calls.append((current_item.title, write)),
|
||||
)
|
||||
|
||||
|
||||
task = SimpleNamespace(imported_items=lambda: items)
|
||||
lyrics_plugin.imported(None, task)
|
||||
|
||||
|
||||
assert calls == [(title, False) for title in expected_titles]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue