-
Notifications
You must be signed in to change notification settings - Fork 1.2k
🦄 refactor: 更换更好的 LRC 解析器 #813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
apoint123
merged 2 commits into
SPlayer-Dev:dev
from
apoint123:refactor/refine-lrc-parser
Feb 3, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import type { LyricLine, LyricWord } from "@applemusic-like-lyrics/lyric"; | ||
|
|
||
| interface ParsedEvent { | ||
| time: number; | ||
| text: string; | ||
| index: number; // 用于保持同时间戳下的原始顺序 | ||
| } | ||
|
|
||
| /** | ||
| * 一个更好的 LRC 解析器,相比于 AMLL 的 LRC 解析器,支持丢弃空行、trim 歌词和自动检测翻译与音译行 | ||
| * @note 如果遇到相同时间戳的歌词行,第一行会作为主歌词行,第二行会作为翻译歌词,第三行会作为音译歌词 | ||
| * @param lrcContent LRC 字符串 | ||
| * @returns LyricLine 数组 | ||
| */ | ||
| export function parseLrc(lrcContent: string): LyricLine[] { | ||
| const lines = lrcContent.split(/\r?\n/); | ||
| const parsedEvents: ParsedEvent[] = []; | ||
|
|
||
| const timeTagRegex = /\[(\d{1,2}):(\d{1,2})(?:[.:](\d{1,3}))?\]/g; | ||
|
|
||
| let globalIndex = 0; | ||
|
|
||
| for (const line of lines) { | ||
| const text = line.replace(timeTagRegex, "").trim(); | ||
| const matches = line.matchAll(timeTagRegex); | ||
|
|
||
| for (const match of matches) { | ||
| const minutes = parseInt(match[1], 10); | ||
| const seconds = parseInt(match[2], 10); | ||
| const fractionStr = match[3] || "0"; | ||
| const milliseconds = parseInt(fractionStr.padEnd(3, "0"), 10); | ||
|
|
||
| const totalMilliseconds = minutes * 60 * 1000 + seconds * 1000 + milliseconds; | ||
|
|
||
| parsedEvents.push({ | ||
| time: totalMilliseconds, | ||
| text: text, | ||
| index: globalIndex++, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| parsedEvents.sort((a, b) => a.time - b.time || a.index - b.index); | ||
|
|
||
| const validLyricLines: LyricLine[] = []; | ||
|
|
||
| let i = 0; | ||
| while (i < parsedEvents.length) { | ||
| const currentTime = parsedEvents[i].time; | ||
|
|
||
| const group: ParsedEvent[] = []; | ||
| while (i < parsedEvents.length && parsedEvents[i].time === currentTime) { | ||
| group.push(parsedEvents[i]); | ||
| i++; | ||
| } | ||
|
|
||
| const nextEvent = i < parsedEvents.length ? parsedEvents[i] : null; | ||
| const endTime = nextEvent ? nextEvent.time : currentTime + 10000; | ||
|
|
||
| const textEvents = group.filter((e) => e.text.length > 0); | ||
|
|
||
| if (textEvents.length === 0) { | ||
| continue; | ||
| } | ||
|
|
||
| const mainLine = createBasicLyricLine(textEvents[0].text, currentTime, endTime); | ||
|
|
||
| // 第二行作为翻译 | ||
| if (textEvents.length > 1) { | ||
| mainLine.translatedLyric = textEvents[1].text; | ||
| } | ||
|
|
||
| // 第三行作为音译 | ||
| if (textEvents.length > 2) { | ||
| mainLine.romanLyric = textEvents[2].text; | ||
| } | ||
|
|
||
| validLyricLines.push(mainLine); | ||
|
|
||
| if (textEvents.length > 3) { | ||
| for (let k = 3; k < textEvents.length; k++) { | ||
| const extraLine = createBasicLyricLine(textEvents[k].text, currentTime, endTime); | ||
| validLyricLines.push(extraLine); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return validLyricLines; | ||
| } | ||
|
|
||
| function createBasicLyricLine(text: string, startTime: number, endTime: number): LyricLine { | ||
| const line = newLyricLine(); | ||
| const word = newLyricWord(); | ||
|
|
||
| word.word = text; | ||
| word.startTime = startTime; | ||
| word.endTime = endTime; | ||
|
|
||
| line.words = [word]; | ||
| line.startTime = startTime; | ||
| line.endTime = endTime; | ||
|
|
||
| return line; | ||
| } | ||
|
|
||
| const newLyricLine = (): LyricLine => ({ | ||
| words: [], | ||
| translatedLyric: "", | ||
| romanLyric: "", | ||
| isBG: false, | ||
| isDuet: false, | ||
| startTime: 0, | ||
| endTime: 0, | ||
| }); | ||
|
|
||
| const newLyricWord = (): LyricWord => ({ | ||
| startTime: 0, | ||
| endTime: 0, | ||
| word: "", | ||
| romanWord: "", | ||
| }); | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.