Files
website/src/helpers/image.ts
2025-07-20 14:15:44 +02:00

83 lines
2.0 KiB
TypeScript

import { rgbaToThumbHash } from "thumbhash";
import ExifReader from 'exifreader';
import type { ImageMetadata } from "astro";
let s: typeof import("sharp") | undefined;
async function getSharp(): Promise<typeof import("sharp") | undefined> {
if (s) return s;
s = (await import("sharp")).default;
return s;
}
export async function generateThumbHash(image: ImageMetadata & { fsPath?: string }) {
const sharp = await getSharp();
if (!sharp) return;
const scaleFactor = 100 / Math.max(image.width, image.height);
const smallWidth = Math.floor(image.width * scaleFactor);
const smallHeight = Math.floor(image.height * scaleFactor);
try {
const smallImg = await sharp(image.fsPath)
.resize(smallWidth, smallHeight)
.withMetadata()
.raw()
.ensureAlpha()
.toBuffer();
const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
return Buffer.from(buffer).toString("base64");
} catch (error) {
console.log(`Could not generate thumbhash for ${image.fsPath}`, error)
return ""
}
}
const allowedExif = [
"ApertureValue",
"DateTimeOriginal",
"ShutterSpeedValue",
"ExposureTime",
"ApertureValue",
"FNumber",
"FocalLength",
"GPSLatitude",
"GPSLongitude",
"GPSAltitude",
"IsoSpeedRatings",
"Make",
"Model",
];
export async function getExifData(image: ImageMetadata) {
if (image.format === "svg") return undefined; // SVGs don't have EXIF data")
const sharp = await getSharp();
if (!sharp) return;
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath;
try {
const buffer = await sharp(imagePath).toBuffer();
const tags = await ExifReader.load(buffer, { async: true });
const out: Record<string, any> = {};
let hasExif = false;
for (const key of allowedExif) {
if (!tags[key]) continue;
hasExif = true;
out[key] = tags[key]?.description;
}
return hasExif ? out : undefined;
} catch (error) {
console.log(`Error reading EXIF data from ${imagePath}`, error);
return undefined
}
}