Skip to content

Commit

Permalink
Append source to the lyrics
Browse files Browse the repository at this point in the history
  • Loading branch information
snejus committed Oct 19, 2024
1 parent 4fb52c9 commit 06a4b2a
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 16 deletions.
35 changes: 23 additions & 12 deletions beetsplug/lyrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def enabled(self) -> bool:

def fetch(
self, artist: str, title: str, album: str, length: int
) -> str | None:
) -> tuple[str, str] | None:
raise NotImplementedError


Expand All @@ -285,6 +285,7 @@ class LRCLyrics:
)

target_duration: float
id: int
duration: float
instrumental: bool
plain: str
Expand All @@ -300,6 +301,7 @@ def make(
) -> LRCLyrics:
return cls(
target_duration,
candidate["id"],
candidate["duration"] or 0.0,
candidate["instrumental"],
candidate["plainLyrics"],
Expand Down Expand Up @@ -379,18 +381,22 @@ def pick_best_match(cls, lyrics: Iterable[LRCLyrics]) -> LRCLyrics | None:

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."""
fetch = partial(self.fetch_candidates, artist, title, album)
make = partial(LRCLyrics.make, target_duration=length)
pick = self.pick_best_match
try:
return next(
item = next(
filter(None, map(pick, (map(make, x) for x in fetch())))
).get_text(self.config["synced"])
)
except StopIteration:
return None

return item.get_text(
self.config["synced"]
), f"{self.BASE_URL}/{item.id}"


class DirectMixin:
"""A backend mixin for fetching lyrics directly."""
Expand Down Expand Up @@ -426,7 +432,7 @@ def encode(cls, text: str) -> str:

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)
Expand All @@ -448,7 +454,7 @@ def fetch(self, artist: str, title: str, *_) -> str | None:
# sometimes there are non-existent lyrics with some content
if "Lyrics | Musixmatch" in lyrics:
return None
return lyrics
return lyrics, url


class Html:
Expand Down Expand Up @@ -554,13 +560,13 @@ def get_results(self, artist: str, title: str) -> Iterable[SearchResult]:
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

Expand Down Expand Up @@ -618,8 +624,12 @@ class Tekstowo(DirectMixin, SoupBackend):
def encode(cls, text: str) -> str:
return cls.non_alpha_to_underscore(unidecode(text.lower()))

def fetch(self, artist: str, title: str, *_) -> str | None:
return self.scrape(self.fetch_text(self.build_url(artist, title)))
def fetch(self, artist: str, title: str, *_) -> tuple[str, str] | None:
url = self.build_url(artist, title)
if lyrics := self.scrape(self.fetch_text(url)):
return lyrics, url

return None

@classmethod
def scrape(cls, html: str) -> str | None:
Expand Down Expand Up @@ -1035,8 +1045,9 @@ def get_lyrics(self, artist: str, title: str, *args) -> str | None:
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

Expand Down
18 changes: 14 additions & 4 deletions test/plugins/test_lyrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,11 +279,12 @@ def _patch_google_search(self, requests_mock, lyrics_page):

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


Expand Down Expand Up @@ -396,6 +397,7 @@ def test_scrape(self, backend, lyrics_html, expecting_lyrics):

def lyrics_match(**overrides):
return {
"id": 1,
"instrumental": False,
"duration": LYRICS_DURATION,
"syncedLyrics": "synced",
Expand Down Expand Up @@ -437,7 +439,9 @@ def fetch_lyrics(self, backend, requests_mock, response_data):
],
)
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",
Expand Down Expand Up @@ -484,4 +488,10 @@ def test_synced_config_option(self, fetch_lyrics, expected_lyrics):
)
@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

0 comments on commit 06a4b2a

Please sign in to comment.