const fs = require('fs'); const path = require('path'); const { API, DEFAULT_RETRIES, DOWNLOAD_DIR } = require('./config'); const { addHistory } = require('./history'); const { fetchLyric, mergeLrc, parseCredits } = require('./lyrics'); const { fetchNeteaseAlbumDetail, fetchNeteaseSongDetail } = require('./metadata'); const { downloadFile, fetchJSON } = require('./network'); const { embedCover } = require('./tagging'); const { sanitize, formatPersonNames, sleep } = require('./utils'); async function downloadSong(songId, level, { downloadLyric = true, embedCoverArt = true, maxRetries = DEFAULT_RETRIES, outputDir = DOWNLOAD_DIR, outputBaseName = null, } = {}) { let lastError = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await downloadSongOnce(songId, level, { downloadLyric, embedCoverArt, outputDir, outputBaseName }); } catch (e) { lastError = e; if (attempt < maxRetries) { const waitSec = attempt * 3; console.log(` ⚠️ 第 ${attempt} 次失败: ${e.message},${waitSec}秒后重试...`); await sleep(waitSec * 1000); } } } console.error(`❌ 歌曲 ${songId} 下载失败 (已重试 ${maxRetries} 次): ${lastError?.message}`); return false; } async function downloadSongOnce(songId, level, { downloadLyric = true, embedCoverArt = true, outputDir = DOWNLOAD_DIR, outputBaseName = null, } = {}) { console.log(`\n🎵 解析歌曲 ID: ${songId} ...`); const result = await fetchJSON(`${API.music}?id=${songId}&level=${level}`); if (result.code !== 200 || !result.data?.url) throw new Error(result.msg || 'API 返回错误'); const { name, artist: rawArtist, album, url: audioUrl, br, size, level: actualLevel, picUrl } = result.data; const artist = formatPersonNames(rawArtist); const ext = audioUrl.match(/\.(mp3|flac|m4a|wav|aac)(\?|$)/i)?.[1] || 'mp3'; const baseName = sanitize(outputBaseName || `${artist} - ${name}`); const fileName = `${baseName}.${ext}`; fs.mkdirSync(outputDir, { recursive: true }); const dest = path.join(outputDir, fileName); console.log(` 🎶 ${name} - ${artist}`); console.log(` 💿 ${album} | ${actualLevel} (${br}kbps) | ${(size / 1048576).toFixed(2)} MB`); if (fs.existsSync(dest)) { const existStat = fs.statSync(dest); const sizeDiff = Math.abs(existStat.size - size) / size; if (sizeDiff < 0.01) { console.log(` ⏭️ 已存在,跳过: ${fileName}`); const lrcPath = path.join(outputDir, baseName + '.lrc'); if (downloadLyric && !fs.existsSync(lrcPath)) { console.log(' 🔧 检测到缺少歌词,自动补全...'); const lyricData = await fetchLyric(songId); if (lyricData?.lrc) { fs.writeFileSync(lrcPath, lyricData.lrc, 'utf-8'); console.log(' 📄 歌词已补全'); if (lyricData.tlyric?.trim()) { fs.writeFileSync(path.join(outputDir, baseName + '.合并翻译.lrc'), mergeLrc(lyricData.lrc, lyricData.tlyric), 'utf-8'); } } } } else { console.log(` ⚠️ 已存在同名文件但大小不同 (现有 ${(existStat.size / 1048576).toFixed(2)} MB, 预期 ${(size / 1048576).toFixed(2)} MB)`); console.log(' ⬇️ 覆盖下载...'); await downloadFile(audioUrl, dest); console.log(` ✅ 下载完成: ${fileName}`); } } else { console.log(' ⬇️ 下载中...'); await downloadFile(audioUrl, dest); console.log(` ✅ 下载完成: ${fileName}`); } let lyricData = null; if (downloadLyric) { lyricData = await fetchLyric(songId); if (lyricData?.lrc) { fs.writeFileSync(path.join(outputDir, baseName + '.lrc'), lyricData.lrc, 'utf-8'); console.log(` 📄 歌词已保存: ${baseName}.lrc`); if (lyricData.tlyric?.trim()) { const mergedPath = path.join(outputDir, baseName + '.合并翻译.lrc'); fs.writeFileSync(mergedPath, mergeLrc(lyricData.lrc, lyricData.tlyric), 'utf-8'); console.log(` 📄 翻译歌词已保存: ${path.basename(mergedPath)}`); } } else { console.log(' ⚠️ 未获取到歌词'); } } if (embedCoverArt && picUrl) { const lrcText = lyricData?.lrc || null; const tagMeta = { name, artist, album, lyrics: lrcText, ...parseCredits(lrcText) }; try { const detail = await fetchNeteaseSongDetail(songId); if (detail) { if (detail.trackNo) tagMeta.trackNo = detail.trackNo; if (detail.disc) tagMeta.disc = detail.disc; if (detail.album?.id) { const albumDetail = await fetchNeteaseAlbumDetail(detail.album.id); if (albumDetail?.albumArtist) tagMeta.albumArtist = albumDetail.albumArtist; if (albumDetail?.isCompilation) tagMeta.compilation = true; if (albumDetail?.publishTime) { const date = new Date(albumDetail.publishTime); tagMeta.year = date.getFullYear(); tagMeta.date = date.toISOString().slice(0, 10); } if (albumDetail?.tags?.length) tagMeta.genre = albumDetail.tags.join(', '); if (albumDetail?.company) tagMeta.publisher = albumDetail.company; if (tagMeta.trackNo && albumDetail?.size) tagMeta.trackNo = `${tagMeta.trackNo}/${albumDetail.size}`; } if (!tagMeta.albumArtist) { const fallbackAlbumArtist = detail.artists?.map(item => item.name).join(' / '); if (fallbackAlbumArtist) tagMeta.albumArtist = fallbackAlbumArtist; } } } catch {} await embedCover(dest, picUrl, tagMeta); } addHistory({ id: songId, name, artist, album, level: actualLevel, bitrate: br, size, file: path.relative(DOWNLOAD_DIR, dest), }); return true; } module.exports = { downloadSong };