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