feat: print plain lyrics without tags
This commit is contained in:
+25
-4
@@ -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}")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user