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( image: ImageMetadata & { fsPath?: string }, ) { const scaleFactor = 100 / Math.max(image.width, image.height); let smallWidth = Math.floor(image.width * scaleFactor); let smallHeight = Math.floor(image.height * scaleFactor); try { const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ?? image.src; if (!imagePath) return; if (imagePath.endsWith(".svg")) return; let sp: ReturnType; if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) { const res = await fetch(imagePath); if (!res.ok) { return; } const buffer = await res.arrayBuffer(); sp = sharp(buffer); } else { sp = sharp(imagePath); } if (!smallWidth || !smallHeight) { const meta = await sp.metadata(); const scaleFactor = 100 / Math.max(meta.width, meta.height); smallWidth = Math.floor(meta.width * scaleFactor); smallHeight = Math.floor(meta.height * scaleFactor); } const smallImg = await sp .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 ?? image.src}`, ); 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 imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ?? image.src; if (!imagePath) return undefined; try { let buffer: ArrayBufferLike; if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) { const res = await fetch(imagePath); buffer = await res.arrayBuffer(); } else { const b = await readFile(imagePath); buffer = b.buffer; } const tags = await ExifReader.load(buffer, { async: true }); if (!buffer) return undefined; const out: Record = {}; 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 ${JSON.stringify(image)}`, error); return undefined; } }