Compare commits
2 Commits
9eadefdb51
...
22781de3eb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22781de3eb
|
||
|
|
71f778671d
|
@@ -2,7 +2,7 @@
|
|||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
import { Picture as AstroImage } from "astro:assets";
|
import { Picture as AstroImage } from "astro:assets";
|
||||||
import { inferRemoteSize } from "astro/assets/utils";
|
import { inferRemoteSize } from "astro/assets/utils";
|
||||||
import { generateThumbHash, getExifData } from "@helpers/image";
|
import { generateThumbHash, getImageBuffer, getExifData } from "@helpers/image";
|
||||||
interface Props {
|
interface Props {
|
||||||
src: ImageMetadata & { fsPath?: string; src?: string };
|
src: ImageMetadata & { fsPath?: string; src?: string };
|
||||||
alt: string;
|
alt: string;
|
||||||
@@ -37,17 +37,18 @@ async function checkImage(image: ImageMetadata) {
|
|||||||
const {
|
const {
|
||||||
src: image,
|
src: image,
|
||||||
loader = true,
|
loader = true,
|
||||||
caption,
|
|
||||||
pictureClass = "",
|
pictureClass = "",
|
||||||
hash = true,
|
hash = true,
|
||||||
alt,
|
alt,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
let thumbhash = hash && (await generateThumbHash(image));
|
|
||||||
const imageOk = await checkImage(image);
|
|
||||||
|
|
||||||
let exif = imageOk && (await getExifData(image));
|
|
||||||
|
const imageOk = await checkImage(image);
|
||||||
|
const imageBuffer = imageOk && await getImageBuffer(image);
|
||||||
|
let thumbhash = imageBuffer && (await generateThumbHash(imageBuffer));
|
||||||
|
let exif = imageBuffer && (await getExifData(imageBuffer));
|
||||||
|
|
||||||
const sizes = [
|
const sizes = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,55 +5,26 @@ import { readFile } from "node:fs/promises";
|
|||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
|
|
||||||
export async function generateThumbHash(
|
export async function generateThumbHash(
|
||||||
image: ImageMetadata & { fsPath?: string },
|
buffer: ArrayBuffer,
|
||||||
) {
|
): Promise<string | undefined> {
|
||||||
const scaleFactor = 100 / Math.max(image.width, image.height);
|
if (!buffer) return;
|
||||||
|
|
||||||
let smallWidth = Math.floor(image.width * scaleFactor);
|
const sp = sharp(buffer);
|
||||||
let smallHeight = Math.floor(image.height * scaleFactor);
|
|
||||||
|
|
||||||
try {
|
const meta = await sp.metadata();
|
||||||
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
|
const scaleFactor = 100 / Math.max(meta.width, meta.height);
|
||||||
image.src;
|
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;
|
const hashBuffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
|
||||||
|
return Buffer.from(hashBuffer).toString("base64");
|
||||||
let sp: ReturnType<typeof sharp>;
|
|
||||||
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 allowedExif = [
|
const allowedExif = [
|
||||||
@@ -72,7 +43,9 @@ const allowedExif = [
|
|||||||
"Model",
|
"Model",
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function getExifData(image: ImageMetadata) {
|
export async function getImageBuffer(
|
||||||
|
image: ImageMetadata,
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
if (image.format === "svg") return undefined; // SVGs don't have EXIF data
|
if (image.format === "svg") return undefined; // SVGs don't have EXIF data
|
||||||
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
|
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
|
||||||
image.src;
|
image.src;
|
||||||
@@ -80,31 +53,31 @@ export async function getExifData(image: ImageMetadata) {
|
|||||||
if (!imagePath) return undefined;
|
if (!imagePath) return undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let buffer: ArrayBufferLike;
|
|
||||||
if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) {
|
if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) {
|
||||||
const res = await fetch(imagePath);
|
const res = await fetch(imagePath, { signal: AbortSignal.timeout(5000) });
|
||||||
buffer = await res.arrayBuffer();
|
return await res.arrayBuffer();
|
||||||
} else {
|
} else {
|
||||||
const b = await readFile(imagePath);
|
const b = await readFile(imagePath);
|
||||||
buffer = b.buffer;
|
return b.buffer as ArrayBuffer;
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
const tags = await ExifReader.load(buffer, { async: true });
|
|
||||||
|
|
||||||
if (!buffer) return undefined;
|
|
||||||
|
|
||||||
const out: Record<string, any> = {};
|
|
||||||
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);
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getExifData(buffer: ArrayBuffer) {
|
||||||
|
if (!buffer) return undefined;
|
||||||
|
|
||||||
|
const tags = await ExifReader.load(buffer, { async: true });
|
||||||
|
|
||||||
|
const out: Record<string, unknown> = {};
|
||||||
|
let hasExif = false;
|
||||||
|
|
||||||
|
for (const key of allowedExif) {
|
||||||
|
if (!tags[key]) continue;
|
||||||
|
hasExif = true;
|
||||||
|
out[key] = tags[key]?.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasExif ? out : undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function listResource(
|
|||||||
): Promise<MemoriumEntry | undefined> {
|
): Promise<MemoriumEntry | undefined> {
|
||||||
const url = `${SERVER_URL}/resources/${id}`;
|
const url = `${SERVER_URL}/resources/${id}`;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.type == "dir") {
|
if (json.type == "dir") {
|
||||||
|
|||||||
Reference in New Issue
Block a user