SPB Git

Repository Public MIT

media.ts 3.4 KB 78 lines typescript

1/**2 * Raster image sniffing for release examples. Pure header parsing: nothing is decoded or rendered.3 * Only PNG, JPEG and WebP are recognised; SVG (scriptable) and everything else return null.4 */5export type ImageKind = "png" | "jpeg" | "webp";6export interface ImageInfo {7  kind: ImageKind;8  mime: "image/png" | "image/jpeg" | "image/webp";9  width: number;10  height: number;11}1213const PNG_SIG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];1415/** Kind by magic bytes only (cheap; used to keep images out of the text scanner). */16export function imageKind(d: Uint8Array): ImageKind | null {17  if (d.length >= 8 && PNG_SIG.every((b, i) => d[i] === b)) return "png";18  if (d.length >= 3 && d[0] === 0xff && d[1] === 0xd8 && d[2] === 0xff) return "jpeg";19  if (d.length >= 12 && d[0] === 0x52 && d[1] === 0x49 && d[2] === 0x46 && d[3] === 0x46 && d[8] === 0x57 && d[9] === 0x45 && d[10] === 0x42 && d[11] === 0x50) return "webp";20  return null;21}2223/** Kind and pixel size, or null when the header is not a well-formed PNG, JPEG or WebP. */24export function sniffImage(data: Uint8Array): ImageInfo | null {25  const d = Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);26  const kind = imageKind(d);27  let width = 0;28  let height = 0;29  if (kind === "png") {30    if (d.length < 33 || d.toString("latin1", 12, 16) !== "IHDR") return null;31    width = d.readUInt32BE(16);32    height = d.readUInt32BE(20);33  } else if (kind === "jpeg") {34    let pos = 2;35    while (pos + 9 < d.length) {36      if (d[pos] !== 0xff) return null;37      const marker = d[pos + 1]!;38      if (marker === 0xff) { pos += 1; continue; }39      if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0x01) { pos += 2; continue; }40      const len = d.readUInt16BE(pos + 2);41      if (len < 2) return null;42      // SOF0..SOF15 except DHT (C4), JPG (C8) and DAC (CC) carry the frame size.43      if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {44        height = d.readUInt16BE(pos + 5);45        width = d.readUInt16BE(pos + 7);46        break;47      }48      if (marker === 0xda || marker === 0xd9) return null;49      pos += 2 + len;50    }51  } else if (kind === "webp") {52    if (d.length < 30) return null;53    const fourcc = d.toString("latin1", 12, 16);54    if (fourcc === "VP8X") {55      width = 1 + d.readUIntLE(24, 3);56      height = 1 + d.readUIntLE(27, 3);57    } else if (fourcc === "VP8 ") {58      if (d[23] !== 0x9d || d[24] !== 0x01 || d[25] !== 0x2a) return null;59      width = d.readUInt16LE(26) & 0x3fff;60      height = d.readUInt16LE(28) & 0x3fff;61    } else if (fourcc === "VP8L") {62      if (d[20] !== 0x2f) return null;63      const bits = d.readUInt32LE(21);64      width = 1 + (bits & 0x3fff);65      height = 1 + ((bits >> 14) & 0x3fff);66    } else return null;67  } else return null;68  if (!(width > 0 && height > 0)) return null;69  return { kind, mime: kind === "png" ? "image/png" : kind === "jpeg" ? "image/jpeg" : "image/webp", width, height };70}7172const EXT_KIND: Record<string, ImageKind> = { png: "png", jpg: "jpeg", jpeg: "jpeg", webp: "webp" };73/** Image kind promised by a file extension, or null when the extension is not an accepted one. */74export function imageKindForPath(path: string): ImageKind | null {75  const ext = path.includes(".") ? path.slice(path.lastIndexOf(".") + 1).toLowerCase() : "";76  return EXT_KIND[ext] ?? null;77}78
Files main
Menu

Theme