import { readdir } from "fs/promises"; import { join } from "path"; import { createHash } from "crypto"; import exifr from "exifr"; import { z } from "zod"; const galleryDir = import.meta.dev ? join(process.cwd(), "public/gallery") : join(process.cwd(), ".output/public/gallery"); const exifSchema = z.object({ DateTimeOriginal: z.date(), Make: z.string(), Model: z.string(), LensModel: z.string(), FNumber: z.number(), ExposureTime: z.number(), ISO: z.number(), FocalLength: z.number(), ExifImageWidth: z.number(), ExifImageHeight: z.number(), }); export default defineCachedEventHandler( async () => { try { const files = await readdir(galleryDir); const imageFiles = files.filter((file) => /\.(jpg|jpeg|png|webp)$/i.test(file), ); const imagesWithExif = await Promise.all( imageFiles.map(async (filename) => { const filePath = join(galleryDir, filename); const rawExif = await exifr.parse(filePath, { tiff: true, exif: true, gps: false, interop: false, ifd1: false, }); const exif = exifSchema.parse(rawExif); return { filename, url: `/gallery/${filename}`, width: exif.ExifImageWidth, height: exif.ExifImageHeight, exif: { date: exif.DateTimeOriginal, camera: `${exif.Make} ${exif.Model}`, lens: exif.LensModel, settings: { aperture: `f/${exif.FNumber}`, shutter: `1/${Math.round(1 / exif.ExposureTime)}s`, iso: String(exif.ISO), focalLength: `${exif.FocalLength}mm`, }, }, }; }), ); return imagesWithExif; } catch (err) { console.log(err); throw createError({ statusCode: 500, message: "Failed to read gallery directory", }); } }, { maxAge: 60 * 60 * 24, getKey: async () => { try { const files = await readdir(galleryDir); const imageFiles = files.filter((file) => /\.(jpg|jpeg|png|webp)$/i.test(file), ); const filesConcat = imageFiles.sort().join(""); const hash = createHash("sha256").update(filesConcat).digest("hex"); return `gallery:${hash}`; } catch { return "gallery:0"; } }, }, );