feat: print plain lyrics without tags

This commit is contained in:
2026-04-01 17:26:09 +02:00
parent 5930f70bf2
commit cd60d3042c
2 changed files with 66 additions and 4 deletions
+25 -4
View File
@@ -18,7 +18,7 @@ from .models import TrackMeta, CacheStatus
from .mpris import get_current_track from .mpris import get_current_track
from .core import LrcManager from .core import LrcManager
from .fetchers import FetcherMethodType from .fetchers import FetcherMethodType
from .lrc import get_sidecar_path from .lrc import get_sidecar_path, print_lyrics, to_plain
app = cyclopts.App( app = cyclopts.App(
@@ -94,6 +94,12 @@ def fetch(
name="--only-synced", negative="", help="Only accept synced (timed) lyrics." name="--only-synced", negative="", help="Only accept synced (timed) lyrics."
), ),
] = False, ] = False,
plain: Annotated[
bool,
cyclopts.Parameter(
name="--plain", negative="", help="Output only the raw lyrics without tags."
),
] = False,
): ):
"""Fetch and print lyrics for the currently playing track.""" """Fetch and print lyrics for the currently playing track."""
track = get_current_track(_player) track = get_current_track(_player)
@@ -114,7 +120,7 @@ def fetch(
logger.error("Only unsynced lyrics available (--only-synced requested).") logger.error("Only unsynced lyrics available (--only-synced requested).")
sys.exit(1) sys.exit(1)
print(result.lyrics) print_lyrics(result.lyrics, plain=plain)
# search # search
@@ -165,6 +171,12 @@ def search(
name="--only-synced", negative="", help="Only accept synced (timed) lyrics." name="--only-synced", negative="", help="Only accept synced (timed) lyrics."
), ),
] = False, ] = False,
plain: Annotated[
bool,
cyclopts.Parameter(
name="--plain", negative="", help="Output only the raw lyrics without tags."
),
] = False,
): ):
"""Search for lyrics by metadata (bypasses MPRIS).""" """Search for lyrics by metadata (bypasses MPRIS)."""
if url and path: if url and path:
@@ -196,7 +208,7 @@ def search(
logger.error("Only unsynced lyrics available (--only-synced requested).") logger.error("Only unsynced lyrics available (--only-synced requested).")
sys.exit(1) sys.exit(1)
print(result.lyrics) print_lyrics(result.lyrics, plain=plain)
# export # export
@@ -224,6 +236,12 @@ def export(
name=["--overwrite", "-f"], negative="", help="Overwrite existing file." name=["--overwrite", "-f"], negative="", help="Overwrite existing file."
), ),
] = False, ] = False,
plain: Annotated[
bool,
cyclopts.Parameter(
name="--plain", negative="", help="Export only the raw lyrics without tags."
),
] = False,
): ):
"""Export lyrics of the current track to a .lrc file.""" """Export lyrics of the current track to a .lrc file."""
track = get_current_track(_player) track = get_current_track(_player)
@@ -263,7 +281,10 @@ def export(
try: try:
with open(output, "w", encoding="utf-8") as f: with open(output, "w", encoding="utf-8") as f:
f.write(result.lyrics) if plain:
f.write(to_plain(result.lyrics))
else:
f.write(result.lyrics)
logger.info(f"Exported lyrics to {output}") logger.info(f"Exported lyrics to {output}")
except Exception as e: except Exception as e:
logger.error(f"Failed to write file: {e}") logger.error(f"Failed to write file: {e}")
+41
View File
@@ -27,6 +27,9 @@ _LRC_LINE_RE = re.compile(r"^\[\d{2,}:\d{2}\.\d{2}\]", re.MULTILINE)
# [offset:+/-xxx] tag — value in milliseconds # [offset:+/-xxx] tag — value in milliseconds
_OFFSET_RE = re.compile(r"^\[offset:\s*([+-]?\d+)\]\s*$", re.MULTILINE | re.IGNORECASE) _OFFSET_RE = re.compile(r"^\[offset:\s*([+-]?\d+)\]\s*$", re.MULTILINE | re.IGNORECASE)
# Matches any number of tags at the start of a line
_LINE_START_TAGS_RE = re.compile(r"^(?:\[[^\]]*\])+")
def _raw_tag_to_cs(mm: str, ss: str, frac: Optional[str]) -> str: def _raw_tag_to_cs(mm: str, ss: str, frac: Optional[str]) -> str:
"""Convert parsed time tag components to standard [mm:ss.cc] string.""" """Convert parsed time tag components to standard [mm:ss.cc] string."""
@@ -176,3 +179,41 @@ def get_sidecar_path(
if ensure_exists and not lrc_path.exists(): if ensure_exists and not lrc_path.exists():
return None return None
return lrc_path return lrc_path
def to_plain(
text: str,
) -> str:
"""Convert lyrics to plain text with all tags stripped.
Assumes text has been normalized by normalize_tags.
"""
lines = []
first = True
for line in text.splitlines():
cleaned = _LINE_START_TAGS_RE.sub("", line).strip()
# Ignore the leading empty lines that is likely caused by tag lines
if not cleaned and not first:
lines.append("")
elif cleaned:
lines.append(cleaned)
first = False
# Remove trailing empty lines that are meaningless
while lines and not lines[-1]:
lines.pop()
return "\n".join(lines)
def print_lyrics(
text: str,
plain: bool = False,
) -> None:
"""Print lyrics, optionally stripping tags.
Assumes text has been normalized by normalize_tags.
"""
if plain:
print(to_plain(text))
else:
print(text)