@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.
100 lines (99 loc) • 4.59 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.localPlay = localPlay;
exports.toLocalM3u8 = toLocalM3u8;
const node_fs_1 = require("node:fs");
const node_http_1 = require("node:http");
const node_path_1 = require("node:path");
const fe_utils_1 = require("@lzwme/fe-utils");
const console_log_colors_1 = require("console-log-colors");
const utils_js_1 = require("./utils.js");
/**
* 边下边看
*/
async function localPlay(m3u8Info) {
if (!m3u8Info?.length)
return null;
const cacheDir = (0, node_path_1.dirname)(m3u8Info[0].tsOut);
const cacheDirname = (0, node_path_1.basename)(cacheDir);
const cacheFilepath = toLocalM3u8(m3u8Info);
const filename = (0, node_path_1.basename)(cacheFilepath);
const info = await createLocalServer((0, node_path_1.dirname)(cacheDir));
const playUrl = `https://m3u8-player.lzw.me?from=sdk&url=${encodeURIComponent(`${info.origin}/${cacheDirname}/${filename}`)}`;
const cmd = `${process.platform === 'win32' ? 'start' : 'open'} ${playUrl}`;
(0, fe_utils_1.execSync)(cmd);
return info;
}
function toLocalM3u8(m3u8Info, m3u8FilePath = '', host = '') {
const cacheDir = (0, node_path_1.dirname)(m3u8Info[0].tsOut);
const cacheDirname = (0, node_path_1.basename)(cacheDir);
if (!m3u8FilePath)
m3u8FilePath = (0, node_path_1.resolve)(cacheDir, `${cacheDirname}.m3u8`);
if ((0, node_fs_1.existsSync)(m3u8FilePath))
return m3u8FilePath;
const m3u8ContentList = [
'#EXTM3U',
'#EXT-X-VERSION:3',
'#EXT-X-ALLOW-CACHE:YES',
`#EXT-X-TARGETDURATION:${Math.max(...m3u8Info.map(d => d.duration))}`,
'#EXT-X-MEDIA-SEQUENCE:0',
// `#EXT-X-KEY:METHOD=AES-128,URI="/api/aes/enc.key"`,
];
if (host && !host.endsWith('/'))
host += '/';
for (const d of m3u8Info) {
if (d.tsOut)
m3u8ContentList.push(`#EXTINF:${Number(d.duration).toFixed(6)},`, `${host}${(0, node_path_1.basename)(d.tsOut)}`);
}
m3u8ContentList.push('#EXT-X-ENDLIST');
const m3u8Content = m3u8ContentList.join('\n');
const ext = (0, node_path_1.extname)(m3u8FilePath);
if (ext !== '.m3u8')
m3u8FilePath = `${m3u8FilePath.replace(ext, '')}.m3u8`;
m3u8FilePath = (0, node_path_1.resolve)(cacheDir, m3u8FilePath);
(0, fe_utils_1.mkdirp)(cacheDir);
(0, node_fs_1.writeFileSync)(m3u8FilePath, m3u8Content, 'utf8');
return m3u8FilePath;
}
async function createLocalServer(baseDir) {
baseDir = (0, node_path_1.resolve)(baseDir);
const port = await (0, fe_utils_1.findFreePort)();
const origin = `http://localhost:${port}`;
const server = (0, node_http_1.createServer)((req, res) => {
const filename = (0, node_path_1.join)(baseDir, decodeURIComponent(req.url));
utils_js_1.logger.debug('[req]', req.url, filename);
if ((0, node_fs_1.existsSync)(filename)) {
const stats = (0, node_fs_1.statSync)(filename);
const ext = (0, node_path_1.extname)(filename);
if (stats.isFile()) {
if (ext === '.m3u8')
res.setHeader('Cache-Control', 'no-cache');
res.writeHead(200, {
'Last-Modified': stats.mtime.toUTCString(),
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Origin': '*',
'Content-Length': String(stats.size),
'Content-Type': ext === '.ts' ? 'video/mp2t' : ext === '.m3u8' ? 'application/vnd.apple.mpegurl' : 'text/plain',
});
(0, node_fs_1.createReadStream)(filename).pipe(res);
return;
}
if (stats.isDirectory()) {
const html = (0, node_fs_1.readdirSync)(filename).map(fname => {
const rpath = (0, node_path_1.resolve)(filename, fname).replace(baseDir, '');
return `<li><a href="${rpath}">${rpath}</a></li>`;
});
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`<ol>${html.join('')}</ol>`);
return;
}
}
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Not found');
}).listen(port, () => {
console.log();
utils_js_1.logger.info('Created Local Server:', console_log_colors_1.color.greenBright(origin));
});
return { port, origin, server };
}