import { rgbaToThumbHash } from "thumbhash"; import ExifReader from "exifreader"; import type { ImageMetadata } from "astro"; 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; let sp: ReturnType; if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) { const res = await fetch(imagePath); if (!res.ok) { return; } sp = sharp(await res.arrayBuffer()); } 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}`, 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 imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ?? image.src; if (!imagePath) return undefined; try { let buffer: ArrayBuffer; if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) { const res = await fetch(imagePath); buffer = await res.arrayBuffer(); } else { buffer = await sharp(imagePath).toBuffer() as unknown as ArrayBuffer; } const tags = await ExifReader.load(buffer, { async: true }); 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; } }