Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Lemony LRC Parser

Python License: MIT PyPI

简体中文 | English

柠檬味的 Python LRC 歌词解析器.

特性

  • 解析标准 LRC 歌词文件
  • 支持 EnhancedLRC / SPL 的逐字歌词标签
  • 支持 metadata 标签 (同名 key 可重复, 不丢重复项)
  • 支持折叠时间标签
  • 支持参考行
  • 支持歌词合并
  • 与简单字幕格式 (SRT / WebVTT) 互转
  • 时间偏移 (apply_delta / << / >> 运算符)
  • 字典序列化 (to_dict() / from_dict())
  • 深拷贝方法 (.copy())
  • 可配置解析与序列化选项
  • 数据一致性验证 API (validate_lyrics / Lyrics.validate())
  • 文本查找 (contains_text() / find_text() / find_regex())
  • 往返保真度不变量测试 (解析结果必过校验 / dumps 一轮收敛 / 行结构稳定)
  • CLI 命令行工具 (python -m lemony_lrc_parser)
  • 解析错误附带行号与原始行 (InvalidLyricsError.line_no / .raw_line)
  • 完整的类型注解

安装

推荐使用 uv.

uv add lemony-lrc-parser

用 pip 也行.

pip install lemony-lrc-parser

可以使用 git 仓库来源来第一时间体验到最新最热的 bug feature.

uv add https://github.com/NingmengLemon/lemony-lrc-parser.git

用法

快速开始

用法参考 json, marshal, pickle 等库

import lemony_lrc_parser as llp

lrc_text = """[ti: 凉雨]
[ar: COP,洛天依]

[00:00.000]风透过人潮渐渐停歇的下雨天
[00:02.740]将平凡的故事翻到末页
[00:05.290]并未留下浓重的墨点
[00:08.150]在字里行间流连
"""

# 解析
lyrics = llp.loads(lrc_text)

# 访问 metadata
print(lyrics.metadata["ti"])  # "凉雨"
print(lyrics.metadata["ar"])  # "COP,洛天依"

# 遍历歌词行
for line in lyrics:
    print(f"{line.start}ms: {line.text}")

# 序列化回 LRC 格式
output = llp.dumps(lyrics)

面向对象接口

Lyrics 类提供面向对象的解析和序列化入口, 推荐优先使用:

from lemony_lrc_parser import Lyrics

lyrics = Lyrics.loads(lrc_text)

# Lyrics 同时是序列容器
print(len(lyrics))  # 行数
print(lyrics[0].text)  # 第一行文本
print(lyrics[-1].text)

# 切片访问
first_three = lyrics[0:3]

# 序列化
lrc_output = lyrics.dumps()

# 行时长 (毫秒); end 未知时为 None
print(lyrics[0].duration)

# __str__ 等价于 dumps()
print(lyrics)

Lyrics 的相等性比行序列 + metadata: 两份只有 [ti:] 不同的歌词不相等, 所以 Lyrics.loads(lyrics.dumps()) == lyrics 可以直接当往返检查用. 与普通列表比较时 只比行 (列表没有 metadata 可比). metadata 的相等性沿用 Metadata 的口径, 比全部 条目, 因此同名 key 的重复项与标签顺序都算数.

repr() 是给调试看的紧凑形式 (Lyrics(lines=2, metadata=Metadata({'ti': 'A'})), 不铺开行与词元), str() 仍等价于 dumps().

逐字歌词

解析逐字 (Enhanced LRC / SPL) 歌词:

lrc_text = "[00:00.000]<00:00.249>风<00:00.467>透<00:00.617>过<00:00.689>人<00:01.078>潮<00:01.217>渐<00:01.408>渐<00:01.556>停<00:01.716>歇<00:01.867>的<00:02.016>下<00:02.339>雨<00:02.459>天[00:02.740]"

lyrics = llp.loads(lrc_text)
line = lyrics[0]

for word in line.content:
    print(f"  [{word.start} -> {word.end}] {word.content!r}")
    # [249 -> 467] '风'
    # [467 -> 617] '透'
    # [617 -> 689] '过'
    # [689 -> 1078] '人'
    # [1078 -> 1217] '潮'
    # [1217 -> 1408] '渐'
    # [1408 -> 1556] '渐'
    # [1556 -> 1716] '停'
    # [1716 -> 1867] '歇'
    # [1867 -> 2016] '的'
    # [2016 -> 2339] '下'
    # [2339 -> 2459] '雨'
    # [2459 -> 2740] '天'

# 行级时间: line.start=0, line.end=2740
# (行尾的 [00:02.740] 会被当作该行的结束时间, 并反写到最后一个词元的 end)

内容搜索 (contains_text / find_text / find_regex)

按文本查找请使用显式方法: in 在四个模型上的历史语义各不相同 (LyricToken / BasicLyricLine 是子串, LyricLine 是词元相等, Lyrics 是行相等), "xxx" in <模型> 保留旧行为但会发 DeprecationWarning:

from lemony_lrc_parser import Lyrics

lyrics = Lyrics.loads(lrc_text)

lyrics.contains_text("雨")  # 任一行 (含参考行) 是否包含该子串
matching = lyrics.find_text("雨")  # 命中的行对象列表
line = lyrics[0]
line.contains_text("雨")  # 只搜主行
line.contains_text("积雨云", include_reference_lines=True)  # 连参考行一起搜

子串判定区分大小写. 需要正则或忽略大小写时用 find_regex / contains_regex (默认也搜参考行):

import re

lyrics.find_regex(re.compile("love", re.IGNORECASE))  # 忽略大小写
lyrics.find_regex(r"^\[.+\]$")  # 正则; str 会被 re.compile
lyrics.contains_regex("love")  # 布尔版

正则匹配是逐行做的: pattern 里的 \n 永远匹配不到 (LRC 的换行是行边界). 返回的是 行对象本身, 需要行号时自己 enumerate(lyrics).

参考行 (Translation / Transliteration)

LRC 文件中, 紧跟在带时间标签行后面的无标签行, 或与主行的时间戳相同的行, 会被解析为参考行, 常用于存放翻译或音译:

lrc_text = """
[00:01.910]歌に形はないけれど
[00:01.910]虽然歌声无形
"""

lyrics = llp.loads(lrc_text)

line = lyrics[0]
print(line.text)  # "歌に形はないけれど"
print(line.reference_lines[0][0].content)  # "虽然歌声无形"

合并歌词

将两份歌词 (如原文和翻译) 按时间标签合并:

main = llp.loads("[00:33.810]帰り道は夕日を背に\n[00:39.580]君の少し後ろを歩く\n")
translation = llp.loads(
    "[00:33.810]背着夕阳走在返家的路上\n[00:39.580]跟在你的后面一起走着\n"
)

# combine 方法: 翻译行挂到同时间点的 reference_lines 中
combined = main.combine(translation)

# 也可以用 + 运算符; 注意 + 固定使用 other_as_refline_only=False,
# 即另一方中时间戳对不上的行会作为新行保留, 不会被丢弃
combined = main + translation

for line in combined:
    print(line.text)  # 主歌词
    for ref in line.reference_lines:
        ref_text = "".join(w.content for w in ref)
        print(f"  -> {ref_text}")  # 参考行

# other_as_refline_only=False 时, 翻译中找不到对应时间点的行会作为新行保留
combined = main.combine(translation, other_as_refline_only=False)

字典序列化 (to_dict / from_dict)

所有数据模型都支持字典序列化, 方便 JSON 传输和 API 对接:

import lemony_lrc_parser as llp
from lemony_lrc_parser import Lyrics

lyrics = llp.loads("[00:01.00]Hello\n[00:02.00]World\n")

# Lyrics → dict
data = lyrics.to_dict()
# {"metadata": {}, "lines": [{"start": 1000, ...}, ...]}

# dict → Lyrics
restored = Lyrics.from_dict(data)

# 也可以对单行、单行内容、单个词元分别操作
line = lyrics[0]
line_dict = line.to_dict()
token_dict = line.content[0].to_dict()

to_dict() 里的 metadata 是有损的 dict 视角 (同名 key 只留最后一次出现的 值); 需要无损导出时用 lyrics.metadata.to_pairs(), 见 同名 key 与三种读法.

拷贝

所有数据模型都提供 .copy() 深拷贝方法, 返回独立的副本:

import lemony_lrc_parser as llp

lyrics = llp.loads("[00:01.00]Hello\n")

# 深拷贝
clone = lyrics.copy()
clone.metadata["ti"] = "New Title"

# 原始对象不受影响
print(lyrics.metadata.get("ti"))  # None

安全变更

Lyrics 继承 UserList, 直接 append / insert / 下标赋值不会排序, 也不会校验; 而 lyrics.sort() 又用不了 (LyricLine 不可比较). 需要按时间顺序维护时用这三个:

from lemony_lrc_parser import Lyrics, LyricLine

lyrics = Lyrics()

lyrics.add_line(line)  # 追加并保持按 start 有序 (与 append 一样存引用)
lyrics.add_line(line, sort=False)  # 批量构建时先攒着, 最后统一排一次
lyrics.sort_inplace()  # 原地按 start 稳定排序
ordered = lyrics.sorted()  # 返回排好序的副本, 原对象不动

sort_inplace() 是稳定排序: 同一时间戳的多行 (对唱、重复副歌) 保持原有相对 顺序.

选项

解析选项

import re
from lemony_lrc_parser import Lyrics, ParseOptions

lrc_text = "[00:01.000]Hello\n[00:05.000]World\n"

lyrics = Lyrics.loads(
    lrc_text,
    options=ParseOptions(
        fill_implicit_line_end=True,  # 是否填充隐式行尾时间
        line_filter=r"纯音乐.*?请欣赏",  # 黑名单过滤 (统一按正则理解, str 会被自动 compile)
        # line_filter=re.compile(r"纯音乐.*?请欣赏"),  # 也可直接传入已编译的正则
    ),
)

# lyrics[0].end == lyrics[1].start == 5000

line_filter 的内容是正则表达式: 传入的字符串会被 re.compile 编译, 之后用 pattern.search 匹配每行文本, 命中的行会被丢弃. 若需要精确的子串匹配 (而非正则), 请用 re.escape(...) 包一层, 例如 line_filter=re.escape("a.c").

过滤只按主行文本判定: 与主行同一时间戳的参考行 (翻译/音译) 会随主行一起被 丢弃, 即使它自身的文本并不匹配.

strict=True 打开严格模式: 解析器不再"丢掉 / 忽略掉"文件里的任何声明, 而是当场 抛 InvalidLyricsError (带 line_no / raw_line):

from lemony_lrc_parser import Lyrics, ParseOptions

# 无锚点的行、越界或乱序的逐字标记、被忽略的结束标记、被丢弃的行尾推断, 都会抛
Lyrics.loads(lrc_text, options=ParseOptions(strict=True))

默认是 False, 因为真实世界的 LRC 很野, 默认宽松才兼容. 两类刻意不抛: line_filter 丢掉的行 (那是调用方自己要求的), 以及 metadata key / offset 的 问题 (它们不影响解析结果, 交给 validate() 报). 触发的具体代码见 解析报告 里的 STRICT_ERROR_CODES.

序列化选项

通过 SerializationOptions 控制序列化行为:

from lemony_lrc_parser import Lyrics, SerializationOptions

output = lyrics.dumps(
    options=SerializationOptions(
        with_metadata=True,  # 是否输出 metadata 段
        use_bracket_for_byword_tag=False,  # 逐字标签使用 [...] 还是 <...> (默认; True 不保证往返, 见下)
        line_tag_decimal_length=3,  # 行标签毫秒位数 (默认 3)
        word_tag_decimal_length=3,  # 逐字标签毫秒位数 (默认 3)
        line_separator="\n",  # 行间分隔字符串 (默认 "\n", 设为 "" 可省去空行)
    ),
)

小数位长

默认 line_tag_decimal_length=3、word_tag_decimal_length=3, 输出格式如 [00:01.000]、<00:01.050>, 保留完整的毫秒精度. 若设为 2, 小数部分表示百分秒 (如 [00:01.00]), 属于有损截断 (例如 555ms 会被截断为 55, 解析回来变成 550ms), 需要按需权衡 (部分老软件可能只支持百分秒).

往返保真度

dumps() 的输出在重新解析后应当还原原对象. 已知的有损写法有两类 (见下表): 属于选项 自身代价的照常写出并 warning (与 metadata 的处理一致), 确实无法用 LRC 表达的则跳过 并记 debug 日志.

情形 写出的文本 重新解析的结果
use_bracket_for_byword_tag=True 且首词元晚于行首 [00:01.000][00:01.500]hello 两行, 文本重复
无正文的参考行 (逐字时间也一起丢) 不写出, 只有 debug 日志 该参考行消失

dumps 的输出是"最多一轮之后的不动点": d2 = dumps(loads(d1)) 与 d3 = dumps(loads(d2)) 必定相等. 当前实现下 (含 2 万份随机语料与 6622 份真实 文件) 首轮就已经是不动点.

另外, 行尾时间若由逐字标签推断而来且与行首时间矛盾 (end <= start), 该推断会被 丢弃 (line.end 置回 None) 并产生 warning —— 解析器不会产出 validate() 判为 error 的行.

关于 SPL

SPL (Salt Player Lyrics) 是目前唯一把 "LRC 家族多年踩到的兼容性问题"写成条文的文档, 本库把它当作 LRC 语义的参照物. 逐条对照的用例在 tests/test_spl_conformance.py, 语料统计与调研过程在 docs/research/spl.md.

解析端完全按 SPL 的地方:

  • 时间戳数字规范 (分 1-3 位 / 秒 1-2 位 / 毫秒 1-6 位; 不足 3 位视为在后位省略 0, 即 [00:01.5] 是 1.5 秒、[00:01.02] 是 1.02 秒).
  • 显式行尾: 同行内的 [start]text[end], 以及独立的空标记行 [end].
  • 空正文行是"纯结束标记": 它不产生歌词行, 也不参与翻译识别, 只给上一行补 end. 因此 [00:20.82] + [00:20.82]lyrics 得到的是"上一行在 20.82s 结束"加"一句正常 歌词", 而不是"一条空行 + 它的翻译".
  • 重复行简写 [t1][t2]text; 翻译的同时间戳识别 (可不紧挨) 与省略时间戳写法 (可多行).
  • 逐字标记 [...] 与 <...> 两种写法, 以及 2026-09-19 修订新增的"延迟首字" [行标签]<首字标签>文本.
  • 逐字标记必须递增且落在 [行首, 行尾] 内, 越界或乱序的标记被忽略 (两侧文本合并, 不丢字) 并记 warning.

有意偏离 / 标准未覆盖的地方:

  • 分 4 位 ([1234:00.000]) 比标准宽松地接受; 秒 ≥ 60 按字面折算并 warning.
  • 毫秒写 4-6 位时按"秒的小数部分"截断到毫秒 ([00:01.450000] → 1450ms), 而不是 按标准的字面读成 450000 毫秒. 标准内部对这两种读法有歧义, 真实语料里 4-6 位出现 0 次.
  • 标签之后只剩空白算正文 (见上面的往返保真度), 标准只说了"不接任何文本内容".
  • [行标签][首字标签]文本 在整行标签非递减、且正文里还有其它时间标签时, 按"首字延迟" 读成一行 (如 [00:05.650][00:05.730]徘[00:06.130]徊[00:06.450]; 标准"局限性"一节 按重复行读); 正文里没有其它时间标签的 [t1][t2]text 仍按重复行简写读成两行. 真实语料里 199 行这种写法有 197 行的两个标签只差 0.1-1.3 秒.
  • 整行只有尖括号标签 (如 <00:01.000><00:02.000>) 保留为一条空正文行, 用于承载 空字幕 cue; dumps 也用同一形式写回.
  • 默认不填充隐式行尾 (end=None 表示"未知"), 需要 SPL 的"持续到下一行开始"语义时 用 ParseOptions(fill_implicit_line_end=True); 字幕导出默认就是这种语义.

元数据语法

[key: value] 只有整行都由该形式构成时才算 metadata: 正文中间的 [key: value] 不会把整行吞掉 (例如 Return [to: sender] now 仍是一行歌词或参考行).

value 里的方括号必须配对:

import lemony_lrc_parser as llp

llp.loads("[al: Album [Deluxe]]\n[00:01.000]x\n").metadata
# Metadata({'al': 'Album [Deluxe]'})   ← 原样保留 (取"配对的第一个 ]"作为边界)

不平衡的写法 (如 [ti: 50% ]off]、[ti: a [b]) 无法确定 value 边界, 整行按普通 正文处理, 写出这类 value 时也会 warning.

常见 key 的类型提示见 MetadataKey / MetadataDict / COMMON_METADATA_KEYS —— 它们只是提示: 库不限制 key 的取值集合 (真实语料里常见 ly、mu、total、tool), 只有 validate() 会按 [A-Za-z][A-Za-z0-9]{0,15} 检查 key 格式.

同名 key 与三种读法

同名 key 重复出现是合法的 ([ti: A] 与 [ti: B] 都能留下), 所以 lyrics.metadata 是 Metadata 而不是裸 dict: 它有序、可重复, 三种读法各有 明确口径, 不靠参数开关切换.

import lemony_lrc_parser as llp

lyrics = llp.loads("[ti: A]\n[ti: B]\n[00:01.000]x\n")
meta = lyrics.metadata

# dict 视角 (有损): 同名取最后一次出现的值, 与旧版的裸 dict 行为一致
meta["ti"]  # 'B'
dict(meta)  # {'ti': 'B'}
lyrics.to_dict()["metadata"]  # {'ti': 'B'}

# 无损视角: 全部条目, 含重复
meta.getlist("ti")  # ['A', 'B']
meta.to_pairs()  # [('ti', 'A'), ('ti', 'B')]
meta.items()  # 同上 (items() 有意覆盖成"全部条目")

# 写入: add 追加, set / meta[k] = v 替换 (位置取首次出现处)
meta.add("ti", "C")  # 变成 A, B, C
meta.set("ti", "D")  # 合并成一条 D

dumps() 按无损视角写出, 所以重复 tag 也能往返. len(meta) 与 meta.keys() 按 Mapping 协议只算不同的 key, 要数全部条目用 len(meta.to_pairs()). 需要无损导出时用 meta.to_pairs(), 需要 JSON 友好时用 meta.to_dict().

Metadata 不是 dict 子类, 所以 isinstance(meta, dict) 是 False, json.dumps(meta) 要改成 json.dumps(meta.to_dict()) (或直接 json.dumps(lyrics.to_dict())).

偏移

使用 Lyrics.apply_delta(ms) 应用时间偏移, ms 会直接加到每个标签的时间戳上, 这意味着传入正数偏移值会导致歌词整体延后出现, 反之同理.

也可以使用重载的 >> / << 运算对歌词进行偏移.

如果应用 offset 会导致任意时间戳变为负数, 将抛出 TimestampUnderflowError, 由调用方自行处理.

应用偏移

通过 Lyrics.apply_delta(ms) 对时间戳应用偏移, 返回一个新对象:

from lemony_lrc_parser import Lyrics

lyrics = Lyrics.loads(lrc_text)

# 正数 → 歌词延后出现 (等价于 lyrics >> 500)
shifted = lyrics.apply_delta(500)

# 负数 → 歌词提前出现 (等价于 lyrics << 500)
shifted = lyrics.apply_delta(-500)

# 使用 << / >> 运算符
shifted = lyrics >> 500  # 延后 500ms
shifted = lyrics << 500  # 提前 500ms

# shifted 的时间戳已被整体偏移, 原始 lyrics 不受影响

如需在序列化前偏移时间戳, 请先调用 apply_delta() 再序列化返回的副本. 如果你的偏移量来自歌词文件元数据, 你可能还需要记得手动清理 lyrics.metadata 中的偏移值.

与 LRC offset 元数据的符号差异

LRC 没有一个标准的符号语义, 但:

  • LRC 的 [offset: +N] 按社区文档与主流实现是"歌词整体提前 N 毫秒", 即时间戳 -= N (调研);
  • 本库的 apply_delta(+ms) 是"时间戳 += ms", 即歌词延后.

所以从 metadata 应用 offset 的正确写法是取负号:

from lemony_lrc_parser import Lyrics

lyrics = Lyrics.loads("[offset: 500]\n[00:10.000]hello\n")
shifted = lyrics.apply_delta(-int(lyrics.metadata["offset"]))
print(shifted[0].start)  # 9500 —— 提前了 500ms

本库不会自动应用 offset (显式优于隐式), 需要由使用者自行处理.

也可以通过 min_timestamp / max_timestamp 快速检查歌词的时间范围:

from lemony_lrc_parser.offset import min_timestamp, max_timestamp

lyrics = Lyrics.loads(lrc_text)
print(min_timestamp(lyrics))  # 最小时间戳 (ms), 无时间戳时为 None
print(max_timestamp(lyrics))  # 最大时间戳 (ms), 无时间戳时为 None

解析报告

parse_lrc 之外还有 parse_with_report: 除歌词外再给一份结构化报告, 说明这次 解析"丢了什么 / 忽略了什么". 与 validate() 互补 —— 报告是事后审计, 校验是查 结果数据本身是否自相矛盾.

import lemony_lrc_parser as llp

lyrics, report = llp.parse_with_report("[00:01.000]a\n\norphan\n[00:02.000]\n")

if report:  # 干净文件的报告是空的
    for event in report:
        print(f"[{event.code}] line {event.line_no}: {event.message}")

report.dropped_lines  # 整行没进结果的 (孤儿行 / 被 line_filter 丢掉的行)
report.of_code("byword-unordered")  # 按事件代码筛选
report.reference_lines_attached, report.end_markers_applied  # 正常操作只计数

事件代码: orphaned-line、line-filtered、byword-before-line-start、 byword-after-line-end、byword-unordered、end-marker-ignored、 inferred-end-discarded.

报告只收值得调用方知道的事件, 正常操作 (挂参考行、结束标记正常收尾) 只进上面两个 计数, 所以干净文件的 report 为空 (bool(report) 是 False). 日志行为不变: 逐字标记那几条仍按原格式打印, 只是报告里会补上行号.

STRICT_ERROR_CODES 是其中"严格模式下会直接抛异常"的子集 (少了 line-filtered, 因为那是调用方自己要丢的), 见 解析选项 里的 strict.

校验

使用 lyrics.validate() 检查歌词数据一致性:

from lemony_lrc_parser import Lyrics, ValidationOptions

lyrics = Lyrics.loads(lrc_text)

# 返回问题列表
issues = lyrics.validate()
for issue in issues:
    print(f"[{issue.severity}] {issue.code}: {issue.message}")

# strict 模式: 遇到 error 级问题时抛出 InvalidLyricsError
issues = lyrics.validate(options=ValidationOptions(strict=True))

检查项包括:

  • 歌词行是否按时间升序排列 (unsorted)
  • 是否存在重复时间戳 (duplicate-start)
  • 行结束时间是否晚于开始时间 (end-not-after-start)
  • 逐字 token 时间是否单调递增 (token-nonmonotonic)
  • 逐字 token 自身区间是否合法 (token-end-not-after-start)
  • 逐字 token 是否在所属行的时间范围内 (token-before-line-start / token-after-line-end)
  • metadata key 格式合法性 (invalid-metadata-key)
  • offset 元数据是否可解析为整数 (offset-not-int)

error 与 warning 的分工: 由解析器写出的数据不会包含任何 error 级问题 (见 tests/test_roundtrip_matrix.py 的不变量测试), 因此出现 error 通常意味着 调用方手工构造了自相矛盾的对象.

命令行

本库不注册 console script, 用 python -m lemony_lrc_parser 调用 (与 python -m json.tool 同样的形态):

# 验证 LRC 文件数据一致性
python -m lemony_lrc_parser validate song.lrc
python -m lemony_lrc_parser validate --strict song.lrc    # 有 error 时退出码为 1

# 整体时间偏移 (毫秒)
python -m lemony_lrc_parser offset --delta 500 song.lrc           # 输出到 stdout
python -m lemony_lrc_parser offset --delta -200 song.lrc -o out.lrc  # 输出到文件

# 转换为字幕格式
python -m lemony_lrc_parser to-srt song.lrc
python -m lemony_lrc_parser to-webvtt song.lrc -o song.vtt

字幕格式转换 (SRT / WebVTT)

Lyrics 可与常见的简单字幕格式互相转换, 便于把歌词用于视频字幕制作, 或把已有字幕导入为歌词:

from lemony_lrc_parser import Lyrics, SubtitleOptions

lyrics = Lyrics.loads("[00:01.000]Hello\n[00:03.000]World\n")

# LRC → SRT / WebVTT
srt_text = lyrics.to_srt()
vtt_text = lyrics.to_webvtt()

# SRT / WebVTT → LRC
lyrics2 = Lyrics.from_srt(srt_text)
lyrics3 = Lyrics.from_webvtt(vtt_text)

# 顶层便捷函数也可用
import lemony_lrc_parser as llp

srt_text = llp.dump_srt(lyrics)
vtt_text = llp.dump_webvtt(lyrics)
lyrics2 = llp.parse_srt(srt_text)
lyrics3 = llp.parse_webvtt(vtt_text)

字幕以 [start, end] 时间区间为单位, 与 LRC 存在语义差异, 转换时的行为可通过 SubtitleOptions 控制:

options = SubtitleOptions(
    fill_end_from_next=True,  # 缺少行尾时间时, 用下一行的 start 补齐
    default_duration_ms=5000,  # 无法推断时长时使用的默认时长 (也用于修正非法区间)
    include_reference_lines=True,  # 是否把参考行 (翻译/音译) 作为 cue 附加文本输出
)

srt_text = lyrics.to_srt(options=options)

转换注意事项:

  • LRC 的逐字标签与 metadata 不会写入字幕 (会被拍平/丢弃).
  • 导出时若某行缺少 end, 会依次尝试用下一行 start 或 default_duration_ms 补齐; end <= start 的非法区间也会被修正.
  • 一条字幕 cue 可含多行文本: 导出时主行在前、参考行在后; 解析时 cue 首行作为主行, 其余行作为参考行.
  • 解析会自动跳过 WebVTT 的 WEBVTT 头部以及 NOTE / STYLE / REGION 块.

参考资料

UwU?

UwU!

License

MIT License

Metadata

Release files for lemony-lrc-parser 0.5.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lemony-lrc-parser 0.5.0b1
File Size Uploaded
lemony_lrc_parser-0.5.0b1.tar.gz 185.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lemony-lrc-parser 0.5.0b1
File Interpreter ABI Platform
lemony_lrc_parser-0.5.0b1-py3-none-any.whl Python 3 none any Details

Total release size: 253.4 kB

Release files / lemony_lrc_parser-0.5.0b1.tar.gz

Download URL lemony_lrc_parser-0.5.0b1.tar.gz
Size 185.9 kB
Tags Source
SHA-256 checksum
How to use checksums
ce8ce2c6aee3deb7a4814fed97231ba96e59453619874649bc374efc89614e44
BLAKE2b-256 checksum
How to use checksums
631b827ecdd7473bb28f04e0f68c3f6cd1a2bd5d59706798ab25a60362982e6a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / lemony_lrc_parser-0.5.0b1-py3-none-any.whl

Download URL lemony_lrc_parser-0.5.0b1-py3-none-any.whl
Size 67.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f0ed88e30c16874c01770e42d3a34ae883987d860ee70d34391da2186675e6f1
BLAKE2b-256 checksum
How to use checksums
f9bdb5afdb0e02256ed8d230c64c5f0422aa1c353adfef1f80ecb4c3b57fb98a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page