From 71f778671d23c257463d28101bd7bf3d55395e96 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Fri, 24 Oct 2025 19:39:31 +0200 Subject: [PATCH] feat: trying to make image generation faster --- src/components/Image.astro | 67 +++++++---------------- src/helpers/image.ts | 107 ++++++++++++++----------------------- src/helpers/memorium.ts | 2 +- 3 files changed, 61 insertions(+), 115 deletions(-) diff --git a/src/components/Image.astro b/src/components/Image.astro index 7ad4658..fa4d2cf 100644 --- a/src/components/Image.astro +++ b/src/components/Image.astro @@ -1,8 +1,7 @@ --- import type { ImageMetadata } from "astro"; import { Picture as AstroImage } from "astro:assets"; -import { inferRemoteSize } from "astro/assets/utils"; -import { generateThumbHash, getExifData } from "@helpers/image"; +import { generateThumbHash, getImageBuffer, getExifData } from "@helpers/image"; interface Props { src: ImageMetadata & { fsPath?: string; src?: string }; alt: string; @@ -14,40 +13,18 @@ interface Props { maxWidth?: number; } -async function checkImage(image: ImageMetadata) { - const src = typeof image === "string" ? image : image.src; - if (!src) return false; - try { - if (src.startsWith("/@fs") || src.startsWith("/_astro")) return true; - const res = await inferRemoteSize(src); - if (res.format) { - image.format = res.format; - return true; - } else { - console.log("Failed to load: ", src); - } - return false; - } catch (err) { - console.log("\n"); - console.log("Failed to fetch: ", src); - return false; - } -} - const { src: image, loader = true, - caption, pictureClass = "", hash = true, alt, maxWidth, } = Astro.props; -let thumbhash = hash && (await generateThumbHash(image)); -const imageOk = await checkImage(image); - -let exif = imageOk && (await getExifData(image)); +const imageBuffer = await getImageBuffer(image); +let thumbhash = imageBuffer && (await generateThumbHash(imageBuffer)); +let exif = imageBuffer && (await getExifData(imageBuffer)); const sizes = [ { @@ -68,23 +45,19 @@ const sizes = [ ].filter((size) => !maxWidth || size.width <= maxWidth); --- -{ - imageOk ? ( - size.width)} - sizes={sizes - .map((size) => `${size.media || "100vw"} ${size.width}px`) - .join(", ")}> - - - ) : undefined -} + size.width)} + sizes={sizes + .map((size) => `${size.media || "100vw"} ${size.width}px`) + .join(", ")}> + + diff --git a/src/helpers/image.ts b/src/helpers/image.ts index fad82e3..8b01192 100644 --- a/src/helpers/image.ts +++ b/src/helpers/image.ts @@ -5,55 +5,26 @@ import { readFile } from "node:fs/promises"; import sharp from "sharp"; export async function generateThumbHash( - image: ImageMetadata & { fsPath?: string }, -) { - const scaleFactor = 100 / Math.max(image.width, image.height); + buffer: ArrayBuffer, +): Promise { + if (!buffer) return; - let smallWidth = Math.floor(image.width * scaleFactor); - let smallHeight = Math.floor(image.height * scaleFactor); + const sp = sharp(buffer); - try { - const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ?? - image.src; + const meta = await sp.metadata(); + const scaleFactor = 100 / Math.max(meta.width, meta.height); + const smallWidth = Math.floor(meta.width * scaleFactor); + const smallHeight = Math.floor(meta.height * scaleFactor); - if (!imagePath) return; + const smallImg = await sp + .resize(smallWidth, smallHeight) + .withMetadata() + .raw() + .ensureAlpha() + .toBuffer(); - if (imagePath.endsWith(".svg")) return; - - let sp: ReturnType; - if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) { - const res = await fetch(imagePath); - if (!res.ok) { - return; - } - const buffer = await res.arrayBuffer(); - sp = sharp(buffer); - } 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}`, - ); - return ""; - } + const hashBuffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg); + return Buffer.from(hashBuffer).toString("base64"); } const allowedExif = [ @@ -72,7 +43,9 @@ const allowedExif = [ "Model", ]; -export async function getExifData(image: ImageMetadata) { +export async function getImageBuffer( + image: ImageMetadata, +): Promise { if (image.format === "svg") return undefined; // SVGs don't have EXIF data const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ?? image.src; @@ -80,31 +53,31 @@ export async function getExifData(image: ImageMetadata) { if (!imagePath) return undefined; try { - let buffer: ArrayBufferLike; if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) { - const res = await fetch(imagePath); - buffer = await res.arrayBuffer(); + const res = await fetch(imagePath, { signal: AbortSignal.timeout(5000) }); + return await res.arrayBuffer(); } else { const b = await readFile(imagePath); - buffer = b.buffer; + return b.buffer as ArrayBuffer; } - - const tags = await ExifReader.load(buffer, { async: true }); - - if (!buffer) return undefined; - - 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); + } catch (err) { return undefined; } } + +export async function getExifData(buffer: ArrayBuffer) { + if (!buffer) return undefined; + + 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; +} diff --git a/src/helpers/memorium.ts b/src/helpers/memorium.ts index 6bc28c1..2183ebf 100644 --- a/src/helpers/memorium.ts +++ b/src/helpers/memorium.ts @@ -28,7 +28,7 @@ export async function listResource( ): Promise { const url = `${SERVER_URL}/resources/${id}`; try { - const response = await fetch(url); + const response = await fetch(url, { signal: AbortSignal.timeout(5000) }); if (response.ok) { const json = await response.json(); if (json.type == "dir") {