Merge pull request #464 from souvenp/feat/flac-tags

feat: flac tags support and optimize cover images
This commit is contained in:
Alger
2025-09-13 22:22:13 +08:00
committed by GitHub
2 changed files with 70 additions and 11 deletions

View File

@@ -32,12 +32,14 @@
"electron-window-state": "^5.0.3",
"express": "^4.18.2",
"file-type": "^21.0.0",
"flac-tagger": "^1.0.7",
"font-list": "^1.5.1",
"husky": "^9.1.7",
"music-metadata": "^11.2.3",
"netease-cloud-music-api-alger": "^4.26.1",
"node-id3": "^0.2.9",
"node-machine-id": "^1.1.12",
"sharp": "^0.34.3",
"vue-i18n": "^11.1.3"
},
"devDependencies": {

View File

@@ -9,7 +9,8 @@ import * as mm from 'music-metadata';
import * as NodeID3 from 'node-id3';
import * as os from 'os';
import * as path from 'path';
import { FlacTagMap, writeFlacTags } from 'flac-tagger';
import sharp from 'sharp';
import { getStore } from './config';
const MAX_CONCURRENT_DOWNLOADS = 3;
@@ -386,7 +387,7 @@ async function downloadMusic(
let formattedFilename = filename;
if (songInfo) {
// 准备替换变量
const artistName = songInfo.ar?.map((a: any) => a.name).join('/') || '未知艺术家';
const artistName = songInfo.ar?.map((a: any) => a.name).join('') || '未知艺术家';
const songName = songInfo.name || filename;
const albumName = songInfo.al?.name || '未知专辑';
@@ -576,8 +577,39 @@ async function downloadMusic(
timeout: 10000
});
// 获取封面图片的buffer
coverImageBuffer = Buffer.from(coverResponse.data);
const originalCoverBuffer = Buffer.from(coverResponse.data);
const TWO_MB = 2 * 1024 * 1024;
// 检查图片大小是否超过2MB
if (originalCoverBuffer.length > TWO_MB) {
const originalSizeMB = (originalCoverBuffer.length / (1024 * 1024)).toFixed(2);
console.log(`封面图大于2MB (${originalSizeMB} MB),开始压缩...`);
try {
// 使用 sharp 进行压缩
coverImageBuffer = await sharp(originalCoverBuffer)
.resize({
width: 1600,
height: 1600,
fit: 'inside',
withoutEnlargement: true
})
.jpeg({
quality: 80,
mozjpeg: true
})
.toBuffer();
const compressedSizeMB = (coverImageBuffer.length / (1024 * 1024)).toFixed(2);
console.log(`封面图压缩完成,新大小: ${compressedSizeMB} MB`);
} catch (compressionError) {
console.error('封面图压缩失败,将使用原图:', compressionError);
coverImageBuffer = originalCoverBuffer; // 如果压缩失败,则回退使用原始图片
}
} else {
// 如果图片不大于2MB直接使用原图
coverImageBuffer = originalCoverBuffer;
}
console.log('封面已准备好,将写入元数据');
}
}
@@ -588,7 +620,7 @@ async function downloadMusic(
const fileFormat = fileExtension.toLowerCase();
const artistNames =
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('/ ') || '未知艺术家';
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('') || '未知艺术家';
// 根据文件类型处理元数据
if (['.mp3'].includes(fileFormat)) {
@@ -598,7 +630,7 @@ async function downloadMusic(
NodeID3.removeTags(finalFilePath);
const tags = {
title: filename,
title: songInfo?.name,
artist: artistNames,
TPE1: artistNames,
TPE2: artistNames,
@@ -634,10 +666,35 @@ async function downloadMusic(
} catch (err) {
console.error('Error writing ID3 tags:', err);
}
} else {
// 对于非MP3文件使用music-metadata来写入元数据可能需要专门的库
// 或者根据不同文件类型使用专用工具,暂时只记录但不处理
console.log(`文件类型 ${fileFormat} 不支持使用NodeID3写入标签跳过元数据写入`);
} else if (['.flac'].includes(fileFormat)) {
try {
const tagMap: FlacTagMap = {
TITLE: songInfo?.name,
ARTIST: artistNames,
ALBUM: songInfo?.al?.name || songInfo?.song?.album?.name || songInfo?.name || filename,
LYRICS: lyricsContent || '',
TRACKNUMBER: songInfo?.no ? String(songInfo.no) : undefined,
DATE: songInfo?.publishTime
? new Date(songInfo.publishTime).getFullYear().toString()
: undefined
};
await writeFlacTags(
{
tagMap,
picture: coverImageBuffer
? {
buffer: coverImageBuffer,
mime: 'image/jpeg'
}
: undefined
},
finalFilePath
);
console.log('FLAC tags written successfully');
} catch (err) {
console.error('Error writing FLAC tags:', err);
}
}
// 保存下载信息
@@ -683,7 +740,7 @@ async function downloadMusic(
// 发送桌面通知
try {
const artistNames =
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('/') ||
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('') ||
'未知艺术家';
const notification = new Notification({
title: '下载完成',