Append source to the lyrics

This commit is contained in:
Šarūnas Nejus 2024-10-19 03:15:03 +01:00
parent bdc564a573
commit 734bcc28a8
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
2 changed files with 33 additions and 15 deletions

View file

@ -266,7 +266,7 @@ class Backend(RequestHandler, metaclass=BackendClass):
def fetch(
self, artist: str, title: str, album: str, length: int
) -> str | None:
) -> tuple[str, str] | None:
raise NotImplementedError
@ -277,6 +277,7 @@ class LRCLyrics:
DURATION_DIFF_TOLERANCE = 0.05
target_duration: float
id: int
duration: float
instrumental: bool
plain: str
@ -292,6 +293,7 @@ class LRCLyrics:
) -> LRCLyrics:
return cls(
target_duration,
candidate["id"],
candidate["duration"] or 0.0,
candidate["instrumental"],
candidate["plainLyrics"],
@ -374,14 +376,15 @@ class LRCLib(Backend):
def fetch(
self, artist: str, title: str, album: str, length: int
) -> str | None:
) -> tuple[str, str] | None:
"""Fetch lyrics text for the given song data."""
evaluate_item = partial(LRCLyrics.make, target_duration=length)
for group in self.fetch_candidates(artist, title, album, length):
candidates = [evaluate_item(item) for item in group]
if item := self.pick_best_match(candidates):
return item.get_text(self.config["synced"])
lyrics = item.get_text(self.config["synced"])
return lyrics, f"{self.GET_URL}/{item.id}"
return None
@ -420,7 +423,7 @@ class MusiXmatch(DirectBackend):
return quote(unidecode(text))
def fetch(self, artist: str, title: str, *_) -> str | None:
def fetch(self, artist: str, title: str, *_) -> tuple[str, str] | None:
url = self.build_url(artist, title)
html = self.fetch_text(url)
@ -442,7 +445,7 @@ class MusiXmatch(DirectBackend):
# sometimes there are non-existent lyrics with some content
if "Lyrics | Musixmatch" in lyrics:
return None
return lyrics
return lyrics, url
class Html:
@ -543,13 +546,13 @@ class SearchBackend(SoupMixin, Backend):
if check_match(candidate):
yield candidate
def fetch(self, artist: str, title: str, *_) -> str | None:
def fetch(self, artist: str, title: str, *_) -> tuple[str, str] | None:
"""Fetch lyrics for the given artist and title."""
for result in self.get_results(artist, title):
if (html := self.fetch_text(result.url)) and (
lyrics := self.scrape(html)
):
return lyrics
return lyrics, result.url
return None
@ -604,11 +607,15 @@ class Tekstowo(SoupMixin, DirectBackend):
def encode(cls, text: str) -> str:
return cls.non_alpha_to_underscore(unidecode(text.lower()))
def fetch(self, artist: str, title: str, *_) -> str | None:
def fetch(self, artist: str, title: str, *_) -> tuple[str, str] | None:
url = self.build_url(artist, title)
# We are expecting to receive a 404 since we are guessing the URL.
# Thus suppress the error so that it does not end up in the logs.
with suppress(NotFoundError):
return self.scrape(self.fetch_text(self.build_url(artist, title)))
if lyrics := self.scrape(self.fetch_text(url)):
return lyrics, url
return None
return None
@ -1014,8 +1021,9 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
self.info("Fetching lyrics for {} - {}", artist, title)
for backend in self.backends:
with backend.handle_request():
if lyrics := backend.fetch(artist, title, *args):
return lyrics
if lyrics_info := backend.fetch(artist, title, *args):
lyrics, url = lyrics_info
return f"{lyrics}\n\nSource: {url}"
return None

View file

@ -279,11 +279,12 @@ class TestLyricsSources(LyricsBackendTest):
def test_backend_source(self, lyrics_plugin, lyrics_page: LyricsPage):
"""Test parsed lyrics from each of the configured lyrics pages."""
lyrics = lyrics_plugin.get_lyrics(
lyrics_info = lyrics_plugin.get_lyrics(
lyrics_page.artist, lyrics_page.track_title, "", 186
)
assert lyrics
assert lyrics_info
lyrics, _ = lyrics_info.split("\n\nSource: ")
assert lyrics == lyrics_page.lyrics
@ -400,6 +401,7 @@ LYRICS_DURATION = 950
def lyrics_match(**overrides):
return {
"id": 1,
"instrumental": False,
"duration": LYRICS_DURATION,
"syncedLyrics": "synced",
@ -428,7 +430,9 @@ class TestLRCLibLyrics(LyricsBackendTest):
[({"synced": True}, "synced"), ({"synced": False}, "plain")],
)
def test_synced_config_option(self, fetch_lyrics, expected_lyrics):
assert fetch_lyrics() == expected_lyrics
lyrics, _ = fetch_lyrics()
assert lyrics == expected_lyrics
@pytest.mark.parametrize(
"response_data, expected_lyrics",
@ -490,4 +494,10 @@ class TestLRCLibLyrics(LyricsBackendTest):
)
@pytest.mark.parametrize("plugin_config", [{"synced": True}])
def test_fetch_lyrics(self, fetch_lyrics, expected_lyrics):
assert fetch_lyrics() == expected_lyrics
lyrics_info = fetch_lyrics()
if lyrics_info is None:
assert expected_lyrics is None
else:
lyrics, _ = fetch_lyrics()
assert lyrics == expected_lyrics