Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions beetsplug/lyrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import requests
from bs4 import BeautifulSoup
from confuse import Optional
from unidecode import unidecode

from beets import plugins, ui
Expand Down Expand Up @@ -1052,6 +1053,7 @@ def __init__(self):
"keep_synced": False,
"local": False,
"print": False,
"rest_directory": None,
"synced": False,
# Musixmatch and Tekstowo are disabled by default as they
# currently block requests with the beets user agent.
Expand Down Expand Up @@ -1084,7 +1086,7 @@ def commands(self):
"--write-rest",
dest="rest_directory",
action="store",
default=None,
default=self.config["rest_directory"].get(Optional(str)),
metavar="dir",
help="write lyrics to given directory as ReST files",
)
Expand Down Expand Up @@ -1122,7 +1124,7 @@ def func(lib: Library, opts, args) -> None:
if opts.rest_directory and (
items := [i for i in items if i.lyrics]
):
RestFiles(Path(opts.rest_directory)).write(items)
RestFiles(Path(opts.rest_directory).expanduser()).write(items)

cmd.func = func
return [cmd]
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ New features
:conf:`plugins.musicbrainz:aliases_as_credits` to make
aliases-as-artist-credit optional.
- :doc:`plugins/badfiles`: Added settings for auto error and warning actions.
- :doc:`plugins/lyrics`: Added a ``rest_directory`` configuration option for
specifying a reStructuredText output directory, semantically equivalent to
``-r, --write-rest``. :bug:`2806`
- :doc:`plugins/tidal`: New flexible attributes are now populated during
imports, including ``tidal_track_id``, ``tidal_album_id``,
``tidal_artist_id``, ``tidal_track_popularity``, ``tidal_album_popularity``,
Expand Down
5 changes: 5 additions & 0 deletions docs/plugins/lyrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Default configuration:
google_API_key: null
google_engine_ID: 009217259823014548361:lndtuqkycfu
print: no
rest_directory: null
sources: [lrclib, google, genius]
synced: no

Expand Down Expand Up @@ -107,6 +108,8 @@ The available options are:
custom search engine`_, which gathers an updated list of sources known to be
scrapeable.
- **print**: Print lyrics to the console.
- **rest_directory**: The directory to which reStructuredText_ (ReST) rendered
lyric documents will be output. See :ref:`rendering-lyrics`.
- **sources**: List of sources to search for lyrics. An asterisk ``*`` expands
to all available sources. The ``google`` source will be automatically
deactivated if no ``google_API_key`` is setup. By default, ``musixmatch`` and
Expand Down Expand Up @@ -147,6 +150,8 @@ lyrics without touching tracks that already have a synced version.
Inversely, the ``-l, --local`` option restricts operations to lyrics that are
locally available, which show lyrics faster without using the network at all.

.. _rendering-lyrics:

Rendering Lyrics into Other Formats
-----------------------------------

Expand Down
90 changes: 87 additions & 3 deletions test/plugins/test_lyrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,21 @@
import textwrap
from functools import partial
from http import HTTPStatus
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING

import pytest
import requests

from beets.library import Item
from beets.test.helper import PluginMixin
from beets.test.helper import PluginMixin, PluginTestHelper
from beets.util.lyrics import Lyrics
from beetsplug import lyrics

from .lyrics_pages import lyrics_pages

if TYPE_CHECKING:
from pathlib import Path

from .lyrics_pages import LyricsPage

PHRASE_BY_TITLE = {
Expand Down Expand Up @@ -843,6 +842,91 @@ def test_write(self, rest_dir: Path, rest_files):
)


class TestLyricsRestDirectory(PluginTestHelper):
plugin = "lyrics"

@pytest.fixture
def lib(self, helper):
return helper.lib

@pytest.mark.parametrize(
"config_path, arg_path, output_path",
[
pytest.param(
"test/config",
"test/cmd",
"test/cmd",
id="config and cmd arg, relative path",
),
pytest.param(
"test/config",
None,
"test/config",
id="config only, relative path",
),
pytest.param(
None, "test/cmd", "test/cmd", id="cmd arg only, relative path"
),
pytest.param(
"/test/config",
"/test/cmd",
"/test/cmd",
id="config and cmd arg, absolute path",
),
pytest.param(
"/test/config",
None,
"/test/config",
id="config only, absolute path",
),
pytest.param(
None, "/test/cmd", "/test/cmd", id="cmd arg only, absolute path"
),
pytest.param(
"~/test/config",
"~/test/cmd",
"~/test/cmd",
id="config and cmd arg, home path",
),
pytest.param(
"~/test/config",
None,
"~/test/config",
id="config only, home path",
),
pytest.param(
None, "~/test/cmd", "~/test/cmd", id="cmd arg only, home path"
),
pytest.param(None, None, None, id="no output"),
],
)
def test_rest_config(
self, monkeypatch, lib, config_path, arg_path, output_path
):
test_capture = {}

class MockRestFiles:
def __init__(self, directory):
test_capture["directory"] = directory

def write(self, items):
test_capture["items"] = items

monkeypatch.setattr(lyrics, "RestFiles", MockRestFiles)

if config_path:
self.config["lyrics"]["rest_directory"] = config_path

cmd_args = [] if arg_path is None else ["-r", arg_path]
self.run_command("lyrics", *cmd_args, lib=lib)

test_output = test_capture.get("directory")
if output_path is None:
assert test_output is None
else:
assert test_output == Path(output_path).expanduser()


class TestLyricsSyltProperty:
"""Unit tests for the Lyrics.sylt timestamp-to-millisecond converter."""

Expand Down
Loading