I'm letting users upload multiple images directly to Amazon-S3 using Multer-S3 and then displaying those images on the front end via a loop. All works perfectly.
However when the images are uploaded via mobile (image taken on an iPhone or Android) the orientation is correct on mobile but does NOT have correct orientation on desktops. Major problem.
This is due to the images EXIF data I believe.
Seems like ImageMagick or Kraken JS https://kraken.io/docs/storage-s3 might be a way to solve it but for the life of me I cannot figure out how to implement either with the way I'm uploading and showing images shown below.
How would I change my code below to auto-orient the images? Note: It must work for multiple images.
Thanks for any help!
Heres's how I'm letting users upload multiple images at a time directly to Amazon-S3:
aws.config.update({
secretAccessKey: 'AccessKey',
accessKeyId: 'KeyID',
region: 'us-east-2'
});
var s3 = new aws.S3();
var storage = multerS3({
limits : { files: 25 },
s3: s3,
bucket: 'files',
key: function (req, file, cb) {
var fileExtension = file.originalname.split(".")[1];
var path = "uploads/" + req.user._id + Date.now() + "." + fileExtension;
cb(null, path);
},
})
var upload = multer({storage: storage}).any("images", 25);
router.post("/", middleware.isLoggedIn, function(req, res, next){
upload(req,res,function(err) {
if(err) {
console.log(err);
res.redirect('/')
}
Listings.findById(req.params.id, function(err, foundListings){
var allimages = []
if(typeof req.files !== "undefined") {
for(var i = 0; i < req.files.length; i++) {
allimages.push(req.files[i].key);
}
}
var currentimages = allimages;
var newListings = {currentimages:currentimages}
//Removed the other Model aspects
Listings.create(newListings, function(err, newlyCreated){
if(err){
console.log(err);
} else {
res.redirect("/listings");
}
});
});
How I'm displaying the images on the front end. Listings.currentimages is an array containing all image links.
app.locals.awspath = "https://s3.us-east-2.amazonaws.com/myfiles/";
// awspath is the file path to my Amazon-S3 path
<div id='allimages'>
<% for(var i = 0; i < listings.currentimages.length; i++ ) { %>
<div class='smallerImages'>
<% var url2 = awspath + listings.currentimages[i] %>
<img class="small" src="<%= url2 %>">
</div>
<% } %>
</div>
The problem is that iOS sets the image's EXIF metadata which causes this behavior. You can use a library that can read the EXIF metadata and rotate the image for you.
jpeg-autorotate (https://github.com/johansatge/jpeg-autorotate) is a very simple lib and has very nice documentation (you should check it out).
var jo = require('jpeg-autorotate');
var fs = require('fs');
// var options = {quality: 85};
var options = {};
var path = '/tmp/Portrait_8.jpg'; // You can use a Buffer, too
jo.rotate(path, options, function(error, buffer, orientation) {
if (error) {
console.log('An error occurred when rotating the file: ' + error.message);
return;
}
console.log('Orientation was: ' + orientation);
// upload the buffer to s3, save to disk or more ...
fs.writeFile("/tmp/output.jpg", buffer, function(err) {
if(err) {
return console.log(err);
}
console.log("The file was saved!");
});
});
You can find some sample images with different EXIF rotation metadata from here
// Name this file index.js and zip it + the node_modules then upload to AWS Lambda
console.log('Loading function');
var aws = require('aws-sdk');
var s3 = new aws.S3({apiVersion: '2006-03-01'});
var jo = require('jpeg-autorotate');
// Rotate an image given a buffer
var autorotateImage = function(data, callback) {
jo.rotate(data, {}, function(error, buffer, orientation) {
if (error) {
console.log('An error occurred when rotating the file: ' + error.message);
callback(error, null);
} else {
console.log('Orientation was: ' + orientation);
callback(null, buffer);
}
});
};
// AWS Lambda runs this on every new file upload to s3
exports.handler = function(event, context, callback) {
console.log('Received event:', JSON.stringify(event, null, 2));
// Get the object from the event and show its content type
var bucket = event.Records[0].s3.bucket.name;
var key = event.Records[0].s3.object.key;
s3.getObject({Bucket: bucket, Key: key}, function(err, data) {
if (err) {
console.log("Error getting object " + key + " from bucket " + bucket +
". Make sure they exist and your bucket is in the same region as this function.");
callback("Error getting file: " + err, null);
} else {
// log the content type, should be an image
console.log('CONTENT TYPE:', data.ContentType);
// rotate the image
autorotateImage(data.Body, function(error, image) {
if (error) {
callback("Error rotating image: " + error, null);
}
const params = {
Bucket: bucket,
Key: 'rotated/' + key,
Body: image
};
// Upload new image, careful not to upload it in a path that will trigger the function again!
s3.putObject(params, function (err, data) {
if (error) {
callback("Error uploading rotated image: " + error, null);
} else {
console.log("Successfully uploaded image on S3", data);
// call AWS Lambda's callback, function was successful!!!
callback(null, data);
}
});
});
}
});
};
Notes This function upload the rotated images to the same bucket but you can easily change that. If you are just starting with AWS Lambda, I'd suggest you learn more about it (https://www.youtube.com/watch?v=eOBq__h4OJ4, https://www.youtube.com/watch?v=PEatXsXIkLc)
Make sure you've the right permissions (read and write), correct function trigger, correct "Handler" when creating the function! Make sure to checkout the function logs in CloudWatch too, makes debugging a lot easier. If it starts timing out, increase the function timeout and increase it's memory.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With