@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
JavaScript
;
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));