const fs = require('fs'); const path = require('path'); const { execSync, execFileSync } = require('child_process'); const { downloadBuffer } = require('./network'); const { formatPersonNames } = require('./utils'); function hasFFmpeg() { try { execSync('ffmpeg -version', { stdio: 'ignore' }); return true; } catch { return false; } } function hasNodeID3() { try { require.resolve('node-id3'); return true; } catch { return false; } } async function embedCover(audioPath, coverUrl, meta) { const ext = path.extname(audioPath).toLowerCase(); const coverPath = audioPath + '.cover.jpg'; try { const coverBuf = await downloadBuffer(coverUrl); fs.writeFileSync(coverPath, coverBuf); if (ext === '.mp3' && hasNodeID3()) { const NodeID3 = require('node-id3'); const tags = { title: meta.name, artist: formatPersonNames(meta.artist), album: meta.album, image: coverPath, }; if (meta.albumArtist) tags.performerInfo = formatPersonNames(meta.albumArtist); if (meta.compilation) tags.TCMP = '1'; if (meta.year) tags.year = String(meta.year); if (meta.date) tags.date = String(meta.date); if (meta.trackNo) tags.trackNumber = String(meta.trackNo); if (meta.disc) tags.partOfSet = String(meta.disc); if (meta.genre) tags.genre = meta.genre; if (meta.composer) tags.composer = formatPersonNames(meta.composer); if (meta.lyricist) tags.textWriter = formatPersonNames(meta.lyricist); if (meta.publisher) tags.publisher = meta.publisher; const userTexts = []; if (meta.arranger) userTexts.push({ description: 'ARRANGER', value: formatPersonNames(meta.arranger) }); if (meta.producer) userTexts.push({ description: 'PRODUCER', value: formatPersonNames(meta.producer) }); if (meta.mixer) userTexts.push({ description: 'MIXER', value: formatPersonNames(meta.mixer) }); if (userTexts.length) tags.userDefinedText = userTexts; if (meta.lyrics) tags.unsynchronisedLyrics = { language: 'chi', text: meta.lyrics }; const ok = NodeID3.write(tags, audioPath); if (ok === true) { console.log(' ✅ 封面+标签+歌词已嵌入 (node-id3)'); return; } } if (hasFFmpeg()) { const tmpPath = audioPath + '.tmp' + ext; const args = [ '-y', '-i', audioPath, '-i', coverPath, '-map', '0:a', '-map', '1:0', '-c', 'copy', '-disposition:v:0', 'attached_pic', ]; const addMeta = (key, value) => { if (value !== undefined && value !== null && value !== '') { args.push('-metadata', `${key}=${value}`); } }; addMeta('title', meta.name); addMeta('artist', formatPersonNames(meta.artist)); addMeta('album', meta.album); addMeta('album_artist', formatPersonNames(meta.albumArtist || meta.artist)); addMeta('compilation', meta.compilation ? '1' : undefined); addMeta('date', meta.date || meta.year); addMeta('track', meta.trackNo); addMeta('disc', meta.disc); addMeta('genre', meta.genre); addMeta('composer', formatPersonNames(meta.composer)); addMeta('publisher', meta.publisher); addMeta('lyricist', formatPersonNames(meta.lyricist)); addMeta('arranger', formatPersonNames(meta.arranger)); addMeta('producer', formatPersonNames(meta.producer)); addMeta('mixer', formatPersonNames(meta.mixer)); if (meta.lyrics && meta.lyrics.length <= 8000) addMeta('lyrics', meta.lyrics); args.push('-metadata:s:v', 'title=Album cover', '-metadata:s:v', 'comment=Cover (front)'); if (ext === '.mp3') args.push('-id3v2_version', '3'); args.push(tmpPath); execFileSync('ffmpeg', args, { stdio: 'ignore' }); fs.renameSync(tmpPath, audioPath); console.log(' ✅ 封面+标签+歌词已嵌入 (ffmpeg)'); } else { console.log(' ⚠️ 跳过封面: 需要 node-id3 (npm i node-id3) 或 ffmpeg'); } } catch (e) { console.error(` ⚠️ 封面嵌入失败: ${e.message}`); } finally { if (fs.existsSync(coverPath)) fs.unlinkSync(coverPath); } } module.exports = { hasFFmpeg, hasNodeID3, embedCover };