Explorar o código

LyricParser.ts

chendeben hai 1 ano
pai
achega
3a77e34862
Modificáronse 1 ficheiros con 140 adicións e 16 borrados
  1. 140 16
      oh_modules/@seagazer/cclyric/src/main/ets/parse/LyricParser.ts

+ 140 - 16
oh_modules/@seagazer/cclyric/src/main/ets/parse/LyricParser.ts

@@ -2,6 +2,7 @@ import { IParser } from './IParser';
 import { Lyric } from '../bean/Lyric';
 import { LyricLine } from '../bean/LyricLine';
 import { printD, printW } from '../extensions/Extension';
+import { LyricWord } from '../bean/LyricWord';
 
 /**
  * The parser to parse the string array of a standard lyric file.
@@ -28,7 +29,7 @@ export class LyricParser implements IParser {
         let album = ""
         let by = ""
         let offset = 0
-        const ignoredTags = [ 'hash', 'sign', 'qq', 'total']; // 定义需要忽略的标签
+        const ignoredTags = [ 'hash', 'sign', 'qq', 'total','Outro']; // 定义需要忽略的标签
         for (let i = 0; i < src.length; i++) {
             let line = src[i]
             console.info(`content The line of file:line ${line}`);
@@ -37,6 +38,7 @@ export class LyricParser implements IParser {
                 printW("the lyric line is empty, carriage return or line feed, line index= " + i)
                 continue
             }
+
             // 检查是否是需要忽略的标签
             const shouldIgnore = ignoredTags.some(tag  => line.indexOf(tag)  > 0);
             if (shouldIgnore) {
@@ -54,23 +56,40 @@ export class LyricParser implements IParser {
             } else if (line.indexOf("offset") > 0) {
                 offset = Number.parseInt(this.parseIdTag(line))
             } else {
-                // [00:00.10]画心 - 张靓颖
-                // [01:05.49][02:08.40]看不穿 是你失落的魂魄
-                let spr = line.split(']')
-                if (spr.length <= 1) {
-                    printW("the lyric line is no timestamp, line index= " + i)
-                    continue
+
+                // 新增逐字歌词解析逻辑[mm:ss.xx] <mm:ss.xx>
+                if (this.isWordByWordLyric(line)) {
+                    const { timeline, words } = this.parseWordByWordLine(line, offset);
+                    lyricLines.push(new LyricLine('', timeline, -1, words))
+                    continue;
                 }
-                // parse text
-                let text = spr[spr.length-1]
-                // printD("text= " + text)
-                // parse timeline
-                for (let i = 0;i < spr.length - 1; i++) {
-                    let timeline = spr[i].replace("[", "")
-                    let timeStamp = this.parseTimeline(timeline)
-                    // printD("timestamp= " + timeStamp)
-                    lyricLines.push(new LyricLine(text, timeStamp - offset, -1))
+
+                    // 新增:逐字歌词[]检测方括号逐字歌词格式 [mm:ss.xxx] 文字
+                if (this.isSquareBracketWordByWordLyric(line))  {
+                    const { timeline, words } = this.parseSquareBracketWordLine(line,  offset);
+                    if (words.length  > 0) {
+                        lyricLines.push(new  LyricLine('', timeline, -1, words))
+                    }
+                } else {
+                    // 原逻辑处理,但支持逐字歌词
+                    // [00:00.10]画心 - 张靓颖
+                    // [01:05.49][02:08.40]看不穿 是你失落的魂魄
+                    let spr = line.split(']');
+                    if (spr.length <= 1) {
+                        printW("the lyric line is no timestamp, line index= " + i)
+                        continue
+                    }
+                    // parse text
+                    let text = spr[spr.length-1]
+                    // ... 原来的文本解析逻辑保持不变 ...
+                    for (let i = 0; i < spr.length - 1; i++) {
+                        let timeline = spr[i].replace("[", "");
+                        let timeStamp = this.parseTimeline(timeline);
+                        lyricLines.push(new LyricLine(text, timeStamp - offset, -1));
+                    }
                 }
+
+
             }
         }
         lyricLines.sort((l1, l2) => {
@@ -85,10 +104,115 @@ export class LyricParser implements IParser {
                 lyricLine.nextTime = next.beginTime
             }
         }
+        // 为逐字歌词填充text(拼接所有歌词词)
+        this.populateTextForWordLyrics(lyricLines);
         let result = new Lyric(artist, title, album, by, offset, lyricLines)
         return result
     }
 
+
+    // 检测方括号格式的逐字歌词 [00:00.000]文[00:01.000]字
+    private isSquareBracketWordByWordLyric(line: string): boolean {
+        return /\[\d{2}:\d{2}\.\d{2,3}\]\S/.test(line);
+    }
+
+    // 解析方括号格式的逐字歌词行
+    private parseSquareBracketWordLine(line: string, offset: number):
+        { timeline: number, words: LyricWord[] } {
+
+        const words: LyricWord[] = [];
+        let firstTimeline = -1;
+
+        // 正则匹配:[00:00.000]中文字
+        const regex = /\[(\d{2}:\d{2}\.\d{2,3})\]([^\[]*)/g;
+        let match;
+
+        while ((match = regex.exec(line))  !== null) {
+            const timeStr = match[1];   // 时间部分 00:00.000
+            const word = match[2].trim(); // 歌词文本
+
+            if (!word) continue; // 跳过空词
+
+            const timeline = this.parseTimeline2(timeStr)  - offset;
+            if (firstTimeline < 0) firstTimeline = timeline;
+
+            words.push(new  LyricWord(word, timeline, 0));
+        }
+
+        // 计算每个词的持续时间
+        for (let i = 0; i < words.length  - 1; i++) {
+            words[i].duration = words[i + 1].startTime - words[i].startTime;
+        }
+        if (words.length  > 0 && words[words.length - 1].duration === 0) {
+            words[words.length - 1].duration = 200; // 默认200ms
+        }
+
+        return { timeline: firstTimeline, words };
+    }
+
+    /******************** 时间解析增强 ********************/
+    private parseTimeline2(timeString: string): number {
+        // 增强支持毫秒/厘秒解析
+        const parts = timeString.split(':');
+        const minutes = parseInt(parts[0], 10);
+
+        const secondParts = parts[1].split('.');
+        const seconds = parseInt(secondParts[0], 10);
+        const fraction = parseInt(secondParts[1], 10);
+
+        // 根据小数位长度判断时间精度
+        const milliseconds = secondParts[1].length === 2 ?
+            fraction * 10 : // 厘秒转毫秒 (01 -> 10ms)
+            fraction;       // 毫秒直接使用
+
+        return minutes * 60000 + seconds * 1000 + milliseconds;
+    }
+
+    // 判断是否是逐字歌词行
+    private isWordByWordLyric(line: string): boolean {
+        const hasBracketTimestamp = /$$\d{2}:\d{2}\.\d{2,3}$$\S/.test(line);
+        const hasAngleTimestamp = /<\d{2}:\d{2}\.\d{2,3}>/.test(line);
+        return hasBracketTimestamp || hasAngleTimestamp;
+    }
+
+    // 解析逐字歌词行
+    private parseWordByWordLine(line: string, offset: number): { timeline: number, words: LyricWord[] } {
+        const words: LyricWord[] = [];
+        let firstTimeline = -1;
+        const regex = /((?:<|$$)(\d{2}:\d{2}\.\d{2,3})(?:>|$$))([^<\[]*)/g;
+        let match;
+
+        while ((match = regex.exec(line)) !== null) {
+            const [_, tag, timeStr, word] = match;
+            if (word.trim() === '') continue;
+
+            const timeline = this.parseTimeline(timeStr) - offset;
+            if (firstTimeline < 0) firstTimeline = timeline;
+
+            words.push(new LyricWord(word.trim(), timeline, 0));
+        }
+
+        // 设置每个词持续时间(下一个词开始时间-当前词开始时间)
+        for (let i = 0; i < words.length - 1; i++) {
+            words[i].duration = words[i + 1].startTime - words[i].startTime;
+        }
+        if (words.length > 0 && words[words.length - 1].duration === 0) {
+            // 最后一个词持续200ms
+            words[words.length - 1].duration = 200;
+        }
+
+        return { timeline: firstTimeline, words };
+    }
+
+    // 为逐字歌词拼接整行文本
+    private populateTextForWordLyrics(lyricLines: LyricLine[]) {
+        lyricLines.forEach(line => {
+            if (line.words.length > 0) {
+                line.text = line.words.map(word => word.word).join('');
+            }
+        });
+    }
+
     private parseIdTag(line: string): string {
         let spr = line.split(":")
         let spr1 = spr[1]