feat: add offset handling for LRC time tags

This commit is contained in:
2026-03-25 21:55:36 +01:00
parent 075853ad5b
commit ec40fbcf13
2 changed files with 48 additions and 3 deletions
+47 -2
View File
@@ -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: