Repository navigation
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
本 PR 为 SPlayer-Next 增加「本地音乐元信息(标签)编辑」能力:渲染层提供编辑对话框与在线候选匹配,主进程新增 IPC 读写标签与封面选择,原生 audio-engine 侧通过 lofty 实现安全读写(临时文件 + 原子覆盖)并补齐写后重新探测与封面缩略图逻辑。
Changes:
- 新增标签编辑弹窗(含在线匹配候选排序、封面替换、内嵌歌词编辑)与歌曲右键入口
- 新增标签读写 IPC 与播放器侧保存/恢复当前播放进度的写入流程,并把写后结果回灌到库/队列缓存
audio-engine新增基于lofty的标签读写实现、NAPI 导出与封面缩略图能力(含测试与依赖更新)
Reviewed changes
Copilot reviewed 23 out of 26 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/tagMatch.ts | 新增在线元数据候选打分排序逻辑 |
| src/stores/queue.ts | 新增按 id 回灌队列 Track 的更新函数 |
| src/stores/library.ts | 新增按 id 回灌曲库 Track 并写缓存的方法 |
| src/i18n/locales/zh-CN.json | 新增“编辑元数据”与标签编辑相关文案(中文) |
| src/i18n/locales/en-US.json | 新增“Edit Metadata”与标签编辑相关文案(英文) |
| src/core/player/index.ts | 新增写标签流程(触达当前曲则 stop→写入→重载/seek/恢复播放)并同步库/队列 |
| src/composables/useTrackMenu.ts | 右键菜单新增“编辑元数据”项与回调 |
| src/components/ui/SCard.vue | 新增 variant(default/primary)以支持标签编辑卡片样式 |
| src/components/modals/TagEditorDialog.vue | 新增标签编辑对话框 UI + 在线匹配 + 保存逻辑 |
| src/components/list/SongList.vue | 接入 TagEditorDialog,并从右键菜单打开 |
| shared/types/tagEditor.ts | 新增标签读写相关共享类型(TrackTags/TagEditRequest/TagWriteOutcome) |
| shared/types/library.ts | 扩展 LibraryApi:readTags/writeTags/pickCoverImage |
| shared/types/errors.ts | 新增 TAG_READ_FAILED / TAG_WRITE_FAILED 错误码 |
| native/audio-engine/src/tag_editor.rs | 新增 lofty 标签读写实现(temp+rename)与测试 |
| native/audio-engine/src/scanner.rs | 放宽 probe_fast/file_stat 可见性以供写后探测复用 |
| native/audio-engine/src/metadata.rs | 抽取封面缓存路径计算,并新增内存内缩略图 JPEG 生成 |
| native/audio-engine/src/lib.rs | NAPI 导出:readTrackTags/writeTrackTags/makeImageThumbnail;并复用扫描结构转换 |
| native/audio-engine/src/decoder.rs | 小幅格式化/简化错误判断写法 |
| native/audio-engine/index.d.ts | 更新原生模块类型声明(新增标签/缩略图相关 API) |
| native/audio-engine/Cargo.toml | 新增 lofty 依赖 |
| electron/preload/index.ts | preload 暴露 library.readTags/writeTags/pickCoverImage |
| electron/main/utils/protocol.ts | cache:// 解析剥离查询串以支持封面缓存失效参数 |
| electron/main/services/scanner.ts | 提取 scannedToUpsert 供扫描与标签写后回灌复用 |
| electron/main/ipc/library.ts | 新增标签读写与封面选择 IPC,并写回数据库/刷新封面 cache URL |
| components.d.ts | 自动组件类型声明更新(加入 TagEditorDialog) |
| Cargo.lock | Rust 依赖锁文件更新(lofty 等) |
Comment on lines
118
to
+122
| const paths = tracks.map((t) => t.path).filter((p): p is string => !!p); | ||
| if (paths.length > 0) await libraryStore.deleteTracks(paths); | ||
| if (paths.length > 0) { | ||
| await libraryStore.deleteTracks(paths); | ||
| await player.purgeDeletedTracks(ids); | ||
| } |
Comment on lines
+234
to
+245
| for (const edit of edits) { | ||
| const { coverPath, coverUrl, ...fields } = edit; | ||
| let cover: Buffer | undefined; | ||
| if (coverPath) { | ||
| cover = await fs.readFile(coverPath); | ||
| } else if (coverUrl) { | ||
| cover = (await fetchBytes(coverUrl, { requireImage: true })) ?? undefined; | ||
| if (!cover) libraryLog.warn(`封面下载失败,跳过封面只写文本: ${coverUrl}`); | ||
| } | ||
| if (cover) replacedCovers.add(edit.path); | ||
| requests.push({ ...fields, cover }); | ||
| } |
Comment on lines
+77
to
+94
| watch( | ||
| () => props.open, | ||
| async (open) => { | ||
| if (!open || !props.track?.path) return; | ||
| loading.value = true; | ||
| original.value = null; | ||
| resetForm(null); | ||
| const result = await window.api.library.readTags(props.track.path); | ||
| loading.value = false; | ||
| if (!result.success || !result.data) { | ||
| if (result.error) handleError(result.error); | ||
| emit("update:open", false); | ||
| return; | ||
| } | ||
| original.value = result.data; | ||
| resetForm(result.data); | ||
| }, | ||
| ); |
Comment on lines
+280
to
+282
| @click="applyCandidate(candidate)" | ||
| @keydown.enter="applyCandidate(candidate)" | ||
| > |
Comment on lines
+213
to
+219
| const outcome = outcomes?.[0]; | ||
| if (outcome?.success) { | ||
| toast.success(t("tagEditor.saveSuccess")); | ||
| emit("update:open", false); | ||
| } else if (outcome?.error) { | ||
| toast.error(`${t("tagEditor.saveFailed")}: ${outcome.error}`); | ||
| } |
Comment on lines
+461
to
+465
| resumeMs = Math.round(playback.getCurrentTime()); | ||
| wasPlaying = status.isPlaying; | ||
| // Windows 下引擎持有文件句柄,必须先停止才能写入 | ||
| await window.api.player.stop(); | ||
| } |
Comment on lines
117
to
+122
| if (pendingDeleteAction.value === "file") { | ||
| const paths = tracks.map((t) => t.path).filter((p): p is string => !!p); | ||
| if (paths.length > 0) await libraryStore.deleteTracks(paths); | ||
| if (paths.length > 0) { | ||
| await libraryStore.deleteTracks(paths); | ||
| await player.purgeDeletedTracks(ids); | ||
| } |
Comment on lines
+237
to
+242
| if (coverPath) { | ||
| cover = await fs.readFile(coverPath); | ||
| } else if (coverUrl) { | ||
| cover = (await fetchBytes(coverUrl, { requireImage: true })) ?? undefined; | ||
| if (!cover) libraryLog.warn(`封面下载失败,跳过封面只写文本: ${coverUrl}`); | ||
| } |
Comment on lines
+195
to
+203
| /// 同目录临时文件路径:{文件名}.tagedit.tmp | ||
| fn temp_path(original: &Path) -> PathBuf { | ||
| let mut name = original | ||
| .file_name() | ||
| .map(|n| n.to_os_string()) | ||
| .unwrap_or_default(); | ||
| name.push(".tagedit.tmp"); | ||
| original.with_file_name(name) | ||
| } |
Comment on lines
+210
to
+214
| saving.value = true; | ||
| const outcomes = await player.saveTrackTags([edit]); | ||
| saving.value = false; | ||
| const outcome = outcomes?.[0]; | ||
| if (outcome?.success) { |
roarrabbit
pushed a commit
to roarrabbit/SPlayer-Next
that referenced
this pull request
Aug 27, 2026
feat: 新增音乐元信息编辑
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
No description provided.