Skip to content

Commit 7893766

Browse files
committed
Improve flags structure and add tests
1 parent c95156a commit 7893766

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
@@ -44,7 +44,7 @@
4444
from logging import Logger
4545

4646
from beets.importer import ImportTask
47-
from beets.library import Item
47+
from beets.library import Item, Library
4848

4949
from ._typing import (
5050
GeniusAPI,
@@ -947,7 +947,6 @@ def translator(self) -> Translator | None:
947947

948948
def __init__(self):
949949
super().__init__()
950-
self.import_stages = [self.imported]
951950
self.config.add(
952951
{
953952
"auto": True,
@@ -966,6 +965,7 @@ def __init__(self):
966965
"fallback": None,
967966
"force": False,
968967
"local": False,
968+
"print": False,
969969
"synced": False,
970970
# Musixmatch is disabled by default as they are currently blocking
971971
# requests with the beets user agent.
@@ -979,14 +979,16 @@ def __init__(self):
979979
self.config["google_engine_ID"].redact = True
980980
self.config["genius_api_key"].redact = True
981981

982+
if self.config["auto"]:
983+
self.import_stages = [self.imported]
984+
982985
def commands(self):
983986
cmd = ui.Subcommand("lyrics", help="fetch song lyrics")
984987
cmd.parser.add_option(
985988
"-p",
986989
"--print",
987-
dest="printlyr",
988990
action="store_true",
989-
default=False,
991+
default=self.config["print"].get(),
990992
help="print lyrics to console",
991993
)
992994
cmd.parser.add_option(
@@ -1001,34 +1003,27 @@ def commands(self):
10011003
cmd.parser.add_option(
10021004
"-f",
10031005
"--force",
1004-
dest="force_refetch",
10051006
action="store_true",
1006-
default=False,
1007+
default=self.config["force"].get(),
10071008
help="always re-download lyrics",
10081009
)
10091010
cmd.parser.add_option(
10101011
"-l",
10111012
"--local",
1012-
dest="local_only",
10131013
action="store_true",
1014-
default=False,
1014+
default=self.config["local"].get(),
10151015
help="do not fetch missing lyrics",
10161016
)
10171017

1018-
def func(lib, opts, args):
1018+
def func(lib: Library, opts, args) -> None:
10191019
# The "write to files" option corresponds to the
10201020
# import_write config value.
1021-
items = list(lib.items(ui.decargs(args)))
1021+
self.config.set(vars(opts))
1022+
items = list(lib.items(args))
10221023
for item in items:
1023-
if not opts.local_only and not self.config["local"]:
1024-
self.fetch_item_lyrics(
1025-
item,
1026-
ui.should_write(),
1027-
opts.force_refetch or self.config["force"],
1028-
)
1029-
if item.lyrics:
1030-
if opts.printlyr:
1031-
ui.print_(item.lyrics)
1024+
self.add_item_lyrics(item, ui.should_write())
1025+
if item.lyrics and opts.print:
1026+
ui.print_(item.lyrics)
10321027

10331028
if opts.rest_directory and (
10341029
items := [i for i in items if i.lyrics]
@@ -1040,32 +1035,34 @@ def func(lib, opts, args):
10401035

10411036
def imported(self, _, task: ImportTask) -> None:
10421037
"""Import hook for fetching lyrics automatically."""
1043-
if self.config["auto"]:
1044-
for item in task.imported_items():
1045-
self.fetch_item_lyrics(item, False, self.config["force"])
1038+
for item in task.imported_items():
1039+
self.add_item_lyrics(item, False)
1040+
1041+
def find_lyrics(self, item: Item) -> str:
1042+
album, length = item.album, round(item.length)
1043+
matches = (
1044+
[
1045+
lyrics
1046+
for t in titles
1047+
if (lyrics := self.get_lyrics(a, t, album, length))
1048+
]
1049+
for a, titles in search_pairs(item)
1050+
)
10461051

1047-
def fetch_item_lyrics(self, item: Item, write: bool, force: bool) -> None:
1052+
return "\n\n---\n\n".join(next(filter(None, matches), []))
1053+
1054+
def add_item_lyrics(self, item: Item, write: bool) -> None:
10481055
"""Fetch and store lyrics for a single item. If ``write``, then the
10491056
lyrics will also be written to the file itself.
10501057
"""
1051-
# Skip if the item already has lyrics.
1052-
if not force and item.lyrics:
1053-
self.info("🔵 Lyrics already present: {}", item)
1058+
if self.config["local"]:
10541059
return
10551060

1056-
lyrics_matches = []
1057-
album, length = item.album, round(item.length)
1058-
for artist, titles in search_pairs(item):
1059-
lyrics_matches = [
1060-
self.get_lyrics(artist, title, album, length)
1061-
for title in titles
1062-
]
1063-
if any(lyrics_matches):
1064-
break
1065-
1066-
lyrics = "\n\n---\n\n".join(filter(None, lyrics_matches))
1061+
if not self.config["force"] and item.lyrics:
1062+
self.info("🔵 Lyrics already present: {}", item)
1063+
return
10671064

1068-
if lyrics:
1065+
if lyrics := self.find_lyrics(item):
10691066
self.info("🟢 Found lyrics: {0}", item)
10701067
if translator := self.translator:
10711068
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
@@ -75,6 +76,7 @@ The available options are:
7576
- **google_engine_ID**: The custom search engine to use.
7677
Default: The `beets custom search engine`_, which gathers an updated list of
7778
sources known to be scrapeable.
79+
- **print**: Print lyrics to the console.
7880
- **sources**: List of sources to search for lyrics. An asterisk ``*`` expands
7981
to all available sources. The ``google`` source will be automatically
8082
deactivated if no ``google_API_key`` is setup.

test/plugins/test_lyrics.py

+37-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import pytest
2626

2727
from beets.library import Item
28-
from beets.test.helper import PluginMixin
28+
from beets.test.helper import PluginMixin, TestHelper
2929
from beetsplug import lyrics
3030

3131
from .lyrics_pages import LyricsPage, lyrics_pages
@@ -42,6 +42,14 @@
4242
}
4343

4444

45+
@pytest.fixture(scope="module")
46+
def helper():
47+
helper = TestHelper()
48+
helper.setup_beets()
49+
yield helper
50+
helper.teardown_beets()
51+
52+
4553
class TestLyricsUtils:
4654
@pytest.mark.parametrize(
4755
"artist, title",
@@ -240,6 +248,27 @@ def test_error_handling(
240248
assert last_log
241249
assert re.search(expected_log_match, last_log, re.I)
242250

251+
@pytest.mark.parametrize(
252+
"plugin_config, found, expected",
253+
[
254+
({}, "new", "old"),
255+
({"force": True}, "new", "new"),
256+
({"force": True, "local": True}, "new", "old"),
257+
({"force": True, "fallback": None}, "", "old"),
258+
({"force": True, "fallback": ""}, "", ""),
259+
({"force": True, "fallback": "default"}, "", "default"),
260+
],
261+
)
262+
def test_overwrite_config(
263+
self, monkeypatch, helper, lyrics_plugin, found, expected
264+
):
265+
monkeypatch.setattr(lyrics_plugin, "find_lyrics", lambda _: found)
266+
item = helper.create_item(id=1, lyrics="old")
267+
268+
lyrics_plugin.add_item_lyrics(item, False)
269+
270+
assert item.lyrics == expected
271+
243272

244273
class LyricsBackendTest(LyricsPluginMixin):
245274
@pytest.fixture
@@ -289,8 +318,13 @@ def _patch_google_search(self, requests_mock, lyrics_page):
289318

290319
def test_backend_source(self, lyrics_plugin, lyrics_page: LyricsPage):
291320
"""Test parsed lyrics from each of the configured lyrics pages."""
292-
lyrics_info = lyrics_plugin.get_lyrics(
293-
lyrics_page.artist, lyrics_page.track_title, "", 186
321+
lyrics_info = lyrics_plugin.find_lyrics(
322+
Item(
323+
artist=lyrics_page.artist,
324+
title=lyrics_page.track_title,
325+
album="",
326+
length=186.0,
327+
)
294328
)
295329

296330
assert lyrics_info

0 commit comments

Comments
 (0)