diff --git a/electron/main/mainIpcMain.js b/electron/main/mainIpcMain.js index 6b167c87..d3a3e164 100644 --- a/electron/main/mainIpcMain.js +++ b/electron/main/mainIpcMain.js @@ -1,9 +1,9 @@ import { ipcMain, dialog, app, clipboard, shell } from "electron"; +import { File, Picture, Id3v2Settings } from "node-taglib-sharp"; import { readDirAsync } from "@main/utils/readDirAsync"; import { parseFile } from "music-metadata"; import { download } from "electron-dl"; import getNeteaseMusicUrl from "@main/utils/getNeteaseMusicUrl"; -import NodeID3 from "node-id3"; import axios from "axios"; 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 { + const { url, data, lyric, name, type } = JSON.parse(songData); + const { path, downloadMeta, downloadCover, downloadLyrics } = JSON.parse(options); if (fs.access(path)) { - const songData = JSON.parse(song); - console.info("开始下载:", songData, data); + console.info("开始下载:", name, url); // 下载歌曲 - const songDownload = await download(win, data.url, { + const songDownload = await download(win, url, { 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, - filename: `${songName}.jpg`, + filename: `${name}.jpg`, }); - // 生成歌曲文件的元数据 - const songTag = { - title: songData.name, - artist: Array.isArray(songData.artists) - ? songData.artists.map((ar) => ar.name).join(" / ") - : songData.artists || "未知歌手", - album: songData.album?.name || songData.album, - image: coverDownload.getSavePath(), - }; + // 读取歌曲文件 + const songFile = File.createFromPath(songDownload.getSavePath()); + // 生成图片信息 + const songCover = Picture.fromPath(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()); - return isSuccess; + return true; } else { console.log(`目录不存在:${path}`); return false; diff --git a/package.json b/package.json index 73e4406f..c2d548d9 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "js-cookie": "^3.0.5", "localforage": "^1.10.0", "music-metadata": "7.14.0", - "node-id3": "^0.2.6", + "node-taglib-sharp": "^5.2.3", "pinia": "^2.1.7", "pinia-plugin-persistedstate": "^3.2.1", "plyr": "^3.7.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ed2c860..d01ebc6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,9 +50,9 @@ dependencies: music-metadata: specifier: 7.14.0 version: 7.14.0 - node-id3: - specifier: ^0.2.6 - version: 0.2.6 + node-taglib-sharp: + specifier: ^5.2.3 + version: 5.2.3 pinia: specifier: ^2.1.7 version: 2.1.7(vue@3.4.4) @@ -2972,6 +2972,17 @@ packages: dev: 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: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -3055,6 +3066,10 @@ packages: '@babel/runtime': 7.23.8 dev: true + /dateformat@3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + dev: false + /debounce-fn@4.0.0: resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} engines: {node: '>=10'} @@ -3703,6 +3718,19 @@ packages: resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==} 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: resolution: {integrity: sha512-vRzZo2YELm68DfR/CX8RMXgeK9BTAANxigrKACPjCXFGEzkCt/QWbqaIXP3W61uaX/hLj0CAo3/EVelpSQXkqA==} engines: {node: '>=12.0.0'} @@ -4074,6 +4102,13 @@ packages: through: 2.3.8 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: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -4361,19 +4396,11 @@ packages: safer-buffer: 2.1.2 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: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - dev: true /idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -4427,6 +4454,11 @@ packages: side-channel: 1.0.4 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: resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} dev: false @@ -4578,6 +4610,11 @@ packages: call-bind: 1.0.5 dev: true + /is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + dev: false + /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -4630,12 +4667,15 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true /isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} dev: false + /itiriri@2.0.1: + resolution: {integrity: sha512-XutdsL9Nm9ejgRlFwOBdWW78Txvs9ehhMFeGCp3GjGIWSeQNgOzOfo+PjuQSmaFgE1ol6Pr2g5Jg7W4BYIploQ==} + dev: false + /jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} @@ -4767,6 +4807,13 @@ packages: /lazy-val@1.0.5: 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: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -4892,6 +4939,13 @@ packages: dependencies: '@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: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} @@ -4918,6 +4972,15 @@ packages: engines: {node: '>= 0.8'} 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: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} dev: false @@ -5139,6 +5202,10 @@ packages: engines: {node: '>= 0.4.0'} dev: false + /nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + dev: false + /node-addon-api@1.7.2: resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} requiresBuild: true @@ -5155,16 +5222,21 @@ packages: engines: {node: '>= 6.13.0'} 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: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} 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: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -5174,6 +5246,13 @@ packages: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} 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: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -5236,10 +5315,34 @@ packages: type-check: 0.4.0 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: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} 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: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -5337,6 +5440,11 @@ packages: engines: {node: '>=0.10.0'} 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: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -5896,6 +6004,11 @@ packages: requiresBuild: true optional: true + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + dev: false + /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -5980,6 +6093,13 @@ packages: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 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: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -5987,6 +6107,11 @@ packages: shebang-regex: 3.0.0 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: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -5999,6 +6124,10 @@ packages: get-intrinsic: 1.2.2 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: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -6211,6 +6340,11 @@ packages: engines: {node: '>=10'} 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: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -6677,6 +6811,11 @@ packages: hasBin: true dev: false + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -6909,6 +7048,13 @@ packages: has-tostringtag: 1.0.0 dev: true + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: false + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} diff --git a/src/components/Modal/DownloadSong.vue b/src/components/Modal/DownloadSong.vue index 996eaf27..acd877ec 100644 --- a/src/components/Modal/DownloadSong.vue +++ b/src/components/Modal/DownloadSong.vue @@ -43,8 +43,9 @@ 下载 @@ -58,7 +59,7 @@ import { storeToRefs } from "pinia"; import { isLogin } from "@/utils/auth"; import { useRouter } from "vue-router"; 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 formatData from "@/utils/formatData"; @@ -66,11 +67,12 @@ const router = useRouter(); const data = siteData(); const settings = siteSettings(); const { userData } = storeToRefs(data); -const { downloadPath } = storeToRefs(settings); +const { downloadPath, downloadMeta, downloadCover, downloadLyrics } = storeToRefs(settings); // 歌曲下载数据 const songId = ref(null); const songData = ref(null); +const lyricData = ref(null); const downloadStatus = ref(false); const downloadSongShow = ref(false); const downloadChoose = ref(null); @@ -79,11 +81,13 @@ const downloadLevel = ref(null); // 获取歌曲详情 const getMusicDetailData = async (id) => { 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) { closeDownloadModal(); console.error("歌曲信息获取失败:", error); @@ -91,26 +95,42 @@ const getMusicDetailData = async (id) => { }; // 歌曲下载 -const toSongDownload = async (song, br) => { - console.log(song, br); - downloadStatus.value = true; - // 获取下载数据 - const result = await getSongDownload(song?.id, br); - // 开始下载 - if (!downloadPath.value && checkPlatform.electron()) { - $notification["warning"]({ - content: "缺少配置", - meta: "请前往设置页配置默认下载目录", - duration: 3000, +const toSongDownload = async (song, lyric, br) => { + try { + console.log(song, lyric, br); + downloadStatus.value = true; + // 获取下载数据 + const result = await getSongDownload(song?.id, br); + // 开始下载 + if (!downloadPath.value && checkPlatform.electron()) { + $notification["warning"]({ + content: "缺少配置", + 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, }); - } - const isDownloaded = await downloadFile(result.data, song, downloadPath.value); - if (isDownloaded) { - $message.success("下载完成"); - closeDownloadModal(); - } else { - downloadStatus.value = false; - $message.error("下载失败,请重试"); + console.log(lyric); + if (isDownloaded) { + $message.success("下载完成"); + closeDownloadModal(); + } else { + downloadStatus.value = false; + $message.error("下载失败,请重试"); + } + } catch (error) { + console.error("歌曲下载出错:", error); + $message.error("歌曲下载失败,请重试"); } }; diff --git a/src/stores/siteSettings.js b/src/stores/siteSettings.js index 07281b72..3e7b2e8e 100644 --- a/src/stores/siteSettings.js +++ b/src/stores/siteSettings.js @@ -48,6 +48,9 @@ const useSiteSettingsStore = defineStore("siteSettings", { showRoma: true, // 是否显示歌词音译 // 下载部分 downloadPath: null, // 默认下载路径 + downloadMeta: true, // 同时下载元信息 + downloadCover: true, // 同时下载封面 + downloadLyrics: true, // 同时下载歌词 }; }, getters: {}, diff --git a/src/utils/helper.js b/src/utils/helper.js index aa0f59b5..f6b2cdee 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -1,6 +1,7 @@ // BlobUrl let lastSongBlobUrl = null; let lastCoverBlobUrl = null; +let lastDownloadBlobUrl = null; /** * 判断当前运行环境 @@ -291,35 +292,40 @@ export const generateId = (fileName) => { * @param {String} songName - 歌曲名称 * @returns {number} - 生成的数字ID */ -export const downloadFile = async (data, song, path = null) => { +export const downloadFile = async (data, song, lyric, options) => { try { const isElectron = checkPlatform.electron(); const songType = data.type.toLowerCase(); // 歌曲名称 const songName = song.name + - " - " + + "-" + (Array.isArray(song.artists) - ? song.artists.map((ar) => ar.name).join(" / ") + ? song.artists.map((ar) => ar.name).join("&") : song.artists || "未知歌手"); - if (isElectron && path) { - console.log("开始下载:", data, song, songName, songType, path); + if (isElectron && options.path) { + console.log("开始下载:", data, song, songName, songType, options.path); return await electron.ipcRenderer.invoke( "downloadFile", - data, - JSON.stringify(song), - songName, - songType, - path, + JSON.stringify({ + url: data?.url, + data: song, + lyric: lyric, + name: songName, + type: songType, + }), + JSON.stringify(options), ); } 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("下载出错,请重试"); const blob = await songRes.blob(); - const url = window.URL.createObjectURL(blob); + lastDownloadBlobUrl = URL.createObjectURL(blob); // 下载数据 const a = document.createElement("a"); - a.href = url; + a.href = lastDownloadBlobUrl; a.download = `${songName}.${songType}`; document.body.appendChild(a); a.click(); diff --git a/src/views/Setting/index.vue b/src/views/Setting/index.vue index 7fafd844..37d123dd 100644 --- a/src/views/Setting/index.vue +++ b/src/views/Setting/index.vue @@ -516,6 +516,21 @@ + +
+ 同时下载歌曲元信息 + 为当前下载歌曲附加封面及歌词等元信息 +
+ +
+ +
下载歌曲时同时下载封面
+ +
+ +
下载歌曲时同时下载歌词
+ +
@@ -611,6 +626,9 @@ const { showSpectrums, siderShowCover, useMusicCache, + downloadMeta, + downloadCover, + downloadLyrics, } = storeToRefs(settings); // 标签页数据