feat: add thumbhashes to images closes #6

This commit is contained in:
2023-08-11 16:13:20 +02:00
parent 6dd8575b15
commit 0acbbd6905
22 changed files with 489 additions and 51 deletions

8
lib/cache/cache.ts vendored
View File

@@ -92,17 +92,13 @@ export function keys(prefix: string) {
return cache.keys(prefix);
}
export async function set<T extends RedisValue>(
export function set<T extends RedisValue>(
id: string,
content: T,
options?: RedisOptions,
) {
log.debug("storing ", { id });
const res = await cache.set(id, content);
if (options?.expires) {
await expire(id, options.expires);
}
return res;
return cache.set(id, content, { ex: options?.expires || undefined });
}
export const cacheFunction = async <T extends (() => Promise<unknown>)>(

72
lib/cache/image.ts vendored
View File

@@ -1,29 +1,79 @@
import { hash } from "@lib/string.ts";
import { hash, isLocalImage } from "@lib/string.ts";
import * as cache from "@lib/cache/cache.ts";
import { ImageMagick } from "https://deno.land/x/imagemagick_deno@0.0.25/mod.ts";
import {
ImageMagick,
MagickGeometry,
} from "https://deno.land/x/imagemagick_deno@0.0.25/mod.ts";
import { createLogger } from "@lib/log.ts";
import { generateThumbhash } from "@lib/thumbhash.ts";
import { SILVERBULLET_SERVER } from "@lib/env.ts";
type ImageCacheOptions = {
url: string;
width: number;
height: number;
mediaType?: string;
suffix?: string;
};
const CACHE_KEY = "images";
const log = createLogger("cache/image");
function getCacheKey({ url: _url, width, height }: ImageCacheOptions) {
const url = new URL(_url);
function getCacheKey(
{ url: _url, width, height, suffix }: ImageCacheOptions,
) {
const isLocal = isLocalImage(_url);
const url = new URL(isLocal ? `${SILVERBULLET_SERVER}/${_url}` : _url);
const _suffix = suffix || `${width}:${height}`;
return `${CACHE_KEY}:${url.hostname}:${
url.pathname.replaceAll("/", ":")
}:${width}:${height}`
}:${_suffix}`
.replace(
"::",
":",
);
}
export function createThumbhash(
image: Uint8Array,
url: string,
): Promise<string> {
return new Promise((res, rej) => {
try {
ImageMagick.read(image.slice(), (_image) => {
_image.resize(new MagickGeometry(100, 100));
_image.getPixels((pixels) => {
const bytes = pixels.toByteArray(
0,
0,
_image.width,
_image.height,
"RGBA",
);
if (!bytes) return;
const hash = generateThumbhash(bytes, _image.width, _image.height);
if (hash) {
cache.set(
getCacheKey({
url,
suffix: "thumbnail",
width: _image.width,
height: _image.height,
}),
hash,
);
res(hash);
}
});
});
} catch (err) {
rej(err);
}
});
}
function verifyImage(
imageBuffer: Uint8Array,
) {
@@ -38,6 +88,18 @@ function verifyImage(
});
}
export function getThumbhash({ url }: { url: string }) {
return cache.get<Uint8Array>(
getCacheKey({
url,
suffix: "thumbnail",
width: 200,
height: 200,
}),
true,
);
}
export async function getImage({ url, width, height }: ImageCacheOptions) {
const cacheKey = getCacheKey({ url, width, height });