avguesser
Version:
Guess the type of an AV file based on its header
133 lines (114 loc) • 3.92 kB
text/typescript
/*
* Copyright (c) 2025 Yahweasel
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
* THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
export interface AVGuessFail {
success: false;
}
export interface AVGuessSuccess {
success: true;
/**
* Format name. For audio and video files, this is the name that
* FFmpeg/libav would give to the format, which is not necessarily the
* common file extension.
*/
format: string;
/**
* Can this format contain audio?
*/
audio: boolean;
/**
* Can this format contain video?
*/
video: boolean;
/**
* Is this format generally used for images (other than, e.g., the album
* cover in ID3 metadata)?
*/
image: boolean;
}
export type AVGuess = AVGuessFail | AVGuessSuccess;
type TypedArray = {
buffer: ArrayBuffer,
byteOffset: number,
byteLength: number
};
/**
* Guess the type of this media file based on its header.
*/
export function guess(headerRaw: ArrayBuffer | TypedArray): AVGuess {
let header: Uint32Array;
if ((<TypedArray> headerRaw).buffer) {
const hrta = <TypedArray> headerRaw;
header = new Uint32Array(
hrta.buffer, hrta.byteOffset, ~~(hrta.byteLength / 4)
);
} else {
const hrab = <ArrayBuffer> headerRaw;
header = new Uint32Array(hrab, 0, ~~(hrab.byteLength / 4));
}
// Return this type
function t(format: string, audio: number, video: number, image: number) {
return <AVGuessSuccess> {
success: true,
format,
audio: !!audio,
video: !!video,
image: !!image
};
}
// Determined based on first word
switch (header[0]) {
case 0x4d524f46: return t("aiff", 1, 0, 0);
case 0x75b22630: return t("asf", 1, 1, 0);
case 0x646e732e: return t("au", 1, 0, 0);
case 0x46464952: {
switch (header[2]) {
case 0x20495641: return t("avi", 1, 1, 0);
case 0x45564157: return t("wav", 1, 0, 0);
case 0x50424557: return t("webp", 0, 1, 1);
}
break;
}
case 0x66666163: return t("caf", 1, 0, 0);
case 0x43614c66: return t("flac", 1, 0, 0);
case 0x01564c46: return t("flv", 1, 1, 0);
case 0x38464947: return t("gif", 0, 1, 1);
case 0xa3df451a: return t("matroska", 1, 1, 0);
case 0x5367674f: return t("ogg", 1, 1, 0);
case 0x474e5089: return t("png", 0, 0, 1);
case 0x464d522e: return t("rm", 1, 1, 0);
case 0x002a4949:
case 0x002b4949:
case 0x2a004d4d:
case 0x2b004d4d:
return t("tiff", 0, 0, 1);
case 0x6b707677: return t("wv", 1, 0, 0);
}
// Determined based on first three bytes
switch (header[0] & 0xffffff) {
case 0x00071f: return t("dv", 1, 1, 0);
case 0xffd8ff: return t("jpeg", 0, 0, 1);
case 0x334449: return t("mp3", 1, 0, 0);
case 0x010000: return t("mpeg", 1, 1, 0);
}
// Determined based on the second word
switch (header[1]) {
case 0x70797466: return t("mp4", 1, 1, 0);
}
// Hail mary for the one-byte determiner of MPEG TS
if ((header[0] & 0xff) === 0x47)
return t("mpegts", 1, 1, 0);
return {
success: false
};
}