46 lines
1.1 KiB
TypeScript
46 lines
1.1 KiB
TypeScript
|
|
const MP4_BRANDS = new Set([
|
||
|
|
"avc1",
|
||
|
|
"dash",
|
||
|
|
"isom",
|
||
|
|
"M4A ",
|
||
|
|
"M4B ",
|
||
|
|
"M4P ",
|
||
|
|
"M4V ",
|
||
|
|
"mp41",
|
||
|
|
"mp42",
|
||
|
|
"MSNV",
|
||
|
|
]);
|
||
|
|
|
||
|
|
function fourCharacters(bytes: Uint8Array, offset: number) {
|
||
|
|
return String.fromCharCode(...bytes.slice(offset, offset + 4));
|
||
|
|
}
|
||
|
|
|
||
|
|
function isMp4Brand(brand: string) {
|
||
|
|
return (
|
||
|
|
MP4_BRANDS.has(brand) ||
|
||
|
|
/^iso[2-9]$/.test(brand) ||
|
||
|
|
/^3g[2p]$/.test(brand.slice(0, 3))
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Rejects HTML/JSON/error payloads and non-MP4 containers before download. */
|
||
|
|
export function hasMp4FileSignature(input: ArrayBuffer | Uint8Array) {
|
||
|
|
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
||
|
|
if (bytes.byteLength < 12 || fourCharacters(bytes, 4) !== "ftyp") {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const declaredSize = new DataView(
|
||
|
|
bytes.buffer,
|
||
|
|
bytes.byteOffset,
|
||
|
|
bytes.byteLength,
|
||
|
|
).getUint32(0);
|
||
|
|
const boxEnd = Math.min(
|
||
|
|
bytes.byteLength,
|
||
|
|
declaredSize >= 12 ? declaredSize : bytes.byteLength,
|
||
|
|
);
|
||
|
|
for (let offset = 8; offset + 4 <= boxEnd; offset += 4) {
|
||
|
|
if (isMp4Brand(fourCharacters(bytes, offset))) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|