feat: add thumbhashes to images closes #6
This commit is contained in:
8
lib/cache/cache.ts
vendored
8
lib/cache/cache.ts
vendored
@@ -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
72
lib/cache/image.ts
vendored
@@ -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 });
|
||||
|
||||
|
Reference in New Issue
Block a user