@lzwme/m3u8-dl
Version:
A free, open-source, and powerful m3u8 video batch downloader with multi-threaded downloading, play-while-downloading, WebUI management, video parsing, and more.
104 lines (103 loc) • 4.12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = exports.getRetry = exports.request = void 0;
exports.isSupportFfmpeg = isSupportFfmpeg;
exports.findFiles = findFiles;
exports.getLocation = getLocation;
exports.formatHeaders = formatHeaders;
exports.checkFileExists = checkFileExists;
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const fe_utils_1 = require("@lzwme/fe-utils");
exports.request = new fe_utils_1.Request({
headers: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' },
reqOptions: { rejectUnauthorized: false },
});
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const getRetry = (url, headers, retries = 3) => (0, fe_utils_1.retry)(() => exports.request.get(url, null, formatHeaders(headers), { rejectUnauthorized: false }), 1000, retries, r => {
if (r.response.statusCode !== 200) {
console.log();
exports.logger.warn(`[retry][${url}][${r.response.statusCode}]`, r.response.statusMessage || r.data);
// throw Error(`[${r.response.statusCode}]${r.response.statusMessage || r.data}`);
}
return r.response.statusCode === 200;
});
exports.getRetry = getRetry;
exports.logger = fe_utils_1.NLogger.getLogger('[M3U8-DL]', { color: fe_utils_1.color });
const ffmpegTestCache = {};
function isSupportFfmpeg(ffmpegBin) {
if (!(ffmpegBin in ffmpegTestCache))
ffmpegTestCache[ffmpegBin] = (0, fe_utils_1.execSync)(`${ffmpegBin} -version`).stderr === '';
return ffmpegTestCache[ffmpegBin];
}
function findFiles(apidir, validate) {
const files = [];
if (apidir && (0, node_fs_1.existsSync)(apidir)) {
const stat = (0, node_fs_1.statSync)(apidir);
if (!validate || validate(apidir, stat)) {
if (stat.isFile()) {
files.push((0, node_path_1.resolve)(apidir));
}
else if (stat.isDirectory()) {
for (const filename of (0, node_fs_1.readdirSync)(apidir)) {
files.push(...findFiles((0, node_path_1.resolve)(apidir, filename)));
}
}
}
}
return files;
}
/** 获取重定向后的 URL */
async function getLocation(url, method = 'HEAD') {
const { res } = await exports.request.req(url, null, { method, headers: { 'content-type': 'text/html' } }, false);
const rurl = res.headers.location || res.headers['x-redirect'] || res.headers['x-location'];
if (typeof rurl === 'string' && rurl !== url)
return getLocation(rurl, method);
return url;
}
/**
* 将传入的 headers 转换为统一的小写键对象格式
* 如果 headers 是字符串,会先将其解析为对象;如果 headers 为空,则返回空对象。
*/
function formatHeaders(headers) {
if (!headers)
return {};
if (typeof headers === 'string') {
headers = headers.trim();
if (headers.startsWith('{') && headers.endsWith('}')) {
try {
headers = JSON.parse(headers);
}
catch (e) {
console.error('解析 headers 失败:', e);
}
}
if (typeof headers === 'string') {
const parsed = {};
headers
.replace(/,\s*([a-zA-Z0-9_-]+:)/g, '\n$1') // 支持如 "Key1: Val1, Key2: Val2" 的格式
.split('\n')
.forEach(line => {
const idx = line.indexOf(':');
if (idx > 0)
parsed[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
});
headers = parsed;
}
}
return (0, fe_utils_1.toLowcaseKeyObject)(headers);
}
/** 异步检查文件是否存在 */
async function checkFileExists(filepath) {
try {
if (!filepath)
return false;
await (0, promises_1.access)(filepath, promises_1.constants.F_OK);
return true;
}
catch (error) {
exports.logger.debug('checkFileExists failed:', filepath, error.message);
return false;
}
}