Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected empty writestream in collectionFS using graphicsmagick

I'm using CollectionFS for managing images. Furthermore I'm using graphicsmagick gm() for manipulating images.

Now I want to crop a already saved image. Therefore on a click event a server-method is called, which does the crop(). But after doing this, in the collection I find an empty image with size=0 updated on the correct date.

I don't see, what I am doing wrong.

shared.js

Images = new FS.Collection("images", {
    stores: [
        new FS.Store.FileSystem("thumbnail", { 
            transformWrite: function(fileObj, readStream, writeStream) {
                gm(readStream, fileObj.name()).autoOrient().resize('96', '96' + '^').gravity('Center').extent('96', '96').stream().pipe(writeStream);
            } 
        }),
        new FS.Store.FileSystem("public"),
    ]
});

server.js

Meteor.methods({
    'crop': function (fileId, selection) {
        var file = Images.findOne({ _id: fileId }),
            read = file.createReadStream('public'),
            write = file.createWriteStream('public');

        gm(read)
            .crop(selection.width, selection.height, selection.left, selection.top)
        .stream()
        .pipe(write);
    }
});

client.js

Template.editor.events({
    'click #crop': function () { 
        var fileId      = '123456789',
            selection   = { height: 100, width: 100, top: 10, left: 10 }; 

        Meteor.call('crop', fileId, selection);
    }
});

Update

As recommended by Christian I'm using a tmp-file for the writeStream, because the writeStream can't be the same like the readStream - which caused the empty result.

But after writing to the tmp-file, the content of it has to be copied back to the public store. How do I do that?

Meteor.methods({
    'crop': function (fileId, selection) {

        var fs   = Meteor.npmRequire('fs'),
            file = Images.findOne({ _id: fileId }),
            read = file.createReadStream('public'),
            filename = '/tmp/gm_' + Date.now(),
            tmp  = fs.createWriteStream(filename);

        gm(read)
            .crop(selection.width, selection.height, selection.left, selection.top)
        .stream()
        .pipe(tmp);

        // After writing to tmp -> copy back to stream and delete tmp-file
    }
});

Update 2 I tried this one:

// Add temp store
new FS.Store.FileSystem("temp")

// Method
Meteor.methods({
    'crop': function (fileId, selection) {
        var file  = Images.findOne({ _id: fileId }),
            read  = file.createReadStream('public'),
            temp  = file.createWriteStream('temp');

        gm(read)
            .crop(selection.width, selection.height, selection.left, selection.top)
        .stream()
        .pipe(tmp)
        .on('end', function () {
            var tmpread  = file.createReadStream('temp'),
                write    = file.createWriteStream('public');

            gm(tmpread).stream().pipe(write);
        });

    }
});
like image 232
user3142695 Avatar asked Oct 30 '22 15:10

user3142695


1 Answers

You can't read and write into the same file. This is equivalent to things like

cat test | grep 1 > test

on the shell. You can try it and see that test will be empty afterwards.

You need to create an intermediate, temporary file in your crop method.


Assuming that is indeed the problem, then this is one way of doing this (not tested):

var fs = Meteor.npmRequire('fs');
var file = Images.findOne({ _id: fileId }),
var read = file.createReadStream('public'),
var filename = '/tmp/gm_' + Date.now();
var tmp = fs.createWriteStream(filename);

var gmread = gm(read)
    .crop(selection.width, selection.height, selection.left, selection.top)
    .stream();

gmread.on('end', function() {
    // done streaming through GM, copy the result back:
    var tmpread = fs.createReadStream(filename);
    var write = file.createWriteStream('public');
    tmpread.pipe(write);
});

gmread.pipe(tmp);
like image 112
Christian Fritz Avatar answered Nov 12 '22 18:11

Christian Fritz