mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Reorganize sharp/blurhash into upload.ts
This commit is contained in:
parent
a294724946
commit
721fe52142
4 changed files with 44 additions and 93 deletions
|
|
@ -1,31 +0,0 @@
|
||||||
/**
|
|
||||||
* Required fields of NIP-94 metadata for images.
|
|
||||||
* Contains the following fields:
|
|
||||||
* * `url` - required, the URL to of the file
|
|
||||||
* * `m` - required, the file mimetype.
|
|
||||||
*/
|
|
||||||
export type Nip94MetadataRequired = Record<'url' | 'm', string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional fields of NIP-94 metadata for images.
|
|
||||||
* Contains the following fields:
|
|
||||||
* * `x` - sha-256 hash
|
|
||||||
* * `ox` - sha-256 hash
|
|
||||||
* * `dim` - image dimensions in ${w}x${h} format
|
|
||||||
* * `blurhash` - the blurhash for the image. useful for image previews etc
|
|
||||||
* * `cid` - the ipfs cid of the image.
|
|
||||||
*/
|
|
||||||
export type Nip94MetadataOptional = Partial<Record<'x' | 'ox' | 'size' | 'dim' | 'blurhash' | 'cid', string>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NIP-94 metadata for images.
|
|
||||||
* Contains the following fields:
|
|
||||||
* * `url` - required, the URL to of the file
|
|
||||||
* * `m` - required, the file mimetype.
|
|
||||||
* * `x` - sha-256 hash
|
|
||||||
* * `ox` - sha-256 hash
|
|
||||||
* * `dim` - image dimensions in ${w}x${h} format
|
|
||||||
* * `blurhash` - the blurhash for the image. useful for image previews etc
|
|
||||||
* * `cid` - the ipfs cid of the image.
|
|
||||||
*/
|
|
||||||
export type Nip94Metadata = Nip94MetadataOptional & Nip94MetadataRequired;
|
|
||||||
|
|
@ -43,7 +43,6 @@ export class S3Uploader implements NUploader {
|
||||||
const { pathStyle, bucket } = Conf.s3;
|
const { pathStyle, bucket } = Conf.s3;
|
||||||
|
|
||||||
const path = (pathStyle && bucket) ? join(bucket, filename) : filename;
|
const path = (pathStyle && bucket) ? join(bucket, filename) : filename;
|
||||||
|
|
||||||
const url = new URL(path, Conf.mediaDomain).toString();
|
const url = new URL(path, Conf.mediaDomain).toString();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import sharp from 'sharp';
|
|
||||||
import { encode } from 'blurhash';
|
|
||||||
import { encodeHex } from '@std/encoding/hex';
|
|
||||||
import type { Nip94MetadataOptional } from '@/interfaces/Nip94Metadata.ts';
|
|
||||||
import { Stickynotes } from '@soapbox/stickynotes';
|
|
||||||
|
|
||||||
const console = new Stickynotes('ditto:uploaders');
|
|
||||||
|
|
||||||
export async function getOptionalNip94Metadata(f: File): Promise<Nip94MetadataOptional> {
|
|
||||||
const tags: Nip94MetadataOptional = {};
|
|
||||||
try {
|
|
||||||
const buffer = await new Response(f.stream()).bytes();
|
|
||||||
const hash = await crypto.subtle.digest('SHA-256', buffer).then(encodeHex);
|
|
||||||
tags.x = tags.ox = hash;
|
|
||||||
const img = sharp(buffer);
|
|
||||||
const metadata = await img.metadata();
|
|
||||||
|
|
||||||
if (metadata.width && metadata.height) {
|
|
||||||
tags.dim = `${metadata.width}x${metadata.height}`;
|
|
||||||
const pixels = await img
|
|
||||||
.raw()
|
|
||||||
.ensureAlpha()
|
|
||||||
.toBuffer({ resolveWithObject: true })
|
|
||||||
.then((buf) => {
|
|
||||||
return new Uint8ClampedArray(buf.data);
|
|
||||||
});
|
|
||||||
tags.blurhash = encode(
|
|
||||||
pixels,
|
|
||||||
metadata.width,
|
|
||||||
metadata.height,
|
|
||||||
// sane default from https://github.com/woltapp/blurhash readme
|
|
||||||
4,
|
|
||||||
4,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Error parsing ipfs metadata: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
|
import { Stickynotes } from '@soapbox/stickynotes';
|
||||||
|
import { crypto } from '@std/crypto';
|
||||||
|
import { encodeHex } from '@std/encoding/hex';
|
||||||
|
import { encode } from 'blurhash';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
|
||||||
import { AppContext } from '@/app.ts';
|
import { AppContext } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
||||||
import { getOptionalNip94Metadata } from '@/utils/image-metadata.ts';
|
|
||||||
import type { Nip94MetadataOptional } from '@/interfaces/Nip94Metadata.ts';
|
const console = new Stickynotes('ditto:uploader');
|
||||||
import { encodeHex } from '@std/encoding/hex';
|
|
||||||
|
|
||||||
interface FileMeta {
|
interface FileMeta {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
|
|
@ -33,30 +37,50 @@ export async function uploadFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = await uploader.upload(file, { signal });
|
const tags = await uploader.upload(file, { signal });
|
||||||
const tagMap = tags.reduce((map, value) => map.set(value[0], value.slice(1)), new Map<string, string[]>());
|
|
||||||
|
|
||||||
const url = tags[0][1];
|
const url = tags[0][1];
|
||||||
|
|
||||||
if (description) {
|
if (description) {
|
||||||
tags.push(['alt', description]);
|
tags.push(['alt', description]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadata: Nip94MetadataOptional | undefined;
|
const x = tags.find(([key]) => key === 'x')?.[1];
|
||||||
if (!tagMap.has('dim')) {
|
const m = tags.find(([key]) => key === 'm')?.[1];
|
||||||
// blurhash needs us to call sharp() anyway to decode the image data.
|
const dim = tags.find(([key]) => key === 'dim')?.[1];
|
||||||
// all getOptionalNip94Metadata does is call these in sequence, plus
|
const blurhash = tags.find(([key]) => key === 'blurhash')?.[1];
|
||||||
// one extra sha256 which is whatever (and actually does come in handy later.)
|
|
||||||
metadata ??= await getOptionalNip94Metadata(file);
|
if (!x) {
|
||||||
tags.push(['dim', metadata.dim!]);
|
const sha256 = encodeHex(await crypto.subtle.digest('SHA-256', file.stream()));
|
||||||
if (!tagMap.has('blurhash')) {
|
tags.push(['x', sha256]);
|
||||||
tags.push(['blurhash', metadata.blurhash!]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m) {
|
||||||
|
tags.push(['m', file.type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blurhash || !dim) {
|
||||||
|
try {
|
||||||
|
const bytes = await new Response(file.stream()).bytes();
|
||||||
|
const img = sharp(bytes);
|
||||||
|
|
||||||
|
const { width, height } = await img.metadata();
|
||||||
|
|
||||||
|
if (!dim && (width && height)) {
|
||||||
|
tags.push(['dim', `${width}x${height}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blurhash && (width && height)) {
|
||||||
|
const pixels = await img
|
||||||
|
.raw()
|
||||||
|
.ensureAlpha()
|
||||||
|
.toBuffer({ resolveWithObject: false })
|
||||||
|
.then((buffer) => new Uint8ClampedArray(buffer));
|
||||||
|
|
||||||
|
const blurhash = encode(pixels, width, height, 4, 4);
|
||||||
|
tags.push(['blurhash', blurhash]);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error parsing image metadata: ${e}`);
|
||||||
}
|
}
|
||||||
if (!tagMap.has('x') || !tagMap.has('ox')) {
|
|
||||||
const hash = metadata?.x ||
|
|
||||||
await crypto.subtle.digest('SHA-256', await new Response(file.stream()).bytes()).then(encodeHex);
|
|
||||||
tags.push(['x', hash!]);
|
|
||||||
tags.push(['ox', hash!]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const upload = {
|
const upload = {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue