rc-plugin/utils/acfun.js
zhiyu1998 dc1aa0769c feat: 插件适配Windows
1. 适配windows
2. 应群友的建议提供小写r一样可以呼出帮助
2023-02-11 02:19:09 +08:00

173 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios from 'axios'
import fs from 'node:fs'
import path from 'path'
import child_process from 'node:child_process'
/**
* 去除JSON的一些转义 \\" -> \" ->"
* @param str
*/
function escapeSpecialChars(str) {
return str.replace(/\\\\"/g, '\\"').replace(/\\"/g, '"');
}
const parseVideoName = (videoInfo) => {
const strAc号 = "ac" + (videoInfo?.dougaId || "");
const str标题 = videoInfo?.title;
const str作者 = videoInfo?.user.name;
const str上传时间 = videoInfo?.createTime;
const str描述 = videoInfo?.description;
const raw = [strAc号, str标题, str作者, str上传时间, str描述]
.map((d) => d || "")
.join("_")
.slice(0, 100);
return raw;
};
const parseVideoNameFixed = (videoInfo) => {
const f = parseVideoName(videoInfo);
const t = f.replaceAll(" ", "-");
return t;
};
async function parseUrl(videoUrlAddress) {
// eg https://www.acfun.cn/v/ac4621380?quickViewId=videoInfo_new&ajaxpipe=1
const urlSuffix = "?quickViewId=videoInfo_new&ajaxpipe=1";
const url = videoUrlAddress + urlSuffix;
const raw = await axios.get(url).then(resp => {
return resp.data
});
// Split
const strsRemoveHeader = raw.split("window.pageInfo = window.videoInfo =");
const strsRemoveTail = strsRemoveHeader[1].split("</script>");
const strJson = strsRemoveTail[0];
const strJsonEscaped = escapeSpecialChars(strJson);
/** Object videoInfo */
const videoInfo = JSON.parse(strJsonEscaped);
const videoName = parseVideoNameFixed(videoInfo);
const ksPlayJson = videoInfo.currentVideoInfo.ksPlayJson;
/** Object ksPlay */
const ksPlay = JSON.parse(ksPlayJson);
const representations = ksPlay.adaptationSet[0].representation;
const urlM3u8s = representations.map((d) => d.url);
return { urlM3u8s, videoName };
}
async function parseM3u8(m3u8Url) {
const m3u8File = await axios.get(m3u8Url).then(resp => resp.data);
/** 分离ts文件链接 */
const rawPieces = m3u8File.split(/\n#EXTINF:.{8},\n/);
/** 过滤头部 */
const m3u8RelativeLinks = rawPieces.slice(1);
/** 修改尾部 去掉尾部多余的结束符 */
const patchedTail =
m3u8RelativeLinks[m3u8RelativeLinks.length - 1].split("\n")[0];
m3u8RelativeLinks[m3u8RelativeLinks.length - 1] = patchedTail;
/** 完整链接直接加m3u8Url的通用前缀 */
const m3u8Prefix = m3u8Url.split("/").slice(0, -1).join("/");
const m3u8FullUrls = m3u8RelativeLinks.map((d) => m3u8Prefix + "/" + d);
/** aria2c下载的文件名就是取url最后一段去掉末尾url参数(?之后是url参数) */
const tsNames = m3u8RelativeLinks.map((d) => d.split("?")[0]);
/** 文件夹名,去掉文件名末尾分片号 */
let outputFolderName = tsNames[0].slice(0, -9);
/** 输出最后合并的文件名加个通用mp4后缀 */
const outputFileName = outputFolderName + ".mp4";
return {
m3u8FullUrls,
tsNames,
outputFolderName,
outputFileName,
};
}
// 下载m3u8
async function downloadM3u8Videos(
m3u8FullUrls,
outputFolderName
) {
/** 新建下载文件夹 在当前运行目录下 */
const outPath = outputFolderName;
/** 批下载 */
const strDownloadParamFiles = m3u8FullUrls
.map(async (d, i) => {
return new Promise((resolve, reject) => {
const writer = fs.createWriteStream(outPath + `${i}.ts`);
axios.get(d, {
headers: {
"User-Agent":
"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
},
responseType: "stream",
}).then(dres => {
dres.data.pipe(writer);
writer.on("finish", () => resolve(true));
writer.on("error", () => reject);
});
})
})
/** 写入下载链接列表文件 */
// fs.writeFileSync(path.resolve(outPath, "urls.txt"), str下载参数文件);
return Promise.all(strDownloadParamFiles);
}
function mergeAcFileToMp4(tsNames, FullFileName, outputFileName, shouldDelete = true) {
/** 合并参数列表 格式file path */
const concatStrs = tsNames.map(
(d, i) => `file ${path.resolve(FullFileName, i + ".ts").replace(/\\/g, "/")}`
);
const ffmpegList = path.resolve(FullFileName, 'file.txt');
fs.writeFileSync(ffmpegList, concatStrs.join("\n"));
const outPath = path.resolve(outputFileName);
// 执行命令
let cmd = 'ffmpeg';
// 判断当前环境
let env;
if (process.platform === "win32") {
env = process.env
} else if (process.platform === "linux") {
env = {
...process.env,
PATH: '/usr/local/bin:' + child_process.execSync('echo $PATH').toString(),
};
} else {
console.log("暂时不支持当前操作系统!")
}
return new Promise((resolve, reject) => {
child_process.exec(
`${ cmd } -y -f concat -safe 0 -i "${ ffmpegList }" -c copy "${ outPath }"`,
{ env },
err => {
if (shouldDelete) {
fs.unlink(FullFileName, f => f);
}
if (err) {
reject(err);
}
resolve({ outputFileName });
},
);
});
}
export { parseUrl, parseM3u8, downloadM3u8Videos, mergeAcFileToMp4 }