ImageMagick not converting pdfs anymore in AWS Lambda - node.js

I've had a AWS Lambda function running on S3 objects for the last 18 months and it died around a month ago after a minor update. I've reverted it but it's still broken. I've looked into doing the most basic conversion of pdf using ImageMagick with no luck so I think AWS has updated something and caused the pdf module to either be removed or stop working.
I've done just the basic function I was basically doing in my core code in Node.js 8.10:
gm(response.Body).setFormat("png").stream((err, stdout,stderr) => {
if (err) {
console.log('broken');
}
const chunks = [];
stdout.on('data', (chunk) => {
chunks.push(chunk);
});
stdout.on('end', () => {
console.log('gm done!');
});
stderr.on('data', (data) => {
console.log('std error data ' + data);
})
});
with the error response:
std error dataconvert: unable to load module `/usr/lib64/ImageMagick-6.7.8/modules-Q16/coders/pdf.la': file not found
I've also tried moving to Node.js 10.x and using the ImageMagick layer that's available through the aws serverless app repository. Trying this on the same code generates this error
std error data convert: FailedToExecuteCommand `'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4 -dGraphicsAlphaBits=4 '-r72x72' '-sOutputFile=/tmp/magick-22TOeBgB4WrfoN%d' '-f/tmp/magick-22KvuEBeuJuyq3' '-f/tmp/magick-22dj24vSktMXsj'' (1) # error/pdf.c/InvokePDFDelegate/292
In both cases the function works correctly when running on an image file instead.
Based on this I think both the aws 8.10 ImageMagick and the layer for 10 are missing the pdf module but I'm unsure how to add it or why it was removed in the first place. Whats the best way to fix this function that was working?
EDIT
So I've downloaded https://github.com/serverlesspub/imagemagick-aws-lambda-2 and built the library manually, uploaded it to Lambda and got it successfully working as a layer however it doesn't include GhostScript of which it is an optional library. I've tried to add it to Makefile_ImageMagick which builds and has some references to Ghostscript in the result but running it doesn't fix the PDF issue (images still work). Whats the best way to add the GhostScript optional library to the Make file?

While the other answers helped there was still a lot of work to get to a workable solution so below is how I managed to fix this, specifically for NodeJS.
Download: https://github.com/sina-masnadi/lambda-ghostscript
zip up the bin directory and upload it as a layer into Lambda.
Add https://github.com/sina-masnadi/node-gs to your NodeJS modules. You can either upload them as part of your project or the way I did it as a layer (along with all your other required ones).
Add https://github.com/serverlesspub/imagemagick-aws-lambda-2 as a layer. Best way to do this is to create a new function in Lambda, Select Browse serverless app repository, search for "ImageMagick" and select "image-magick-lambda-layer" (You can also build it and upload it as a layer too).
Add the three layers to your function, I've done it in this order
GhostScript
ImageMagick
NodeJS modules
Add the appPath to the require statement for ImageMagick and GhostScript:
var gm = require("gm").subClass({imageMagick: true, appPath: '/opt/bin/'});
var gs = require('gs');
Mine was in an async waterfall so before my previous processing function I added this function to convert to a png if wasn't an image already:
function convertIfPdf(response, next) {
if (fileType == "pdf") {
fs.writeFile("/tmp/temp.pdf", response.Body, function(err) {
if (!err) {
gs().batch().nopause().executablePath('/opt/bin/./gs').device('png16m').input("/tmp/temp.pdf").output('/tmp/temp.png').exec(function (err, stdout, stderr){
if (!err && !stderr) {
var data = fs.readFileSync('/tmp/temp.png');
next(null, data);
} else {
console.log(err);
console.log(stderr);
}
});
}
});
} else {
next(null, response.Body);
}
}
From then on you can do what you were previously doing in ImageMagick as it's in the same format. There may be better ways to do the pdf conversion but I was having issues with the GS library unless working with files. If there are better ways let me know.
If you are having issues loading the libraries make sure the path is correct, it is dependent on how you zipped it up.

I had the same problem. Two cloud services processing thousands of PDF pages a day failing because of the pdf.la not found error.
The solution was to switch from Image Magick to GhostScript to convert PDFs to PNGs and then use ImageMagick with PNGs (if needed). This way, IM never has to deal with PDFs and wont need the pdf.la file.
To use GhostScript on AWS Lambda just upload the gs binary in the function zip file.

You can add a Layer to your lambda function to make it work again until the 22/07/2019.
The ARN of the Layer that you need to add is the following : arn:aws:lambda:::awslayer:AmazonLinux1703
The procedure is described at upcoming-updates-to-the-aws-lambda-execution-environment
Any long term solution would be wonderful.

I had the issue where ghostscript was no longer found.
Previously, I had referenced ghostscript via:
var gs = '/usr/bin/gs';
Since AWS lambda stopped providing that package, I went and included it directly into my lambda function which worked for me. I just downloaded the files from https://github.com/sina-masnadi/lambda-ghostscript and placed it in a folder called 'ghostscript' Then referenced it as so:
var path = require('path')
var gs = path.join(__dirname,"ghostscript","bin","gs")

Related

Best way to copy a directory from an external drive to a local folder with electronjs?

Just wondering if anyone has ever attempted to copy a directory from an external drive (connected via USB) to a local folder.
I am using ElectronJS so I can use my JavaScript, HTML/CSS skills to create a desktop application without utilising a C language. (i.e. C# or C++) With ElectronJS there's a lot less to worry about.
Here is the list of things I've tried so far:
basic fs.copyFile (using copyFile intially and will then loop round the directory to copy all files)
var fs = require('fs');
window.test = () => {
fs.moveSync("targetFile","destDir", function(err) {
if(err){
console.log(err);
}else{
console.log("copy complete")
}
});
}
fs.moveSync is not a function even though Visual Studio Code brought up moveSync as a suggestion when I entered fs. (ctrl + space)
using child_process functions to copy files using the command line.
Code is:
var process = require('child_process')
window.test = function(){
process.exec('ipconfig', function(err, stdout, stderr){
if(err){
console.log(err);
}else{
console.log(stdout)
}
})
}
Then bundled with browserify. Bundle.js is then imported into the html file and the test function is called on the click of a button. I'm aware the command is ipconfig for now, this was merely used to see if a command could be executed. It appears it could because I was getting process.exec is not defined.
use the node-hid node module to read and trasfer data from the external drive.
The exposed functions within this module were also reported as being undefined. And I thought about the use case longer I thought a simple copy process would suffice because external drive can be accessed like any other folder in the file explorer.
Unfortunately, all of the above have failed and I've spent the most part of the day looking for alternative modules and/or solutions.
Thanks in advance because any help to achieve this would be much appreciated.
Thanks
Patrick
The npm package fs-extra should solve your problem.
It has the move function, which
Moves a file or directory, even across devices
Ended up adding this to my preload.js for:
window.require = require;
It will work for now but is due to be depreciated.
I'll use this for now and make other updates when I have to.

Load images from local directory in Fabric

I am trying to load images from a local folder using fabric.js in node.
There seems to be very little up to date documentation on how to do this.
Most example use fabric.Image.fromURL(imageurl)
As far as I'm aware, this only works for web urls, not local paths.
Correct me if I'm wrong, but I have tried
fabric.Image.fromURL(imgpath, (img) => {
...
}
which throws the error Coul not load img: /image/path/img.jpg
Where
fs.readFile(imagepath, (err, i) => {
...
})
will successfully read the file, i will be a buffer.
What is the correct way to load a local image.
I know there is a fabric.Image.fromObject but I have no idea what type of object it wants.
I am currently loading the image into a 2d canvas object, converting it with canvas.toDataURL() and putting that url into fabric.Image.fromURL() which works but converting the image to a url is very slow due to large images. There must be a way to load the image directly and avoid this problem.
If you are using fabricjs 3+, that uses the new jsdom, you can use the file urls!
fabric.Image.fromURL(file://${__dirname}${filepath});
Check here on the fabricJS codebase how they handle reading files in browser and node for the visual test images
https://github.com/fabricjs/fabric.js/blob/master/test/lib/visualTestLoop.js#L139
try this one:
fabric.Image.fromURL(require("../../assets/mockup/100.png"), (img) => {...}

Facing issues when uploaded a zip code in aws lambda

I am new to aws and just started working around with aws lambda by following some youtube tutorials and was able to write aws lambda functions successfully on the web editor itself.
But I tried with the uploading zip file from my local system in which i wrote a node.js code that use modules "fs" and "fill-pdf". But when I tried to run the code it was giving me error.
"error" : module not found "/var/task/index".
I searched through internet and found some links like :
https://github.com/lob/lambda-pdftk-example
I tried this but it also shows same error.
Here is my code :
var index = require('index');
var fillPdf = require("fill-pdf");
var fs = require('fs');
var formDate = {
'Employee Name': 'MyName',
'Company Name': 'ComapnyName'
};
var pdfTemplatePath = "my.pdf";
fillPdf.generatePdf(formDate, pdfTemplatePath, function(err,
output) {
if ( !err ) {
fs.writeFile('message.pdf', output, function (err) {
if (err) throw err;
console.log('It\'s saved! in same location.');
});
}
});
The thing is that I don't know what could be the reason that this error is coming.Thanks for any help.
Make sure you're not zipping the folder, but its contents. Check that your zip contains index.js in its root level
The error may occur due to the following :
1. Properly zip the folder wait for it's zipping process completion and
then upload.
2. First run the main.js file locally like using node main.js and check
are there any errors showing in the terminal window, if it does then
fix them and then upload.
3. Also there must be handler file that lambda needs, which is must
so if you have the handler.js file then when in aws lambda you
create a lambda function and check the configuration setting there
then do update the name of the handler file name with yours like by
default it is index.js may be you would have lambda.js do change it
with lambda name (example lambda.handler)
Remove the line var index = require('index'); as it is not used in your code. I'm not sure why it can't find the module after installing it, but in you current example you don't need it.
This error occurs it means your zip is
not in valid form in which aws demands.
If you double click on zip you will find your folder inside that your code file,but lambda wants that when you double click on zip it shoud show direct code files.
To achive this:
open terminal
cd your-lambda-folder
zip -r index.zip *
then upload index.zip to lambda

node.js issues with Meteor's file system

I have tried to figure out what i am missing from this puzzle between. Node.js and Meteor.js. Meteor is built on Node.js i know this. But Meteor doesn't not work properly with Node.js. Either I need to do 20 more steps to get the same result, which I don't know what they are. Or there is a serious bug between the two. Standalone Node.js runs the command below just fine. Running the same commands on Meteor cause errors or undefined results. Wish i had a why to solve this or they need to patch this so it will work the way it should work.
examples #1
var fs = require('fs');
fs.readFile('file.txt', 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
console.log(data);
});
example #2
var jetpack = require('fs-jetpack');
var data = jetpack.read('file.txt');
console.log(data);
example #3
var fs = require ('fs');
var readMe = fs.readFileSync('file.txt', 'utf8');
console.log(readMe);
You shouldn't try to load files like this because you don't know what the folder structure looks like. Meteor creates builds from your project directory, both in development and production mode. This means that even though you have a file.txt in your project folder, it doesn't end up in the same place in the build (or it isn't even included in the build at all).
For example, your code tries to read the file from the development build folder .meteor/local/build/programs/server. However, this folder doesn't contain file.txt.
Solution: Store file.txt in the private folder of your project and use Assets.getText to read it. If you still want to use the functions from fs to load the file, you can retrieve the absolute path with Assets.absoluteFilePath.

using node-fluent-ffmpeg to transcode with ffmpeg on windows not working

I'm trying to use the module node-fluent-ffmpeg (https://github.com/schaermu/node-fluent-ffmpeg) to transcode and stream a videofile. Since I'm on a Windows machine, I first downloaded FFMpeg from the official site (http://ffmpeg.zeranoe.com/builds/). Then I extracted the files in the folder C:/FFmpeg and added the path to the system path (to the bin folder to be precise). I checked if FFmpeg worked by typing in the command prompt: ffmpeg -version. And it gave a successful response.
After that I went ahead and copied/altered the following code from the module (https://github.com/schaermu/node-fluent-ffmpeg/blob/master/examples/express-stream.js):
app.get('/video/:filename', function(req, res) {
res.contentType('avi');
console.log('Setting up stream')
var stream = 'c:/temp/' + req.params.filename
var proc = new ffmpeg({ source: configfileResults.moviepath + req.params.filename, nolog: true, timeout: 120, })
.usingPreset('divx')
.withAspect('4:3')
.withSize('640x480')
.writeToStream(res, function(retcode, error){
if (!error){
console.log('file has been converted succesfully',retcode);
}else{
console.log('file conversion error',error);
}
});
});
I've properly setup the client with flowplayer and tried to get it running but
nothing happens. I checked the console and it said:
file conversion error timeout
After that I increased the timeout but somehow, It only starts when I reload the page. But of course immediately stops because of the page reload. Do I need to make a separate node server just for the transcoding of files? Or is there some sort of event I need to trigger?
I'm probably missing something simple but I can't seem to get it to work.
Hopefully someone can point out what I've missed.
Thanks
I've fixed it by using videoJs instead of Flowplayer. The way flowplayer was launched did not work properly in my case. So I init the stream and then initialize videojs to show the stream. Which works great.

Resources