diff --git a/bun.lockb b/bun.lockb index 561a349..e13b368 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 2508897..bdc17df 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,10 @@ "svelte-check": "^3.7.1", "typescript": "^5.4.5", "vite-plugin-wasm": "^3.3.0", - "vitest": "^1.6.0", + "vitest": "^2.1.4", "@types/bun": "^1.1.6", "concurrently": "^9.0.1", "cross-env": "^7.0.3" }, - "dependencies": { - } + "dependencies": {} } diff --git a/packages/core/lyrics/LRCtoAMLL.ts b/packages/core/lyrics/LRCtoAMLL.ts deleted file mode 100644 index 58b9d56..0000000 --- a/packages/core/lyrics/LRCtoAMLL.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { LyricLine } from '@applemusic-like-lyrics/core'; -import type { ScriptItem } from './LRCparser'; - -export default function mapLRCtoAMLL(line: ScriptItem, i: number, lines: ScriptItem[]): LyricLine { - return { - words: [ - { - word: line.text, - startTime: line.start * 1000, - endTime: line.end * 1000 - } - ], - startTime: line.start * 1000, - endTime: line.end * 1000, - translatedLyric: line.translation ?? "", - romanLyric: '', - isBG: false, - isDuet: false - }; -} diff --git a/packages/core/lyrics/ttml/index.ts b/packages/core/lyrics/ttml/index.ts index 58bddce..618c85d 100644 --- a/packages/core/lyrics/ttml/index.ts +++ b/packages/core/lyrics/ttml/index.ts @@ -2,7 +2,6 @@ import type { LrcJsonData } from '../type'; import { parseTTML as ttmlParser } from './parser'; import type { LyricLine } from './ttml-types'; -export * from './writer'; export type * from './ttml-types'; export function parseTTML(text: string) { diff --git a/packages/core/lyrics/ttml/writer.ts b/packages/core/lyrics/ttml/writer.ts deleted file mode 100644 index aa8aa21..0000000 --- a/packages/core/lyrics/ttml/writer.ts +++ /dev/null @@ -1,254 +0,0 @@ -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("code"); - - 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/packages/core/package.json b/packages/core/package.json index 1f87840..8981348 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,8 +23,7 @@ "tailwindcss": "^3.4.3", "typescript": "^5.4.5", "vite": "5.4.6", - "vite-plugin-wasm": "^3.3.0", - "vitest": "^1.6.0" + "vite-plugin-wasm": "^3.3.0" }, "dependencies": { "@applemusic-like-lyrics/core": "^0.1.3", diff --git a/packages/web/src/test/lrcParser.test.ts b/packages/core/test/lyrics/lrcParser.test.ts similarity index 50% rename from packages/web/src/test/lrcParser.test.ts rename to packages/core/test/lyrics/lrcParser.test.ts index fcc8fed..0c59f95 100644 --- a/packages/web/src/test/lrcParser.test.ts +++ b/packages/core/test/lyrics/lrcParser.test.ts @@ -1,13 +1,11 @@ import { describe, expect, it } from 'vitest'; import fs from 'fs'; -import { parseLRC } from '../../packages/core/lyrics/lrc/parser'; +import { parseLRC } from '@core/lyrics/lrc/parser'; describe('LRC parser test', () => { - const test01Buffer = fs.readFileSync('./src/test/resources/test-01.lrc'); + const test01Buffer = fs.readFileSync('./packages/core/test/lyrics/resources/test-01.lrc'); const test01Text = test01Buffer.toString('utf-8'); - const test02Buffer = fs.readFileSync('./src/test/resources/test-02.lrc'); - const test02Text = test02Buffer.toString('utf-8'); - const test03Buffer = fs.readFileSync('./src/test/resources/test-03.lrc'); + const test03Buffer = fs.readFileSync('./packages/core/test/lyrics/resources/test-03.lrc'); const test03Text = test03Buffer.toString('utf-8'); const lf_alternatives = ['\n', '\r\n', '\r']; @@ -26,17 +24,6 @@ describe('LRC parser test', () => { expect(result.scripts!![1].start).toBe(49000 + 588); } }) - it('Parses test-02.lrc', () => { - const result = parseLRC(test02Text, { wordDiv: ' ', strict: true }); - - expect(result.ti).toBe("Somebody to Love"); - expect(result.ar).toBe("Jefferson Airplane"); - expect(result.scripts!!.length).toBe(3); - expect(result.scripts!![0].text).toBe("When the truth is found to be lies"); - expect(result.scripts!![0].start).toBe(0); - expect(result.scripts!![0].words!![1].beginIndex).toBe("[00:00.00] <00:00.04> When <00:00.16> the".indexOf("the")); - expect(result.scripts!![0].words!![1].start).toBe(160); - }); it('Parses test-03.lrc', () => { const result = parseLRC(test03Text, { wordDiv: ' ', strict: true }); expect(result.scripts!![5].text).toBe("བྲོ་ར་འདི་ལ་བྲོ་ཅིག་འཁྲབ།"); @@ -46,18 +33,6 @@ describe('LRC parser test', () => { expect(result.scripts!![11].singer).toBeUndefined(); expect(result.scripts!![11].translation).toBe("我们在此相聚"); }); - it('Rejects some invalid LRCs', () => { - const cases = [ - "[<00:00.00>] <00:00.04> When <00:00.16> the", - "[00:00.00] <00:00.04> <00:00.16> the", - "[00:00.00> <00:00.04> When <00:00.16> the", - "<00:00.00> <00:00.04> When <00:00.16> the", - "<1:00:00.00> <00:00.04> When <00:00.16> the", - ] - for (const c of cases) { - expect(() => parseLRC(c, { strict: true })).toThrow(); - } - }) it('Accepts some weird but parsable LRCs', () => { const cases = [ "[ti: []]", @@ -74,14 +49,4 @@ describe('LRC parser test', () => { expect(() => parseLRC(c, { strict: false })).not.toThrow(); } }) - it('Parses a legacy LRC', () => { - const result = parseLRC(test02Text, { wordDiv: ' ', strict: true, legacy: true }); - - expect(result.ti).toBe("Somebody to Love"); - expect(result.ar).toBe("Jefferson Airplane"); - expect(result.scripts!!.length).toBe(3); - expect(result.scripts!![1].text).toBe("<00:07.67> And <00:07.94> all <00:08.36> the <00:08.63> joy <00:10.28> within <00:10.53> you <00:13.09> dies"); - expect(result.scripts!![1].start).toBe(6000 + 470); - result.scripts!!.forEach((s) => expect(s.words).not.toBeDefined()); - }); }); \ No newline at end of file diff --git a/packages/electron/src/test/resources/test-01.lrc b/packages/core/test/lyrics/resources/test-01.lrc similarity index 100% rename from packages/electron/src/test/resources/test-01.lrc rename to packages/core/test/lyrics/resources/test-01.lrc diff --git a/packages/electron/src/test/resources/test-03.lrc b/packages/core/test/lyrics/resources/test-03.lrc similarity index 100% rename from packages/electron/src/test/resources/test-03.lrc rename to packages/core/test/lyrics/resources/test-03.lrc diff --git a/packages/electron/src/index.test.js b/packages/core/test/server/index.test.ts similarity index 55% rename from packages/electron/src/index.test.js rename to packages/core/test/server/index.test.ts index 371f9e7..5b8e034 100644 --- a/packages/electron/src/index.test.js +++ b/packages/core/test/server/index.test.ts @@ -1,28 +1,5 @@ import { describe, it, expect } from 'vitest'; -import formatDuration from '$lib/utils/formatDuration.js'; -import { safePath } from '$lib/server/safePath'; - -describe('formatDuration test', () => { - it('converts 120 seconds to "2:00"', () => { - expect(formatDuration(120)).toBe('2:00'); - }); - - it('converts 119.935429 seconds to "1:59"', () => { - expect(formatDuration(119.935429)).toBe('1:59'); - }); - - it('converts 185 seconds to "3:05"', () => { - expect(formatDuration(185)).toBe('3:05'); - }); - - it('converts 601 seconds to "10:01"', () => { - expect(formatDuration(601)).toBe('10:01'); - }); - - it('converts 3601 seconds to "1:00:01"', () => { - expect(formatDuration(3601)).toBe('1:00:01'); - }); -}); +import { safePath } from '@core/server/safePath.js'; describe('safePath test', () => { const base = "data/subdir"; diff --git a/packages/core/test/utils/index.test.ts b/packages/core/test/utils/index.test.ts new file mode 100644 index 0000000..35919b3 --- /dev/null +++ b/packages/core/test/utils/index.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest'; +import formatDuration from '@core/utils/formatDuration'; + +describe('formatDuration test', () => { + it('converts 120 seconds to "2:00"', () => { + expect(formatDuration(120)).toBe('2:00'); + }); + + it('converts 119.935429 seconds to "1:59"', () => { + expect(formatDuration(119.935429)).toBe('1:59'); + }); + + it('converts 185 seconds to "3:05"', () => { + expect(formatDuration(185)).toBe('3:05'); + }); + + it('converts 601 seconds to "10:01"', () => { + expect(formatDuration(601)).toBe('10:01'); + }); + + it('converts 3601 seconds to "1:00:01"', () => { + expect(formatDuration(3601)).toBe('1:00:01'); + }); +}); \ No newline at end of file diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index f3e740d..70a0ecc 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,6 +1,7 @@ { "extends": ["../../tsconfig.base.json"], "compilerOptions": { + "target": "ES2019", "baseUrl": ".", "paths": { "@core/*": ["./*"] @@ -13,6 +14,7 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "types": ["bun"] + "types": ["bun"], + "moduleResolution": "bundler" } } diff --git a/packages/electron/src/electron.js b/packages/electron/src/electron.js index 3996498..146bac5 100644 --- a/packages/electron/src/electron.js +++ b/packages/electron/src/electron.js @@ -2,7 +2,6 @@ import windowStateManager from 'electron-window-state'; import { app, BrowserWindow, ipcMain } from 'electron'; import contextMenu from 'electron-context-menu'; import serve from 'electron-serve'; -import { join } from 'path'; try { require('electron-reloader')(module); diff --git a/packages/electron/src/test/lrcParser.test.ts b/packages/electron/src/test/lrcParser.test.ts deleted file mode 100644 index fcc8fed..0000000 --- a/packages/electron/src/test/lrcParser.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import fs from 'fs'; -import { parseLRC } from '../../packages/core/lyrics/lrc/parser'; - -describe('LRC parser test', () => { - const test01Buffer = fs.readFileSync('./src/test/resources/test-01.lrc'); - const test01Text = test01Buffer.toString('utf-8'); - const test02Buffer = fs.readFileSync('./src/test/resources/test-02.lrc'); - const test02Text = test02Buffer.toString('utf-8'); - const test03Buffer = fs.readFileSync('./src/test/resources/test-03.lrc'); - const test03Text = test03Buffer.toString('utf-8'); - - const lf_alternatives = ['\n', '\r\n', '\r']; - - it('Parses test-01.lrc', () => { - for (const lf of lf_alternatives) { - const text = test01Text.replaceAll('\n', lf); - - const result = parseLRC(text, { wordDiv: '', strict: true }); - - expect(result.ar).toBe("洛天依"); - expect(result.ti).toBe("中华少女·终"); - expect(result.al).toBe("中华少女"); - expect(result["tool"]).toBe("歌词滚动姬 https://lrc-maker.github.io"); - expect(result.scripts!![1].text).toBe("因果与恩怨牵杂等谁来诊断"); - expect(result.scripts!![1].start).toBe(49000 + 588); - } - }) - it('Parses test-02.lrc', () => { - const result = parseLRC(test02Text, { wordDiv: ' ', strict: true }); - - expect(result.ti).toBe("Somebody to Love"); - expect(result.ar).toBe("Jefferson Airplane"); - expect(result.scripts!!.length).toBe(3); - expect(result.scripts!![0].text).toBe("When the truth is found to be lies"); - expect(result.scripts!![0].start).toBe(0); - expect(result.scripts!![0].words!![1].beginIndex).toBe("[00:00.00] <00:00.04> When <00:00.16> the".indexOf("the")); - expect(result.scripts!![0].words!![1].start).toBe(160); - }); - it('Parses test-03.lrc', () => { - const result = parseLRC(test03Text, { wordDiv: ' ', strict: true }); - expect(result.scripts!![5].text).toBe("བྲོ་ར་འདི་ལ་བྲོ་ཅིག་འཁྲབ།"); - expect(result.scripts!![5].translation).toBe("在舞池里舞一舞"); - expect(result.scripts!![6].text).toBe("祝祷转过千年 五色经幡飘飞"); - expect(result.scripts!![6].singer).toBe("a"); - expect(result.scripts!![11].singer).toBeUndefined(); - expect(result.scripts!![11].translation).toBe("我们在此相聚"); - }); - it('Rejects some invalid LRCs', () => { - const cases = [ - "[<00:00.00>] <00:00.04> When <00:00.16> the", - "[00:00.00] <00:00.04> <00:00.16> the", - "[00:00.00> <00:00.04> When <00:00.16> the", - "<00:00.00> <00:00.04> When <00:00.16> the", - "<1:00:00.00> <00:00.04> When <00:00.16> the", - ] - for (const c of cases) { - expect(() => parseLRC(c, { strict: true })).toThrow(); - } - }) - it('Accepts some weird but parsable LRCs', () => { - const cases = [ - "[ti: []]", - "[ar: [<]]", - "[ar: ]", - "[ar: a b c]", - "[00:00.00] <00:00.04> When the <00:00.16> the", - "[00:00.00] [00:00.04] When [00:00.16] the", - "[00:00.0000000] <00:00.04> When <00:00.16> the", - "[00:00.00] <00:00.04> [When] <00:00.16> the", - ]; - - for (const c of cases) { - expect(() => parseLRC(c, { strict: false })).not.toThrow(); - } - }) - it('Parses a legacy LRC', () => { - const result = parseLRC(test02Text, { wordDiv: ' ', strict: true, legacy: true }); - - expect(result.ti).toBe("Somebody to Love"); - expect(result.ar).toBe("Jefferson Airplane"); - expect(result.scripts!!.length).toBe(3); - expect(result.scripts!![1].text).toBe("<00:07.67> And <00:07.94> all <00:08.36> the <00:08.63> joy <00:10.28> within <00:10.53> you <00:13.09> dies"); - expect(result.scripts!![1].start).toBe(6000 + 470); - result.scripts!!.forEach((s) => expect(s.words).not.toBeDefined()); - }); -}); \ No newline at end of file diff --git a/packages/electron/src/test/resources/test-02.lrc b/packages/electron/src/test/resources/test-02.lrc deleted file mode 100644 index 5f67268..0000000 --- a/packages/electron/src/test/resources/test-02.lrc +++ /dev/null @@ -1,9 +0,0 @@ -[ti: Somebody to Love] -[ar: Jefferson Airplane] -[al: Surrealistic Pillow] -[lr: Lyricists of that song] -[length: 2:58] - -[00:00.00] <00:00.04> When <00:00.16> the <00:00.82> truth <00:01.29> is <00:01.63> found <00:03.09> to <00:03.37> be <00:05.92> lies -[00:06.47] <00:07.67> And <00:07.94> all <00:08.36> the <00:08.63> joy <00:10.28> within <00:10.53> you <00:13.09> dies -[00:13.34] <00:14.32> Don't <00:14.73> you <00:15.14> want <00:15.57> somebody <00:16.09> to <00:16.46> love \ No newline at end of file diff --git a/packages/web/src/index.test.js b/packages/web/src/index.test.js deleted file mode 100644 index 371f9e7..0000000 --- a/packages/web/src/index.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import formatDuration from '$lib/utils/formatDuration.js'; -import { safePath } from '$lib/server/safePath'; - -describe('formatDuration test', () => { - it('converts 120 seconds to "2:00"', () => { - expect(formatDuration(120)).toBe('2:00'); - }); - - it('converts 119.935429 seconds to "1:59"', () => { - expect(formatDuration(119.935429)).toBe('1:59'); - }); - - it('converts 185 seconds to "3:05"', () => { - expect(formatDuration(185)).toBe('3:05'); - }); - - it('converts 601 seconds to "10:01"', () => { - expect(formatDuration(601)).toBe('10:01'); - }); - - it('converts 3601 seconds to "1:00:01"', () => { - expect(formatDuration(3601)).toBe('1:00:01'); - }); -}); - -describe('safePath test', () => { - const base = "data/subdir"; - it('rejects empty string', () => { - expect(safePath('', { base })).toBe(null); - }); - it('accepts a regular path', () => { - expect(safePath('subsubdir/file.txt', { base })).toBe('data/subdir/subsubdir/file.txt'); - }); - it('rejects path with ..', () => { - expect(safePath('../file.txt', { base })).toBe(null); - }); - it('accepts path with .', () => { - expect(safePath('./file.txt', { base })).toBe('data/subdir/file.txt'); - }); - it('accepts path traversal within base', () => { - expect(safePath('subsubdir/../file.txt', { base })).toBe('data/subdir/file.txt'); - }); - it('rejects path with subdir if noSubDir is true', () => { - expect(safePath('subsubdir/file.txt', { base, noSubDir: true })).toBe(null); - }); -}); \ No newline at end of file diff --git a/packages/web/src/routes/api/database/song/[id]/+server.ts b/packages/web/src/routes/api/database/song/[id]/+server.ts index 1545dc0..3c8a69d 100644 --- a/packages/web/src/routes/api/database/song/[id]/+server.ts +++ b/packages/web/src/routes/api/database/song/[id]/+server.ts @@ -1,8 +1,9 @@ -import { safePath } from '$lib/server/safePath'; -import { getCurrentFormattedDateTime } from '$lib/utils/songUpdateTime'; +import { safePath } from '@core/server/safePath'; +import { getCurrentFormattedDateTime } from '@core/utils/songUpdateTime'; import { json, error } from '@sveltejs/kit'; import fs from 'fs'; import type { RequestHandler } from './$types'; +import type { MusicMetadata } from '@core/server/database/musicInfo'; export const GET: RequestHandler = async ({ params }) => { const filePath = safePath(`${params.id}.json`, { base: './data/song' }); diff --git a/packages/web/src/test/resources/test-01.lrc b/packages/web/src/test/resources/test-01.lrc deleted file mode 100644 index 4eb0a24..0000000 --- a/packages/web/src/test/resources/test-01.lrc +++ /dev/null @@ -1,56 +0,0 @@ -[ti: 中华少女·终] -[ar: 洛天依] -[al: 中华少女] -[tool: 歌词滚动姬 https://lrc-maker.github.io] -[00:46.706] 我想要仗剑天涯却陷入纷乱 -[00:49.588] 因果与恩怨牵杂等谁来诊断 -[00:52.284] 暗箭在身后是否该回身看 -[00:55.073] 人心有了鬼心房便要过鬼门关 -[00:57.875] 早已茫然染了谁的血的这抹长衫 -[01:00.702] 独木桥上独目瞧的人一夫当关 -[01:03.581] 复仇或是诅咒缠在身上的宿命 -[01:06.591] 棋子在棋盘被固定移动不停转 -[01:09.241] 仇恨与仇恨周而复始往返 -[01:12.586] 酒楼深胭脂一点分隔光暗 -[01:15.205] 数求问天涯海角血债偿还 -[01:18.015] 终是神念迷茫只做旁观 -[01:21.087] 是非恩怨三生纠葛轮转 -[01:23.709] 回望从前苦笑将杯酒斟满 -[01:26.573] 那时明月今日仍旧皎洁 -[01:29.115] 只叹换拨人看 -[01:31.024] 你可是这样的少年 -[01:33.971] 梦想着穿越回从前 -[01:36.554] 弦月下着青衫抚长剑 -[01:42.341] 风起时以血绘长卷 -[01:45.276] 三寸剑只手撼江山 -[01:47.838] 拂衣去逍遥天地间 -[01:52.991] -[02:16.707] 黄藓绿斑苔痕将岁月扒谱 -[02:20.077] 望眼欲穿你何时寄来家书 -[02:22.788] 踱步间院落飞絮聚散化作愁字 -[02:25.601] 当泪水成河能否凫上一位游子 -[02:28.050] 当庭间嫣红轻轻闻着雨声 -[02:30.841] 电闪雷鸣院中青翠摇曳倚风 -[02:33.362] 青丝落指尖模糊记忆更为清晰 -[02:36.334] 那一朵英姿遮挡于一抹旌旗飘 -[02:39.511] 厮杀与厮杀周而复始招摇 -[02:42.576] 血渍滑落于枪尖映照宵小 -[02:45.726] 城池下红莲飞溅绽放照耀 -[02:48.509] 碧落黄泉再无叨扰 -[02:51.338] 北风呼啸三生等待轮转 -[02:53.660] 山崖古道思绪被光阴晕染 -[02:56.895] 那时明月今日仍旧皎洁 -[02:59.293] 只叹孤身人看 -[03:01.335] 你可是这样的少年 -[03:04.377] 梦想着穿越回从前 -[03:06.924] 北风里铁衣冷槊光寒 -[03:12.607] 一朝去大小三百战 -[03:15.623] 岁月欺万里定江山 -[03:18.126] 再与她同游天地间 -[03:24.356] 说书人或许会留恋 -[03:27.057] 但故事毕竟有终点 -[03:29.590] 最好的惊堂木是时间 -[03:35.157] 就让我合上这书卷 -[03:38.242] 愿那些梦中的玩伴 -[03:40.857] 梦醒后仍然是少年 -[03:46.139] \ No newline at end of file diff --git a/packages/web/src/test/resources/test-02.lrc b/packages/web/src/test/resources/test-02.lrc deleted file mode 100644 index 5f67268..0000000 --- a/packages/web/src/test/resources/test-02.lrc +++ /dev/null @@ -1,9 +0,0 @@ -[ti: Somebody to Love] -[ar: Jefferson Airplane] -[al: Surrealistic Pillow] -[lr: Lyricists of that song] -[length: 2:58] - -[00:00.00] <00:00.04> When <00:00.16> the <00:00.82> truth <00:01.29> is <00:01.63> found <00:03.09> to <00:03.37> be <00:05.92> lies -[00:06.47] <00:07.67> And <00:07.94> all <00:08.36> the <00:08.63> joy <00:10.28> within <00:10.53> you <00:13.09> dies -[00:13.34] <00:14.32> Don't <00:14.73> you <00:15.14> want <00:15.57> somebody <00:16.09> to <00:16.46> love \ No newline at end of file diff --git a/packages/web/src/test/resources/test-03.lrc b/packages/web/src/test/resources/test-03.lrc deleted file mode 100644 index 20a669c..0000000 --- a/packages/web/src/test/resources/test-03.lrc +++ /dev/null @@ -1,77 +0,0 @@ -[ti: 雪山之眼] -[ar: 洛天依 & 旦增益西] -[al: 游四方] -[tool: 歌词滚动姬 https://lrc-maker.github.io] -[length: 04:17.400] -[00:34.280] 浸透了经卷 记忆的呼喊 -[00:37.800] 雪珠滚落山巅 栽下一个春天 -[00:47.390] 松石敲响玲珑清脆的银花 -[00:51.600] 穿过玛瑙的红霞 -[00:54.430] 在她眼中结编 亘久诗篇 -[01:05.440] a: བྲོ་ར་འདི་ལ་བྲོ་ཅིག་འཁྲབ། | 在舞池里舞一舞 -[01:08.780] a: 祝祷转过千年 五色经幡飘飞 -[01:12.040] 奏起悠扬巴叶 任岁月拨弦 -[01:19.130] གཞས་ར་འདི་ལ་གཞས་གཅིག་བཏང་། 我在歌坛献首歌 -[01:22.330] 宫殿 塔尖 彩绘 日月 同辉 -[01:25.810] 那层厚重壁垒化身 蝉翼一片 -[01:29.110] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། | 我们在此相聚 -[01:30.790] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[01:32.510] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[01:34.120] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[01:35.920] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[01:37.630] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[01:39.350] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[01:41.050] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[01:42.740] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[01:44.630] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[01:46.280] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[01:48.010] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[01:49.600] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[01:51.380] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[01:53.070] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[01:54.820] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[01:58.580] སྔོན་དང་པོ་གྲུབ་ཐོབ་ཐང་སྟོང་རྒྱལ་པོས་མཛད་པའི་མཛད་ཚུལ་དུ། དང་པོ་རྔོན་པའི་ས་སྦྱངས་ས་འདུལ། གཉིས་པ་རྒྱ་ལུའི་བྱིན་འབེབས། གསུམ་པ་ལྷ་མོའི་གླུ་གར་སོགས་རིན་ཆེན་གསུང་མགུར་གཞུང་བཟང་མང་པོ་འདུག་སྟེ། དེ་ཡང་མ་ཉུང་གི་ཚིག་ལ་དུམ་མཚམས་གཅིག་ཞུས་པ་བྱུང་བ་ཡིན་པ་ལགས་སོ། 如祖师唐东杰布所著,一有温巴净地,二有甲鲁祈福,三有仙女歌舞,所著繁多,在此简略献之。 -[02:24.240] 浸透了经卷 记忆的呼喊 -[02:27.450] 雪珠滚落山巅 栽下一个春天 -[02:37.090] 松石敲响玲珑清脆的银花 -[02:41.280] 穿过玛瑙的红霞 -[02:44.010] 在她眼中结编 亘久诗篇 -[02:55.250] བྲོ་ར་འདི་ལ་བྲོ་ཅིག་འཁྲབ། 在舞池里舞一舞 -[02:58.410] 祝祷转过千年 五色经幡飘飞 -[03:01.750] 奏起悠扬巴叶 任岁月拨弦 -[03:08.840] གཞས་ར་འདི་ལ་གཞས་གཅིག་བཏང་། 我在歌坛献首歌 -[03:12.050] 宫殿 塔尖 彩绘 日月 同辉 -[03:15.400] 那层厚重壁垒化身 蝉翼一片 -[03:18.850] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[03:20.480] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[03:22.210] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[03:23.910] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[03:25.662] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[03:27.391] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[03:29.096] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[03:30.789] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[03:32.496] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[03:34.175] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[03:35.876] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[03:37.606] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[03:39.290] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[03:41.030] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[03:42.679] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[03:44.455] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[03:46.176] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[03:47.910] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[03:49.625] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[03:51.293] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[03:53.005] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[03:54.742] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[03:56.479] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[03:58.159] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[03:59.859] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[04:01.548] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[04:03.312] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[04:05.026] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[04:06.721] ང་ཚོ་འདི་ལ་འཛོམས་འཛོམས། 我们在此相聚 -[04:08.479] གཏན་དུ་འཛོམས་རྒྱུ་བྱུང་ན། 希望可以常聚 -[04:10.175] གཏན་དུ་འཛོམས་པའི་མི་ལ། 在此相聚的人们 -[04:11.923] སྙུན་གཞི་གོད་ཆགས་མ་གཏོང༌། 祝愿平安富足 -[04:17.400] \ No newline at end of file