Files
max-richter.dev/src/helpers/image.ts
2025-10-24 19:39:31 +02:00

84 lines
2.1 KiB
TypeScript

import { rgbaToThumbHash } from "thumbhash";
import ExifReader from "exifreader";
import type { ImageMetadata } from "astro";
import { readFile } from "node:fs/promises";
import sharp from "sharp";
export async function generateThumbHash(
buffer: ArrayBuffer,
): Promise<string | undefined> {
if (!buffer) return;
const sp = sharp(buffer);
const meta = await sp.metadata();
const scaleFactor = 100 / Math.max(meta.width, meta.height);
const smallWidth = Math.floor(meta.width * scaleFactor);
const smallHeight = Math.floor(meta.height * scaleFactor);
const smallImg = await sp
.resize(smallWidth, smallHeight)
.withMetadata()
.raw()
.ensureAlpha()
.toBuffer();
const hashBuffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
return Buffer.from(hashBuffer).toString("base64");
}
const allowedExif = [
"ApertureValue",
"DateTimeOriginal",
"ShutterSpeedValue",
"ExposureTime",
"ApertureValue",
"FNumber",
"FocalLength",
"GPSLatitude",
"GPSLongitude",
"GPSAltitude",
"IsoSpeedRatings",
"Make",
"Model",
];
export async function getImageBuffer(
image: ImageMetadata,
): Promise<ArrayBuffer> {
if (image.format === "svg") return undefined; // SVGs don't have EXIF data
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
image.src;
if (!imagePath) return undefined;
try {
if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) {
const res = await fetch(imagePath, { signal: AbortSignal.timeout(5000) });
return await res.arrayBuffer();
} else {
const b = await readFile(imagePath);
return b.buffer as ArrayBuffer;
}
} catch (err) {
return undefined;
}
}
export async function getExifData(buffer: ArrayBuffer) {
if (!buffer) return undefined;
const tags = await ExifReader.load(buffer, { async: true });
const out: Record<string, unknown> = {};
let hasExif = false;
for (const key of allowedExif) {
if (!tags[key]) continue;
hasExif = true;
out[key] = tags[key]?.description;
}
return hasExif ? out : undefined;
}