-
-
-
-
+
+
Pihkaal's Gallery
+
+ Started on March 2025. I love macro photography of insects and
+ flowers.
+
+
+
+
+
-
-
+
+
diff --git a/package.json b/package.json
index 959d410..5c88b3e 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@tresjs/cientos": "^5.1.2",
"@tresjs/core": "^5.2.0",
"@tresjs/nuxt": "^5.1.2",
+ "exifr": "^7.1.3",
"gsap": "3.13.0",
"pinia": "3.0.4",
"tailwindcss": "^4.1.18",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 64cf97f..b3d352c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,6 +16,9 @@ importers:
"@tresjs/nuxt":
specifier: ^5.1.2
version: 5.1.2(f1341efb47d1338baf7454f3b929107c)
+ exifr:
+ specifier: ^7.1.3
+ version: 7.1.3
gsap:
specifier: 3.13.0
version: 3.13.0
@@ -5572,6 +5575,12 @@ packages:
}
engines: { node: ">=16.17" }
+ exifr@7.1.3:
+ resolution:
+ {
+ integrity: sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==,
+ }
+
expand-template@2.0.3:
resolution:
{
@@ -14097,6 +14106,8 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
+ exifr@7.1.3: {}
+
expand-template@2.0.3: {}
exsolve@1.0.8: {}
diff --git a/server/api/gallery.get.ts b/server/api/gallery.get.ts
new file mode 100644
index 0000000..f694373
--- /dev/null
+++ b/server/api/gallery.get.ts
@@ -0,0 +1,82 @@
+import { readdir } from "fs/promises";
+import { join } from "path";
+import exifr from "exifr";
+import { z } from "zod";
+
+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(),
+});
+
+export default defineCachedEventHandler(
+ async () => {
+ const publicDir = join(process.cwd(), "public/gallery");
+
+ try {
+ const files = await readdir(publicDir);
+ const imageFiles = files.filter((file) =>
+ /\.(jpg|jpeg|png|webp)$/i.test(file),
+ );
+
+ const imagesWithExif = await Promise.all(
+ imageFiles.map(async (filename) => {
+ const filePath = join(publicDir, 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}`,
+ 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 () => {
+ const publicDir = join(process.cwd(), "public/gallery");
+ try {
+ const files = await readdir(publicDir);
+ const imageFiles = files.filter((file) =>
+ /\.(jpg|jpeg|png|webp)$/i.test(file),
+ );
+ return `gallery:${imageFiles.length}`;
+ } catch {
+ return "gallery:0";
+ }
+ },
+ },
+);