UNPKG

yun-playlist-downloader

Version:
188 lines (186 loc) 5.4 kB
#!/usr/bin/env node 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);