refactor: split downloader into modules
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync, execFileSync } = require('child_process');
|
||||
const { downloadBuffer } = require('./network');
|
||||
|
||||
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: meta.artist,
|
||||
album: meta.album,
|
||||
image: coverPath,
|
||||
};
|
||||
if (meta.albumArtist) tags.performerInfo = meta.albumArtist;
|
||||
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 = meta.composer;
|
||||
if (meta.lyricist) tags.textWriter = meta.lyricist;
|
||||
if (meta.publisher) tags.publisher = meta.publisher;
|
||||
|
||||
const userTexts = [];
|
||||
if (meta.arranger) userTexts.push({ description: 'ARRANGER', value: meta.arranger });
|
||||
if (meta.producer) userTexts.push({ description: 'PRODUCER', value: meta.producer });
|
||||
if (meta.mixer) userTexts.push({ description: 'MIXER', value: 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', meta.artist);
|
||||
addMeta('album', meta.album);
|
||||
addMeta('album_artist', meta.albumArtist || meta.artist);
|
||||
addMeta('date', meta.date || meta.year);
|
||||
addMeta('track', meta.trackNo);
|
||||
addMeta('disc', meta.disc);
|
||||
addMeta('genre', meta.genre);
|
||||
addMeta('composer', meta.composer);
|
||||
addMeta('publisher', meta.publisher);
|
||||
addMeta('lyricist', meta.lyricist);
|
||||
addMeta('arranger', meta.arranger);
|
||||
addMeta('producer', meta.producer);
|
||||
addMeta('mixer', 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 };
|
||||
Reference in New Issue
Block a user