-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(lyric): enhance local matching with NCM metadata and prioritize local sources #969
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kid141252010
wants to merge
17
commits into
imsyy:dev
Choose a base branch
from
kid141252010:feat/lyric-match-ncm
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
a0fc863
fix(lyric): sync clear store lyrics and strict pattern match
kid1412520 68830f8
add local ttml ncmid search
kid1412520 dd88ef8
feat: enhance local lyric matching with metadata and portable indexin…
kid1412520 97aa9bc
fix local lyric match
kid1412520 4f9037f
feat(lyric): use /search/match api for local song metadata matching
kid1412520 65b6b5d
feat(ui): display current netease matched scmId in local song match m…
kid1412520 a0c7c79
feat: parallelize TTML check to short-circuit local song matching
kid1412520 09205b1
feat: prioritize global TTML folder matching by musicName before onli…
kid1412520 d32d5ae
fix: prioritize local same-directory lyrics over global overrides
kid1412520 de091a1
feat: enhance local lyric matching with metadata and portable indexin…
kid1412520 6a3ed85
fix local lyric match
kid1412520 7cf4b50
feat(lyric): use /search/match api for local song metadata matching
kid1412520 13d1d94
feat(ui): display current netease matched scmId in local song match m…
kid1412520 b59c74a
feat: parallelize TTML check to short-circuit local song matching
kid1412520 934eee3
feat: prioritize global TTML folder matching by musicName before onli…
kid1412520 e268a9e
fix: prioritize local same-directory lyrics over global overrides
kid1412520 78fe5f8
Delete pr_manifesto.md.resolved
kid141252010 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,12 @@ | ||
| import { app, dialog, ipcMain, shell } from "electron"; | ||
| import { access, mkdir, unlink, writeFile, stat } from "node:fs/promises"; | ||
| import { access, mkdir, unlink, writeFile, stat, readFile } from "node:fs/promises"; | ||
| import { isAbsolute, join, normalize, relative, resolve } from "node:path"; | ||
| import { createHash } from "node:crypto"; | ||
| import { Worker } from "node:worker_threads"; | ||
| import { ipcLog } from "../logger"; | ||
| import { LocalMusicService } from "../services/LocalMusicService"; | ||
| import { DownloadService } from "../services/DownloadService"; | ||
| import { scanTtmlIdMapping, matchLocalTtmlByName } from "../services/TtmlScannerService"; | ||
| import { MusicMetadataService } from "../services/MusicMetadataService"; | ||
| import { useStore } from "../store"; | ||
| import { chunkArray } from "../utils/helper"; | ||
|
|
@@ -53,7 +55,7 @@ const runToolsJobInWorker = async (payload: Record<string, unknown>) => { | |
| worker.removeAllListeners("message"); | ||
| worker.removeAllListeners("error"); | ||
| worker.removeAllListeners("exit"); | ||
| worker.terminate().catch(() => {}); | ||
| worker.terminate().catch(() => { }); | ||
| }; | ||
|
|
||
| worker.once( | ||
|
|
@@ -100,7 +102,7 @@ const runToolsJobInWorker = async (payload: Record<string, unknown>) => { | |
| } catch (e) { | ||
| const message = e instanceof Error ? e.message : String(e); | ||
| ipcLog.warn(`[AudioAnalysis] 启动分析失败: ${message}`); | ||
| worker.terminate().catch(() => {}); | ||
| worker.terminate().catch(() => { }); | ||
| return null; | ||
| } | ||
| }; | ||
|
|
@@ -145,7 +147,7 @@ const handleLocalMusicSync = async ( | |
| (current, total) => { | ||
| event.sender.send("music-sync-progress", { current, total }); | ||
| }, | ||
| () => {}, | ||
| () => { }, | ||
| ); | ||
| // 处理音乐封面路径 | ||
| const finalTracks = processMusicList(allTracks, coverDir); | ||
|
|
@@ -257,8 +259,23 @@ const initFileIpc = (): void => { | |
| }); | ||
|
|
||
| // 读取本地歌词 | ||
| ipcMain.handle("read-local-lyric", async (_, lyricDirs: string[], id: number) => { | ||
| return musicMetadataService.readLocalLyric(lyricDirs, id); | ||
| ipcMain.handle("read-local-lyric", async (_, lyricDirs: string[], id: number, songName?: string, artists?: string[]) => { | ||
| return musicMetadataService.readLocalLyric(lyricDirs, id, songName, artists); | ||
| }); | ||
|
|
||
| // 手动扫描本地 TTML 歌词目录,建立 ncmMusicId 映射缓存 | ||
| ipcMain.handle("scan-ttml-lyrics", async (_, lyricDirs: string[]) => { | ||
| try { | ||
| const count = await scanTtmlIdMapping(lyricDirs); | ||
| return { success: true, count }; | ||
| } catch (error: any) { | ||
| return { success: false, message: error?.message || String(error) }; | ||
| } | ||
| }); | ||
|
|
||
| // 尝试通过歌名快速在本地缓存中寻找对应的 TTML 文件信息并提取其关联的 ncmId | ||
| ipcMain.handle("match-local-ttml-by-name", async (_, lyricDirs: string[], songName: string) => { | ||
| return matchLocalTtmlByName(lyricDirs, songName); | ||
| }); | ||
|
|
||
| // 删除文件 | ||
|
|
@@ -524,6 +541,71 @@ const initFileIpc = (): void => { | |
| return null; | ||
| } | ||
| }); | ||
|
|
||
| // 获取并确保匹配索引目录存在 | ||
| const getMatchIndexDir = async () => { | ||
| const dir = join(app.getPath("userData"), "local-data", "match-index"); | ||
| try { | ||
| await access(dir); | ||
| } catch { | ||
| await mkdir(dir, { recursive: true }); | ||
| } | ||
| return dir; | ||
| }; | ||
|
|
||
| // 读取便携式本地匹配索引数据库 | ||
| ipcMain.handle("get-local-match-index", async (_event, dirPath: string) => { | ||
| try { | ||
| const matchIndexDir = await getMatchIndexDir(); | ||
| const dirHash = createHash("md5").update(dirPath).digest("hex"); | ||
| const indexPath = join(matchIndexDir, `${dirHash}.json`); | ||
|
|
||
| const exists = await access(indexPath).then(() => true).catch(() => false); | ||
| if (!exists) return {}; | ||
|
|
||
| const content = await readFile(indexPath, "utf-8"); | ||
| return JSON.parse(content); | ||
| } catch (e) { | ||
| ipcLog.warn(`Failed to read local match index for ${dirPath}:`, String(e)); | ||
| return {}; | ||
| } | ||
| }); | ||
|
|
||
| // 保存便携式本地匹配索引数据库 | ||
| ipcMain.handle( | ||
| "save-local-match-index", | ||
| async (_event, dirPath: string, fileName: string, ncmId: number | null) => { | ||
| try { | ||
| const matchIndexDir = await getMatchIndexDir(); | ||
| const dirHash = createHash("md5").update(dirPath).digest("hex"); | ||
| const indexPath = join(matchIndexDir, `${dirHash}.json`); | ||
|
|
||
| let indexData: Record<string, number | null> = {}; | ||
|
|
||
| // 先尝试读取已有索引 | ||
| const exists = await access(indexPath).then(() => true).catch(() => false); | ||
| if (exists) { | ||
| const content = await readFile(indexPath, "utf-8"); | ||
| try { | ||
| indexData = JSON.parse(content); | ||
| } catch { | ||
| // 解析失败不阻断,直接覆盖 | ||
| } | ||
| } | ||
|
|
||
| // 更新记录 | ||
| indexData[fileName] = ncmId; | ||
|
|
||
| // 写入索引文件 | ||
| // 格式化输出方便用户必要时查看,也可最小化 | ||
| await writeFile(indexPath, JSON.stringify(indexData, null, 2), "utf-8"); | ||
| return { success: true }; | ||
| } catch (e) { | ||
| ipcLog.error(`Failed to save local match index for ${dirPath}:`, String(e)); | ||
| return { success: false, error: String(e) }; | ||
| } | ||
| } | ||
| ); | ||
|
Comment on lines
+575
to
+608
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
为了解决这个问题,建议引入一个基于文件路径的锁机制,确保对同一个索引文件的读-改-写操作是原子性的。可以使用一个 例如: const saveIndexLocks = new Map<string, Promise<void>>();
ipcMain.handle("save-local-match-index", async (...) => {
// ... 获取 indexPath
const indexPath = join(matchIndexDir, `${dirHash}.json`);
const previousLock = saveIndexLocks.get(indexPath) || Promise.resolve();
const newLock = previousLock.then(async () => {
// ... 原有的读、改、写逻辑
});
saveIndexLocks.set(indexPath, newLock);
try {
await newLock;
return { success: true };
} catch (e) {
// ... 错误处理
return { success: false, error: String(e) };
} finally {
// 清理锁,防止内存泄漏
if (saveIndexLocks.get(indexPath) === newLock) {
saveIndexLocks.delete(indexPath);
}
}
}); |
||
| }; | ||
|
|
||
| export default initFileIpc; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IPC handlers
read-local-lyric,scan-ttml-lyrics, andmatch-local-ttml-by-nameaccept arbitrary directory paths from the renderer and pass them to recursive file scanning functions (FastGlob). This allows a compromised renderer to trigger expensive disk-wide scans, leading to Denial of Service or information disclosure. Paths should be validated against an allow-list of user-defined music directories.