diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 817afad20..02f64ab87 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -26,12 +26,19 @@ import struct 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 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 @@ -47,10 +54,6 @@ try: 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"<[^>]*>") @@ -256,20 +259,27 @@ class Backend: 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: int + ) -> str | None: + raise NotImplementedError class LRCLib(Backend): base_url = "https://lrclib.net/api/get" - def fetch(self, artist, title, album=None, length=None): - params = { + def fetch( + self, artist: str, title: str, album: str, length: int + ) -> str | None: + params: dict[str, str | int] = { "artist_name": artist, "track_name": title, - "album_name": album, - "duration": length, } + if album: + params["album_name"] = album + + if length: + params["duration"] = length try: response = requests.get( @@ -322,7 +332,7 @@ class MusiXmatch(DirectBackend): 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) @@ -370,7 +380,7 @@ class Genius(Backend): "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, @@ -501,7 +511,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) @@ -675,7 +685,7 @@ class Google(Backend): 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, @@ -885,10 +895,7 @@ class LyricsPlugin(plugins.BeetsPlugin): 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: @@ -974,15 +981,13 @@ class LyricsPlugin(plugins.BeetsPlugin): 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. """ @@ -991,18 +996,17 @@ class LyricsPlugin(plugins.BeetsPlugin): self._log.info("lyrics already present: {0}", item) return - lyrics = None - album = item.album - length = round(item.length) + lyrics_matches = [] + album, length = item.album, round(item.length) for artist, titles in search_pairs(item): - lyrics = [ - self.get_lyrics(artist, title, album=album, length=length) + lyrics_matches = [ + self.get_lyrics(artist, title, album, length) for title in titles ] - if any(lyrics): + if any(lyrics_matches): break - lyrics = "\n\n---\n\n".join(filter(None, lyrics)) + lyrics = "\n\n---\n\n".join(filter(None, lyrics_matches)) if lyrics: self._log.info("fetched lyrics: {0}", item) @@ -1027,18 +1031,20 @@ class LyricsPlugin(plugins.BeetsPlugin): 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 b7dec832d..b44d072f4 100644 --- a/test/plugins/test_lyrics.py +++ b/test/plugins/test_lyrics.py @@ -201,7 +201,7 @@ class LyricsBackendTest(PluginMixin): """ title = "Lady Madonna" - lyrics = backend.fetch("The Beatles", title) + lyrics = backend.fetch("The Beatles", title, "", 0) assert lyrics assert PHRASE_BY_TITLE[title] in lyrics.lower() @@ -366,7 +366,7 @@ class TestLRCLibLyrics(LyricsBackendTest): def fetch_lyrics(self, backend, requests_mock, response_data): requests_mock.get(lyrics.LRCLib.base_url, json=response_data) - return partial(backend.fetch, "la", "la", "la") + return partial(backend.fetch, "la", "la", "la", 0) @pytest.mark.parametrize( "response_data",