split off nip94 metadata stuff into its own file, port changes to s3uploader

This commit is contained in:
Siddharth Singh 2024-10-27 10:16:00 +05:30
parent ff8374103e
commit 713260e110
No known key found for this signature in database
4 changed files with 97 additions and 62 deletions

View file

@ -0,0 +1,31 @@
/**
* 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;

View file

@ -1,11 +1,6 @@
import { NUploader } from '@nostrify/nostrify';
import { z } from 'zod';
import sharp from 'sharp';
import { Stickynotes } from '@soapbox/stickynotes';
import { encode } from 'blurhash';
import { encodeHex } from '@std/encoding/hex';
const console = new Stickynotes('ditto:uploader:ipfs');
import { getOptionalNip94Metadata } from '@/utils/image-metadata.ts';
export interface IPFSUploaderOpts {
baseUrl: string;
@ -13,22 +8,6 @@ export interface IPFSUploaderOpts {
fetch?: typeof fetch;
}
function toByteArray(f: File): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('loadend', (m) => {
if (m?.target?.result instanceof ArrayBuffer) {
resolve(new Uint8Array(m.target.result));
} else reject('Error loading file: readAsArrayBufferFailed');
});
reader.addEventListener('error', (e) => reject(e));
reader.readAsArrayBuffer(f);
});
}
type Nip94Metadata =
& Record<'url' | 'm', string>
& Partial<Record<'x' | 'ox' | 'size' | 'dim' | 'blurhash' | 'cid', string>>;
/**
* IPFS uploader. It expects an IPFS node up and running.
* It will try to connect to `http://localhost:5001` by default,
@ -58,44 +37,14 @@ export class IPFSUploader implements NUploader {
});
const { Hash: cid } = IPFSUploader.schema().parse(await response.json());
const tags: Nip94Metadata = {
return Object.entries({
url: new URL(`/ipfs/${cid}`, this.baseUrl).toString(),
m: file.type,
cid,
size: file.size.toString(),
};
try {
const buffer = await toByteArray(file);
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}`);
}
console.debug(tags);
return Object.entries(tags) as [['url', string], ...string[][]];
...await getOptionalNip94Metadata(file),
}) as [['url', string], ...string[][]];
}
async delete(cid: string, opts?: { signal?: AbortSignal }): Promise<void> {

View file

@ -7,6 +7,7 @@ import { encodeHex } from '@std/encoding/hex';
import { extensionsByType } from '@std/media-types';
import { Conf } from '@/config.ts';
import { getOptionalNip94Metadata } from '@/utils/image-metadata.ts';
export interface S3UploaderOpts {
endPoint: string;
@ -45,12 +46,12 @@ export class S3Uploader implements NUploader {
const path = (pathStyle && bucket) ? join(bucket, filename) : filename;
const url = new URL(path, Conf.mediaDomain).toString();
return [
['url', url],
['m', file.type],
['x', sha256],
['size', file.size.toString()],
];
return Object.entries({
url: url,
m: file.type,
size: file.size.toString(),
...await getOptionalNip94Metadata(file),
}) as [['url', string], ...string[][]];
}
async delete(objectName: string) {

View file

@ -0,0 +1,54 @@
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');
function toByteArray(f: File): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('loadend', (m) => {
if (m?.target?.result instanceof ArrayBuffer) {
resolve(new Uint8Array(m.target.result));
} else reject('Error loading file: readAsArrayBufferFailed');
});
reader.addEventListener('error', (e) => reject(e));
reader.readAsArrayBuffer(f);
});
}
export async function getOptionalNip94Metadata(f: File): Promise<Nip94MetadataOptional> {
const tags: Nip94MetadataOptional = {};
try {
const buffer = await toByteArray(f);
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;
}