From 935bb649583206a8d7a576a492fe012d2ae3807f Mon Sep 17 00:00:00 2001 From: alikia2x Date: Wed, 30 Oct 2024 21:09:46 +0800 Subject: [PATCH] ref: clean project, especially for tests --- bun.lockb | Bin 223530 -> 233255 bytes package.json | 5 +- packages/core/lyrics/LRCtoAMLL.ts | 20 -- packages/core/lyrics/ttml/index.ts | 1 - packages/core/lyrics/ttml/writer.ts | 254 ------------------ packages/core/package.json | 3 +- .../test/lyrics}/lrcParser.test.ts | 41 +-- .../test/lyrics}/resources/test-01.lrc | 0 .../test/lyrics}/resources/test-03.lrc | 0 .../test/server/index.test.ts} | 25 +- packages/core/test/utils/index.test.ts | 24 ++ packages/core/tsconfig.json | 4 +- packages/electron/src/electron.js | 1 - packages/electron/src/test/lrcParser.test.ts | 87 ------ .../electron/src/test/resources/test-02.lrc | 9 - packages/web/src/index.test.js | 47 ---- .../routes/api/database/song/[id]/+server.ts | 5 +- packages/web/src/test/resources/test-01.lrc | 56 ---- packages/web/src/test/resources/test-02.lrc | 9 - packages/web/src/test/resources/test-03.lrc | 77 ------ 20 files changed, 37 insertions(+), 631 deletions(-) delete mode 100644 packages/core/lyrics/LRCtoAMLL.ts delete mode 100644 packages/core/lyrics/ttml/writer.ts rename packages/{web/src/test => core/test/lyrics}/lrcParser.test.ts (50%) rename packages/{electron/src/test => core/test/lyrics}/resources/test-01.lrc (100%) rename packages/{electron/src/test => core/test/lyrics}/resources/test-03.lrc (100%) rename packages/{electron/src/index.test.js => core/test/server/index.test.ts} (55%) create mode 100644 packages/core/test/utils/index.test.ts delete mode 100644 packages/electron/src/test/lrcParser.test.ts delete mode 100644 packages/electron/src/test/resources/test-02.lrc delete mode 100644 packages/web/src/index.test.js delete mode 100644 packages/web/src/test/resources/test-01.lrc delete mode 100644 packages/web/src/test/resources/test-02.lrc delete mode 100644 packages/web/src/test/resources/test-03.lrc diff --git a/bun.lockb b/bun.lockb index 561a3490b2e570f342f33dd0ef4d514969291d04..e13b36807dbc7e2049ff1494d05281e28d38a292 100755 GIT binary patch delta 47005 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 XGk1MsHxA~>^&56nZ#7Hu>d=1!-|`_u delta 41123 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 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