This release is a pre-release and may not be stable for production use.
Lemony LRC Parser
柠檬味的 Python LRC 歌词解析器.
Lemon-flavored LRC Parser for Python.
Features
- 解析标准 LRC 歌词文件
- 支持 EnhancedLRC / SPL 的逐字歌词标签
- 支持 metadata 标签
- 支持折叠时间标签
- 支持参照行
- 支持歌词合并
- 与简单字幕格式 (SRT / WebVTT) 互转
- 时间偏移 (
apply_delta/<</>>运算符) - 字典序列化 (
to_dict()/from_dict()) - 深拷贝方法链 (
.copy()) - 可配置解析与序列化选项
- 数据一致性验证 API (
validate_lyrics/Lyrics.validate()) - CLI 命令行工具 (
lemonyrics/python -m lemony_lrc_parser) - 解析错误附带行号与原始行 (
InvalidLyricsError.line_no/.raw_line) - 完整的类型注解
Installation
推荐使用 uv.
It's recommended to use uv.
uv add lemony-lrc-parser
用 pip 也行.
It's okay to use pip.
pip install lemony-lrc-parser
可以使用 git 仓库来源来第一时间体验到最新最热的 bug feature.
You can use git repo as a source to catch the newest bugs features.
uv add https://github.com/NingmengLemon/lemony-lrc-parser.git
Usage
Quick Start
json, marshal or pickle -like usages
import lemony_lrc_parser as llp
lrc_text = """[ti: Never Gonna Give You Up]
[ar: Rick Astley]
[00:18.684]We're no strangers to love
[00:18.684]我们都是情场老手
[00:22.657]You know the rules and so do I
[00:22.657]你和我都知道爱情的规则
[00:27.070]A full commitment's what I'm thinking of
[00:27.070]我在想的正是一份实打实的承诺
[00:31.459]You wouldn't get this from any other guy
[00:31.459]你从其他人那里得不到的
"""
# 解析
lyrics = llp.loads(lrc_text)
# 访问 metadata
print(lyrics.metadata["ti"]) # "Never Gonna Give You Up"
print(lyrics.metadata["ar"]) # "Rick Astley"
# 遍历歌词行
for line in lyrics:
print(f"{line.start}ms: {line.text}")
# 序列化回 LRC 格式
output = llp.dumps(lyrics)
OOP Interface
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()
# __str__ 等价于 dumps()
print(lyrics)
Word-level Lyrics
解析逐字 (Enhanced LRC / SPL) 歌词:
lrc_text = "[00:01.00]<00:01.00>Never <00:01.50>gonna <00:02.00>give <00:02.50>you <00:03.00>up[00:03.50]"
lyrics = llp.loads(lrc_text)
line = lyrics[0]
for word in line.content:
print(f" [{word.start} -> {word.end}] {word.content!r}")
# [1000 -> 1500] 'Never '
# [1500 -> 2000] 'gonna '
# [2000 -> 2500] 'give '
# [2500 -> 3000] 'you '
# [3000 -> None] 'up'
# 行级时间: line.start=1000, line.end=3500
Reference Lines (Translation / Transliteration)
LRC 文件中, 紧跟在带时间标签行后面的无标签行, 或与主行的时间戳相同的行, 会被解析为参考行, 常用于存放翻译或音译:
lrc_text = """[00:01.00]Hello
你好
[00:02.00]World
[00:02.00]世界
"""
lyrics = llp.loads(lrc_text)
line = lyrics[0]
print(line.text) # "Hello"
print(line.reference_lines[0][0].content) # "你好"
Combining Lyrics
将两份歌词 (如原文和翻译) 按时间标签合并:
main = llp.loads("[00:01.00]Hello\n[00:02.00]World\n")
translation = llp.loads("[00:01.00]你好\n[00:02.00]世界\n")
# combine 方法: 翻译行挂到同时间点的 reference_lines 中
combined = main.combine(translation)
# 也可以用 + 运算符
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)
Dict Serialization (to_dict / from_dict)
所有数据模型都支持字典序列化, 方便 JSON 传输和 API 对接:
import lemony_lrc_parser as llp
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()
Copy
所有数据模型都提供 .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
Options
Parsing Options
import re
from lemony_lrc_parser import Lyrics
from lemony_lrc_parser.models import 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").
Serialization Options
通过 SerializationOptions 控制序列化行为:
from lemony_lrc_parser import Lyrics
from lemony_lrc_parser.models import SerializationOptions
output = lyrics.dumps(
options=SerializationOptions(
with_metadata=True, # 是否输出 metadata 段
use_bracket_for_byword_tag=False, # 逐字标签使用 [...] 还是 <...> (默认)
line_tag_decimal_length=3, # 行标签毫秒位数 (默认 3)
word_tag_decimal_length=3, # 逐字标签毫秒位数 (默认 3)
line_separator="\n", # 行间分隔字符串 (默认 "\n", 设为 "" 可省去空行)
),
)
Length of Decimal Part
默认 line_tag_decimal_length=3、word_tag_decimal_length=3, 输出格式如 [00:01.000]、<00:01.050>, 保留完整的毫秒精度.
若设为 2, 小数部分表示百分秒 (如 [00:01.00]), 属于有损截断 (例如 555ms 会被截断为 55, 解析回来变成 550ms), 需要按需权衡 (部分老软件可能只支持百分秒).
Offset
使用 Lyrics.apply_delta(ms) 应用时间偏移, ms 会直接加到每个标签的时间戳上,
这意味着传入正数偏移值会导致歌词整体延后出现, 反之同理.
也可以使用重载的 >> / << 运算, 私以为这样会更好理解一些.
如果应用 offset 会导致时间戳变为负数, 将抛出 TimestampUnderflowError, 由调用方自行处理.
Applying Offset
通过 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 不受影响
如果偏移会导致时间戳变为负数, 将抛出 TimestampUnderflowError.
如需在序列化前偏移时间戳, 请先调用 apply_delta() 再序列化返回的副本.
如果你的偏移量来自歌词文件元数据, 你可能还需要记得手动清理 lyrics.metadata 中的偏移值.
也可以通过 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
Validation
使用 lyrics.validate() 检查歌词数据一致性:
from lemony_lrc_parser import Lyrics, validate_lyrics
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-before-line-start/token-after-line-end) - metadata key 格式合法性 (
invalid-metadata-key) offset元数据是否可解析为整数 (offset-not-int)
CLI Usage
安装后可通过命令行直接使用:
# 验证 LRC 文件数据一致性
lemonyrics validate song.lrc
lemonyrics validate --strict song.lrc # 有 error 时退出码为 1
# 整体时间偏移 (毫秒)
lemonyrics offset --delta 500 song.lrc # 输出到 stdout
lemonyrics offset --delta -200 song.lrc -o out.lrc # 输出到文件
# 转换为字幕格式
lemonyrics to-srt song.lrc
lemonyrics to-webvtt song.lrc -o song.vtt
也可以使用 python -m lemony_lrc_parser 作为入口.
Subtitle Conversion (SRT / WebVTT)
Lyrics 可与常见的简单字幕格式互相转换, 便于把歌词用于视频字幕制作,
或把已有字幕导入为歌词:
from lemony_lrc_parser import Lyrics
from lemony_lrc_parser.models import 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块.
References
UwU?
UwU!
License
MIT License
Metadata
Release files for lemony-lrc-parser 0.4.0b3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lemony_lrc_parser-0.4.0b3.tar.gz | 149.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lemony_lrc_parser-0.4.0b3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 186.4 kB
Release files / lemony_lrc_parser-0.4.0b3.tar.gz
| Download URL | lemony_lrc_parser-0.4.0b3.tar.gz |
|---|---|
| Size | 149.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e9cd79d7b8b9a23c2ae6533ed0ceb9930335d97d270b81a47fe21c3fb266bf54
|
|
BLAKE2b-256 checksum How to use checksums |
aa7db3940e2ba77f281b31c93390c1b7915c109f3e1291c57386488365b7bc1d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / lemony_lrc_parser-0.4.0b3-py3-none-any.whl
| Download URL | lemony_lrc_parser-0.4.0b3-py3-none-any.whl |
|---|---|
| Size | 37.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
51fdc3b1b1233b433c6d09ffe74cf2001b7dbf06538486c2674c7462ded300e9
|
|
BLAKE2b-256 checksum How to use checksums |
ee8c3315c241ea74c73fff9d6a9f5e1eb2e1dbb58d245ea7f559d28baf73d402
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|