multi-downloader-nx_mirror/modules/playready/pssh.ts
AnimeDL 460b4c1d0e Convert main files to tabs
Still need to do .tsx files and type declaration files
2025-09-26 10:41:01 -07:00

129 lines
4 KiB
TypeScript

import { Parser } from 'binary-parser';
import { Buffer } from 'buffer';
import WRMHeader from './wrmheader';
const SYSTEM_ID = Buffer.from('9a04f07998404286ab92e65be0885f95', 'hex');
const PSSHBox = new Parser()
.uint32('length')
.string('pssh', { length: 4, assert: 'pssh' })
.uint32('fullbox')
.buffer('system_id', { length: 16 })
.uint32('data_length')
.buffer('data', {
length: 'data_length',
});
const PlayreadyObject = new Parser()
.useContextVars()
.uint16('type')
.uint16('length')
.choice('data', {
tag: 'type',
choices: {
1: new Parser().string('data', {
length: function () {
return (this as any).$parent.length;
},
encoding: 'utf16le',
}),
},
defaultChoice: new Parser().buffer('data', {
length: function () {
return (this as any).$parent.length;
},
}),
});
const PlayreadyHeader = new Parser()
.uint32('length')
.uint16('record_count')
.array('records', {
length: 'record_count',
type: PlayreadyObject,
});
function isPlayreadyPsshBox(data: Buffer): boolean {
if (data.length < 28) return false;
return data.subarray(12, 28).equals(SYSTEM_ID);
}
function isUtf16(data: Buffer): boolean {
for (let i = 1; i < data.length; i += 2) {
if (data[i] !== 0) {
return false;
}
}
return true;
}
function* getWrmHeaders(wrm_header: any): IterableIterator<string> {
for (const record of wrm_header.records) {
if (record.type === 1 && typeof record.data === 'string') {
yield record.data;
}
}
}
export class PSSH {
public wrm_headers: string[];
constructor(data: string | Buffer) {
if (!data) {
throw new Error('Data must not be empty');
}
if (typeof data === 'string') {
try {
data = Buffer.from(data, 'base64');
} catch (e) {
throw new Error(`Could not decode data as Base64: ${e}`);
}
}
try {
if (isPlayreadyPsshBox(data)) {
const pssh_box = PSSHBox.parse(data);
const psshData = pssh_box.data;
if (isUtf16(psshData)) {
this.wrm_headers = [psshData.toString('utf16le')];
} else if (isUtf16(psshData.subarray(6))) {
this.wrm_headers = [psshData.subarray(6).toString('utf16le')];
} else if (isUtf16(psshData.subarray(10))) {
this.wrm_headers = [psshData.subarray(10).toString('utf16le')];
} else {
const playready_header = PlayreadyHeader.parse(psshData);
this.wrm_headers = Array.from(getWrmHeaders(playready_header));
}
} else {
if (isUtf16(data)) {
this.wrm_headers = [data.toString('utf16le')];
} else if (isUtf16(data.subarray(6))) {
this.wrm_headers = [data.subarray(6).toString('utf16le')];
} else if (isUtf16(data.subarray(10))) {
this.wrm_headers = [data.subarray(10).toString('utf16le')];
} else {
const playready_header = PlayreadyHeader.parse(data);
this.wrm_headers = Array.from(getWrmHeaders(playready_header));
}
}
} catch (e) {
throw new Error(
'Could not parse data as a PSSH Box nor a PlayReadyHeader'
);
}
}
// Header downgrade
public get_wrm_headers(downgrade_to_v4: boolean = false): string[] {
return this.wrm_headers.map(
downgrade_to_v4 ? this.downgradePSSH : (_) => _
);
}
private downgradePSSH(wrm_header: string): string {
const header = new WRMHeader(wrm_header);
return header.to_v4_0_0_0();
}
}