feat: support selective album downloads and tag cleanup
This commit is contained in:
+59
-14
@@ -38,6 +38,27 @@ function createRL() {
|
||||
return { rl, ask };
|
||||
}
|
||||
|
||||
function parseNumberSelection(input, total, { emptyMeansAll = false } = {}) {
|
||||
const value = input.trim().toLowerCase();
|
||||
if (value === 'a' || (!value && emptyMeansAll)) {
|
||||
return Array.from({ length: total }, (_, index) => index + 1);
|
||||
}
|
||||
if (!value || value === '0') return [];
|
||||
|
||||
const indices = new Set();
|
||||
for (const part of value.split(/[,,\s]+/)) {
|
||||
const rangeMatch = part.match(/^(\d+)-(\d+)$/);
|
||||
if (rangeMatch) {
|
||||
const start = parseInt(rangeMatch[1], 10);
|
||||
const end = parseInt(rangeMatch[2], 10);
|
||||
for (let number = start; number <= end; number++) indices.add(number);
|
||||
} else if (/^\d+$/.test(part)) {
|
||||
indices.add(parseInt(part, 10));
|
||||
}
|
||||
}
|
||||
return [...indices].filter(number => number >= 1 && number <= total).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
function printBanner() {
|
||||
console.log('');
|
||||
console.log('╔══════════════════════════════════════════╗');
|
||||
@@ -62,7 +83,7 @@ function printMenu() {
|
||||
console.log('│ 9. 下载历史 │');
|
||||
console.log('│ 0. 设置音质等级 │');
|
||||
console.log('│ s. 按歌名搜索 │');
|
||||
console.log('│ a. 下载完整专辑 │');
|
||||
console.log('│ a. 下载专辑/指定曲目 │');
|
||||
console.log('│ b. 批量ID下载 │');
|
||||
console.log('│ f. 从文件批量下载 │');
|
||||
console.log('│ n. 从文件名识别并补全标签 │');
|
||||
@@ -962,22 +983,34 @@ async function main() {
|
||||
console.log(` ${String(i + 1).padStart(2)}. ${track.name} - ${artists}`);
|
||||
});
|
||||
|
||||
const trackPick = await ask('\n选择歌曲序号 (如 1,3,5 或 2-6,a/回车=全部,0=取消): ');
|
||||
const selectedIndices = parseNumberSelection(trackPick, album.tracks.length, { emptyMeansAll: true });
|
||||
if (!selectedIndices.length) {
|
||||
if (trackPick.trim() !== '0') console.log('⚠️ 没有有效的歌曲序号');
|
||||
break;
|
||||
}
|
||||
const selectedTracks = selectedIndices.map(number => ({
|
||||
track: album.tracks[number - 1],
|
||||
trackIndex: number - 1,
|
||||
}));
|
||||
|
||||
level = await askLevel(ask, level);
|
||||
const confirm = await ask(`确认下载整张专辑? (y/n): `);
|
||||
const confirm = await ask(`确认下载选中的 ${selectedTracks.length} 首歌曲? (y/n): `);
|
||||
if (confirm.trim().toLowerCase() !== 'y') break;
|
||||
|
||||
const albumTarget = getAlbumDownloadTarget(album, album.tracks[0], 0);
|
||||
const firstItem = selectedTracks[0];
|
||||
const albumTarget = getAlbumDownloadTarget(album, firstItem.track, firstItem.trackIndex);
|
||||
await saveAlbumCover(album.picUrl, albumTarget.outputDir);
|
||||
|
||||
for (let i = 0; i < album.tracks.length; i++) {
|
||||
const track = album.tracks[i];
|
||||
for (let i = 0; i < selectedTracks.length; i++) {
|
||||
const { track, trackIndex } = selectedTracks[i];
|
||||
const artists = track.ar?.map(a => a.name).join(' / ') || album.artist?.name || '未知';
|
||||
const target = getAlbumDownloadTarget(album, track, i);
|
||||
const target = getAlbumDownloadTarget(album, track, trackIndex);
|
||||
await waitIfPaused();
|
||||
console.log(`\n[${i + 1}/${album.tracks.length}] ${track.name} - ${artists}`);
|
||||
console.log(`\n[${i + 1}/${selectedTracks.length}] ${track.name} - ${artists}`);
|
||||
try { await downloadSong(track.id, level, target); }
|
||||
catch (e) { console.error(` ❌ 出错: ${e.message}`); }
|
||||
if (i < album.tracks.length - 1) await sleep(500);
|
||||
if (i < selectedTracks.length - 1) await sleep(500);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1050,13 +1083,25 @@ async function main() {
|
||||
}
|
||||
|
||||
console.log(`\n💿 共 ${albumsToDownload.length} 张专辑,${allSongs.length} 首歌曲`);
|
||||
allSongs.forEach((item, index) => {
|
||||
const albumSuffix = albumsToDownload.length > 1 ? ` [${item.albumName}]` : '';
|
||||
console.log(` ${String(index + 1).padStart(2)}. ${item.song.name} - ${item.song.artists}${albumSuffix}`);
|
||||
});
|
||||
const songPick = await ask('\n选择歌曲序号 (如 1,3,5 或 2-6,a/回车=全部,0=取消): ');
|
||||
const selectedIndices = parseNumberSelection(songPick, allSongs.length, { emptyMeansAll: true });
|
||||
if (!selectedIndices.length) {
|
||||
if (songPick.trim() !== '0') console.log('⚠️ 没有有效的歌曲序号');
|
||||
break;
|
||||
}
|
||||
const songsToDownload = selectedIndices.map(number => allSongs[number - 1]);
|
||||
|
||||
level = await askLevel(ask, level);
|
||||
const confirm = await ask(`确认下载? (y/n): `);
|
||||
const confirm = await ask(`确认下载选中的 ${songsToDownload.length} 首歌曲? (y/n): `);
|
||||
if (confirm.trim().toLowerCase() !== 'y') break;
|
||||
|
||||
const savedCoverDirs = new Set();
|
||||
for (let i = 0; i < allSongs.length; i++) {
|
||||
const item = allSongs[i];
|
||||
for (let i = 0; i < songsToDownload.length; i++) {
|
||||
const item = songsToDownload[i];
|
||||
const s = item.song;
|
||||
const target = getAlbumDownloadTarget({
|
||||
name: item.albumName,
|
||||
@@ -1068,10 +1113,10 @@ async function main() {
|
||||
savedCoverDirs.add(target.outputDir);
|
||||
}
|
||||
await waitIfPaused();
|
||||
console.log(`\n[${i + 1}/${allSongs.length}] ${s.name} - ${s.artists}`);
|
||||
console.log(`\n[${i + 1}/${songsToDownload.length}] ${s.name} - ${s.artists}`);
|
||||
try { await downloadSong(s.id, level, target); }
|
||||
catch (e) { console.error(` ❌ 出错: ${e.message}`); }
|
||||
if (i < allSongs.length - 1) await sleep(500);
|
||||
if (i < songsToDownload.length - 1) await sleep(500);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2023,7 +2068,7 @@ async function main() {
|
||||
console.log(` ║ 3 输入歌单 ID 下载全部 - 输入歌单ID批量下载 ║`);
|
||||
console.log(` ║ s 按歌名搜索 - 按歌名精确搜索,支持试听/详情/下载 ║`);
|
||||
console.log(` ║ 6 按歌手批量下载 - 搜索歌手并选择下载其歌曲 ║`);
|
||||
console.log(` ║ a 下载完整专辑 - 输入专辑ID下载全部曲目,也支持名称搜索 ║`);
|
||||
console.log(` ║ a 下载专辑 - 输入专辑ID或名称,可按序号选择曲目 ║`);
|
||||
console.log(` ║ ║`);
|
||||
console.log(` ║ 【查看与管理】 ║`);
|
||||
console.log(` ║ 4 查看歌单歌曲列表 - 浏览歌单内容,可选择性下载 ║`);
|
||||
|
||||
Reference in New Issue
Block a user