diff --git a/scripts/utils.ts b/scripts/utils.ts index 0d27fe79d..eb3b4e2f0 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -1,9 +1,13 @@ -import { MasterPlaylist, MediaPlaylist, Variant } from 'hls-parser/types' -import { parse as parsePlaylist } from 'hls-parser' +import axios, { AxiosProxyConfig, AxiosRequestConfig } from 'axios' +import { parse as parsePlaylist, setOptions } from 'hls-parser' +import { parse as parseManifest } from 'mpd-parser' +import { SocksProxyAgent } from 'socks-proxy-agent' +import { ProxyParser } from './core/proxyParser.js' import { TESTING } from './constants.js' import normalizeUrl from 'normalize-url' import { orderBy } from 'es-toolkit' -import axios from 'axios' +import path from 'node:path' +import fs from 'node:fs' export function isURI(string: string): boolean { try { @@ -27,32 +31,110 @@ export function truncate(string: string, limit: number = 100) { return string.slice(0, limit - 3) + '...' } +type StreamInfo = { + resolution: { width: number; height: number } + bandwidth: number + frameRate: number + codecs: string +} + export async function getStreamInfo( url: string, - options: { httpUserAgent: string | null; httpReferrer: string | null } -): Promise { - let playlist: MasterPlaylist | MediaPlaylist | undefined - + options: { + httpUserAgent?: string | null + httpReferrer?: string | null + timeout?: number + proxy?: string + } +): Promise { + let data: string | undefined if (TESTING) { - playlist = (await import('../tests/__data__/input/playlist_update/playlist.mjs')) - .default as unknown as MasterPlaylist + if (url.includes('.m3u8')) { + data = fs.readFileSync( + path.resolve(__dirname, '../tests/__data__/input/playlist_update/playlist.m3u8'), + 'utf8' + ) + } else if (url.includes('.mpd')) { + data = fs.readFileSync( + path.resolve(__dirname, '../tests/__data__/input/playlist_update/manifest.mpd'), + 'utf8' + ) + } } else { try { - const response = await axios(url, { - signal: AbortSignal.timeout(30000), + const timeout = options.timeout || 1000 + let request: AxiosRequestConfig = { + signal: AbortSignal.timeout(timeout), + responseType: 'text', headers: { 'User-Agent': options.httpUserAgent || 'Mozilla/5.0', Referer: options.httpReferrer } - }) + } - playlist = parsePlaylist(response.data) + if (options.proxy !== undefined) { + const proxyParser = new ProxyParser() + const proxy = proxyParser.parse(options.proxy) as AxiosProxyConfig + if ( + proxy.protocol && + ['socks', 'socks5', 'socks5h', 'socks4', 'socks4a'].includes(String(proxy.protocol)) + ) { + const socksProxyAgent = new SocksProxyAgent(options.proxy) + + request = { ...request, ...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent } } + } else { + request = { ...request, ...{ proxy } } + } + } + + const response = await axios(url, request) + + data = response.data } catch {} } - if (playlist && playlist.isMasterPlaylist && playlist.variants.length) { - return orderBy(playlist.variants, ['bandwidth'], ['desc'])[0] + if (!data) return undefined + + let info: StreamInfo | undefined + + if (url.includes('.m3u8')) { + setOptions({ silent: true }) + + try { + const playlist = parsePlaylist(data) + + if (playlist && playlist.isMasterPlaylist && playlist.variants.length) { + const v = orderBy(playlist.variants, ['bandwidth'], ['desc'])[0] + + if (v && v.resolution && v.frameRate && v.codecs) { + info = { + resolution: { width: v.resolution.width, height: v.resolution.height }, + bandwidth: v.bandwidth, + frameRate: v.frameRate, + codecs: v.codecs + } + } + } + } catch {} + } else if (url.includes('.mpd')) { + const manifest = parseManifest(data, { + manifestUri: url, + eventHandler: ({ type, message }) => console.log(`${type}: ${message}`) + }) + + const playlist = orderBy(manifest.playlists, [p => p.attributes.BANDWIDTH], ['desc'])[0] + + if (playlist) { + const attr = playlist.attributes + + info = { + resolution: { width: attr.RESOLUTION.width, height: attr.RESOLUTION.height }, + bandwidth: attr.BANDWIDTH, + frameRate: attr['FRAME-RATE'], + codecs: attr.CODECS + } + } } - return undefined + return info }