diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 53ad96472e..ae43b3a2a1 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -26,13 +26,20 @@ import unicodedata import warnings from functools import partial -from typing import ClassVar +from typing import TYPE_CHECKING, ClassVar from urllib.parse import quote, urlencode import requests from typing_extensions import TypedDict from unidecode import unidecode +import beets +from beets import plugins, ui + +if TYPE_CHECKING: + from beets.importer import ImportTask + from beets.library import Item + try: import bs4 from bs4 import SoupStrainer @@ -48,10 +55,6 @@ except ImportError: HAS_LANGDETECT = False - -import beets -from beets import plugins, ui - DIV_RE = re.compile(r"<(/?)div>?", re.I) COMMENT_RE = re.compile(r"", re.S) TAG_RE = re.compile(r"<[^>]*>") @@ -258,8 +261,10 @@ def fetch_url(self, url): self._log.debug("failed to fetch: {0} ({1})", url, r.status_code) return None - def fetch(self, artist, title, album=None, length=None): - raise NotImplementedError() + def fetch( + self, artist: str, title: str, album: str, length: float + ) -> str | None: + raise NotImplementedError class LRCLibItem(TypedDict): @@ -321,11 +326,7 @@ def pick_lyrics( return min(data, key=lambda item: cls.get_rank(target_duration, item)) def fetch( - self, - artist: str, - title: str, - album: str | None = None, - length: float = 0.0, + self, artist: str, title: str, album: str, length: float ) -> str | None: """Fetch lyrics for the given artist, title, and album.""" params = { @@ -389,7 +390,7 @@ def encode(cls, text: str) -> str: return quote(unidecode(text)) - def fetch(self, artist, title, album=None, length=None): + def fetch(self, artist: str, title: str, *_) -> str | None: url = self.build_url(artist, title) html = self.fetch_url(url) @@ -437,7 +438,7 @@ def __init__(self, config, log): "User-Agent": USER_AGENT, } - def fetch(self, artist, title, album=None, length=None): + def fetch(self, artist: str, title: str, *_) -> str | None: """Fetch lyrics from genius.com Because genius doesn't allow accessing lyrics via the api, @@ -568,7 +569,7 @@ class Tekstowo(DirectBackend): def encode(cls, text: str) -> str: return cls.non_alpha_to_underscore(unidecode(text.lower())) - def fetch(self, artist, title, album=None, length=None): + def fetch(self, artist: str, title: str, *_) -> str | None: if html := self.fetch_url(self.build_url(artist, title)): return self.extract_lyrics(html) @@ -742,7 +743,7 @@ def is_page_candidate(self, url_link, url_title, title, artist): ratio = difflib.SequenceMatcher(None, song_title, title).ratio() return ratio >= typo_ratio - def fetch(self, artist, title, album=None, length=None): + def fetch(self, artist: str, title: str, *_) -> str | None: query = f"{artist} {title}" url = "https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s" % ( self.api_key, @@ -952,10 +953,7 @@ def func(lib, opts, args): for item in items: if not opts.local_only and not self.config["local"]: self.fetch_item_lyrics( - lib, - item, - write, - opts.force_refetch or self.config["force"], + item, write, opts.force_refetch or self.config["force"] ) if item.lyrics: if opts.printlyr: @@ -1041,15 +1039,13 @@ def writerest_indexes(self, directory): with open(conffile, "w") as output: output.write(REST_CONF_TEMPLATE) - def imported(self, session, task): + def imported(self, _, task: ImportTask) -> None: """Import hook for fetching lyrics automatically.""" if self.config["auto"]: for item in task.imported_items(): - self.fetch_item_lyrics( - session.lib, item, False, self.config["force"] - ) + self.fetch_item_lyrics(item, False, self.config["force"]) - def fetch_item_lyrics(self, lib, item, write, force): + def fetch_item_lyrics(self, item: Item, write: bool, force: bool) -> None: """Fetch and store lyrics for a single item. If ``write``, then the lyrics will also be written to the file itself. """ @@ -1058,18 +1054,17 @@ def fetch_item_lyrics(self, lib, item, write, force): self._log.info("lyrics already present: {0}", item) return - lyrics = None - album = item.album - length = round(item.length) - for artist, titles in search_pairs(item): - lyrics = [ - self.get_lyrics(artist, title, album=album, length=length) - for title in titles + album, length = item.album, item.length + lyrics_matches = ( + [ + lyrics + for t in titles + if (lyrics := self.get_lyrics(artist, t, album, length)) ] - if any(lyrics): - break + for artist, titles in search_pairs(item) + ) - lyrics = "\n\n---\n\n".join(filter(None, lyrics)) + lyrics = "\n\n---\n\n".join(next(filter(None, lyrics_matches), [])) if lyrics: self._log.info("fetched lyrics: {0}", item) @@ -1094,18 +1089,20 @@ def fetch_item_lyrics(self, lib, item, write, force): item.try_write() item.store() - def get_lyrics(self, artist, title, album=None, length=None): + def get_lyrics(self, artist: str, title: str, *args) -> str | None: """Fetch lyrics, trying each source in turn. Return a string or None if no lyrics were found. """ for backend in self.backends: - lyrics = backend.fetch(artist, title, album=album, length=length) + lyrics = backend.fetch(artist, title, *args) if lyrics: self._log.debug( "got lyrics from backend: {0}", backend.__class__.__name__ ) return _scrape_strip_cruft(lyrics, True) + return None + def append_translation(self, text, to_lang): from xml.etree import ElementTree diff --git a/test/plugins/test_lyrics.py b/test/plugins/test_lyrics.py index eac9ef77cf..a29582fc02 100644 --- a/test/plugins/test_lyrics.py +++ b/test/plugins/test_lyrics.py @@ -205,7 +205,7 @@ def test_backend_source(self, backend): """ title = "Lady Madonna" - res = backend.fetch("The Beatles", title) + res = backend.fetch("The Beatles", title, "", 0.0) assert res assert PHRASE_BY_TITLE[title] in res.lower()