UNPKG

@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
"use strict"; 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; } }