fix image uploads, use sharp to get image metadata instead of image-information

This commit is contained in:
Siddharth Singh 2024-10-27 09:11:29 +05:30
parent 975737b0c3
commit 025ae904b6
No known key found for this signature in database
3 changed files with 198 additions and 25 deletions

View file

@ -41,7 +41,6 @@
"@gfx/canvas-wasm": "jsr:@gfx/canvas-wasm@^0.4.2",
"@hono/hono": "jsr:@hono/hono@^4.4.6",
"@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1",
"@jcayzac/image-information": "npm:@jcayzac/image-information@1.1.1",
"@lambdalisue/async": "jsr:@lambdalisue/async@^2.1.1",
"@negrel/webpush": "jsr:@negrel/webpush@^0.3.0",
"@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0",
@ -69,6 +68,7 @@
"entities": "npm:entities@^4.5.0",
"fast-stable-stringify": "npm:fast-stable-stringify@^1.0.0",
"formdata-helper": "npm:formdata-helper@^0.3.0",
"get-pixels": "npm:get-pixels@3.3.3",
"hono-rate-limiter": "npm:hono-rate-limiter@^0.3.0",
"iso-639-1": "npm:iso-639-1@2.1.15",
"isomorphic-dompurify": "npm:isomorphic-dompurify@^2.16.0",
@ -87,6 +87,7 @@
"postgres": "https://gitlab.com/soapbox-pub/postgres.js/-/raw/e79d7d2039446fbf7a37d4eca0d17e94a94b8b53/deno/mod.js",
"prom-client": "npm:prom-client@^15.1.2",
"question-deno": "https://raw.githubusercontent.com/ocpu/question-deno/10022b8e52555335aa510adb08b0a300df3cf904/mod.ts",
"sharp": "npm:sharp@^0.33.5",
"tldts": "npm:tldts@^6.0.14",
"tseep": "npm:tseep@^1.2.1",
"type-fest": "npm:type-fest@^4.3.0",

170
deno.lock generated
View file

@ -36,6 +36,7 @@
"jsr:@nostrify/nostrify@~0.22.4": "0.22.4",
"jsr:@nostrify/nostrify@~0.22.5": "0.22.5",
"jsr:@nostrify/policies@0.33": "0.33.0",
"jsr:@nostrify/policies@0.33.1": "0.33.1",
"jsr:@nostrify/policies@0.34": "0.34.0",
"jsr:@nostrify/policies@0.35": "0.35.0",
"jsr:@nostrify/policies@0.36": "0.36.0",
@ -80,7 +81,6 @@
"jsr:@std/streams@0.223": "0.223.0",
"npm:@electric-sql/pglite@~0.2.8": "0.2.8",
"npm:@isaacs/ttlcache@^1.4.1": "1.4.1",
"npm:@jcayzac/image-information@1.1.1": "1.1.1",
"npm:@noble/hashes@^1.4.0": "1.4.0",
"npm:@noble/secp256k1@2": "2.1.0",
"npm:@scure/base@^1.1.6": "1.1.6",
@ -118,6 +118,7 @@
"npm:png-to-ico@^2.1.8": "2.1.8",
"npm:postgres@3.4.4": "3.4.4",
"npm:prom-client@^15.1.2": "15.1.2",
"npm:sharp@~0.33.5": "0.33.5",
"npm:tldts@^6.0.14": "6.1.18",
"npm:tseep@^1.2.1": "1.2.1",
"npm:type-fest@^4.3.0": "4.18.2",
@ -657,15 +658,99 @@
"@electric-sql/pglite@0.2.8": {
"integrity": "sha512-0wSmQu22euBRzR5ghqyIHnBH4MfwlkL5WstOrrA3KOsjEWEglvoL/gH92JajEUA6Ufei/+qbkB2hVloC/K/RxQ=="
},
"@emnapi/runtime@1.3.1": {
"integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
"dependencies": [
"tslib"
]
},
"@img/sharp-darwin-arm64@0.33.5": {
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"dependencies": [
"@img/sharp-libvips-darwin-arm64"
]
},
"@img/sharp-darwin-x64@0.33.5": {
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"dependencies": [
"@img/sharp-libvips-darwin-x64"
]
},
"@img/sharp-libvips-darwin-arm64@1.0.4": {
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="
},
"@img/sharp-libvips-darwin-x64@1.0.4": {
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="
},
"@img/sharp-libvips-linux-arm64@1.0.4": {
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="
},
"@img/sharp-libvips-linux-arm@1.0.5": {
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="
},
"@img/sharp-libvips-linux-s390x@1.0.4": {
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="
},
"@img/sharp-libvips-linux-x64@1.0.4": {
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="
},
"@img/sharp-libvips-linuxmusl-arm64@1.0.4": {
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="
},
"@img/sharp-libvips-linuxmusl-x64@1.0.4": {
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="
},
"@img/sharp-linux-arm64@0.33.5": {
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"dependencies": [
"@img/sharp-libvips-linux-arm64"
]
},
"@img/sharp-linux-arm@0.33.5": {
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"dependencies": [
"@img/sharp-libvips-linux-arm"
]
},
"@img/sharp-linux-s390x@0.33.5": {
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"dependencies": [
"@img/sharp-libvips-linux-s390x"
]
},
"@img/sharp-linux-x64@0.33.5": {
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"dependencies": [
"@img/sharp-libvips-linux-x64"
]
},
"@img/sharp-linuxmusl-arm64@0.33.5": {
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"dependencies": [
"@img/sharp-libvips-linuxmusl-arm64"
]
},
"@img/sharp-linuxmusl-x64@0.33.5": {
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"dependencies": [
"@img/sharp-libvips-linuxmusl-x64"
]
},
"@img/sharp-wasm32@0.33.5": {
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"dependencies": [
"@emnapi/runtime"
]
},
"@img/sharp-win32-ia32@0.33.5": {
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="
},
"@img/sharp-win32-x64@0.33.5": {
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="
},
"@isaacs/ttlcache@1.4.1": {
"integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="
},
"@jcayzac/image-information@1.1.1": {
"integrity": "sha512-WXM5RTu3tTuAPXPx4ytbywjqTxnjBUWM1sY+7A9UPqPwQbM9V3jkY/rzo1OJ6VYSpogFdK4cyc+o9FZFZLW+nw==",
"dependencies": [
"image-size"
]
},
"@noble/ciphers@0.5.3": {
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w=="
},
@ -802,6 +887,29 @@
"string-width"
]
},
"color-convert@2.0.1": {
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": [
"color-name"
]
},
"color-name@1.1.4": {
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"color-string@1.9.1": {
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": [
"color-name",
"simple-swizzle"
]
},
"color@4.2.3": {
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": [
"color-convert",
"color-string"
]
},
"colorette@2.0.20": {
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
},
@ -865,6 +973,9 @@
"delayed-stream@1.0.0": {
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"detect-libc@2.0.3": {
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
},
"dom-serializer@2.0.0": {
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dependencies": [
@ -1007,6 +1118,9 @@
"inherits@2.0.4": {
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"is-arrayish@0.3.2": {
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"is-fullwidth-code-point@4.0.0": {
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="
},
@ -1308,6 +1422,36 @@
"xmlchars"
]
},
"semver@7.6.3": {
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
},
"sharp@0.33.5": {
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"dependencies": [
"@img/sharp-darwin-arm64",
"@img/sharp-darwin-x64",
"@img/sharp-libvips-darwin-arm64",
"@img/sharp-libvips-darwin-x64",
"@img/sharp-libvips-linux-arm",
"@img/sharp-libvips-linux-arm64",
"@img/sharp-libvips-linux-s390x",
"@img/sharp-libvips-linux-x64",
"@img/sharp-libvips-linuxmusl-arm64",
"@img/sharp-libvips-linuxmusl-x64",
"@img/sharp-linux-arm",
"@img/sharp-linux-arm64",
"@img/sharp-linux-s390x",
"@img/sharp-linux-x64",
"@img/sharp-linuxmusl-arm64",
"@img/sharp-linuxmusl-x64",
"@img/sharp-wasm32",
"@img/sharp-win32-ia32",
"@img/sharp-win32-x64",
"color",
"detect-libc",
"semver"
]
},
"shebang-command@2.0.0": {
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dependencies": [
@ -1323,6 +1467,12 @@
"signal-exit@4.1.0": {
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
},
"simple-swizzle@0.2.2": {
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": [
"is-arrayish"
]
},
"slice-ansi@5.0.0": {
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
"dependencies": [
@ -1411,6 +1561,9 @@
"tseep@1.2.1": {
"integrity": "sha512-VFnsNcPGC4qFJ1nxbIPSjTmtRZOhlqLmtwRqtLVos8mbRHki8HO9cy9Z1e89EiWyxFmq6LBviI9TQjijxw/mEw=="
},
"tslib@2.6.2": {
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"type-fest@3.13.1": {
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="
},
@ -2130,7 +2283,6 @@
"jsr:@std/streams@0.223",
"npm:@electric-sql/pglite@~0.2.8",
"npm:@isaacs/ttlcache@^1.4.1",
"npm:@jcayzac/image-information@1.1.1",
"npm:@noble/secp256k1@2",
"npm:@scure/base@^1.1.6",
"npm:blurhash@2.0.5",
@ -2140,6 +2292,7 @@
"npm:entities@^4.5.0",
"npm:fast-stable-stringify@1",
"npm:formdata-helper@0.3",
"npm:get-pixels@3.3.3",
"npm:hono-rate-limiter@0.3",
"npm:iso-639-1@2.1.15",
"npm:isomorphic-dompurify@^2.16.0",
@ -2156,6 +2309,7 @@
"npm:path-to-regexp@^7.1.0",
"npm:png-to-ico@^2.1.8",
"npm:prom-client@^15.1.2",
"npm:sharp@~0.33.5",
"npm:tldts@^6.0.14",
"npm:tseep@^1.2.1",
"npm:type-fest@^4.3.0",

View file

@ -1,11 +1,11 @@
import { NUploader } from '@nostrify/nostrify';
import { z } from 'zod';
import { probe } from '@jcayzac/image-information';
import sharp from 'sharp';
import { Stickynotes } from '@soapbox/stickynotes';
import { encode } from 'blurhash';
import { encodeHex } from '@std/encoding/hex';
const console = new Stickynotes('ditto:ipfs:uploader');
const console = new Stickynotes('ditto:uploader:ipfs');
export interface IPFSUploaderOpts {
baseUrl: string;
@ -25,6 +25,9 @@ function toByteArray(f: File): Promise<Uint8Array> {
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.
@ -55,29 +58,44 @@ export class IPFSUploader implements NUploader {
});
const { Hash: cid } = IPFSUploader.schema().parse(await response.json());
const tags: [['url', string], ...string[][]] = [
['url', new URL(`/ipfs/${cid}`, this.baseUrl).toString()],
['m', file.type],
['cid', cid],
['size', file.size.toString()],
];
const tags: Nip94Metadata = {
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.push(['x', hash], ['ox', hash]);
const metadata = probe(buffer);
if (metadata) {
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
const blurhash = encode(new Uint8ClampedArray(buffer), metadata.width, metadata.height, 4, 4);
tags.push(['blurhash', blurhash]);
tags.push(['dim', `${metadata.width}x${metadata.height}`]);
4,
4,
);
}
} catch (e) {
console.error(`Error parsing ipfs metadata: ${e}`);
}
return tags;
console.debug(tags);
return Object.entries(tags) as [['url', string], ...string[][]];
}
async delete(cid: string, opts?: { signal?: AbortSignal }): Promise<void> {