feat: add exif data to image tags
Some checks failed
Deploy to SFTP Server / build (push) Failing after 1m34s

This commit is contained in:
max_richter 2024-06-21 16:15:48 +02:00
parent 08c852a9e8
commit e5726437ed
5 changed files with 62 additions and 17 deletions

View File

@ -16,6 +16,7 @@
"@astrojs/tailwind": "^5.1.0", "@astrojs/tailwind": "^5.1.0",
"astro": "^4.11.0", "astro": "^4.11.0",
"astro-i18n-aut": "^0.7.0", "astro-i18n-aut": "^0.7.0",
"exifreader": "^4.23.3",
"svelte": "^4.2.18", "svelte": "^4.2.18",
"svelte-gestures": "^5.0.1", "svelte-gestures": "^5.0.1",
"tailwindcss": "^3.4.4", "tailwindcss": "^3.4.4",

View File

@ -26,6 +26,9 @@ importers:
astro-i18n-aut: astro-i18n-aut:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0(astro@4.11.0(@types/node@20.14.7)(typescript@5.5.2))(kleur@4.1.5) version: 0.7.0(astro@4.11.0(@types/node@20.14.7)(typescript@5.5.2))(kleur@4.1.5)
exifreader:
specifier: ^4.23.3
version: 4.23.3
svelte: svelte:
specifier: ^4.2.18 specifier: ^4.2.18
version: 4.2.18 version: 4.2.18
@ -952,6 +955,10 @@ packages:
'@vscode/l10n@0.0.18': '@vscode/l10n@0.0.18':
resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==}
'@xmldom/xmldom@0.8.10':
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
engines: {node: '>=10.0.0'}
acorn-jsx@5.3.2: acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
@ -1346,6 +1353,9 @@ packages:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'} engines: {node: '>=16.17'}
exifreader@4.23.3:
resolution: {integrity: sha512-/Ii4jiNp/5BXdKOiWXZYrWmZFn/ANu3bMVGO7GFQufao5M52/fK2OsAPMH34PL4S79z1eZBzAoaYyBXit0zzVA==}
extend-shallow@2.0.1: extend-shallow@2.0.1:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3871,6 +3881,9 @@ snapshots:
'@vscode/l10n@0.0.18': {} '@vscode/l10n@0.0.18': {}
'@xmldom/xmldom@0.8.10':
optional: true
acorn-jsx@5.3.2(acorn@8.12.0): acorn-jsx@5.3.2(acorn@8.12.0):
dependencies: dependencies:
acorn: 8.12.0 acorn: 8.12.0
@ -4327,6 +4340,10 @@ snapshots:
signal-exit: 4.1.0 signal-exit: 4.1.0
strip-final-newline: 3.0.0 strip-final-newline: 3.0.0
exifreader@4.23.3:
optionalDependencies:
'@xmldom/xmldom': 0.8.10
extend-shallow@2.0.1: extend-shallow@2.0.1:
dependencies: dependencies:
is-extendable: 0.1.1 is-extendable: 0.1.1

View File

@ -1,7 +1,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 { generateThumbHash } from "@helpers/image"; import { generateThumbHash, getExifData } from "@helpers/image";
interface Props { interface Props {
src: ImageMetadata; src: ImageMetadata;
alt: string; alt: string;
@ -24,6 +24,8 @@ const {
let thumbhash = hash ? await generateThumbHash(image) : ""; let thumbhash = hash ? await generateThumbHash(image) : "";
let exif = await getExifData(image);
const sizes = [ const sizes = [
{ {
width: 240, width: 240,
@ -47,6 +49,7 @@ const sizes = [
src={image} src={image}
alt={alt} alt={alt}
data-thumbhash={thumbhash} data-thumbhash={thumbhash}
data-exif={JSON.stringify(exif)}
pictureAttributes={{ pictureAttributes={{
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`, class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
}} }}

View File

@ -150,6 +150,7 @@
try { try {
let rawExif = image.getAttribute("data-exif"); let rawExif = image.getAttribute("data-exif");
exif = JSON.parse(rawExif); exif = JSON.parse(rawExif);
console.log(exif);
} catch (error) { } catch (error) {
// No biggie // No biggie
} }
@ -187,8 +188,7 @@
class:active={currentIndex === i} class:active={currentIndex === i}
on:click={() => { on:click={() => {
currentIndex = i; currentIndex = i;
}} }} />
/>
{/each} {/each}
</div> </div>
{/if} {/if}
@ -204,21 +204,18 @@
on:swipe={handleSwipe} on:swipe={handleSwipe}
on:wheel|passive={handleScroll} on:wheel|passive={handleScroll}
on:mousemove={handleMouseMove} on:mousemove={handleMouseMove}
on:pointermove={handlePointerMove} on:pointermove={handlePointerMove}>
>
{#if progress[currentIndex] && progress[currentIndex] < 0.99} {#if progress[currentIndex] && progress[currentIndex] < 0.99}
<div <div
transition:fade transition:fade
id="progress" id="progress"
style={`transform: scaleX(${progress[currentIndex]});`} style={`transform: scaleX(${progress[currentIndex]});`} />
/>
{/if} {/if}
<img <img
class="background" class="background"
src={images[currentIndex].preview} src={images[currentIndex].preview}
alt="background blur" alt="background blur" />
/>
<span> <span>
<img <img
@ -227,15 +224,14 @@
}px ${window.innerHeight - my}px`} }px ${window.innerHeight - my}px`}
srcset={images[currentIndex].loaded ? "" : images[currentIndex].src} srcset={images[currentIndex].loaded ? "" : images[currentIndex].src}
src={images[currentIndex].loaded} src={images[currentIndex].loaded}
alt={images[currentIndex].alt} alt={images[currentIndex].alt} />
/>
</span> </span>
</div> </div>
{#if images[currentIndex].exif} {#if images[currentIndex].exif}
{@const exif = images[currentIndex].exif} {@const exif = images[currentIndex].exif}
<div class="exif" on:click={() => console.log(exif)}> <div class="exif" on:click={() => console.log(exif)}>
{#if "FocalLength" in exif} {#if "FocalLength" in exif}
{exif.FocalLength}mm | {exif.FocalLength} |
{/if} {/if}
{#if "FNumber" in exif} {#if "FNumber" in exif}

View File

@ -1,16 +1,14 @@
let loadingSharp = false; let loadingSharp = false;
import { rgbaToThumbHash } from "thumbhash"; import { rgbaToThumbHash } from "thumbhash";
import ExifReader from 'exifreader';
let s: typeof import("sharp") | undefined; let s: typeof import("sharp") | undefined;
async function getSharp(): Promise<typeof import("sharp") | undefined> { async function getSharp(): Promise<typeof import("sharp") | undefined> {
if (s) return s; if (s) return s;
s = (await import("sharp")).default;
if (import.meta.env.MODE !== "development") { return s;
s = (await import("sharp")).default;
return s;
}
if (!loadingSharp) { if (!loadingSharp) {
loadingSharp = true; loadingSharp = true;
@ -42,3 +40,33 @@ export async function generateThumbHash(image: { width: number, height: number }
const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg); const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
return Buffer.from(buffer).toString("base64"); 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<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;
}