Skip to content

Image upload stream based resizing #54

Open
@KGMaxey

Description

I'm looking to upload images to my remix/ react route v7 application and automatically generate resized versions of the images, and store them all. I was stoked to find this collection of libraries to support file streaming and avoiding buffering entire files, however I'm having a little trouble understanding a non-hacky way to do this. I've previously used Sharp in other projects to handle resizing images and then piping that output stream to something like S3 storage.

Here's what I've been able to quickly come up with, it works, but it seems like a bit of unintentional luck that it works. I'm hoping there's something about this I'm missing that someone can help me work through.

  1. I'm struggling with the different typings of stream interfaces
  2. As you can see, it appears to be ignoring/ not needing byteLength on my LazyContent objects
  3. I had to cheat by spreading the original FileUpload pointed to the tee'd off stream to avoid stream locks, but I couldn't create a new LazyFile because I didn't seem to have access to the original byteLength for that file.
const id = nanoid()
const uploadHandler = async (fileUpload: FileUpload) => {
  if (fileUpload.fieldName === 'file') {
    const extension = path.extname(fileUpload.name)
    const key = `${id}${extension}`

    const [ogStream, resizeStream] = fileUpload.stream().tee()

    const stream = Readable.fromWeb(resizeStream as ReadableStream<Uint8Array>)

    // Use Sharp to resize the image to 256x256 and 1024x1024
    const thumbnailStream = stream.pipe(sharp().resize(256, 256, { fit: 'inside' }))
    const fullsizeStream = stream.pipe(sharp().resize(1024, 1024, { fit: 'inside' }))

    const thumbnailContent: LazyContent = {
      byteLength: 1, // This seems to not matter what value we set
      stream: () => Readable.toWeb(thumbnailStream) as globalThis.ReadableStream<Uint8Array>
    }
    const fullsizeContent: LazyContent = {
      byteLength: 1, // This seems to not matter what value we set
      stream: () => Readable.toWeb(fullsizeStream) as globalThis.ReadableStream<Uint8Array>
    }

    await Promise.all([
      fileStorage.set(key, { ...fileUpload, stream: () => ogStream } as FileUpload), // Everything else is the same, just use the tee'd stream
      fileStorage.set(`${key}?w=256`, new LazyFile(thumbnailContent, `${key}?w=256`, { type: fileUpload.type })),
      fileStorage.set(`${key}?w=1024`, new LazyFile(fullsizeContent, `${key}?w=1024`, { type: fileUpload.type }))
    ])

    return fileStorage.get(key)
  }
}

I'm going to dig into the implementation of LazyFile more and see if I can't answer my own question but on initial usage, this is the best I could come up with.

Thanks in advance for any help!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions