Files
lyroc/backend-deno/modules/lyrics.ts
2025-11-05 14:24:15 +08:00

329 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 歌词管理模块 (Deno 版本)
* 负责歌词的获取、解析和缓存
*/
import { MusicStatus } from "./apple_music.ts";
import { DatabaseSync } from 'node:sqlite'
export interface LyricsData {
current_lyric_time: number | null;
current_lyric: string | null;
next_lyric: string | null;
next_next_lyric: string | null;
track_name: string | null;
artist_name: string | null;
}
interface LyricsLine {
time: number;
text: string;
}
interface LyricsCache {
track_name: string | null;
artist: string | null;
lyrics_text: string | null;
source: string | null;
deleted: boolean;
}
// 全局歌词缓存
let lyricsCache: LyricsCache = {
track_name: null,
artist: null,
lyrics_text: null,
source: null,
deleted: false,
};
const DB_PATH = "./lyrics.db";
const db = new DatabaseSync(DB_PATH);
/**
* 初始化数据库
*/
export async function initDB(): Promise<void> {
try {
db.exec(`
CREATE TABLE IF NOT EXISTS lyrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
track_name TEXT NOT NULL,
artist TEXT NOT NULL,
lyrics_text TEXT NOT NULL,
source TEXT NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT 0
)
`);
console.log(`数据库初始化完成: ${DB_PATH}`);
} catch (error) {
console.error(`数据库初始化失败: ${error}`);
}
}
/**
* 清空歌词缓存
*/
export function clearLyricsCache(): void {
lyricsCache = {
track_name: null,
artist: null,
lyrics_text: null,
source: null,
deleted: false,
};
}
/**
* 从数据库加载歌词
*/
async function loadLyricsFromDB(
trackName: string,
artist: string
): Promise<string | null> {
try {
const content = db.prepare(`SELECT lyrics_text FROM lyrics WHERE track_name = ? AND artist = ?`).get(trackName, artist);
return content.lyrics_text;
} catch (error) {
return null;
}
}
/**
* 保存歌词到数据库
*/
export async function saveLyrics(
trackName: string,
artist: string,
lyricsText: string,
source: string
): Promise<void> {
try {
db.prepare(`DELETE FROM lyrics WHERE track_name = ? AND artist = ?`).run(trackName, artist);
db.prepare(`INSERT INTO lyrics (track_name, artist, lyrics_text, source) VALUES (?, ?, ?, ?)`).run(trackName, artist, lyricsText, source);
lyricsCache.track_name = trackName;
lyricsCache.artist = artist;
lyricsCache.lyrics_text = lyricsText;
lyricsCache.source = source;
console.log(`保存歌词成功: ${trackName} - ${artist}`);
} catch (error) {
console.error(`保存歌词失败: ${error}`);
}
}
/**
* 从服务器 API 搜索歌词
*/
export async function searchLyrics(
trackName: string,
artistName: string
): Promise<string | null> {
try {
const searchVariants = [
{ title: trackName, artist: artistName },
{ title: trackName, artist: "" },
{ title: trackName.replace(/[vV]er\.\s*\d*/g, ""), artist: artistName },
{ title: trackName.replace(/\([^)]*\)/g, "").trim(), artist: artistName },
];
for (const variant of searchVariants) {
try {
const encodedTitle = encodeURIComponent(variant.title);
const encodedArtist = encodeURIComponent(variant.artist);
let url = `http://123.57.93.143:28883/lyrics?title=${encodedTitle}`;
if (encodedArtist) {
url += `&artist=${encodedArtist}`;
}
const response = await fetch(url, {
headers: {
Authorization: "fzt_tom",
"User-Agent": "MacLyric/1.0",
},
});
if (response.ok) {
const lyricsText = await response.text();
if (lyricsText) {
return lyricsText;
}
}
} catch (error) {
console.warn(`搜索歌词变体失败: ${error}`);
continue;
}
}
return null;
} catch (error) {
console.error(`搜索歌词时出错: ${error}`);
return null;
}
}
/**
* 解析 LRC 格式歌词
*/
function parseLrcLyrics(lyricsText: string): LyricsLine[] {
const lines = lyricsText.split("\n");
const lyricsLines: LyricsLine[] = [];
for (const line of lines) {
const match = line.match(/^\[(\d{2}):(\d{2})\.(\d{2,3})\](.*)$/);
if (match) {
const minutes = parseInt(match[1]);
const seconds = parseInt(match[2]);
const milliseconds = parseInt(match[3].padEnd(3, "0"));
const text = match[4].trim();
const time = minutes * 60 + seconds + milliseconds / 1000;
lyricsLines.push({ time, text });
}
}
return lyricsLines.sort((a, b) => a.time - b.time);
}
/**
* 格式化歌词数据
*/
function formatLrcLyrics(
lyricsText: string,
currentPosition?: number
): LyricsData {
const lyricsLines = parseLrcLyrics(lyricsText);
if (lyricsLines.length === 0) {
return {
current_lyric_time: null,
current_lyric: null,
next_lyric: null,
next_next_lyric: null,
track_name: null,
artist_name: null,
};
}
if (currentPosition === undefined) {
return {
current_lyric_time: lyricsLines[0].time,
current_lyric: lyricsLines[0].text,
next_lyric: lyricsLines.length > 1 ? lyricsLines[1].text : null,
next_next_lyric: lyricsLines.length > 2 ? lyricsLines[2].text : null,
track_name: null,
artist_name: null,
};
}
let currentIndex = -1;
for (let i = 0; i < lyricsLines.length; i++) {
if (lyricsLines[i].time <= currentPosition) {
currentIndex = i;
} else {
break;
}
}
const currentLyric = currentIndex >= 0 ? lyricsLines[currentIndex] : null;
const nextLyric =
currentIndex + 1 < lyricsLines.length
? lyricsLines[currentIndex + 1]
: null;
const nextNextLyric =
currentIndex + 2 < lyricsLines.length
? lyricsLines[currentIndex + 2]
: null;
return {
current_lyric_time: currentLyric?.time || null,
current_lyric: currentLyric?.text || null,
next_lyric: nextLyric?.text || null,
next_next_lyric: nextNextLyric?.text || null,
track_name: null,
artist_name: null,
};
}
/**
* 删除歌词
*/
export async function deleteLyrics(trackName: string, artist: string): Promise<void> {
try {
db.prepare(`DELETE FROM lyrics WHERE track_name = ? AND artist = ?`).run(trackName, artist);
console.log(`删除歌词成功: ${trackName} - ${artist}`);
} catch (error) {
console.error(`删除歌词失败: ${error}`);
}
}
/**
* 获取当前歌词数据
*/
export async function getLyricsData(status: MusicStatus): Promise<LyricsData> {
// 如果没有播放,清空缓存并返回空
if (status.status !== "playing" && status.status !== "paused") {
clearLyricsCache();
return {
current_lyric_time: null,
current_lyric: null,
next_lyric: null,
next_next_lyric: null,
track_name: null,
artist_name: null,
};
}
const trackName = status.track_name;
const artist = status.artist;
const position = status.position || 0;
// 检查是否切换了歌曲
if (lyricsCache.track_name !== trackName || lyricsCache.artist !== artist) {
console.log(`歌曲切换,重新获取歌词: ${trackName} - ${artist}`);
// 清空缓存
lyricsCache.track_name = trackName;
lyricsCache.artist = artist;
lyricsCache.lyrics_text = null;
lyricsCache.deleted = false;
// 1. 先尝试从数据库获取
const lyricsText = await loadLyricsFromDB(trackName || "", artist || "");
if (lyricsText) {
lyricsCache.lyrics_text = lyricsText;
lyricsCache.source = "db";
}
// 2. 如果本地没有且不是被删除的歌词尝试从网易云音乐API获取歌词
else if (!lyricsCache.lyrics_text && !lyricsCache.deleted) {
const lyricsText = await searchLyrics(trackName || "", artist || "");
if (lyricsText) {
// 保存到数据库
await saveLyrics(trackName || "", artist || "", lyricsText, "server");
lyricsCache.lyrics_text = lyricsText;
lyricsCache.source = "server";
}
}
}
// 如果有缓存的歌词,直接解析
if (lyricsCache.lyrics_text) {
const lyricsData = formatLrcLyrics(lyricsCache.lyrics_text, position);
return {
...lyricsData,
track_name: trackName || null,
artist_name: artist || null,
};
}
// 没有歌词
return {
current_lyric_time: null,
current_lyric: null,
next_lyric: null,
next_next_lyric: null,
track_name: trackName || null,
artist_name: artist || null,
};
}