import { rgbaToThumbHash } from "thumbhash"; import ExifReader from 'exifreader'; import type { ImageMetadata } from "astro"; let s: typeof import("sharp") | undefined; async function getSharp(): Promise { 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 = {}; 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 } }