feat: add exif data to image tags
Some checks failed
Deploy to SFTP Server / build (push) Failing after 1m34s
Some checks failed
Deploy to SFTP Server / build (push) Failing after 1m34s
This commit is contained in:
parent
08c852a9e8
commit
e5726437ed
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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}`,
|
||||||
}}
|
}}
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user