import { CriUserAgent, UserAgent } from './constants'; import { Parser } from 'm3u8-parser'; import * as crypto from 'crypto'; import fs from 'fs'; type Manifest = { allowCache: boolean; endList: boolean; mediaSequence: number; discontinuitySequence: number; targetDuration: number; discontinuityStarts: number[]; segments: { duration: number; uri: string; timeline: number; key: { method: string; uri: string; iv: Record; }; }[]; mediaGroups: { AUDIO: { [key: string]: { default: string; autoselect: boolean; uri: string; }; }; VIDEO: { [key: string]: { default: string; autoselect: boolean; uri: string; }; }; 'CLOSED-CAPTIONS': { [key: string]: { default: string; autoselect: boolean; uri: string; }; }; SUBTITLES: { [key: string]: { default: string; autoselect: boolean; uri: string; }; }; }; playlists: { attributes: { AUDIO: string; 'FRAME-RATE': number; RESOLUTION: { width: number; height: number; }; CODECS: string; 'AVERAGE-BANDWIDTH': string; BANDWIDTH: number; }; uri: string; timeline: number; }[]; }; export function getMediaPair(targetVideoId: string, targetAudioId: string, m3u8: string) { const parser = new Parser(); parser.push(m3u8); parser.end(); const manifest: Manifest = parser.manifest; const audio = manifest.mediaGroups.AUDIO[targetAudioId]; const video = manifest.playlists.find(playlist => playlist.uri.includes(targetVideoId)); return { audio, video, }; } export const downloadAesM3u8 = async (url: string, cookie: string) => { const m3u8 = await fetch(url, { headers: { Cookie: cookie, 'User-Agent': UserAgent, }, }).then(res => res.text()); const parser = new Parser(); parser.push(m3u8); parser.end(); const manifest: Manifest = parser.manifest; // console.log(JSON.stringify(manifest, null, 2)); const initSegment = await fetch(manifest.segments[0].map.uri, { headers: { Cookie: cookie, 'User-Agent': UserAgent, }, }).then(res => res.arrayBuffer()); const iv = Buffer.alloc(Object.keys(manifest.segments[0].key.iv).length * 4); for (const key in manifest.segments[0].key.iv) { iv.writeUInt32BE(manifest.segments[0].key.iv[key], parseInt(key) * 4); } const key = await fetch(manifest.segments[0].key.uri, { headers: { Cookie: cookie, 'User-Agent': UserAgent, }, }).then(res => res.arrayBuffer()); const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(key), iv); const segments: Buffer[] = []; segments.push(Buffer.from(initSegment)); let i = 0; for (const segment of manifest.segments) { const res = await fetch(segment.uri, { headers: { Cookie: cookie, 'User-Agent': UserAgent, }, }); const buffer = await res.arrayBuffer(); const arrayBufferView = new Uint8Array(buffer); let decrypted = decipher.update(arrayBufferView); if (i > 0) { decrypted = decrypted.subarray(32, decrypted.length) } fs.writeFileSync(`${i}.mp4`, Buffer.from(decrypted)); segments.push(decrypted); i++; } segments.push(decipher.final()); const blob = new Blob(segments, { type: 'video/mp4' }); return blob.arrayBuffer(); }; export function bufferToHexString(buffer: Buffer) { return Array.prototype.map .call(buffer, function (x: number) { return ('00' + x.toString(16)).slice(-2); }) .join(''); } export function objToUrlParams(obj: Record) { return Object.keys(obj) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) .join('&'); } export function objToCookieString(obj: Record) { return Object.keys(obj) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) .join('; '); } export function cookieStringToObj(cookieString: string) { const obj: Record = {}; cookieString.split(';').forEach(cookie => { const [key, value] = cookie.trim().split('='); obj[key] = value; }); return obj; }