Skip to content

Commit 36f0b3c

Browse files
committed
Improve flags structure and add tests
1 parent 49d92ff commit 36f0b3c

File tree

3 files changed

+74
-41
lines changed

3 files changed

+74
-41
lines changed

beetsplug/lyrics.py

+35-38
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
if TYPE_CHECKING:
4444
from beets.importer import ImportTask
45-
from beets.library import Item
45+
from beets.library import Item, Library
4646

4747
from ._typing import (
4848
GeniusAPI,
@@ -934,7 +934,6 @@ def translator(self) -> Translator | None:
934934

935935
def __init__(self):
936936
super().__init__()
937-
self.import_stages = [self.imported]
938937
self.config.add(
939938
{
940939
"auto": True,
@@ -953,6 +952,7 @@ def __init__(self):
953952
"fallback": None,
954953
"force": False,
955954
"local": False,
955+
"print": False,
956956
"synced": False,
957957
# Musixmatch is disabled by default as they are currently blocking
958958
# requests with the beets user agent.
@@ -966,14 +966,16 @@ def __init__(self):
966966
self.config["google_engine_ID"].redact = True
967967
self.config["genius_api_key"].redact = True
968968

969+
if self.config["auto"]:
970+
self.import_stages = [self.imported]
971+
969972
def commands(self):
970973
cmd = ui.Subcommand("lyrics", help="fetch song lyrics")
971974
cmd.parser.add_option(
972975
"-p",
973976
"--print",
974-
dest="printlyr",
975977
action="store_true",
976-
default=False,
978+
default=self.config["print"].get(),
977979
help="print lyrics to console",
978980
)
979981
cmd.parser.add_option(
@@ -988,34 +990,27 @@ def commands(self):
988990
cmd.parser.add_option(
989991
"-f",
990992
"--force",
991-
dest="force_refetch",
992993
action="store_true",
993-
default=False,
994+
default=self.config["force"].get(),
994995
help="always re-download lyrics",
995996
)
996997
cmd.parser.add_option(
997998
"-l",
998999
"--local",
999-
dest="local_only",
10001000
action="store_true",
1001-
default=False,
1001+
default=self.config["local"].get(),
10021002
help="do not fetch missing lyrics",
10031003
)
10041004

1005-
def func(lib, opts, args):
1005+
def func(lib: Library, opts, args) -> None:
10061006
# The "write to files" option corresponds to the
10071007
# import_write config value.
1008-
items = list(lib.items(ui.decargs(args)))
1008+
self.config.set(vars(opts))
1009+
items = list(lib.items(args))
10091010
for item in items:
1010-
if not opts.local_only and not self.config["local"]:
1011-
self.fetch_item_lyrics(
1012-
item,
1013-
ui.should_write(),
1014-
opts.force_refetch or self.config["force"],
1015-
)
1016-
if item.lyrics:
1017-
if opts.printlyr:
1018-
ui.print_(item.lyrics)
1011+
self.add_item_lyrics(item, ui.should_write())
1012+
if item.lyrics and opts.print:
1013+
ui.print_(item.lyrics)
10191014

10201015
if opts.rest_directory and (
10211016
items := [i for i in items if i.lyrics]
@@ -1027,32 +1022,34 @@ def func(lib, opts, args):
10271022

10281023
def imported(self, _, task: ImportTask) -> None:
10291024
"""Import hook for fetching lyrics automatically."""
1030-
if self.config["auto"]:
1031-
for item in task.imported_items():
1032-
self.fetch_item_lyrics(item, False, self.config["force"])
1025+
for item in task.imported_items():
1026+
self.add_item_lyrics(item, False)
1027+
1028+
def find_lyrics(self, item: Item) -> str:
1029+
album, length = item.album, round(item.length)
1030+
matches = (
1031+
[
1032+
lyrics
1033+
for t in titles
1034+
if (lyrics := self.get_lyrics(a, t, album, length))
1035+
]
1036+
for a, titles in search_pairs(item)
1037+
)
10331038

1034-
def fetch_item_lyrics(self, item: Item, write: bool, force: bool) -> None:
1039+
return "\n\n---\n\n".join(next(filter(None, matches), []))
1040+
1041+
def add_item_lyrics(self, item: Item, write: bool) -> None:
10351042
"""Fetch and store lyrics for a single item. If ``write``, then the
10361043
lyrics will also be written to the file itself.
10371044
"""
1038-
# Skip if the item already has lyrics.
1039-
if not force and item.lyrics:
1040-
self.info("🔵 Lyrics already present: {}", item)
1045+
if self.config["local"]:
10411046
return
10421047

1043-
lyrics_matches = []
1044-
album, length = item.album, round(item.length)
1045-
for artist, titles in search_pairs(item):
1046-
lyrics_matches = [
1047-
self.get_lyrics(artist, title, album, length)
1048-
for title in titles
1049-
]
1050-
if any(lyrics_matches):
1051-
break
1052-
1053-
lyrics = "\n\n---\n\n".join(filter(None, lyrics_matches))
1048+
if not self.config["force"] and item.lyrics:
1049+
self.info("🔵 Lyrics already present: {}", item)
1050+
return
10541051

1055-
if lyrics:
1052+
if lyrics := self.find_lyrics(item):
10561053
self.info("🟢 Found lyrics: {0}", item)
10571054
if translator := self.translator:
10581055
initial_lyrics = lyrics

docs/plugins/lyrics.rst

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Default configuration:
4747
force: no
4848
google_API_key: null
4949
google_engine_ID: 009217259823014548361:lndtuqkycfu
50+
print: no
5051
sources: [lrclib, google, genius, tekstowo]
5152
synced: no
5253
@@ -74,6 +75,7 @@ The available options are:
7475
- **google_engine_ID**: The custom search engine to use.
7576
Default: The `beets custom search engine`_, which gathers an updated list of
7677
sources known to be scrapeable.
78+
- **print**: Print lyrics to the console.
7779
- **sources**: List of sources to search for lyrics. An asterisk ``*`` expands
7880
to all available sources. The ``google`` source will be automatically
7981
deactivated if no ``google_API_key`` is setup.

test/plugins/test_lyrics.py

+37-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import pytest
2424

2525
from beets.library import Item
26-
from beets.test.helper import PluginMixin
26+
from beets.test.helper import PluginMixin, TestHelper
2727
from beetsplug import lyrics
2828

2929
from .lyrics_pages import LyricsPage, lyrics_pages
@@ -35,6 +35,14 @@
3535
}
3636

3737

38+
@pytest.fixture(scope="module")
39+
def helper():
40+
helper = TestHelper()
41+
helper.setup_beets()
42+
yield helper
43+
helper.teardown_beets()
44+
45+
3846
class TestLyricsUtils:
3947
@pytest.mark.parametrize(
4048
"artist, title",
@@ -232,6 +240,27 @@ def test_error_handling(
232240
assert last_log
233241
assert re.search(expected_log_match, last_log, re.I)
234242

243+
@pytest.mark.parametrize(
244+
"plugin_config, found, expected",
245+
[
246+
({}, "new", "old"),
247+
({"force": True}, "new", "new"),
248+
({"force": True, "local": True}, "new", "old"),
249+
({"force": True, "fallback": None}, "", "old"),
250+
({"force": True, "fallback": ""}, "", ""),
251+
({"force": True, "fallback": "default"}, "", "default"),
252+
],
253+
)
254+
def test_overwrite_config(
255+
self, monkeypatch, helper, lyrics_plugin, found, expected
256+
):
257+
monkeypatch.setattr(lyrics_plugin, "find_lyrics", lambda _: found)
258+
item = helper.create_item(id=1, lyrics="old")
259+
260+
lyrics_plugin.add_item_lyrics(item, False)
261+
262+
assert item.lyrics == expected
263+
235264

236265
class LyricsBackendTest(LyricsPluginMixin):
237266
@pytest.fixture
@@ -281,8 +310,13 @@ def _patch_google_search(self, requests_mock, lyrics_page):
281310

282311
def test_backend_source(self, lyrics_plugin, lyrics_page: LyricsPage):
283312
"""Test parsed lyrics from each of the configured lyrics pages."""
284-
lyrics_info = lyrics_plugin.get_lyrics(
285-
lyrics_page.artist, lyrics_page.track_title, "", 186
313+
lyrics_info = lyrics_plugin.find_lyrics(
314+
Item(
315+
artist=lyrics_page.artist,
316+
title=lyrics_page.track_title,
317+
album="",
318+
length=186.0,
319+
)
286320
)
287321

288322
assert lyrics_info

0 commit comments

Comments
 (0)