From a7ee561e4074f839f340c77a1f21c8f5657865c7 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 2 Oct 2024 00:03:27 +0900 Subject: [PATCH] Paralellize image uploads (#5535) * Remove unsafe type coercions from posting embed * Extract resolveEmbed into a separate function * Refactor to if-else because these are mutually exclusive * Refactor resolveEmbed to early returns * Separate resolving embed and media * Parallelize image upload * Prioritize not dropping media --- src/lib/api/index.ts | 254 ++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 137 deletions(-) diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 1e51c7f255..08d4cb9629 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -13,6 +13,8 @@ import { RichText, } from '@atproto/api' +import {isNetworkError} from '#/lib/strings/errors' +import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' import {logger} from '#/logger' import {ComposerImage, compressImage} from '#/state/gallery' import {writePostgateRecord} from '#/state/queries/postgate' @@ -22,8 +24,6 @@ import { threadgateAllowUISettingToAllowRecordValue, writeThreadgateRecord, } from '#/state/queries/threadgate' -import {isNetworkError} from 'lib/strings/errors' -import {shortenLinks, stripInvalidMentions} from 'lib/strings/rich-text-manip' import {LinkMeta} from '../link-meta/link-meta' import {uploadBlob} from './upload-blob' @@ -60,13 +60,6 @@ interface PostOpts { } export async function post(agent: BskyAgent, opts: PostOpts) { - let embed: - | AppBskyEmbedImages.Main - | AppBskyEmbedExternal.Main - | AppBskyEmbedRecord.Main - | AppBskyEmbedVideo.Main - | AppBskyEmbedRecordWithMedia.Main - | undefined let reply let rt = new RichText({text: opts.rawText.trimEnd()}, {cleanNewlines: true}) @@ -77,134 +70,7 @@ export async function post(agent: BskyAgent, opts: PostOpts) { rt = shortenLinks(rt) rt = stripInvalidMentions(rt) - // add quote embed if present - if (opts.quote) { - embed = { - $type: 'app.bsky.embed.record', - record: { - uri: opts.quote.uri, - cid: opts.quote.cid, - }, - } as AppBskyEmbedRecord.Main - } - - // add image embed if present - if (opts.images?.length) { - logger.debug(`Uploading images`, { - count: opts.images.length, - }) - - const images: AppBskyEmbedImages.Image[] = [] - for (const image of opts.images) { - opts.onStateChange?.(`Uploading image #${images.length + 1}...`) - - logger.debug(`Compressing image`) - const {path, width, height, mime} = await compressImage(image) - - logger.debug(`Uploading image`) - const res = await uploadBlob(agent, path, mime) - - images.push({ - image: res.data.blob, - alt: image.alt, - aspectRatio: {width, height}, - }) - } - - if (opts.quote) { - embed = { - $type: 'app.bsky.embed.recordWithMedia', - record: embed, - media: { - $type: 'app.bsky.embed.images', - images, - }, - } as AppBskyEmbedRecordWithMedia.Main - } else { - embed = { - $type: 'app.bsky.embed.images', - images, - } as AppBskyEmbedImages.Main - } - } - - // add video embed if present - if (opts.video) { - const captions = await Promise.all( - opts.video.captions - .filter(caption => caption.lang !== '') - .map(async caption => { - const {data} = await agent.uploadBlob(caption.file, { - encoding: 'text/vtt', - }) - return {lang: caption.lang, file: data.blob} - }), - ) - if (opts.quote) { - embed = { - $type: 'app.bsky.embed.recordWithMedia', - record: embed, - media: { - $type: 'app.bsky.embed.video', - video: opts.video.blobRef, - alt: opts.video.altText || undefined, - captions: captions.length === 0 ? undefined : captions, - aspectRatio: opts.video.aspectRatio, - } as AppBskyEmbedVideo.Main, - } as AppBskyEmbedRecordWithMedia.Main - } else { - embed = { - $type: 'app.bsky.embed.video', - video: opts.video.blobRef, - alt: opts.video.altText || undefined, - captions: captions.length === 0 ? undefined : captions, - aspectRatio: opts.video.aspectRatio, - } as AppBskyEmbedVideo.Main - } - } - - // add external embed if present - if (opts.extLink && !opts.images?.length) { - if (opts.extLink.embed) { - embed = opts.extLink.embed - } else { - let thumb - if (opts.extLink.localThumb) { - opts.onStateChange?.('Uploading link thumbnail...') - - const {path, mime} = opts.extLink.localThumb.source - const res = await uploadBlob(agent, path, mime) - - thumb = res.data.blob - } - - if (opts.quote) { - embed = { - $type: 'app.bsky.embed.recordWithMedia', - record: embed, - media: { - $type: 'app.bsky.embed.external', - external: { - uri: opts.extLink.uri, - title: opts.extLink.meta?.title || '', - description: opts.extLink.meta?.description || '', - thumb, - }, - } as AppBskyEmbedExternal.Main, - } as AppBskyEmbedRecordWithMedia.Main - } else { - embed = { - $type: 'app.bsky.embed.external', - external: { - uri: opts.extLink.uri, - title: opts.extLink.meta?.title || '', - description: opts.extLink.meta?.description || '', - thumb, - }, - } as AppBskyEmbedExternal.Main - } - } - } + const embed = await resolveEmbed(agent, opts) // add replyTo if post is a reply to another post if (opts.replyTo) { @@ -313,3 +179,117 @@ export async function post(agent: BskyAgent, opts: PostOpts) { return res } + +async function resolveEmbed( + agent: BskyAgent, + opts: PostOpts, +): Promise< + | AppBskyEmbedImages.Main + | AppBskyEmbedVideo.Main + | AppBskyEmbedExternal.Main + | AppBskyEmbedRecord.Main + | AppBskyEmbedRecordWithMedia.Main + | undefined +> { + const media = await resolveMedia(agent, opts) + if (opts.quote) { + const quoteRecord = { + $type: 'app.bsky.embed.record', + record: { + uri: opts.quote.uri, + cid: opts.quote.cid, + }, + } + if (media) { + return { + $type: 'app.bsky.embed.recordWithMedia', + record: quoteRecord, + media, + } + } else { + return quoteRecord + } + } + if (media) { + return media + } + if (opts.extLink?.embed) { + return opts.extLink.embed + } + return undefined +} + +async function resolveMedia( + agent: BskyAgent, + opts: PostOpts, +): Promise< + | AppBskyEmbedExternal.Main + | AppBskyEmbedImages.Main + | AppBskyEmbedVideo.Main + | undefined +> { + if (opts.images?.length) { + logger.debug(`Uploading images`, { + count: opts.images.length, + }) + opts.onStateChange?.(`Uploading images...`) + const images: AppBskyEmbedImages.Image[] = await Promise.all( + opts.images.map(async (image, i) => { + logger.debug(`Compressing image #${i}`) + const {path, width, height, mime} = await compressImage(image) + logger.debug(`Uploading image #${i}`) + const res = await uploadBlob(agent, path, mime) + return { + image: res.data.blob, + alt: image.alt, + aspectRatio: {width, height}, + } + }), + ) + return { + $type: 'app.bsky.embed.images', + images, + } + } + if (opts.video) { + const captions = await Promise.all( + opts.video.captions + .filter(caption => caption.lang !== '') + .map(async caption => { + const {data} = await agent.uploadBlob(caption.file, { + encoding: 'text/vtt', + }) + return {lang: caption.lang, file: data.blob} + }), + ) + return { + $type: 'app.bsky.embed.video', + video: opts.video.blobRef, + alt: opts.video.altText || undefined, + captions: captions.length === 0 ? undefined : captions, + aspectRatio: opts.video.aspectRatio, + } + } + if (opts.extLink) { + if (opts.extLink.embed) { + return undefined + } + let thumb + if (opts.extLink.localThumb) { + opts.onStateChange?.('Uploading link thumbnail...') + const {path, mime} = opts.extLink.localThumb.source + const res = await uploadBlob(agent, path, mime) + thumb = res.data.blob + } + return { + $type: 'app.bsky.embed.external', + external: { + uri: opts.extLink.uri, + title: opts.extLink.meta?.title || '', + description: opts.extLink.meta?.description || '', + thumb, + }, + } + } + return undefined +}