Search code examples
node.jsmeteorgraphicsmagick

Use GraphicsMagick methods depends on dimensions with CollectionFS


I am looking for a working way to to use the GM methods in CollectionFS transformWrite function depending o the image size. There is a size method implemented in GM but this works async and so it seems to be not possible to use.

I tried the following:

gm(readStream, fileObj.name()).size(function(err, dimensions){
    if (err) {
        console.log('err with getting size:');
        console.log(err);
    }
    console.log('Result of media_size:');
    console.log(dimensions);
    // here do smth depends on the dimensions ...

    gm(readStream, fileObj.name()).resize('1200', '630').stream().pipe(writeStream);


});

When i use the above snippet in the CollectionFS function I get this error:

Error: gm().stream() or gm().write() with a non-readable stream.

This seems to be a problem that I use a async function - when removing the async function the upload works perfectly but then I have no access to the dimensions of the uploaded image.

Is there a solution to get the dimensions of the image in a sync way when having just access to fileObj, readStream & writeStream ?

Edit:

Thanks Jasper for the hint with the wrapAsync. I tested it and have this code in use:

var imgsize;
var img = gm(readStream, fileObj.name());
imgsize = Meteor.wrapAsync(img.size, img);
console.log('call wrapAsync:');
var result;
try {
    result = imgsize();
} catch (e) {
    console.log('Error:');
    console.log(e)
}
console.log('((after imgsize()))');

When take a look at the console.logs the script stops after "call wrapAsync" - also there is no error returning so its very difficult to tell whats the problem. I also tried this with the NPM package "imagesize" with Meteor.wrapAsync(imagesize); and then imgsize(readStream) which causes the same: No console log after "call wrapAsync:".


Solution

  • The core of the problem is not the asynchronous behavior of gm().size(), but the fact that you use the readStream twice. First you use it to get the size of the image, which empties readStream. Then you try to use it again to resize but because it has ended, you get an error telling you the stream is non-readable.

    I found the solution at the bottom of the gm package's streams documenation:

    GOTCHA: when working with input streams and any 'identify' operation (size, format, etc), you must pass "{bufferStream: true}" if you also need to convert (write() or stream()) the image afterwards NOTE: this buffers the readStream in memory!

    Based on that and the small example below, we can change your code to:

    gm(readStream, fileObj.name()).size({ bufferStream: true }, function(err, dimensions){
        if (err) {
            console.log('err with getting size:');
            console.log(err);
        }
        console.log('Result of media_size:');
        console.log(dimensions);
        // here do smth depends on the dimensions ...
    
        this.resize('1200', '630').stream().pipe(writeStream);
    });
    

    Inside of the callback, this refers to the image you're working on and you can use it to continue your chain.

    I tested this in a small sample meteor application, and it works!