Featured image of post APlayer 歌词不显示?先把 LRC 时间戳规范化

APlayer 歌词不显示?先把 LRC 时间戳规范化

APlayer 会静默跳过 [02:54.4] 这类"松散"时间戳的歌词行;发布前把 LRC 时间戳规范化成 [mm:ss.xx] / [mm:ss.xxx] 即可。现已在 music 仓库的 CI 里自动处理。

一次性的小坑记录,面向人阅读。结论先放这:APlayer 歌词某些行不显示,十有八九是时间戳格式不对,不是歌词内容缺了。

症状

给博客的 APlayer 播放器挂歌词(.lrc)后,有些歌一切正常,有些歌却有几行死活不显示 / 不滚动——可歌词文件里那几行明明在。控制台也不报错,就是静悄悄地少了几句。

根因

APlayer 的 LRC 解析器对时间戳挑食:它只稳定接受 [mm:ss.xx][mm:ss.xxx](两位或三位毫秒)这种"规整"写法。遇到下面这些松散写法,它会直接跳过整行

  • [02:54.4] —— 只有一位小数
  • [2:54.40] —— 分钟只有一位
  • [02:54] 之外各种位数不齐的组合

而很多歌词来源(网易云导出、第三方站点、手抄)恰恰就是这种松散格式。于是同一份歌单里,规整的歌好好的,松散的歌就缺行——表现得很"随机",其实规律就是时间戳。

修复:发布前规范化时间戳

思路很简单:把每个时间戳重算成总秒数,再补零、把毫秒补齐到两/三位。核心就一段正则替换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import re
from decimal import Decimal, ROUND_HALF_UP

LRC_TS = re.compile(r"\[(\d{1,2}):(\d{1,2})(?:\.(\d+))?\]")

def _normalize(m: re.Match) -> str:
    mm, ss, frac = m.group(1), m.group(2), m.group(3)
    total = int(mm) * 60 + int(ss)
    if frac is None:
        return f"[{total // 60:02d}:{total % 60:02d}]"
    if len(frac) in (2, 3):                       # 已规整
        return f"[{total // 60:02d}:{total % 60:02d}.{frac}]"
    if len(frac) == 1:                            # [02:54.4] -> .400
        ms = int(frac) * 100
    else:                                         # 四位以上,四舍五入到毫秒
        ms = int((Decimal(f"0.{frac}") * 1000).quantize(Decimal("1"), ROUND_HALF_UP))
    if ms >= 1000:
        total, ms = total + 1, 0
    return f"[{total // 60:02d}:{total % 60:02d}.{ms:03d}]"

def normalize_lrc(text: str) -> str:
    return "\n".join(LRC_TS.sub(_normalize, line) for line in text.splitlines())

[02:54.4][02:54.400][2:5][02:05],APlayer 就都认了。

现在:已经自动化,不用再手动改

这一步已经接进 lihan3238/music 仓库的 CI——lrc_compat.py 里的 normalize_lrc_filetest.py 调用,music-sync.ymllrc/** 变更时自动跑,发布前自动规范化。所以现在往歌单里丢任意来源的 .lrc,流水线会自己把时间戳修好,前端再也不会缺行。

一句话

APlayer 歌词不显示,先查时间戳是不是 [mm:ss.xx] / [mm:ss.xxx],别先怀疑歌词内容或播放器本身。

Licensed under CC BY-NC-SA 4.0
最后更新于 6月 04, 2026 18:46 +0800
潇洒人间一键仙
使用 Hugo 构建
主题 StackJimmy 设计