diff --git a/src/interfaces/Nip94Metadata.ts b/src/interfaces/Nip94Metadata.ts deleted file mode 100644 index 6069ac31..00000000 --- a/src/interfaces/Nip94Metadata.ts +++ /dev/null @@ -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>; - -/** - * 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; diff --git a/src/uploaders/S3Uploader.ts b/src/uploaders/S3Uploader.ts index c784cdab..b74796ab 100644 --- a/src/uploaders/S3Uploader.ts +++ b/src/uploaders/S3Uploader.ts @@ -43,7 +43,6 @@ export class S3Uploader implements NUploader { const { pathStyle, bucket } = Conf.s3; const path = (pathStyle && bucket) ? join(bucket, filename) : filename; - const url = new URL(path, Conf.mediaDomain).toString(); return [ diff --git a/src/utils/image-metadata.ts b/src/utils/image-metadata.ts deleted file mode 100644 index 4429cc7a..00000000 --- a/src/utils/image-metadata.ts +++ /dev/null @@ -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 { - 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; -} diff --git a/src/utils/upload.ts b/src/utils/upload.ts index 9f4c90a4..df0be70b 100644 --- a/src/utils/upload.ts +++ b/src/utils/upload.ts @@ -1,11 +1,15 @@ 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 { Conf } from '@/config.ts'; import { DittoUpload, dittoUploads } from '@/DittoUploads.ts'; -import { getOptionalNip94Metadata } from '@/utils/image-metadata.ts'; -import type { Nip94MetadataOptional } from '@/interfaces/Nip94Metadata.ts'; -import { encodeHex } from '@std/encoding/hex'; + +const console = new Stickynotes('ditto:uploader'); interface FileMeta { pubkey: string; @@ -33,30 +37,50 @@ export async function uploadFile( } const tags = await uploader.upload(file, { signal }); - const tagMap = tags.reduce((map, value) => map.set(value[0], value.slice(1)), new Map()); - const url = tags[0][1]; if (description) { tags.push(['alt', description]); } - let metadata: Nip94MetadataOptional | undefined; - if (!tagMap.has('dim')) { - // blurhash needs us to call sharp() anyway to decode the image data. - // all getOptionalNip94Metadata does is call these in sequence, plus - // one extra sha256 which is whatever (and actually does come in handy later.) - metadata ??= await getOptionalNip94Metadata(file); - tags.push(['dim', metadata.dim!]); - if (!tagMap.has('blurhash')) { - tags.push(['blurhash', metadata.blurhash!]); - } + const x = tags.find(([key]) => key === 'x')?.[1]; + const m = tags.find(([key]) => key === 'm')?.[1]; + const dim = tags.find(([key]) => key === 'dim')?.[1]; + const blurhash = tags.find(([key]) => key === 'blurhash')?.[1]; + + if (!x) { + const sha256 = encodeHex(await crypto.subtle.digest('SHA-256', file.stream())); + tags.push(['x', sha256]); } - 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!]); + + 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}`); + } } const upload = {