yun-playlist-downloader
Version:
NetEase CloudMusic downloader
188 lines (186 loc) • 5.4 kB
JavaScript
import {
DEFAULT_COOKIE_FILE,
baseDebug,
downloadSong,
getAdapter,
getFileName,
readCookie
} from "./chunk-PXDJ2GZT.js";
// src/bin.ts
import { dl } from "dl-vampire";
import createEsmUtils from "esm-utils";
import filenamify from "filenamify";
import humanizeDuration from "humanize-duration";
import { padStart } from "lodash-es";
import logSymbols from "log-symbols";
import ms from "ms";
import path from "path";
import pmap from "promise.map";
import rcFactory from "rc";
import updateNotifier from "update-notifier";
import { hideBin } from "yargs/helpers";
import yargs from "yargs/yargs";
var debug = baseDebug.extend("cli");
var { require: require2 } = createEsmUtils(import.meta);
var packageJson = require2("../package.json");
var { version } = packageJson;
updateNotifier({ pkg: packageJson }).notify();
var DEFAULT_FORMAT = ":name/:singer - :songName.:ext";
if (process.argv.some((s) => s.match(/(dj)?radio/))) {
DEFAULT_FORMAT = ":name/:programDate 第:programOrder期 - :songName.:ext";
}
var config = rcFactory("yun", {
"concurrency": 5,
"format": DEFAULT_FORMAT,
"quality": 999,
"retry-timeout": 3,
// 3 mins
"retry-times": 3,
// 3 times
"skip": true,
"progress": true
});
var parser = yargs(hideBin(process.argv)).command(
"$0 <url>",
"网易云音乐 歌单/专辑 下载器",
// builder
(yargs2) => {
return yargs2.scriptName("yun").usage("Usage: $0 <url> [options]").positional("url", { describe: "歌单/专辑/电台的链接 or 歌单 ID", type: "string" }).alias({
h: "help",
v: "version",
c: "concurrency",
f: "format",
q: "quality",
s: "skip",
p: "progress"
}).options({
concurrency: {
desc: "同时下载数量",
type: "number",
default: 5
},
format: {
desc: "文件格式",
type: "string",
default: DEFAULT_FORMAT
},
quality: {
desc: "音质, 默认 999k 即最大码率, 可选 128/192/320",
type: "number",
default: 999,
choices: [128, 192, 320, 999]
},
retryTimeout: {
desc: "下载超时(分)",
type: "number",
default: 3
},
retryTimes: {
desc: "下载重试次数",
type: "number",
default: 3
},
skip: {
desc: "对于已存在文件且大小合适则跳过",
type: "boolean",
default: true
},
progress: {
desc: "是否显示进度条",
type: "boolean",
default: true
},
cover: {
desc: "下载封面",
type: "boolean",
default: false
},
cookie: {
desc: "cookie文件",
type: "string",
default: DEFAULT_COOKIE_FILE
}
}).config(config).example(`$0 'https://music.163.com/#/playlist?id=7392714527'`, "下载歌单").example("$0 7392714527", "使用 id 下载歌单").example("$0 -c 10 <url>", "10首同时下载").example('$0 -f ":singer - :songName.:ext" <url>', '下载文件名为 "歌手 - 歌名"').epilog("帮助 & 文档: https://github.com/magicdawn/yun-playlist-downloader");
}
).version(version).help();
var argv = await parser.argv;
var {
url,
concurrency,
format,
quality,
retryTimeout,
retryTimes,
skip: skipExists,
progress,
cover
} = argv;
console.log(`
当前参数
concurrency: ${concurrency}
format: ${format}
retry-timeout: ${retryTimeout} (分钟)
retry-times: ${retryTimes} (次)
quality: ${quality}k
skip: ${skipExists}
progress: ${progress}
cover: ${cover}
`);
quality *= 1e3;
retryTimeout = ms(`${retryTimeout} minutes`);
readCookie(argv.cookie);
if (url && /^\d+$/.test(url)) {
url = `https://music.163.com/#/playlist?id=${url}`;
}
var start = Date.now();
var adapter = getAdapter(url);
var name = await adapter.getTitle();
console.log(`正在下载『${name}』,请稍候...`);
if (cover) {
const coverUrl = await adapter.getCover();
if (!coverUrl) {
console.log(`${logSymbols.warning} [cover]: 没有找到封面`);
} else {
const coverExt = path.extname(coverUrl) || ".jpg";
const coverFile = `${filenamify(name)}/cover${coverExt}`;
await dl({ url: coverUrl, file: coverFile });
console.log(`${logSymbols.success} [cover]: 封面已下载 ${coverFile}`);
}
}
var songs = await adapter.getSongs(quality);
debug("songs : %j", songs);
var removed = songs.filter((x) => !x.url);
var keeped = songs.filter((x) => x.url);
if (removed.length) {
console.log(`${logSymbols.warning} [版权受限] 不可下载 ${removed.length}/${songs.length}`);
for (let i of removed) {
console.log(` ${i.singer} - ${i.songName}`);
}
}
console.log(`${logSymbols.info} 可下载 ${keeped.length}/${songs.length} 首`);
var len = keeped.length.toString().length;
keeped.forEach((item, index) => {
item.rawIndex = index;
item.index = padStart(String(index + 1), len, "0");
});
await pmap(
keeped,
(song) => {
const file = getFileName({ format, song, url, name });
return downloadSong({
url: song.url,
file,
song,
totalLength: keeped.length,
retryTimeout,
retryTimes,
skipExists,
progress
});
},
concurrency
);
var dur = humanizeDuration(Date.now() - start, { language: "zh_CN" });
console.log("下载完成, 耗时%s", dur);
process.exit(0);