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.

100 lines (99 loc) 4.59 kB
"use strict"; 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 }; }