let loadingSharp = false; import { rgbaToThumbHash } from "thumbhash"; import ExifReader from 'exifreader'; let s: typeof import("sharp") | undefined; async function getSharp(): Promise { if (s) return s; s = (await import("sharp")).default; return s; if (!loadingSharp) { loadingSharp = true; setTimeout(async () => { s = (await import("sharp")).default; }, 1000); return; } } export async function generateThumbHash(image: { width: number, height: number }) { 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); //@ts-ignore 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"); } const allowedExif = [ "ApertureValue", "DateTimeOriginal", "ExposureTime", "ApertureValue", "FNumber", "FocalLength", "GPSLatitude", "GPSLongitude", "GPSAltitude", "IsoSpeedRatings", ]; export async function getExifData(image: { fsPath: string }) { const sharp = await getSharp(); if (!sharp) return; const tags = await ExifReader.load(image.fsPath, { 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; }