feat: add offset handling for LRC time tags
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ from lrcfetch.core import LrcManager
|
||||
|
||||
app = typer.Typer(
|
||||
help="LRCFetch — Fetch line-synced lyrics for your music player.",
|
||||
add_completion=False,
|
||||
add_completion=True,
|
||||
)
|
||||
|
||||
manager = LrcManager()
|
||||
|
||||
+47
-2
@@ -18,10 +18,55 @@ LRC_LINE_RE = re.compile(r"^\[(\d{2}:\d{2}[.:]\d{2,3})\]", re.MULTILINE)
|
||||
# All-zero tags
|
||||
_ZERO_TAG_RE = re.compile(r"^\[00:00[.:]0{2,3}\]$")
|
||||
|
||||
# [offset:+/-xxx] tag — value in milliseconds
|
||||
_OFFSET_RE = re.compile(r"^\[offset:\s*([+-]?\d+)\]\s*$", re.MULTILINE | re.IGNORECASE)
|
||||
|
||||
# Time tag for offset application: captures mm, ss, cc/ccc
|
||||
_TIME_TAG_RE = re.compile(r"\[(\d{2}):(\d{2})\.(\d{2,3})\]")
|
||||
|
||||
|
||||
def _apply_offset(text: str) -> str:
|
||||
"""Parse [offset:±ms] tag and shift all time tags accordingly.
|
||||
|
||||
Per LRC spec, a positive offset means lyrics appear sooner (subtract
|
||||
from timestamps), negative means later (add to timestamps).
|
||||
"""
|
||||
m = _OFFSET_RE.search(text)
|
||||
if not m:
|
||||
return text
|
||||
offset_ms = int(m.group(1))
|
||||
if offset_ms == 0:
|
||||
return _OFFSET_RE.sub("", text).strip("\n")
|
||||
|
||||
# Remove the offset tag line
|
||||
text = _OFFSET_RE.sub("", text)
|
||||
|
||||
def _shift(match: re.Match) -> str:
|
||||
mm, ss, cs = int(match.group(1)), int(match.group(2)), match.group(3)
|
||||
# Normalize centiseconds to milliseconds
|
||||
if len(cs) == 2:
|
||||
ms = int(cs) * 10
|
||||
fmt_cs = 2
|
||||
else:
|
||||
ms = int(cs)
|
||||
fmt_cs = 3
|
||||
total_ms = (mm * 60 + ss) * 1000 + ms - offset_ms
|
||||
total_ms = max(0, total_ms)
|
||||
new_mm = total_ms // 60000
|
||||
new_ss = (total_ms % 60000) // 1000
|
||||
new_cs = total_ms % 1000
|
||||
if fmt_cs == 2:
|
||||
new_cs = new_cs // 10
|
||||
return f"[{new_mm:02d}:{new_ss:02d}.{new_cs:02d}]"
|
||||
return f"[{new_mm:02d}:{new_ss:02d}.{new_cs:03d}]"
|
||||
|
||||
return _TIME_TAG_RE.sub(_shift, text)
|
||||
|
||||
|
||||
def normalize_tags(text: str) -> str:
|
||||
"""Convert non-standard time tags [mm:ss:cc] to standard [mm:ss.cc]."""
|
||||
return _COLON_TAG_RE.sub(r"[\1.\2]", text)
|
||||
"""Normalize LRC time tags: colon format → dot format, then apply offset."""
|
||||
text = _COLON_TAG_RE.sub(r"[\1.\2]", text)
|
||||
return _apply_offset(text)
|
||||
|
||||
|
||||
def is_synced(text: str) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user