|
|
@@ -8,14 +8,6 @@ import { LyricWord } from '../bean/LyricWord';
|
|
|
* The parser to parse the string array of a standard lyric file.
|
|
|
*/
|
|
|
export class LyricParser implements IParser {
|
|
|
- // Example lyric file:
|
|
|
- // [ti:画心]
|
|
|
- // [ar:张靓颖]
|
|
|
- // [al:432326]
|
|
|
- // [by:]
|
|
|
- // [offset:0]
|
|
|
- // [00:00.10]画心 - 张靓颖
|
|
|
- // [01:05.49]看不穿 是你失落的魂魄
|
|
|
|
|
|
/**
|
|
|
* Parse the string array to a Lyric.
|
|
|
@@ -30,6 +22,23 @@ export class LyricParser implements IParser {
|
|
|
let by = ""
|
|
|
let offset = 0
|
|
|
const ignoredTags = [ 'id', 'hash', 'sign', 'qq', 'total','Outro','Intro']; // 定义需要忽略的标签
|
|
|
+
|
|
|
+ // 首先检测是否是纯文本歌词(没有时间标签)
|
|
|
+ let hasValidTimeTag = false;
|
|
|
+ for (let i = 0; i < src.length; i++) {
|
|
|
+ let line = src[i].trim();
|
|
|
+ if (line.length > 0 && /^\[\d{2}:\d{2}\.\d{2,3}\]/.test(line)) {
|
|
|
+ hasValidTimeTag = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有时间标签,作为纯文本歌词处理
|
|
|
+ if (!hasValidTimeTag) {
|
|
|
+ printD("检测到纯文本歌词(无时间标签),为每行添加默认时间标签");
|
|
|
+ return this.parsePlainTextLyric(src);
|
|
|
+ }
|
|
|
+
|
|
|
for (let i = 0; i < src.length; i++) {
|
|
|
let line = src[i]
|
|
|
console.info(`content The line of file:line ${line}`);
|
|
|
@@ -332,4 +341,83 @@ export class LyricParser implements IParser {
|
|
|
|
|
|
return minutes * 60000 + seconds * 1000 + milliseconds;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析纯文本歌词(没有时间标签的歌词)
|
|
|
+ * @param src 歌词行数组
|
|
|
+ * @returns Lyric 对象
|
|
|
+ */
|
|
|
+ private parsePlainTextLyric(src: Array<string>): Lyric {
|
|
|
+ let lyricLines = new Array<LyricLine>()
|
|
|
+ let title = ""
|
|
|
+ let artist = ""
|
|
|
+ let album = ""
|
|
|
+ let by = ""
|
|
|
+ let offset = 0
|
|
|
+
|
|
|
+ // 为纯文本歌词设置一个起始时间(比如从歌曲开始10秒后)
|
|
|
+ // 这样可以避免一播放就滚走
|
|
|
+ const START_TIME = 10000 // 10秒
|
|
|
+ const LINE_INTERVAL = 5000 // 每行间隔5秒
|
|
|
+
|
|
|
+ for (let i = 0; i < src.length; i++) {
|
|
|
+ let line = src[i].trim()
|
|
|
+
|
|
|
+ // 跳过空行
|
|
|
+ if (line.length === 0 || line === "\n" || line === "\r" || line === "\r\n") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // 跳过元数据行(以 [ti:、[ar:、[al:、[by:、[offset: 等开头)
|
|
|
+ if (line.startsWith('[ti:') || line.startsWith('[ar:') || line.startsWith('[al:') ||
|
|
|
+ line.startsWith('[by:') || line.startsWith('[offset:') || line.startsWith('[length:') ||
|
|
|
+ line.startsWith('[id:') || line.startsWith('[hash:') || line.startsWith('[sign:')) {
|
|
|
+ // 解析元数据
|
|
|
+ if (line.startsWith("[ti:")) {
|
|
|
+ title = this.parseIdTag(line);
|
|
|
+ } else if (line.startsWith("[ar:")) {
|
|
|
+ artist = this.parseIdTag(line);
|
|
|
+ } else if (line.startsWith("[al:")) {
|
|
|
+ album = this.parseIdTag(line);
|
|
|
+ } else if (line.startsWith("[by:")) {
|
|
|
+ by = this.parseIdTag(line);
|
|
|
+ } else if (line.startsWith("[offset:")) {
|
|
|
+ offset = Number.parseInt(this.parseIdTag(line));
|
|
|
+ }
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // 跳过标签行(如 [Verse]、[Chorus] 等)
|
|
|
+ if (line === "[Verse]" || line === "[Chorus]" || line === "[PreChorus]" ||
|
|
|
+ line === "[Bridge]" || line === "[Intro]" || line === "[Outro]") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // 跳过其他以 [ 开头但不是时间标签的行
|
|
|
+ if (line.startsWith('[') && !/^\[\d{2}:\d{2}\.\d{2,3}\]/.test(line)) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // 为每行设置递增的时间,这样歌词会按顺序显示
|
|
|
+ // 但因为时间间隔较大(5秒),用户可以慢慢阅读
|
|
|
+ let beginTime = START_TIME + (lyricLines.length * LINE_INTERVAL)
|
|
|
+ lyricLines.push(new LyricLine(line, beginTime, -1))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置 nextTime
|
|
|
+ for (let i = 0; i < lyricLines.length; i++) {
|
|
|
+ let lyricLine = lyricLines[i]
|
|
|
+ if (i == lyricLines.length - 1) {
|
|
|
+ // 最后一行,设置一个较大的 nextTime 让它持续显示
|
|
|
+ lyricLine.nextTime = lyricLine.beginTime + 60000 // 持续显示1分钟
|
|
|
+ } else {
|
|
|
+ let next = lyricLines[i + 1]
|
|
|
+ lyricLine.nextTime = next.beginTime
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ printD(`纯文本歌词解析完成,共 ${lyricLines.length} 行歌词`)
|
|
|
+ let result = new Lyric(artist, title, album, by, offset, lyricLines)
|
|
|
+ return result
|
|
|
+ }
|
|
|
}
|