Improve flags structure and add tests

This commit is contained in:
Šarūnas Nejus 2024-10-23 13:06:21 +01:00
parent c95156adcd
commit 7893766e4c
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
3 changed files with 74 additions and 41 deletions

View file

@ -44,7 +44,7 @@ if TYPE_CHECKING:
from logging import Logger from logging import Logger
from beets.importer import ImportTask from beets.importer import ImportTask
from beets.library import Item from beets.library import Item, Library
from ._typing import ( from ._typing import (
GeniusAPI, GeniusAPI,
@ -947,7 +947,6 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.import_stages = [self.imported]
self.config.add( self.config.add(
{ {
"auto": True, "auto": True,
@ -966,6 +965,7 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
"fallback": None, "fallback": None,
"force": False, "force": False,
"local": False, "local": False,
"print": False,
"synced": False, "synced": False,
# Musixmatch is disabled by default as they are currently blocking # Musixmatch is disabled by default as they are currently blocking
# requests with the beets user agent. # requests with the beets user agent.
@ -979,14 +979,16 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
self.config["google_engine_ID"].redact = True self.config["google_engine_ID"].redact = True
self.config["genius_api_key"].redact = True self.config["genius_api_key"].redact = True
if self.config["auto"]:
self.import_stages = [self.imported]
def commands(self): def commands(self):
cmd = ui.Subcommand("lyrics", help="fetch song lyrics") cmd = ui.Subcommand("lyrics", help="fetch song lyrics")
cmd.parser.add_option( cmd.parser.add_option(
"-p", "-p",
"--print", "--print",
dest="printlyr",
action="store_true", action="store_true",
default=False, default=self.config["print"].get(),
help="print lyrics to console", help="print lyrics to console",
) )
cmd.parser.add_option( cmd.parser.add_option(
@ -1001,34 +1003,27 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
cmd.parser.add_option( cmd.parser.add_option(
"-f", "-f",
"--force", "--force",
dest="force_refetch",
action="store_true", action="store_true",
default=False, default=self.config["force"].get(),
help="always re-download lyrics", help="always re-download lyrics",
) )
cmd.parser.add_option( cmd.parser.add_option(
"-l", "-l",
"--local", "--local",
dest="local_only",
action="store_true", action="store_true",
default=False, default=self.config["local"].get(),
help="do not fetch missing lyrics", help="do not fetch missing lyrics",
) )
def func(lib, opts, args): def func(lib: Library, opts, args) -> None:
# The "write to files" option corresponds to the # The "write to files" option corresponds to the
# import_write config value. # import_write config value.
items = list(lib.items(ui.decargs(args))) self.config.set(vars(opts))
items = list(lib.items(args))
for item in items: for item in items:
if not opts.local_only and not self.config["local"]: self.add_item_lyrics(item, ui.should_write())
self.fetch_item_lyrics( if item.lyrics and opts.print:
item, ui.print_(item.lyrics)
ui.should_write(),
opts.force_refetch or self.config["force"],
)
if item.lyrics:
if opts.printlyr:
ui.print_(item.lyrics)
if opts.rest_directory and ( if opts.rest_directory and (
items := [i for i in items if i.lyrics] items := [i for i in items if i.lyrics]
@ -1040,32 +1035,34 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
def imported(self, _, task: ImportTask) -> None: def imported(self, _, task: ImportTask) -> None:
"""Import hook for fetching lyrics automatically.""" """Import hook for fetching lyrics automatically."""
if self.config["auto"]: for item in task.imported_items():
for item in task.imported_items(): self.add_item_lyrics(item, False)
self.fetch_item_lyrics(item, False, self.config["force"])
def fetch_item_lyrics(self, item: Item, write: bool, force: bool) -> None: def find_lyrics(self, item: Item) -> str:
album, length = item.album, round(item.length)
matches = (
[
lyrics
for t in titles
if (lyrics := self.get_lyrics(a, t, album, length))
]
for a, titles in search_pairs(item)
)
return "\n\n---\n\n".join(next(filter(None, matches), []))
def add_item_lyrics(self, item: Item, write: bool) -> None:
"""Fetch and store lyrics for a single item. If ``write``, then the """Fetch and store lyrics for a single item. If ``write``, then the
lyrics will also be written to the file itself. lyrics will also be written to the file itself.
""" """
# Skip if the item already has lyrics. if self.config["local"]:
if not force and item.lyrics: return
if not self.config["force"] and item.lyrics:
self.info("🔵 Lyrics already present: {}", item) self.info("🔵 Lyrics already present: {}", item)
return return
lyrics_matches = [] if lyrics := self.find_lyrics(item):
album, length = item.album, round(item.length)
for artist, titles in search_pairs(item):
lyrics_matches = [
self.get_lyrics(artist, title, album, length)
for title in titles
]
if any(lyrics_matches):
break
lyrics = "\n\n---\n\n".join(filter(None, lyrics_matches))
if lyrics:
self.info("🟢 Found lyrics: {0}", item) self.info("🟢 Found lyrics: {0}", item)
if translator := self.translator: if translator := self.translator:
initial_lyrics = lyrics initial_lyrics = lyrics

View file

@ -47,6 +47,7 @@ Default configuration:
force: no force: no
google_API_key: null google_API_key: null
google_engine_ID: 009217259823014548361:lndtuqkycfu google_engine_ID: 009217259823014548361:lndtuqkycfu
print: no
sources: [lrclib, google, genius, tekstowo] sources: [lrclib, google, genius, tekstowo]
synced: no synced: no
@ -75,6 +76,7 @@ The available options are:
- **google_engine_ID**: The custom search engine to use. - **google_engine_ID**: The custom search engine to use.
Default: The `beets custom search engine`_, which gathers an updated list of Default: The `beets custom search engine`_, which gathers an updated list of
sources known to be scrapeable. sources known to be scrapeable.
- **print**: Print lyrics to the console.
- **sources**: List of sources to search for lyrics. An asterisk ``*`` expands - **sources**: List of sources to search for lyrics. An asterisk ``*`` expands
to all available sources. The ``google`` source will be automatically to all available sources. The ``google`` source will be automatically
deactivated if no ``google_API_key`` is setup. deactivated if no ``google_API_key`` is setup.

View file

@ -25,7 +25,7 @@ from pathlib import Path
import pytest import pytest
from beets.library import Item from beets.library import Item
from beets.test.helper import PluginMixin from beets.test.helper import PluginMixin, TestHelper
from beetsplug import lyrics from beetsplug import lyrics
from .lyrics_pages import LyricsPage, lyrics_pages from .lyrics_pages import LyricsPage, lyrics_pages
@ -42,6 +42,14 @@ PHRASE_BY_TITLE = {
} }
@pytest.fixture(scope="module")
def helper():
helper = TestHelper()
helper.setup_beets()
yield helper
helper.teardown_beets()
class TestLyricsUtils: class TestLyricsUtils:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"artist, title", "artist, title",
@ -240,6 +248,27 @@ class TestLyricsPlugin(LyricsPluginMixin):
assert last_log assert last_log
assert re.search(expected_log_match, last_log, re.I) assert re.search(expected_log_match, last_log, re.I)
@pytest.mark.parametrize(
"plugin_config, found, expected",
[
({}, "new", "old"),
({"force": True}, "new", "new"),
({"force": True, "local": True}, "new", "old"),
({"force": True, "fallback": None}, "", "old"),
({"force": True, "fallback": ""}, "", ""),
({"force": True, "fallback": "default"}, "", "default"),
],
)
def test_overwrite_config(
self, monkeypatch, helper, lyrics_plugin, found, expected
):
monkeypatch.setattr(lyrics_plugin, "find_lyrics", lambda _: found)
item = helper.create_item(id=1, lyrics="old")
lyrics_plugin.add_item_lyrics(item, False)
assert item.lyrics == expected
class LyricsBackendTest(LyricsPluginMixin): class LyricsBackendTest(LyricsPluginMixin):
@pytest.fixture @pytest.fixture
@ -289,8 +318,13 @@ class TestLyricsSources(LyricsBackendTest):
def test_backend_source(self, lyrics_plugin, lyrics_page: LyricsPage): def test_backend_source(self, lyrics_plugin, lyrics_page: LyricsPage):
"""Test parsed lyrics from each of the configured lyrics pages.""" """Test parsed lyrics from each of the configured lyrics pages."""
lyrics_info = lyrics_plugin.get_lyrics( lyrics_info = lyrics_plugin.find_lyrics(
lyrics_page.artist, lyrics_page.track_title, "", 186 Item(
artist=lyrics_page.artist,
title=lyrics_page.track_title,
album="",
length=186.0,
)
) )
assert lyrics_info assert lyrics_info