From d19b230809484fbca2267e89676c4361c1c408d0 Mon Sep 17 00:00:00 2001 From: alikia2x Date: Thu, 1 Aug 2024 21:40:09 +0800 Subject: [PATCH] ref: add new AMLL dynamic lyric --- bun.lockb | Bin 158500 -> 183342 bytes package.json | 14 +- src/lib/components/newLyrics.svelte | 132 +++++++++++ src/lib/lyrics/mapLyric.ts | 18 ++ src/lib/ttml/index.ts | 3 + src/lib/ttml/parser.ts | 168 ++++++++++++++ src/lib/ttml/ttml-types.ts | 26 +++ src/lib/ttml/writer.ts | 260 ++++++++++++++++++++++ src/routes/import/[id]/lyric/+page.svelte | 2 +- src/routes/play/[id]/+page.svelte | 84 ++++--- vite.config.js | 5 +- 11 files changed, 682 insertions(+), 30 deletions(-) create mode 100644 src/lib/components/newLyrics.svelte create mode 100644 src/lib/lyrics/mapLyric.ts create mode 100644 src/lib/ttml/index.ts create mode 100644 src/lib/ttml/parser.ts create mode 100644 src/lib/ttml/ttml-types.ts create mode 100644 src/lib/ttml/writer.ts diff --git a/bun.lockb b/bun.lockb index 383e35438a2601032d8d77bc2b3996534a1547dc..98d6a6f1a97ad0c58225eedab2760fd223520d6e 100755 GIT binary patch delta 43890 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 OsCC-%-E6IQxBmt7#22mr diff --git a/package.json b/package.json index 773c30a..3850db9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aquavox", - "version": "1.15.0", + "version": "2.0.0", "private": false, "scripts": { "dev": "vite dev", @@ -34,15 +34,27 @@ "tailwindcss": "^3.4.3", "typescript": "^5.4.5", "vite": "^5.2.11", + "vite-plugin-wasm": "^3.3.0", "vitest": "^1.6.0" }, "type": "module", "dependencies": { + "@applemusic-like-lyrics/core": "^0.1.3", + "@applemusic-like-lyrics/lyric": "^0.2.2", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", + "@pixi/app": "^7.4.2", + "@pixi/core": "^7.4.2", + "@pixi/display": "^7.4.2", + "@pixi/filter-blur": "^7.4.2", + "@pixi/filter-bulge-pinch": "^5.1.1", + "@pixi/filter-color-matrix": "^7.4.2", + "@pixi/sprite": "^7.4.2", "@types/bun": "^1.1.6", "bezier-easing": "^2.1.0", "jotai": "^2.8.0", "jotai-svelte": "^0.0.2", + "jss": "^10.10.0", + "jss-preset-default": "^10.10.0", "localforage": "^1.10.0", "lrc-parser-ts": "^1.0.3", "music-metadata-browser": "^2.5.10", diff --git a/src/lib/components/newLyrics.svelte b/src/lib/components/newLyrics.svelte new file mode 100644 index 0000000..bded294 --- /dev/null +++ b/src/lib/components/newLyrics.svelte @@ -0,0 +1,132 @@ + + +
diff --git a/src/lib/lyrics/mapLyric.ts b/src/lib/lyrics/mapLyric.ts new file mode 100644 index 0000000..13f96b2 --- /dev/null +++ b/src/lib/lyrics/mapLyric.ts @@ -0,0 +1,18 @@ +import type { LyricLine } from "@applemusic-like-lyrics/core"; +import { + type LyricLine as RawLyricLine, + parseLrc, + parseYrc, + parseLys, + parseQrc, +} from "@applemusic-like-lyrics/lyric"; + +export const mapLyric = (line: RawLyricLine, i: number, lines: RawLyricLine[]): LyricLine => ({ + words: line.words, + startTime: line.words[0]?.startTime ?? 0, + endTime: line.words[line.words.length - 1]?.endTime ?? Infinity, + translatedLyric: '', + romanLyric: '', + isBG: false, + isDuet: false +}); diff --git a/src/lib/ttml/index.ts b/src/lib/ttml/index.ts new file mode 100644 index 0000000..79fec1c --- /dev/null +++ b/src/lib/ttml/index.ts @@ -0,0 +1,3 @@ +export * from "./parser"; +export * from "./writer"; +export type * from "./ttml-types"; diff --git a/src/lib/ttml/parser.ts b/src/lib/ttml/parser.ts new file mode 100644 index 0000000..3cdb8eb --- /dev/null +++ b/src/lib/ttml/parser.ts @@ -0,0 +1,168 @@ +/** + * @fileoverview + * 解析 TTML 歌词文档到歌词数组的解析器 + * 用于解析从 Apple Music 来的歌词文件,且扩展并支持翻译和音译文本。 + * @see https://www.w3.org/TR/2018/REC-ttml1-20181108/ + */ + +import type { + LyricLine, + LyricWord, + TTMLLyric, + TTMLMetadata, +} from "./ttml-types"; + +const timeRegexp = + /^(((?[0-9]+):)?(?[0-9]+):)?(?[0-9]+([.:]([0-9]+))?)/; +function parseTimespan(timeSpan: string): number { + const matches = timeRegexp.exec(timeSpan); + if (matches) { + const hour = Number(matches.groups?.hour || "0"); + const min = Number(matches.groups?.min || "0"); + const sec = Number(matches.groups?.sec.replace(/:/, ".") || "0"); + return Math.floor((hour * 3600 + min * 60 + sec) * 1000); + } + throw new TypeError(`时间戳字符串解析失败:${timeSpan}`); +} + +export function parseTTML(ttmlText: string): TTMLLyric { + const domParser = new DOMParser(); + const ttmlDoc: XMLDocument = domParser.parseFromString( + ttmlText, + "application/xml", + ); + + let mainAgentId = "v1"; + + const metadata: TTMLMetadata[] = []; + for (const meta of ttmlDoc.querySelectorAll("meta")) { + if (meta.tagName === "amll:meta") { + const key = meta.getAttribute("key"); + if (key) { + const value = meta.getAttribute("value"); + if (value) { + const existing = metadata.find((m) => m.key === key); + if (existing) { + existing.value.push(value); + } else { + metadata.push({ + key, + value: [value], + }); + } + } + } + } + } + + for (const agent of ttmlDoc.querySelectorAll("ttm\\:agent")) { + if (agent.getAttribute("type") === "person") { + const id = agent.getAttribute("xml:id"); + if (id) { + mainAgentId = id; + } + } + } + + const lyricLines: LyricLine[] = []; + + function parseParseLine(lineEl: Element, isBG = false, isDuet = false) { + const line: LyricLine = { + words: [], + translatedLyric: "", + romanLyric: "", + isBG, + isDuet: + !!lineEl.getAttribute("ttm:agent") && + lineEl.getAttribute("ttm:agent") !== mainAgentId, + startTime: 0, + endTime: 0, + }; + if (isBG) line.isDuet = isDuet; + let haveBg = false; + + for (const wordNode of lineEl.childNodes) { + if (wordNode.nodeType === Node.TEXT_NODE) { + line.words?.push({ + word: wordNode.textContent ?? "", + startTime: 0, + endTime: 0, + }); + } else if (wordNode.nodeType === Node.ELEMENT_NODE) { + const wordEl = wordNode as Element; + const role = wordEl.getAttribute("ttm:role"); + + if (wordEl.nodeName === "span" && role) { + if (role === "x-bg") { + parseParseLine(wordEl, true, line.isDuet); + haveBg = true; + } else if (role === "x-translation") { + line.translatedLyric = wordEl.innerHTML; + } else if (role === "x-roman") { + line.romanLyric = wordEl.innerHTML; + } + } else if (wordEl.hasAttribute("begin") && wordEl.hasAttribute("end")) { + const word: LyricWord = { + word: wordNode.textContent ?? "", + startTime: parseTimespan(wordEl.getAttribute("begin") ?? ""), + endTime: parseTimespan(wordEl.getAttribute("end") ?? ""), + }; + const emptyBeat = wordEl.getAttribute("amll:empty-beat"); + if (emptyBeat) { + word.emptyBeat = Number(emptyBeat); + } + line.words.push(word); + } + } + } + + if (line.isBG) { + const firstWord = line.words?.[0]; + if (firstWord?.word.startsWith("(")) { + firstWord.word = firstWord.word.substring(1); + if (firstWord.word.length === 0) { + line.words.shift(); + } + } + + const lastWord = line.words?.[line.words.length - 1]; + if (lastWord?.word.endsWith(")")) { + lastWord.word = lastWord.word.substring(0, lastWord.word.length - 1); + if (lastWord.word.length === 0) { + line.words.pop(); + } + } + } + + const startTime = lineEl.getAttribute("begin"); + const endTime = lineEl.getAttribute("end"); + if (startTime && endTime) { + line.startTime = parseTimespan(startTime); + line.endTime = parseTimespan(endTime); + } else { + line.startTime = line.words + .filter((v) => v.word.trim().length > 0) + .reduce((pv, cv) => Math.min(pv, cv.startTime), Infinity); + line.endTime = line.words + .filter((v) => v.word.trim().length > 0) + .reduce((pv, cv) => Math.max(pv, cv.endTime), 0); + } + + if (haveBg) { + const bgLine = lyricLines.pop(); + lyricLines.push(line); + if (bgLine) lyricLines.push(bgLine); + } else { + lyricLines.push(line); + } + } + + for (const lineEl of ttmlDoc.querySelectorAll("body p[begin][end]")) { + parseParseLine(lineEl); + } + + return { + metadata, + lyricLines: lyricLines, + }; +} diff --git a/src/lib/ttml/ttml-types.ts b/src/lib/ttml/ttml-types.ts new file mode 100644 index 0000000..644a870 --- /dev/null +++ b/src/lib/ttml/ttml-types.ts @@ -0,0 +1,26 @@ +export interface TTMLMetadata { + key: string; + value: string[]; +} + +export interface TTMLLyric { + metadata: TTMLMetadata[]; + lyricLines: LyricLine[]; +} + +export interface LyricWord { + startTime: number; + endTime: number; + word: string; + emptyBeat?: number; +} + +export interface LyricLine { + words: LyricWord[]; + translatedLyric: string; + romanLyric: string; + isBG: boolean; + isDuet: boolean; + startTime: number; + endTime: number; +} diff --git a/src/lib/ttml/writer.ts b/src/lib/ttml/writer.ts new file mode 100644 index 0000000..30668a1 --- /dev/null +++ b/src/lib/ttml/writer.ts @@ -0,0 +1,260 @@ +/** + * @fileoverview + * 用于将内部歌词数组对象导出成 TTML 格式的模块 + * 但是可能会有信息会丢失 + */ + +import type { LyricLine, LyricWord, TTMLLyric } from "./ttml-types"; + +function msToTimestamp(timeMS: number): string { + let time = timeMS; + if (!Number.isSafeInteger(time) || time < 0) { + return "00:00.000"; + } + if (time === Infinity) { + return "99:99.999"; + } + time = time / 1000; + const secs = time % 60; + time = (time - secs) / 60; + const mins = time % 60; + const hrs = (time - mins) / 60; + + const h = hrs.toString().padStart(2, "0"); + const m = mins.toString().padStart(2, "0"); + const s = secs.toFixed(3).padStart(6, "0"); + + if (hrs > 0) { + return `${h}:${m}:${s}`; + } + return `${m}:${s}`; +} + +export function exportTTML(ttmlLyric: TTMLLyric, pretty = false): string { + const params: LyricLine[][] = []; + const lyric = ttmlLyric.lyricLines; + + let tmp: LyricLine[] = []; + for (const line of lyric) { + if (line.words.length === 0 && tmp.length > 0) { + params.push(tmp); + tmp = []; + } else { + tmp.push(line); + } + } + + if (tmp.length > 0) { + params.push(tmp); + } + + const doc = new Document(); + + function createWordElement(word: LyricWord): Element { + const span = doc.createElement("span"); + span.setAttribute("begin", msToTimestamp(word.startTime)); + span.setAttribute("end", msToTimestamp(word.endTime)); + if (word.emptyBeat) { + span.setAttribute("amll:empty-beat", `${word.emptyBeat}`); + } + span.appendChild(doc.createTextNode(word.word)); + return span; + } + + const ttRoot = doc.createElement("tt"); + + ttRoot.setAttribute("xmlns", "http://www.w3.org/ns/ttml"); + ttRoot.setAttribute("xmlns:ttm", "http://www.w3.org/ns/ttml#metadata"); + ttRoot.setAttribute("xmlns:amll", "http://www.example.com/ns/amll"); + ttRoot.setAttribute( + "xmlns:itunes", + "http://music.apple.com/lyric-ttml-internal", + ); + + doc.appendChild(ttRoot); + + const head = doc.createElement("head"); + + ttRoot.appendChild(head); + + const body = doc.createElement("body"); + const hasOtherPerson = !!lyric.find((v) => v.isDuet); + + const metadataEl = doc.createElement("metadata"); + const mainPersonAgent = doc.createElement("ttm:agent"); + mainPersonAgent.setAttribute("type", "person"); + mainPersonAgent.setAttribute("xml:id", "v1"); + + metadataEl.appendChild(mainPersonAgent); + + if (hasOtherPerson) { + const otherPersonAgent = doc.createElement("ttm:agent"); + otherPersonAgent.setAttribute("type", "other"); + otherPersonAgent.setAttribute("xml:id", "v2"); + + metadataEl.appendChild(otherPersonAgent); + } + + for (const metadata of ttmlLyric.metadata) { + for (const value of metadata.value) { + const metaEl = doc.createElement("amll:meta"); + metaEl.setAttribute("key", metadata.key); + metaEl.setAttribute("value", value); + metadataEl.appendChild(metaEl); + } + } + + head.appendChild(metadataEl); + + let i = 0; + + const guessDuration = lyric[lyric.length - 1]?.endTime ?? 0; + body.setAttribute("dur", msToTimestamp(guessDuration)); + + for (const param of params) { + const paramDiv = doc.createElement("div"); + const beginTime = param[0]?.startTime ?? 0; + const endTime = param[param.length - 1]?.endTime ?? 0; + + paramDiv.setAttribute("begin", msToTimestamp(beginTime)); + paramDiv.setAttribute("end", msToTimestamp(endTime)); + + for (let lineIndex = 0; lineIndex < param.length; lineIndex++) { + const line = param[lineIndex]; + const lineP = doc.createElement("p"); + const beginTime = line.startTime ?? 0; + const endTime = line.endTime; + + lineP.setAttribute("begin", msToTimestamp(beginTime)); + lineP.setAttribute("end", msToTimestamp(endTime)); + + lineP.setAttribute("ttm:agent", line.isDuet ? "v2" : "v1"); + lineP.setAttribute("itunes:key", `L${++i}`); + + if (line.words.length > 1) { + let beginTime = Infinity; + let endTime = 0; + for (const word of line.words) { + if (word.word.trim().length === 0) { + lineP.appendChild(doc.createTextNode(word.word)); + } else { + const span = createWordElement(word); + lineP.appendChild(span); + beginTime = Math.min(beginTime, word.startTime); + endTime = Math.max(endTime, word.endTime); + } + } + lineP.setAttribute("begin", msToTimestamp(line.startTime)); + lineP.setAttribute("end", msToTimestamp(line.endTime)); + } else if (line.words.length === 1) { + const word = line.words[0]; + lineP.appendChild(doc.createTextNode(word.word)); + lineP.setAttribute("begin", msToTimestamp(word.startTime)); + lineP.setAttribute("end", msToTimestamp(word.endTime)); + } + + const nextLine = param[lineIndex + 1]; + if (nextLine?.isBG) { + lineIndex++; + const bgLine = nextLine; + const bgLineSpan = doc.createElement("span"); + bgLineSpan.setAttribute("ttm:role", "x-bg"); + + if (bgLine.words.length > 1) { + let beginTime = Infinity; + let endTime = 0; + for ( + let wordIndex = 0; + wordIndex < bgLine.words.length; + wordIndex++ + ) { + const word = bgLine.words[wordIndex]; + if (word.word.trim().length === 0) { + bgLineSpan.appendChild(doc.createTextNode(word.word)); + } else { + const span = createWordElement(word); + if (wordIndex === 0) { + span.prepend(doc.createTextNode("(")); + } else if (wordIndex === bgLine.words.length - 1) { + span.appendChild(doc.createTextNode(")")); + } + bgLineSpan.appendChild(span); + beginTime = Math.min(beginTime, word.startTime); + endTime = Math.max(endTime, word.endTime); + } + } + bgLineSpan.setAttribute("begin", msToTimestamp(beginTime)); + bgLineSpan.setAttribute("end", msToTimestamp(endTime)); + } else if (bgLine.words.length === 1) { + const word = bgLine.words[0]; + bgLineSpan.appendChild(doc.createTextNode(`(${word.word})`)); + bgLineSpan.setAttribute("begin", msToTimestamp(word.startTime)); + bgLineSpan.setAttribute("end", msToTimestamp(word.endTime)); + } + + if (bgLine.translatedLyric) { + const span = doc.createElement("span"); + span.setAttribute("ttm:role", "x-translation"); + span.setAttribute("xml:lang", "zh-CN"); + span.appendChild(doc.createTextNode(bgLine.translatedLyric)); + bgLineSpan.appendChild(span); + } + + if (bgLine.romanLyric) { + const span = doc.createElement("span"); + span.setAttribute("ttm:role", "x-roman"); + span.appendChild(doc.createTextNode(bgLine.romanLyric)); + bgLineSpan.appendChild(span); + } + + lineP.appendChild(bgLineSpan); + } + + if (line.translatedLyric) { + const span = doc.createElement("span"); + span.setAttribute("ttm:role", "x-translation"); + span.setAttribute("xml:lang", "zh-CN"); + span.appendChild(doc.createTextNode(line.translatedLyric)); + lineP.appendChild(span); + } + + if (line.romanLyric) { + const span = doc.createElement("span"); + span.setAttribute("ttm:role", "x-roman"); + span.appendChild(doc.createTextNode(line.romanLyric)); + lineP.appendChild(span); + } + + paramDiv.appendChild(lineP); + } + + body.appendChild(paramDiv); + } + + ttRoot.appendChild(body); + + if (pretty) { + const xsltDoc = new DOMParser().parseFromString( + [ + '', + ' ', + ' ', + ' ', + " ", + ' ', + ' ', + " ", + ' ', + "", + ].join("\n"), + "application/xml", + ); + + const xsltProcessor = new XSLTProcessor(); + xsltProcessor.importStylesheet(xsltDoc); + const resultDoc = xsltProcessor.transformToDocument(doc); + + return new XMLSerializer().serializeToString(resultDoc); + } + return new XMLSerializer().serializeToString(doc); +} diff --git a/src/routes/import/[id]/lyric/+page.svelte b/src/routes/import/[id]/lyric/+page.svelte index a52a1f8..192174c 100644 --- a/src/routes/import/[id]/lyric/+page.svelte +++ b/src/routes/import/[id]/lyric/+page.svelte @@ -16,7 +16,7 @@

歌词文件

- +
diff --git a/src/routes/play/[id]/+page.svelte b/src/routes/play/[id]/+page.svelte index b7558eb..514b51e 100644 --- a/src/routes/play/[id]/+page.svelte +++ b/src/routes/play/[id]/+page.svelte @@ -4,15 +4,19 @@ import Background from '$lib/components/background.svelte'; import Cover from '$lib/components/cover.svelte'; import InteractiveBox from '$lib/components/interactiveBox.svelte'; - import Lyrics from '$lib/components/lyrics.svelte'; import extractFileName from '$lib/extractFileName'; import localforage from 'localforage'; import { writable } from 'svelte/store'; - import lrcParser, { type LrcJsonData } from '$lib/lyrics/parser'; import userAdjustingProgress from '$lib/state/userAdjustingProgress'; import type { IAudioMetadata } from 'music-metadata-browser'; - import { onMount } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; import progressBarRaw from '$lib/state/progressBarRaw'; + import { parseTTML, type TTMLLyric } from '$lib/ttml'; + import type { LyricLine, LyricLineMouseEvent, LyricPlayer } from '@applemusic-like-lyrics/core'; + import NewLyrics from '$lib/components/newLyrics.svelte'; + import { LyricPlayer as CoreLyricPlayer } from '@applemusic-like-lyrics/core'; + import { parseLrc } from '@applemusic-like-lyrics/lyric'; + import { mapLyric } from '$lib/lyrics/mapLyric'; const audioId = $page.params.id; let audioPlayer: HTMLAudioElement | null = null; @@ -25,9 +29,9 @@ let paused: boolean = true; let launched = false; let prepared: string[] = []; - let originalLyrics: LrcJsonData; - let lyricsText: string[] = []; + let lyricLines: LyricLine[]; let hasLyrics: boolean; + let lyricPlayer: LyricPlayer = new CoreLyricPlayer(); const coverPath = writable(''); let mainInterval: ReturnType; @@ -44,26 +48,26 @@ ] }); ms.setActionHandler('play', function () { - if (audioPlayer===null) return; + if (audioPlayer === null) return; audioPlayer.play(); paused = false; }); ms.setActionHandler('pause', function () { - if (audioPlayer===null) return; + if (audioPlayer === null) return; audioPlayer.pause(); paused = true; }); ms.setActionHandler('seekbackward', function () { - if (audioPlayer===null) return; + if (audioPlayer === null) return; if (audioPlayer.currentTime > 4) { audioPlayer.currentTime = 0; } }); ms.setActionHandler('previoustrack', function () { - if (audioPlayer===null) return; + if (audioPlayer === null) return; if (audioPlayer.currentTime > 4) { audioPlayer.currentTime = 0; } @@ -85,7 +89,7 @@ prepared.push('cover'); }); localforage.getItem(`${audioId}-file`, function (err, file) { - if (audioPlayer===null) return; + if (audioPlayer === null) return; if (file) { const f = file as File; audioFile = f; @@ -99,10 +103,26 @@ if (file) { const f = file as File; f.text().then((lr) => { - originalLyrics = lrcParser(lr); - if (!originalLyrics.scripts) return; - for (const line of originalLyrics.scripts) { - lyricsText.push(line.text); + if (f.name.endsWith('.ttml')) { + lyricLines = parseTTML(lr).lyricLines; + hasLyrics = true; + } else if (f.name.endsWith('.lrc')) { + lyricLines = parseLrc(lr).map((line, i, lines) => ({ + words: [ + { + word: line.words[0]?.word ?? '', + startTime: line.words[0]?.startTime ?? 0, + endTime: lines[i + 1]?.words?.[0]?.startTime ?? Infinity + } + ], + startTime: line.words[0]?.startTime ?? 0, + endTime: lines[i + 1]?.words?.[0]?.startTime ?? Infinity, + translatedLyric: '', + romanLyric: '', + isBG: false, + isDuet: false + })); + hasLyrics = true; } }); } @@ -110,7 +130,7 @@ } function playAudio() { - if (audioPlayer===null) return; + if (audioPlayer === null) return; if (audioPlayer.duration) { duration = audioPlayer.duration; } @@ -140,6 +160,7 @@ if (audioPlayer) { audioPlayer.currentTime = duration * progress; currentProgress = duration * progress; + lyricPlayer.calcLayout(false, true); } } @@ -158,17 +179,19 @@ $: { clearInterval(mainInterval); mainInterval = setInterval(() => { - if (audioPlayer===null) return; - if ($userAdjustingProgress === false) - currentProgress = audioPlayer.currentTime; + if (audioPlayer === null) return; + if ($userAdjustingProgress === false) currentProgress = audioPlayer.currentTime; progressBarRaw.set(audioPlayer.currentTime); }, 50); } - onMount(() => { - if (audioPlayer===null) return; - audioPlayer.volume = localStorage.getItem('volume') ? Number(localStorage.getItem('volume')) : 1; - }); + onMount(() => { + if (audioPlayer === null) return; + audioPlayer.volume = localStorage.getItem('volume') ? Number(localStorage.getItem('volume')) : 1; + }); + onDestroy(() => { + if (audioPlayer === null) return; + }); $: { if (audioPlayer) { @@ -177,7 +200,10 @@ } } - $: hasLyrics = !!originalLyrics; + function onLyricLineClick(e: LyricLineMouseEvent) { + lyricPlayer.resetScroll(); + adjustProgress(lyricLines[e.lineIndex].startTime / 1000 / duration); + } readDB(); @@ -202,18 +228,24 @@ {hasLyrics} /> - +