bk
Version:
Download bilibili videos;B站视频下载;支持新版BV号;支持分P及一键多P下载;支持多质量选择;
216 lines (192 loc) • 6.68 kB
JavaScript
;
const biliTool = require('./biliTool');
const ktool = require('ktool');
const outVideo = require('./outVideo');
// const readlineSync = require('readline-sync');
const stdinPlus = ktool.stdinPlus;
const Path = require('path');
const Fs = require('fs');
const pwdDir = process.cwd();
const saveDir = 'video';
const cookieTxt = 'cookie.txt';
const saveDirPath = Path.join(pwdDir, saveDir);
const cookieFilePath = Path.join(pwdDir, cookieTxt);
const noMergeTxt = '不转码(未安装ffmpeg请选择此项)';
const checkCookieFile = async function() {
if (!Fs.existsSync(saveDirPath)) {
Fs.mkdirSync(saveDirPath);
console.log('创建输出目录:', saveDirPath);
}
let isSetCookie = 0;
if (!Fs.existsSync(cookieFilePath)) {
Fs.writeFileSync(cookieFilePath, '');
console.log('创建cookie配置文件:', cookieFilePath);
console.log('[可选操作]:如有新cookie请复制到以上文件(' + cookieTxt + ')中,以下载与账号权限匹配的视频质量.');
isSetCookie = await stdinPlus.keySelectSync(['使用空cookie', '我已录入新cookie']);
} else {
isSetCookie = 1;
}
if (isSetCookie < 0) {
console.log('取消退出');
return false;
}
if (isSetCookie !== 0) {
const newCookie = '' + Fs.readFileSync(cookieFilePath);
biliTool.setCookie(newCookie);
console.log('使用cookie: %s\n', newCookie);
} else {
console.log('使用空cookie \n');
}
outVideo.setSaveDirPath(saveDirPath);
return true;
};
const XOR_CODE = 23442827791579n;
const MASK_CODE = 2251799813685247n;
const MAX_bvid = 1n << 51n;
const BASE = 58n;
const data = 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
const bv2av = function(bvid) {
const bvidArr = Array.from(bvid);
[bvidArr[3], bvidArr[9]] = [bvidArr[9], bvidArr[3]];
[bvidArr[4], bvidArr[7]] = [bvidArr[7], bvidArr[4]];
bvidArr.splice(0, 3);
const tmp = bvidArr.reduce((pre, bvidChar) => pre * BASE + BigInt(data.indexOf(bvidChar)), 0n);
return Number((tmp & MASK_CODE) ^ XOR_CODE);
};
// 质量值对应
// '高清1080P60(大会员)': 116,
// '高清1080P+(大会员)': 112,
// '高清1080P': 80,
// '高清720P60(大会员)': 74,
// '高清720P': 64,
// '清晰480P': 32,
// '流畅360P': 16,
const downloadOne = async function(bvid, cid, title, pNum, quality, outFormat, isNeedQulityCheck) {
let oneQality = -1;
title = title.replace('\\', '-').replace('/', '-');
if (isNeedQulityCheck) {
const dInfo = await biliTool.getDownloadInfo(bvid, cid, 16);
for (let i = 0, len = dInfo.accept_quality.length; i < len; i++) {
if (dInfo.accept_quality[i] === quality) {
oneQality = quality;
break;
}
}
if (oneQality === -1) {
oneQality = dInfo.accept_quality[0]; //如果没有指定的质量,默认使用最佳质量
}
} else {
oneQality = quality;
}
// console.log('oneQality:', oneQality);
const downloadInfo = await biliTool.getDownloadInfo(bvid, cid, oneQality);
// console.log('downloadInfo',downloadInfo);
const dUrlArr = downloadInfo.durl;
for (let i = 0, len = dUrlArr.length; i < len; i++) {
const item = dUrlArr[i];
let extName = null;
const paraIndex = item.url.indexOf('?');
if (paraIndex > 1) {
extName = Path.extname(item.url.substring(0, paraIndex));
} else {
extName = Path.extname(item.url);
}
const downloadResult = await biliTool.downloadFile(bvid, item.size, item.length, item.url, `[p${pNum}]${title}-${i}` + extName);
const filePath = Path.parse(downloadResult.name);
// 合并&转码视频
if (outFormat === noMergeTxt) {
console.log('跳过执行转码&合并');
return;
}
await ktool.promi(outVideo.mergeVideos)(filePath.name, filePath.ext.substring(1), len, outFormat);
// console.log('one done. cid:', cid);
}
};
async function main() {
if (!checkCookieFile()) {
return;
}
let pNum = 0;
let input = await stdinPlus.questionSync('请输入BiliBili的av/BV号(或url):');
if (input.length < 6) {
console.log('错误: url/av/BV号 录入错误');
process.exit();
return;
}
let p1 = input.lastIndexOf('BV1');
if (p1 < 0) {
console.log('错误: url/av/BV号 录入错误');
process.exit();
return;
}
let bvid = input.substring(p1);
p1 = bvid.indexOf('/');
if (p1 > 0) {
bvid = bvid.substring(0, p1);
}
bvid = bvid.trim();
console.log('bvid:[%s]', bvid);
// 检查是否是多P
const videoInfo = await biliTool.getVideoInfo(bvid);
if (videoInfo.pages && videoInfo.pages.length > 1) {
const pageArr = [];
const pageCount = videoInfo.pages.length;
for (let i = 0; i < pageCount; i++) {
const pageOne = videoInfo.pages[i];
pageArr.push(pageOne.part);
}
pageArr.push('===>下载所有分P');
const pNumStr = await stdinPlus.keySelectSync(pageArr, '请选择分P号:');
pNum = parseInt(pNumStr);
if (pNum < 0) {
console.log('取消退出');
process.exit();
return;
}
if (pNum >= pageArr.length - 1) {
pNum = -999; //最后一个选项,表示下载所有
}
// console.log('pNum:', pNum);
} else {
console.log('单P -> ' + videoInfo.title);
}
const downloadInfo = await biliTool.getDownloadInfo(bvid, videoInfo.cid, 16);
// console.log('downloadInfo',downloadInfo);
const qualityDesArr = downloadInfo.accept_description;
const qualityArr = downloadInfo.accept_quality;
const qualityIndex = await stdinPlus.keySelectSync(qualityDesArr, '请选择下载质量?');
if (qualityIndex < 0) {
console.log('取消退出');
process.exit();
return;
}
const quality = qualityArr[qualityIndex];
// console.log('quality', quality);
const outFormatArr = ['mp4', 'mp3', noMergeTxt];
const outFormatIndex = await stdinPlus.keySelectSync(outFormatArr, '请选择转码&合并的输出格式?');
if (outFormatIndex < 0) {
console.log('取消退出');
process.exit();
return;
}
const outFormat = outFormatArr[outFormatIndex];
// console.log('outFormat:', outFormat);
try {
if (pNum !== -999) {
const vOne = videoInfo.pages[pNum];
await downloadOne(bvid, vOne.cid, vOne.part || videoInfo.title, pNum + 1, quality, outFormat);
} else {
for (let i = 0, len = videoInfo.pages.length; i < len; i++) {
const vOne = videoInfo.pages[i];
await downloadOne(bvid, vOne.cid, vOne.part, vOne.page, quality, outFormat, true);
}
}
console.log('====> 所有任务完成.');
process.exit();
} catch (err) {
console.error(err);
}
}
exports.setCookie = biliTool.setCookie;
exports.main = main;
//