2024-06-25 11:18:16 +09:00

191 lines
3.9 KiB
TypeScript

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<string, number>;
};
}[];
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<string, string | number>) {
return Object.keys(obj)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
.join('&');
}
export function objToCookieString(obj: Record<string, string>) {
return Object.keys(obj)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
.join('; ');
}
export function cookieStringToObj(cookieString: string) {
const obj: Record<string, string> = {};
cookieString.split(';').forEach(cookie => {
const [key, value] = cookie.trim().split('=');
obj[key] = value;
});
return obj;
}