🐞 fix: 修复下载歌曲元信息不正确导致无法正常播放 #113

This commit is contained in:
imsyy
2024-01-11 16:09:09 +08:00
parent 984d747179
commit adbda459ba
7 changed files with 276 additions and 78 deletions

View File

@@ -1,9 +1,9 @@
import { ipcMain, dialog, app, clipboard, shell } from "electron"; import { ipcMain, dialog, app, clipboard, shell } from "electron";
import { File, Picture, Id3v2Settings } from "node-taglib-sharp";
import { readDirAsync } from "@main/utils/readDirAsync"; import { readDirAsync } from "@main/utils/readDirAsync";
import { parseFile } from "music-metadata"; import { parseFile } from "music-metadata";
import { download } from "electron-dl"; import { download } from "electron-dl";
import getNeteaseMusicUrl from "@main/utils/getNeteaseMusicUrl"; import getNeteaseMusicUrl from "@main/utils/getNeteaseMusicUrl";
import NodeID3 from "node-id3";
import axios from "axios"; import axios from "axios";
import fs from "fs/promises"; import fs from "fs/promises";
@@ -193,37 +193,42 @@ const mainIpcMain = (win) => {
}); });
// 下载文件至指定目录 // 下载文件至指定目录
ipcMain.handle("downloadFile", async (_, data, song, songName, songType, path) => { ipcMain.handle("downloadFile", async (_, songData, options) => {
try { try {
const { url, data, lyric, name, type } = JSON.parse(songData);
const { path, downloadMeta, downloadCover, downloadLyrics } = JSON.parse(options);
if (fs.access(path)) { if (fs.access(path)) {
const songData = JSON.parse(song); console.info("开始下载:", name, url);
console.info("开始下载:", songData, data);
// 下载歌曲 // 下载歌曲
const songDownload = await download(win, data.url, { const songDownload = await download(win, url, {
directory: path, directory: path,
filename: `${songName}.${songType}`, filename: `${name}.${type}`,
}); });
// 若不为 mp3,则不进行元信息写入 // 若关闭,则不进行元信息写入
if (songType !== "mp3") return true; if (!downloadMeta) return true;
// 下载封面 // 下载封面
const coverDownload = await download(win, songData.cover, { const coverDownload = await download(win, data.cover, {
directory: path, directory: path,
filename: `${songName}.jpg`, filename: `${name}.jpg`,
}); });
// 生成歌曲文件的元数据 // 读取歌曲文件
const songTag = { const songFile = File.createFromPath(songDownload.getSavePath());
title: songData.name, // 生成图片信息
artist: Array.isArray(songData.artists) const songCover = Picture.fromPath(coverDownload.getSavePath());
? songData.artists.map((ar) => ar.name).join(" / ")
: songData.artists || "未知歌手",
album: songData.album?.name || songData.album,
image: coverDownload.getSavePath(),
};
// 保存修改后的元数据 // 保存修改后的元数据
const isSuccess = NodeID3.write(songTag, songDownload.getSavePath()); Id3v2Settings.forceDefaultVersion = true;
Id3v2Settings.defaultVersion = 3;
songFile.tag.title = data.name || "未知曲目";
songFile.tag.album = data.album?.name || "未知专辑";
songFile.tag.performers = data?.artists?.map((ar) => ar.name) || ["未知艺术家"];
if (downloadLyrics) songFile.tag.lyrics = lyric;
if (downloadCover) songFile.tag.pictures = [songCover];
// 保存元信息
songFile.save();
songFile.dispose();
// 删除封面 // 删除封面
await fs.unlink(coverDownload.getSavePath()); await fs.unlink(coverDownload.getSavePath());
return isSuccess; return true;
} else { } else {
console.log(`目录不存在:${path}`); console.log(`目录不存在:${path}`);
return false; return false;

View File

@@ -41,7 +41,7 @@
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"music-metadata": "7.14.0", "music-metadata": "7.14.0",
"node-id3": "^0.2.6", "node-taglib-sharp": "^5.2.3",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"plyr": "^3.7.8", "plyr": "^3.7.8",

182
pnpm-lock.yaml generated
View File

@@ -50,9 +50,9 @@ dependencies:
music-metadata: music-metadata:
specifier: 7.14.0 specifier: 7.14.0
version: 7.14.0 version: 7.14.0
node-id3: node-taglib-sharp:
specifier: ^0.2.6 specifier: ^5.2.3
version: 0.2.6 version: 5.2.3
pinia: pinia:
specifier: ^2.1.7 specifier: ^2.1.7
version: 2.1.7(vue@3.4.4) version: 2.1.7(vue@3.4.4)
@@ -2972,6 +2972,17 @@ packages:
dev: true dev: true
optional: true optional: true
/cross-spawn@6.0.5:
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
engines: {node: '>=4.8'}
dependencies:
nice-try: 1.0.5
path-key: 2.0.1
semver: 5.7.2
shebang-command: 1.2.0
which: 1.3.1
dev: false
/cross-spawn@7.0.3: /cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -3055,6 +3066,10 @@ packages:
'@babel/runtime': 7.23.8 '@babel/runtime': 7.23.8
dev: true dev: true
/dateformat@3.0.3:
resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
dev: false
/debounce-fn@4.0.0: /debounce-fn@4.0.0:
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3703,6 +3718,19 @@ packages:
resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==} resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==}
dev: true dev: true
/execa@1.0.0:
resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==}
engines: {node: '>=6'}
dependencies:
cross-spawn: 6.0.5
get-stream: 4.1.0
is-stream: 1.1.0
npm-run-path: 2.0.2
p-finally: 1.0.0
signal-exit: 3.0.7
strip-eof: 1.0.0
dev: false
/express-fileupload@1.4.3: /express-fileupload@1.4.3:
resolution: {integrity: sha512-vRzZo2YELm68DfR/CX8RMXgeK9BTAANxigrKACPjCXFGEzkCt/QWbqaIXP3W61uaX/hLj0CAo3/EVelpSQXkqA==} resolution: {integrity: sha512-vRzZo2YELm68DfR/CX8RMXgeK9BTAANxigrKACPjCXFGEzkCt/QWbqaIXP3W61uaX/hLj0CAo3/EVelpSQXkqA==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -4074,6 +4102,13 @@ packages:
through: 2.3.8 through: 2.3.8
dev: false dev: false
/get-stream@4.1.0:
resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==}
engines: {node: '>=6'}
dependencies:
pump: 3.0.0
dev: false
/get-stream@5.2.0: /get-stream@5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -4361,19 +4396,11 @@ packages:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
dev: false dev: false
/iconv-lite@0.6.2:
resolution: {integrity: sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/iconv-lite@0.6.3: /iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
dev: true
/idb@7.1.1: /idb@7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
@@ -4427,6 +4454,11 @@ packages:
side-channel: 1.0.4 side-channel: 1.0.4
dev: true dev: true
/invert-kv@3.0.1:
resolution: {integrity: sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==}
engines: {node: '>=8'}
dev: false
/iota-array@1.0.0: /iota-array@1.0.0:
resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==}
dev: false dev: false
@@ -4578,6 +4610,11 @@ packages:
call-bind: 1.0.5 call-bind: 1.0.5
dev: true dev: true
/is-stream@1.1.0:
resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
engines: {node: '>=0.10.0'}
dev: false
/is-stream@2.0.1: /is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -4630,12 +4667,15 @@ packages:
/isexe@2.0.0: /isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/isstream@0.1.2: /isstream@0.1.2:
resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
dev: false dev: false
/itiriri@2.0.1:
resolution: {integrity: sha512-XutdsL9Nm9ejgRlFwOBdWW78Txvs9ehhMFeGCp3GjGIWSeQNgOzOfo+PjuQSmaFgE1ol6Pr2g5Jg7W4BYIploQ==}
dev: false
/jackspeak@2.3.6: /jackspeak@2.3.6:
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -4767,6 +4807,13 @@ packages:
/lazy-val@1.0.5: /lazy-val@1.0.5:
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
/lcid@3.1.1:
resolution: {integrity: sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==}
engines: {node: '>=8'}
dependencies:
invert-kv: 3.0.1
dev: false
/leven@3.1.0: /leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -4892,6 +4939,13 @@ packages:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
/map-age-cleaner@0.1.3:
resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==}
engines: {node: '>=6'}
dependencies:
p-defer: 1.0.0
dev: false
/matcher@3.0.0: /matcher@3.0.0:
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -4918,6 +4972,15 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/mem@5.1.1:
resolution: {integrity: sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==}
engines: {node: '>=8'}
dependencies:
map-age-cleaner: 0.1.3
mimic-fn: 2.1.0
p-is-promise: 2.1.0
dev: false
/merge-descriptors@1.0.1: /merge-descriptors@1.0.1:
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
dev: false dev: false
@@ -5139,6 +5202,10 @@ packages:
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
dev: false dev: false
/nice-try@1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
dev: false
/node-addon-api@1.7.2: /node-addon-api@1.7.2:
resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
requiresBuild: true requiresBuild: true
@@ -5155,16 +5222,21 @@ packages:
engines: {node: '>= 6.13.0'} engines: {node: '>= 6.13.0'}
dev: false dev: false
/node-id3@0.2.6:
resolution: {integrity: sha512-w8GuKXLlPpDjTxLowCt/uYMhRQzED3cg2GdSG1i6RSGKeDzPvxlXeLQuQInKljahPZ0aDnmyX7FX8BbJOM7REg==}
dependencies:
iconv-lite: 0.6.2
dev: false
/node-releases@2.0.14: /node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
dev: true dev: true
/node-taglib-sharp@5.2.3:
resolution: {integrity: sha512-cqomQ+pD/4HPYpVS5UpDkyN3YQwX67YHSXFk9udSI0VgFKdHSG9/PJFKLehsuwWUr5HKVqRVgSJjBIeOvyLajA==}
engines: {node: '>=12.16.1'}
dependencies:
dateformat: 3.0.3
iconv-lite: 0.6.3
itiriri: 2.0.1
os-locale: 4.0.0
uuid: 8.3.2
dev: false
/normalize-path@3.0.0: /normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -5174,6 +5246,13 @@ packages:
resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
engines: {node: '>=10'} engines: {node: '>=10'}
/npm-run-path@2.0.2:
resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==}
engines: {node: '>=4'}
dependencies:
path-key: 2.0.1
dev: false
/nth-check@2.1.1: /nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
dependencies: dependencies:
@@ -5236,10 +5315,34 @@ packages:
type-check: 0.4.0 type-check: 0.4.0
dev: true dev: true
/os-locale@4.0.0:
resolution: {integrity: sha512-HsSR1+2l6as4Wp2SGZxqLnuFHxVvh1Ir9pvZxyujsC13egZVe7P0YeBLN0ijQzM/twrO5To3ia3jzBXAvpMTEA==}
engines: {node: '>=8'}
dependencies:
execa: 1.0.0
lcid: 3.1.1
mem: 5.1.1
dev: false
/p-cancelable@2.1.1: /p-cancelable@2.1.1:
resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
engines: {node: '>=8'} engines: {node: '>=8'}
/p-defer@1.0.0:
resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==}
engines: {node: '>=4'}
dev: false
/p-finally@1.0.0:
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
engines: {node: '>=4'}
dev: false
/p-is-promise@2.1.0:
resolution: {integrity: sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==}
engines: {node: '>=6'}
dev: false
/p-limit@2.3.0: /p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -5337,6 +5440,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/path-key@2.0.1:
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
engines: {node: '>=4'}
dev: false
/path-key@3.1.1: /path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -5896,6 +6004,11 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
dev: false
/semver@6.3.1: /semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true hasBin: true
@@ -5980,6 +6093,13 @@ packages:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
dev: false dev: false
/shebang-command@1.2.0:
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
engines: {node: '>=0.10.0'}
dependencies:
shebang-regex: 1.0.0
dev: false
/shebang-command@2.0.0: /shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -5987,6 +6107,11 @@ packages:
shebang-regex: 3.0.0 shebang-regex: 3.0.0
dev: true dev: true
/shebang-regex@1.0.0:
resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
engines: {node: '>=0.10.0'}
dev: false
/shebang-regex@3.0.0: /shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -5999,6 +6124,10 @@ packages:
get-intrinsic: 1.2.2 get-intrinsic: 1.2.2
object-inspect: 1.13.1 object-inspect: 1.13.1
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: false
/signal-exit@4.1.0: /signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -6211,6 +6340,11 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/strip-eof@1.0.0:
resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==}
engines: {node: '>=0.10.0'}
dev: false
/strip-json-comments@3.1.1: /strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -6677,6 +6811,11 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: false
/vary@1.1.2: /vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -6909,6 +7048,13 @@ packages:
has-tostringtag: 1.0.0 has-tostringtag: 1.0.0
dev: true dev: true
/which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
dependencies:
isexe: 2.0.0
dev: false
/which@2.0.2: /which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}

View File

@@ -43,8 +43,9 @@
<n-button <n-button
:disabled="!downloadChoose" :disabled="!downloadChoose"
:loading="downloadStatus" :loading="downloadStatus"
:focusable="false"
type="primary" type="primary"
@click="toSongDownload(songData, downloadChoose)" @click="toSongDownload(songData, lyricData, downloadChoose)"
> >
下载 下载
</n-button> </n-button>
@@ -58,7 +59,7 @@ import { storeToRefs } from "pinia";
import { isLogin } from "@/utils/auth"; import { isLogin } from "@/utils/auth";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { siteData, siteSettings } from "@/stores"; import { siteData, siteSettings } from "@/stores";
import { getSongDetail, getSongDownload } from "@/api/song"; import { getSongDetail, getSongDownload, getSongLyric } from "@/api/song";
import { downloadFile, checkPlatform } from "@/utils/helper"; import { downloadFile, checkPlatform } from "@/utils/helper";
import formatData from "@/utils/formatData"; import formatData from "@/utils/formatData";
@@ -66,11 +67,12 @@ const router = useRouter();
const data = siteData(); const data = siteData();
const settings = siteSettings(); const settings = siteSettings();
const { userData } = storeToRefs(data); const { userData } = storeToRefs(data);
const { downloadPath } = storeToRefs(settings); const { downloadPath, downloadMeta, downloadCover, downloadLyrics } = storeToRefs(settings);
// 歌曲下载数据 // 歌曲下载数据
const songId = ref(null); const songId = ref(null);
const songData = ref(null); const songData = ref(null);
const lyricData = ref(null);
const downloadStatus = ref(false); const downloadStatus = ref(false);
const downloadSongShow = ref(false); const downloadSongShow = ref(false);
const downloadChoose = ref(null); const downloadChoose = ref(null);
@@ -79,11 +81,13 @@ const downloadLevel = ref(null);
// 获取歌曲详情 // 获取歌曲详情
const getMusicDetailData = async (id) => { const getMusicDetailData = async (id) => {
try { try {
const result = await getSongDetail(id); const songResult = await getSongDetail(id);
const lyricResult = await getSongLyric(id);
// 获取歌曲详情 // 获取歌曲详情
songData.value = formatData(result?.songs?.[0], "song")[0]; songData.value = formatData(songResult?.songs?.[0], "song")[0];
lyricData.value = lyricResult?.lrc?.lyric || null;
// 生成音质列表 // 生成音质列表
generateLists(result); generateLists(songResult);
} catch (error) { } catch (error) {
closeDownloadModal(); closeDownloadModal();
console.error("歌曲信息获取失败:", error); console.error("歌曲信息获取失败:", error);
@@ -91,26 +95,42 @@ const getMusicDetailData = async (id) => {
}; };
// 歌曲下载 // 歌曲下载
const toSongDownload = async (song, br) => { const toSongDownload = async (song, lyric, br) => {
console.log(song, br); try {
downloadStatus.value = true; console.log(song, lyric, br);
// 获取下载数据 downloadStatus.value = true;
const result = await getSongDownload(song?.id, br); // 获取下载数据
// 开始下载 const result = await getSongDownload(song?.id, br);
if (!downloadPath.value && checkPlatform.electron()) { // 开始下载
$notification["warning"]({ if (!downloadPath.value && checkPlatform.electron()) {
content: "缺少配置", $notification["warning"]({
meta: "请前往设置页配置默认下载目录", content: "缺少配置",
duration: 3000, meta: "请前往设置页配置默认下载目录",
duration: 3000,
});
}
if (!result.data?.url) {
downloadStatus.value = false;
return $message.error("下载失败,请重试");
}
// 获取下载结果
const isDownloaded = await downloadFile(result.data, song, lyric, {
path: downloadPath.value,
downloadMeta: downloadMeta.value,
downloadCover: downloadCover.value,
downloadLyrics: downloadLyrics.value,
}); });
} console.log(lyric);
const isDownloaded = await downloadFile(result.data, song, downloadPath.value); if (isDownloaded) {
if (isDownloaded) { $message.success("下载完成");
$message.success("下载完成"); closeDownloadModal();
closeDownloadModal(); } else {
} else { downloadStatus.value = false;
downloadStatus.value = false; $message.error("下载失败,请重试");
$message.error("下载失败,请重试"); }
} catch (error) {
console.error("歌曲下载出错:", error);
$message.error("歌曲下载失败,请重试");
} }
}; };

View File

@@ -48,6 +48,9 @@ const useSiteSettingsStore = defineStore("siteSettings", {
showRoma: true, // 是否显示歌词音译 showRoma: true, // 是否显示歌词音译
// 下载部分 // 下载部分
downloadPath: null, // 默认下载路径 downloadPath: null, // 默认下载路径
downloadMeta: true, // 同时下载元信息
downloadCover: true, // 同时下载封面
downloadLyrics: true, // 同时下载歌词
}; };
}, },
getters: {}, getters: {},

View File

@@ -1,6 +1,7 @@
// BlobUrl // BlobUrl
let lastSongBlobUrl = null; let lastSongBlobUrl = null;
let lastCoverBlobUrl = null; let lastCoverBlobUrl = null;
let lastDownloadBlobUrl = null;
/** /**
* 判断当前运行环境 * 判断当前运行环境
@@ -291,35 +292,40 @@ export const generateId = (fileName) => {
* @param {String} songName - 歌曲名称 * @param {String} songName - 歌曲名称
* @returns {number} - 生成的数字ID * @returns {number} - 生成的数字ID
*/ */
export const downloadFile = async (data, song, path = null) => { export const downloadFile = async (data, song, lyric, options) => {
try { try {
const isElectron = checkPlatform.electron(); const isElectron = checkPlatform.electron();
const songType = data.type.toLowerCase(); const songType = data.type.toLowerCase();
// 歌曲名称 // 歌曲名称
const songName = const songName =
song.name + song.name +
" - " + "-" +
(Array.isArray(song.artists) (Array.isArray(song.artists)
? song.artists.map((ar) => ar.name).join(" / ") ? song.artists.map((ar) => ar.name).join("&")
: song.artists || "未知歌手"); : song.artists || "未知歌手");
if (isElectron && path) { if (isElectron && options.path) {
console.log("开始下载:", data, song, songName, songType, path); console.log("开始下载:", data, song, songName, songType, options.path);
return await electron.ipcRenderer.invoke( return await electron.ipcRenderer.invoke(
"downloadFile", "downloadFile",
data, JSON.stringify({
JSON.stringify(song), url: data?.url,
songName, data: song,
songType, lyric: lyric,
path, name: songName,
type: songType,
}),
JSON.stringify(options),
); );
} else { } else {
const songRes = await fetch(data.url.replace(/^http:/, "https:")); // 清理过期的 Blob 链接
if (lastDownloadBlobUrl) URL.revokeObjectURL(lastDownloadBlobUrl);
const songRes = await fetch(data?.url.replace(/^http:/, "https:"));
if (!songRes.ok) throw new Error("下载出错,请重试"); if (!songRes.ok) throw new Error("下载出错,请重试");
const blob = await songRes.blob(); const blob = await songRes.blob();
const url = window.URL.createObjectURL(blob); lastDownloadBlobUrl = URL.createObjectURL(blob);
// 下载数据 // 下载数据
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = lastDownloadBlobUrl;
a.download = `${songName}.${songType}`; a.download = `${songName}.${songType}`;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();

View File

@@ -516,6 +516,21 @@
</n-button> </n-button>
</n-flex> </n-flex>
</n-card> </n-card>
<n-card class="set-item">
<div class="name">
同时下载歌曲元信息
<n-text class="tip">为当前下载歌曲附加封面及歌词等元信息</n-text>
</div>
<n-switch v-model:value="downloadMeta" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">下载歌曲时同时下载封面</div>
<n-switch v-model:value="downloadCover" :disabled="!downloadMeta" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">下载歌曲时同时下载歌词</div>
<n-switch v-model:value="downloadLyrics" :disabled="!downloadMeta" :round="false" />
</n-card>
</div> </div>
<!-- 其他 --> <!-- 其他 -->
<div class="set-type"> <div class="set-type">
@@ -611,6 +626,9 @@ const {
showSpectrums, showSpectrums,
siderShowCover, siderShowCover,
useMusicCache, useMusicCache,
downloadMeta,
downloadCover,
downloadLyrics,
} = storeToRefs(settings); } = storeToRefs(settings);
// 标签页数据 // 标签页数据