I was working on a lambda function which will be triggered via the amazon rest api.
I have created the following
POST
end point on REST API console Now as a first try I created the function such that it will accept the param as
{
"userProfileImagePath":"facebook users profile image path via GET /{user-id}/picture"
}
The lambda function will get the image using request
module and then upload to a bucket.
Since I was using multiple modules I have created everything locally and zipped them including the node_modules
and uploaded them to lambda function console by specifying the Handler
name.
So far its good and it worked pretty well.
Now to extend this I was trying to send 2 profile images - one is from the user - the other is one of his/her friend - Merge both the images into one and then upload the merged image to the destination file.
I tried different approach for the merging and nothing worked. Found some solutions on here and seems like they really do not like streams
Here is what I did so far and as I mentioned both the images are getting uploaded to a bucket however the merging seems to fail, I ran out of idea to accomplish this, would be really helpful if you can give me some directions on how to do it.
Currently it uses async module to do individual tasks
Basically there will be a template image and over the template image multiple images will be placed (merged), here in the code below I am considering userProfileImagePath
as the main template and trying to merge another image into it.
Please let me know if there is a different way to do this.
Here is what I did so far
/**
* This is a test script which runs as a lambda function on amazon
* The lamda function is linked with an amazon end point
* The script will accept a image url (facebook/gravator) etc and will upload to a destination s3 bucket and returns the url
* The param looks like
{
"userProfileImagePath":"https://fbcdn-profile-a.......",
"friendProfileImagePath":"https://fbcdn-profile-a......."
}
*/
var exec = require('child_process').exec,
async = require('async'),
request = require('request'),
gm = require('gm'),
imageMagick = gm.subClass({ imageMagick: true }),
aws = require('aws-sdk');
exports.handler = function(req, context) {
var errMsg = '',
userProfileImageName = 'user_profile',
friendProfileImageName = 'friend_profile',
mergedImageName = 'final_image',
destinationBucket = 'destination_bucket',
response = {} ,
s3 = new aws.S3();
if (req.userProfileImagePath === '') {
errMsg = 'Missing the userProfileImage';
}
if (req.friendProfileImagePath === '') {
errMsg = 'Missing the friendProfileImagePath ';
}
if (errMsg === '') {
async.auto({
copyUserImageToS3 : function(autoCallback) {
console.log('MyImage :'+req.userProfileImagePath);
//var myImageData = {} ;
request({
url: req.userProfileImagePath,
encoding: null
}, function(err, res, body) {
if (err) { return autoCallback(err); }
s3.putObject({
Bucket: destinationBucket,
Key: userProfileImageName+'.jpg',
ContentType: res.headers['content-type'],
ContentLength: res.headers['content-length'],
Body: body, // buffer
ACL:'public-read'
}, autoCallback);
});
},
copyFriendImageToS3 : function(autoCallback) {
console.log('FriendImage :'+req.friendProfileImagePath);
//var friendImageData = {} ;
request({
url: req.friendProfileImagePath,
encoding: null
}, function(err, res, body) {
if (err) { return autoCallback(err); }
s3.putObject({
Bucket: destinationBucket,
Key: friendProfileImageName+'.jpg',
ContentType: res.headers['content-type'],
ContentLength: res.headers['content-length'],
Body: body, // buffer
ACL:'public-read'
}, autoCallback);
});
},
getUserImageFromS3 : ['copyUserImageToS3', function(autoCallback,results) {
s3.getObject({
Bucket: destinationBucket,
Key: userProfileImageName+'.jpg',
}, autoCallback);
}],
getFriendImageFromS3 : ['copyFriendImageToS3', function(autoCallback,results) {
s3.getObject({
Bucket: destinationBucket,
Key: friendProfileImageName+'.jpg',
}, autoCallback);
}],
mergeImages : ['getUserImageFromS3','getFriendImageFromS3', function(autoCallback,results) {
console.log('Here');
gm()
.in('-page', '+0+0') // Custom place for each of the images
.in(results.getUserImageFromS3.body)
.in('-page', '+100+100')
.in(results.getFriendImageFromS3.body)
.minify()
.mosaic()
.toBuffer('JPG', function (err, buffer) {
if (err) { return autoCallback(err); }
s3.putObject({
Bucket: destinationBucket,
Key: mergedImageName+'.jpg',
ContentType: results.getUserImageFromS3.headers['content-type'],
ContentLength: results.getUserImageFromS3.headers['content-length'],
Body: buffer, // buffer
ACL:'public-read'
}, autoCallback);
});
}],
},
function(err, results) {
if (err) {
response = {
"stat":"error",
"msg":"Error manipulating the image :: "+err
} ;
context.done(null,response);
} else {
response = {
"stat":"ok",
"imageUrl":"https://s3-................../"+mergedImageName+".jpg"
} ;
context.done(null,response);
}
});
} else {
response = {
"stat":"error",
"msg": errMsg
} ;
context.done(null,response);
}
};
UPDATE
I tried to have the code run locally after doing some changes, and use the file system for the operation and it appears to be working, not sure how to make it working the same on lamba using s3 Here is the working code locally using the file system
/**
* This will download both the images locally and then merge them
* We can merge multiple images as we need on a base template specifiying the position as shown in the code
* need to make sure that the graphicsmagick is inatalled
* sudo apt-get install graphicsmagick
*/
var exec = require('child_process').exec,
async = require('async'),
request = require('request'),
fs = require('fs'),
gm = require('gm'),
imageMagick = gm.subClass({ imageMagick: true }),
userProfileImagePath ='https://fbcdn-profile-a.akamaihd.net...',
friendProfileImagePath ='https://fbcdn-profile-a.akamaihd.net..';
exports.mergeFile = function(req, context) {
var errMsg = '',
userProfileImageName = 'user_profile',
friendProfileImageName = 'friend_profile',
mergedImageName = 'final_image',
destinationBucket = 'testimagemanipulator',
response = {} ;
if (errMsg === '') {
async.auto({
copyUserImage : function(autoCallback) {
request({
url: userProfileImagePath,
encoding: null
}, function(err, res, body) {
if (err) { return autoCallback(err); }
fs.writeFile(__dirname +'/public/images/'+userProfileImageName+'.jpg', body, 'binary', function(err) {
if(err) { return autoCallback(err); }
return autoCallback();
});
});
},
copyFriendImage : function(autoCallback) {
request({
url: friendProfileImagePath,
encoding: null
}, function(err, res, body) {
if (err) { return autoCallback(err); }
fs.writeFile(__dirname +'/public/images/'+friendProfileImageName+'.jpg', body, 'binary', function(err) {
if(err) { return autoCallback(err); }
return autoCallback();
});
});
},
mergeImages : ['copyUserImage','copyFriendImage', function(autoCallback,results) {
console.log('Here');
gm()
.in('-page', '+0+0') // Custom place for each of the images
.in(__dirname +'/public/images/'+userProfileImageName+'.jpg')
.in('-page', '+140+50')
.in(__dirname +'/public/images/'+friendProfileImageName+'.jpg')
.minify() // Halves the size, 512x512 -> 256x256
.mosaic() // Merges the images as a matrix
.write(__dirname +'/public/images/'+mergedImageName+'.jpg', function (err) {
if (err) { return autoCallback(err); }
fs.unlink(__dirname +'/public/images/'+userProfileImageName+'.jpg');
fs.unlink(__dirname +'/public/images/'+friendProfileImageName+'.jpg');
return autoCallback() ;
});
}],
},
function(err, results) {
if (err) {
response = {
"stat":"error",
"msg":"Error manipulating the image :: "+err
} ;
console.log(response) ;
} else {
response = {
"stat":"ok",
"imageUrl":__dirname +'/public/images/'+mergedImageName+'.jpg'
} ;
console.log(response) ;
}
});
} else {
response = {
"stat":"error",
"msg": errMsg
} ;
console.log(response) ;
}
};
module.exports.mergeFile() ;
Ok looks like it would be difficult to achieve what I was looking as explained into the question above. However after doing several try and different modules none of the try worked. As explained the purpose of the lambda function is
Get a public image urls (facebook)
Download them and merge together, it could be merged over an image template also
So far the best image manipulation module is gm
and this could be used on aws lambda all we need to make sure its a subclass of Image Magic
The next is merging operation could be done by using aws /tmp
folder where the images could be placed temporarily and after the merging is done they could be removed from there.
Here is a working lambda script which is linked to an aws REST API
/**
* This is a test script which runs as a lambda function on amazon
* The lamda function is linked with an amazon end point
* The script will accept a image url (facebook/gravator) etc and will upload to a destination s3 bucket and returns the url
* The param looks like
{
"userProfileImagePath":"https://fbcdn-profile-a.akamaihd.net/....",
"friendProfileImagePath":"https://fbcdn-profile-a.akamaihd.net/...."
}
*/
var exec = require('child_process').exec,
async = require('async'),
request = require('request'),
gm = require('gm').subClass({ imageMagick: true }),
fs = require('fs'),
aws = require('aws-sdk');
exports.handler = function(req, context) {
var errMsg = '',
userProfileImageName = 'user_profile',
friendProfileImageName = 'friend_profile',
mergedImageName = 'final_image',
destinationBucket = 'mybucket',
response = {} ,
s3 = new aws.S3();
if (req.userProfileImagePath === '') {
errMsg = 'Missing the userProfileImage';
}
if (req.friendProfileImagePath === '') {
errMsg = 'Missing the friendProfileImagePath ';
}
if (errMsg === '') {
async.auto({
copyUserImage : function(autoCallback) {
request({
url: req.userProfileImagePath,
encoding: null
}, function(err, res, body) {
if (err) { return autoCallback(err); }
fs.writeFile('/tmp/'+userProfileImageName+'.jpg', body, 'binary', function(err) {
if(err) { return autoCallback(err); }
return autoCallback();
});
});
},
copyFriendImage : function(autoCallback) {
request({
url: req.friendProfileImagePath,
encoding: null
}, function(err, res, body) {
if (err) { return autoCallback(err); }
fs.writeFile('/tmp/'+friendProfileImageName+'.jpg', body, 'binary', function(err) {
if(err) { return autoCallback(err); }
return autoCallback();
});
});
},
mergeImages : ['copyUserImage','copyFriendImage', function(autoCallback,results) {
var bgImage = '/tmp/'+userProfileImageName+'.jpg',
frontImage = '/tmp/'+friendProfileImageName+'.jpg';
gm()
.in('-page', '+0+0') // Custom place for each of the images
.in(bgImage)
.in('-page', '+140+50')
.in(frontImage)
.mosaic() // Merges the images as a matrix
.font("Arial")
.fontSize(50)
.fill('black')
.drawText(1, 1, 'Hello World', 'Center')
.fill('blue')
.drawText(0, 0, 'Hello World', 'Center')
.write('/tmp/'+mergedImageName+'.jpg', function (err) {
if (err) { return autoCallback(err); }
var stream = fs.createReadStream('/tmp/'+mergedImageName+'.jpg');
var stats = fs.statSync('/tmp/'+mergedImageName+'.jpg');
console.log('Merged File size :'+stats['size']);
s3.upload({
Bucket: destinationBucket,
Key: mergedImageName+'.jpg',
ContentType: 'image/jpeg',
ContentLength: stats['size'],
Body: stream, // buffer
ACL:'public-read'
}, autoCallback);
});
}],
},
function(err, results) {
if (err) {
response = {
"stat":"error",
"msg":"Error manipulating the image :: "+err
} ;
context.done(null,response);
} else {
response = {
"stat":"ok",
"imageUrl":"https://domain.amazonaws.com/mybucket/"+mergedImageName+".jpg"
} ;
context.done(null,response);
}
});
} else {
response = {
"stat":"error",
"msg": errMsg
} ;
context.done(null,response);
}
};