-1

I have a backend that receives a JPEG image as a File object (image/jpeg).
I need to convert the JPEG image to WebP.
The resulting WebP must be a File object (image/webp).

For example:

File object (image/jpeg) => to stream => to webp => to File object (image/webp)

I got as far as converting the stream to webp, but I can't find any info on how to convert it back to File (or at least a modified Blob with the same exact properties/methods of File).

Just to be clear, this is what I mean by File object:

File {
  _events: [Object: null prototype] {},
  _eventsCount: 0,
  _maxListeners: undefined,
  size: 38718,
  path: '/var/folders/17/lk7n1gkd6m54kzmr0g7kqdn80000gp/T/upload_62e5a3f95bccb09b605722a21737cbc3',
  name: '09_scan_qr.jpg',
  type: 'image/jpeg',
  hash: null,
  lastModifiedDate: 2023-01-05T03:37:24.134Z,
  _writeStream: WriteStream {
    fd: null,
    path: '/var/folders/17/lk7n1gkd6m54kzmr0g7kqdn80000gp/T/upload_62e5a3f95bccb09b605722a21737cbc3',
    flags: 'w',
    mode: 438,
    start: undefined,
    pos: undefined,
    bytesWritten: 38718,
    closed: false,
    _writableState: WritableState {...},
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    [Symbol(kFs)]: {...}
}
AX2
  • 135
  • 12
  • 1
    When you say you receive the JPEG image as a File object, where does that come from? From what library? I ask because `File` is not something built into nodejs. It's normally a browser thing. And, why does it have to remain a File object after converting? What are you trying to do with it after converting? There are several very good image converters, but they don't generally work with a File object of the type defined in the browser. – jfriend00 Jan 05 '23 at 05:05
  • @jfriend00 From the frontend, naturally. I need to intercept it, convert it, and pass it on to the controller, which takes a File object and I have no domain over its code, so I can't change it to accept anything other than a File. The only option is to convert File(jpg) to File(webp). – AX2 Jan 05 '23 at 11:00
  • 1
    @AX2 And where/how does this "File" object come from on the backend? As jfriend00 said, there is no build in `File` class in node. – Marc Jan 05 '23 at 12:39
  • @Marc To give some context then. I'm working with Strapi (headless CMS built on Koa). Each Strapi app has an admin panel (React app) and the backend itself. It also supports plugins that follow this structure (admin panel component and backend). I'm using an official plugin called strapi-plugin-upload, that renders a form on the admin panel for the user to upload files. The uploaded File objects are then sent to the backend, to be persisted on the database. – AX2 Jan 05 '23 at 23:51
  • @Marc And I needed to convert all the uploaded images to webp, which the official plugin sadly doesn't support yet. So I had to create a soft of middleware on the backend that intercepts the POST request containing the File objects, converts File(jpg) to File(webp) and lets them continue on their way to the controller to be processed and saved by the plugin, as webp. – AX2 Jan 05 '23 at 23:54

1 Answers1

0

I figured it out. The solution, at least for my case, is something like this:

const sharp = require('sharp');
const fse = require('fs-extra');

// This represents the File object that needs to be converted
let file = FILE_OBJECT;

// Convert the file referenced by the File object to webp
await fse.createReadStream(file.path).pipe(sharp().webp()).toFile(file.path);

// Update the properties of the File object to match the new file on-disk
file.name = file.name.replace(/\.(jpe?g|gif|png|tiff)$/, ".webp"); 
file.size = fse.statSync(file.path).size;
file.type = 'image/webp';

After this, file is a File(webp), by which I mean a File object that references a webp image

AX2
  • 135
  • 12
  • This code uses nodejs streams for the conversion, not FILE objects. And, you write to disk and then after using streams for everything, you then load the whole file into memory so you can construct something that looks like a FILE object. It's hard for us to see why you did this or how this fits the question you asked. But, I guess you're happy with the results. – jfriend00 Jan 05 '23 at 17:40
  • @jfriend As I explained in another comment, I'm trying to create a sort of middleware here. Receive File, manipulate it, forward File. The manipulation step so happens to be converting to webp, which I figure requires using streams. To forward the result of the manipulation, I needed it to be a 'File' again. – AX2 Jan 06 '23 at 00:01
  • I just would have never guessed from the question you wrote that this was an acceptable answer - particularly since its quite an inefficient way to do things. That's the confusion. But, I guess you're fine with it - I just never understood what was OK from the question. – jfriend00 Jan 06 '23 at 00:12
  • @jfriend00 Yeah, shortly after posting my reply, I was thinking about your comment and realised I was doing things in a very roundabout way. I could've just converted the file in place (i.e. overwrite the file during the conversion, keeping its path) and then just update file.name, file.size and file.type to match the newly converted file. No need to re-load the converted file into memory and create a Blob from it. Thanks. I'll also update the code. – AX2 Jan 06 '23 at 01:24
  • The poorly written question and solution stemmed from the fact that I misunderstood how File objects work and also my inexperience with streams. I though a File object actually contained the contents of a file in-memory. So I was very confused as to how to convert this in-memory data. As I understand it now, a File object is merely a reference to an actual file on the storage device – AX2 Jan 06 '23 at 01:52
  • That's why I asked about where the File object came from in the first place since it isn't a standard nodejs thing. Per the `File` object that is in the browser and is described [here](https://w3c.github.io/FileAPI/#file-section) in this W3C document, the data from the file may be in-memory or on disk - it depends. In fact, it contains a method to read it from storage into an `ArrayBuffer` or the `File` constructor can be just given the bits of the file to start with (where they will be in memory). – jfriend00 Jan 06 '23 at 01:57