mirror of
https://github.com/beetbox/beets.git
synced 2025-12-22 16:43:25 +01:00
Improve flags structure and add tests
This commit is contained in:
parent
c95156adcd
commit
7893766e4c
3 changed files with 74 additions and 41 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue