stojanovic / lambda-audio

Run Sound eXchange (SoX), the Swiss Army knife of audio manipulation, with Lame on AWS Lambda
GNU General Public License v2.0
59 stars 4 forks source link

Error: spawn /tmp/soxi ENOENT #1

Open serverlesspolska opened 6 years ago

serverlesspolska commented 6 years ago

Hello,

I think I will need your help with my issue. I'm using serverless framework and have lambda function that downloads mp3 file from a bucket, then saves it in /tmp/input.mp3

Next I'm executing code:

   import lambdaAudio from 'lambda-audio'

    const doAudioMagic = () => {
        console.log('executing soxi')
        lambdaAudio.soxi('/tmp/input.mp3')
            .then(response => {
                // Do something with the info
                console.log(response)
            })
            .catch(errorResponse => {
                console.log('Error from the soxi command:', errorResponse)
            })
    }

And I get following error:

Error: spawn /tmp/soxi ENOENT
    at exports._errnoException (util.js:1018:11)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:193:32)
    at onErrorNT (internal/child_process.js:367:16)
    at _combinedTickCallback (internal/process/next_tick.js:80:11)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

So the soxi file is not present the the path, but why? How should I "install" lambda-audio so the file is properly copied to the /tmp/ path?

stojanovic commented 6 years ago

Hey, it's possible that input.mp3 is not present at that location at the moment you are executing soxi. Can you do following:

  1. Check if index.mp3 exists (ie. log fs.existsSync('/tmp/input.mp3')).
  2. Check if sox works, soxi is just a symlink to it, if sox works it's a symlink issue.
  3. If file exists, please provide a minimal project that I can run and reproduce the issue.

Thanks

serverlesspolska commented 6 years ago

Hi @stojanovic

I changed soxi to lame, the effect is the same:

Here is my lambda function code

// eslint-disable-next-line import/prefer-default-export
import {getS3File, uploadToBucket} from "./Common"
// import lambdaAudio from 'lambda-audio'
import fs from 'fs'

export const main = (event, context, callback) => {
    const p = new Promise((resolve) => {
        resolve('success');
    });
    console.log('Extract info from mp3')
    console.log('Input data', event)

    const doAudioMagic = () => {
        const lambdaAudio = require('lambda-audio')
        console.log('executing lambda-audio')
        lambdaAudio.lame('-b 64 /tmp/input.mp3 /tmp/output.mp3')
            .then(response => {
                fs.readFile("/tmp/output2.mp3", (err, file) => {
                    if (err) throw err;
                    console.log(data);
                    uploadToBucket(event.bucket, 'output2.mp3', file, '', data.ContentType)
                        .then(console.log('Uploaded'))

                })
            })
            .catch(errorResponse => {
                console.log('Error from the lame command:', errorResponse)
            })
    }

    const processFile = data => {
        fs.writeFile("/tmp/input.mp3", data.Body, function (err) {
            if (err) {
                return console.log(err);
            }
            console.log("File saved successfully!");
            const mp3Exists = fs.existsSync('/tmp/input.mp3')
            console.log('result of fs.existsSync(\'/tmp/input.mp3\'): ', mp3Exists)
            console.log('Readdir /tmp')
            fs.readdirSync('/tmp/').forEach(console.log)
            if (mp3Exists) {
                doAudioMagic()
            }

        })
    }

    const data = getS3File(event.bucket, event.key, processFile)

    const results = {
        message: 'Go Serverless Step Function',
    }

    p
        .then(() => callback(null, JSON.stringify(results)))
        .catch(e => callback(e));
};

and the logs

Extract info from mp3
Input data { bucket: 'REDACTED', key: 'REDACTED.mp3' }
getS3File with those params: REDACTED.mp3 from REDACTED
File saved successfully!
result of fs.existsSync('/tmp/input.mp3'):  true
Readdir /tmp
input.mp3 0 [ 'input.mp3' ]
executing lambda-audio
Error: spawn /bin/lame ENOENT
    at exports._errnoException (util.js:1018:11)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:193:32)
    at onErrorNT (internal/child_process.js:367:16)
    at _combinedTickCallback (internal/process/next_tick.js:80:11)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

Looks like soxi, sox, and lame files are not copied over to /tmp/ directory. But why?

stojanovic commented 6 years ago

Hm, are you using babel? If you are using "files" in package.json it's possible that you are not deploying sox at all. It shouldn't be in /tmp, sox is bundled with the module, but soxi is symlink, and lib creates it in /tmp because npm pack ignores symlinks and Claudia.js that I am using is using npm pack under the hood.

Can you try without babel or make sure sox is deployed? ie. code can look like this (not tested):

// eslint-disable-next-line import/prefer-default-export
cons {getS3File, uploadToBucket} = require('./Common')
const lambdaAudio = require('lambda-audio')
const fs = require('fs')

function doAudioMagic() {
  return lambdaAudio.lame('-b 64 /tmp/input.mp3 /tmp/output.mp3')
    .then(response => {
      // This is ok, because Lambda function is not doing anything else anyway
      const file = fs.readFile('/tmp/output2.mp3')

      return uploadToBucket(event.bucket, 'output2.mp3', file, '', data.ContentType)
}

function processFile(data) {
  return new Promise((resolve, reject) => {
    fs.writeFile('/tmp/input.mp3', data.Body, (err, file) => {
      if (err) {
        return reject(err)
      }

      return file
    })
  })
}

exports.handler = function(event, context, callback) {
  getS3File(event.bucket, event.key, processFile)
    .then(data => processFile(data))
    .then(() => doAudioMagic())
    .then(() => {
       const results = {  message: 'Go Serverless Step Function' }
       callback(null, JSON.stringify(results))
    })
    .catch(e => callback(e));
}
serverlesspolska commented 6 years ago

Hi, I'm using serverless framework with aws-nodejs-ecma-script https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-nodejs-ecma-script which automatically transpiles code using webpack

I'll try your code today

stojanovic commented 6 years ago

Hm, I am not sure what that template sends to AWS Lambda, but you need to make sure that sox exists. You can probably download it from S3 as an alternative.

serverlesspolska commented 6 years ago

Hi @stojanovic

Finally, I managed to include sox and lame binaries in the generated .zip that is uploaded into lambda. Unfortunately, I still have problems, though.

No matter if the contents of the zip file are like that:

unzip -l .serverless\musicquiz.zip
Archive:  .serverless\musicquiz.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
   484896  1980-01-01 00:00   bin/lame
  2913860  1980-01-01 00:00   bin/sox
   763687  1980-01-01 00:00   ExtractInfo.js
     3316  1980-01-01 00:00   SaveIntoDynamo.js
  3062571  1980-01-01 00:00   TriggerOrchestrator.js
---------                     -------
  7228330                     5 files

or

Archive:  .serverless\musicquiz.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
   763689  1980-01-01 00:00   ExtractInfo.js
   484896  1980-01-01 00:00   node_modules/lambda-audio/bin/lame
  2913860  1980-01-01 00:00   node_modules/lambda-audio/bin/sox
     3316  1980-01-01 00:00   SaveIntoDynamo.js
  3062571  1980-01-01 00:00   TriggerOrchestrator.js
---------                     -------
  7228332                     5 files

I still get the same error from lambda function:

Error: spawn /bin/lame ENOENT
    at exports._errnoException (util.js:1018:11)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:193:32)
    at onErrorNT (internal/child_process.js:367:16)
    at _combinedTickCallback (internal/process/next_tick.js:80:11)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

Files are available to the functions. Following code:

    const myPath = "bin/lame"
    console.log(`File ${myPath} exists ${fs.existsSync(myPath)}`)
    console.log(`File /${myPath} exists ${fs.existsSync('/'+myPath)}`)

generates:

File bin/lame exists true
File /bin/lame exists false

So I think this is just a matter of putting sox and lame files in proper path in the zip file, so they are where lambda-audio expectes them to be.

Could you please tell me where those files should be located? Where they are in the .zip file generated by ClaudiaJS?