Skip to content

feat: 新增音乐元信息编辑 - #35

Merged
imsyy merged 8 commits into
devfrom
feat/edit
Jun 13, 2026
Merged

imsyy merged 8 commits into
devfrom
feat/edit

Conversation

@imsyy

@imsyy imsyy commented Jun 12, 2026

Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings June 12, 2026 10:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 thread native/audio-engine/src/tag_editor.rs Resolved
Copilot AI review requested due to automatic review settings June 12, 2026 14:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 27 changed files in this pull request and generated 5 comments.

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}`);
}
Copilot AI review requested due to automatic review settings June 12, 2026 16:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 32 changed files in this pull request and generated 4 comments.

Comment thread src/core/player/index.ts
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)
}
Copilot AI review requested due to automatic review settings June 13, 2026 09:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 44 changed files in this pull request and generated 3 comments.

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) {
@imsyy
imsyy merged commit 82afa22 into dev Jun 13, 2026
6 checks passed
@imsyy
imsyy deleted the feat/edit branch June 13, 2026 10:35
roarrabbit pushed a commit to roarrabbit/SPlayer-Next that referenced this pull request Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants