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.

98 lines (97 loc) 3.91 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseM3U8 = parseM3U8; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const fe_utils_1 = require("@lzwme/fe-utils"); const m3u8_parser_1 = require("m3u8-parser"); const utils_1 = require("./utils"); /** * 解析 m3u8 文件 * @param content m3u8 文件的内容,可为 http 远程地址、本地文件路径 * @param cacheDir 缓存文件保存目录 */ async function parseM3U8(content, cacheDir = './cache', headers) { let url = process.cwd(); if (content.startsWith('http')) { url = content; content = (await (0, utils_1.getRetry)(url, headers)).data; } else if (!content.includes('\n') && (0, node_fs_1.existsSync)(content)) { url = (0, node_path_1.resolve)(process.cwd(), content); content = await node_fs_1.promises.readFile(url, 'utf8'); } if (!content) { utils_1.logger.error('获取播放列表为空!', url); } utils_1.logger.debug('starting parsing m3u8 file:', url); let parser = new m3u8_parser_1.Parser(); parser.push(content); parser.end(); utils_1.logger.debug('parser.manifest', parser.manifest); if (parser.manifest.playlists?.length > 0) { let maxBandwidthItem = parser.manifest.playlists[0]; for (const item of parser.manifest.playlists) { if (!maxBandwidthItem || (item.attributes?.BANDWIDTH || 0) > (maxBandwidthItem.attributes?.BANDWIDTH || 0)) { maxBandwidthItem = item; } } url = new URL(maxBandwidthItem.uri, url).toString(); utils_1.logger.debug('maxBandwidthItem', maxBandwidthItem, url); content = (await (0, utils_1.getRetry)(url, headers)).data; parser = new m3u8_parser_1.Parser(); parser.push(content); parser.end(); } const tsList = parser.manifest.segments || []; const result = { manifest: parser.manifest, /** ts 文件数量 */ tsCount: tsList.length, /** 总时长 */ duration: 0, data: [], /** 加密相关信息 */ crypto: {}, }; if (!result.tsCount) { utils_1.logger.error('m3u8 file error!\n', url, content); return result; } for (const [i, item] of tsList.entries()) { if (!item.uri.includes('://')) item.uri = new URL(item.uri, url).toString(); if (item.key) { const tsKeyInfo = item.key; if (!tsKeyInfo.uri.includes('://')) tsKeyInfo.uri = new URL(tsKeyInfo.uri, url).toString(); if (tsKeyInfo?.uri && !result.crypto[tsKeyInfo.uri]) { const r = await (0, utils_1.getRetry)(tsKeyInfo.uri, headers); if (r.response.statusCode !== 200) { utils_1.logger.error('获取加密 key 失败:', tsKeyInfo.uri, r.response.statusCode, r.data); } else { result.crypto[tsKeyInfo.uri] = { uri: tsKeyInfo.uri, method: tsKeyInfo.method.toUpperCase() || 'AES-128', iv: typeof tsKeyInfo.iv === 'string' ? new Uint8Array(Buffer.from(tsKeyInfo.iv)) : tsKeyInfo.iv, key: r.buffer, }; } } } result.data.push({ index: i, duration: item.duration, timeline: item.timeline || result.duration, uri: item.uri, tsOut: (0, node_path_1.resolve)(cacheDir, `${(0, fe_utils_1.md5)(item.uri)}.ts`), keyUri: item.key?.uri || '', m3u8: url, }); result.duration += item.duration; } result.duration = +Number(result.duration).toFixed(2); return result; } // parseM3U8('', 't.m3u8').then(d => console.log(d));