5

Background

I am wiring up a firebase function in node. Purpose is to parse an inbound audio clip to a set length. Using ffmpeg and fluent-ffmpeg.

Problem

When the function is triggered in firebase, I am getting ENOENT error when Fluent-Ffmpeg attempts to access the Ffmpeg binary

Firebase Debug Output

Error: { Error: spawn ./Cloud/functions/node_modules/ffmpeg-binaries/bin/ffmpeg 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) code: 'ENOENT', errno: 'ENOENT', syscall: 'spawn ./Cloud/functions/node_modules/ffmpeg-binaries/bin/ffmpeg', path: './Cloud/functions/node_modules/ffmpeg-binaries/bin/ffmpeg',
spawnargs: [ '-formats' ] }

Expected Outcome

Inbound file is downloaded to a temp directory, cropped, and re-uploaded to firebase storage as the cropped file.

Environment

  • mac client / firebase storage
  • node v8.1.0
  • ffmpeg v3.2.2
  • fluent-ffmpeg v2.1.2

Code [Updated To Reflect Svenskunganka's Change. Now Works]

const ffmpeg = require('fluent-ffmpeg');
const PREVIEW_PREFIX = 'preview_';

exports.generatePreviewClip = functions.storage.object('audioFiles').onChange(event => {

      //console.log('Times this function has run: ', run++);

      const object = event.data; // The Storage object.
      const fileBucket = object.bucket; // The Storage bucket that contains the file.
      const filePath = object.name; // File path in the bucket.
      const contentType = object.contentType; // File content type.
      const resourceState = object.resourceState; // The resourceState is 'exists' or 'not_exists' (for file/folder deletions).
      const metageneration = object.metageneration; // Number of times metadata has been generated. New objects have a value of 1.

      // Exit if this is triggered on a file that is not an audio file.
      if (!contentType.startsWith('audio/')) {
        console.log('This is not an audio file.');
        console.log('This is the file:', filePath);
        return;
      }

      // Get the file name.
      const fileName = path.basename(filePath);
      console.log('Working with filename', fileName);
      // Exit if the file is already an audio clip.
      if (fileName.startsWith(PREVIEW_PREFIX)) {
        console.log('Already a preview clip.');
        return;
      }

      // Exit if this is a move or deletion event.
      if (event.data.resourceState === 'not_exists') {
        console.log('This is a deletion event.');
        return;
      }

      // Exit if file exists but is not new and is only being triggered
      // because of a metadata change.
      if (resourceState === 'exists' && metageneration > 1) {
        console.log('This is a metadata change event.');
        return;
      }

      // Download file from bucket.

      const bucket = gcs.bucket(fileBucket);
      const tempFilePath = path.join(os.tmpdir(), fileName);
      return bucket.file(filePath).download({
        destination: tempFilePath
      }).then(() => {

        console.log('Audio file downloaded locally to temp directory', tempFilePath);

    var ffmpegPath = require("ffmpeg-binaries").ffmpegPath();
    var ffprobePath = require("ffmpeg-binaries").ffprobePath();

    // Generate a croped file using ffmpeg.
    var command = new ffmpeg(tempFilePath);
        command.setFfmpegPath(ffmpegPath);
        command.setFfprobePath(ffprobePath);

        command
              .setStartTime('00:00:03')
              .setDuration('10')
              .output(tempFilePath)
              .on('end', function() {
                    console.log('Audio Crop Done Successfully');
               })
               .on('error', function(err)
               {
                  console.log('Error:', err);
               }).run();

              }).then(() => {
        console.log('Preview file created at', tempFilePath);
        // We add a 'preview_' prefix to the audio file name. that's how it will appear in firebase.
        const previewFileName = PREVIEW_PREFIX + fileName;
        console.log('previewFileName is', previewFileName)
        const previewFilePath = path.join(path.dirname(filePath), previewFileName);
        console.log('previewFilePath is', previewFilePath);
        // Uploading the preview file.
        return bucket.upload(tempFilePath, {destination: previewFilePath});
      // Once the file has been uploaded delete the local file to free up disk space.
      }).then(() => fs.unlinkSync(tempFilePath));

      // [END audio file generation]

    });

Contents and Structure of my ffmpeg-binaries/bin Directory

-rwxrwxrwx  1 sherpa  staff    24M Dec 10  2016 ffmpeg
-rwxr--r--  1 sherpa  staff    35M Jan 12  2017 ffmpeg.exe
-rwxr--r--  1 sherpa  staff    35M Jan 12  2017 ffplay.exe
-rwxrwxrwx  1 sherpa  staff    24M Dec 10  2016 ffprobe
-rwxr--r--  1 sherpa  staff    35M Jan 12  2017 ffprobe.exe
-rwxrwxrwx  1 sherpa  staff    22M Dec 10  2016 ffserver

Things I Have Tried

  • I can execute ffmpeg from the command line
  • sudo chmod -R u+x ffmpeg-binaries/
  • ffmpeg set in global path
  • used ffmpeg.exe binary in setFfmpegPath, got same result
    • Error: { Error: spawn ./Cloud/functions/node_modules/ffmpeg-binaries/bin/ffmpeg.exe ENOENT
  • played with numerous different setFfmpegPath path structures, e.g:
    • ./Cloud/functions/node_modules/ffmpeg-binaries/bin/ffmpeg
    • node_modules/ffmpeg-binaries/bin/ffmpeg
    • ./Cloud/functions/node_modules/ffmpeg-binaries/bin/

Thanks for any suggestions.

Peter
  • 5,251
  • 16
  • 63
  • 98
  • If you `ls node_modules/ffmpeg-binaries/bin/`, are the binaries there? The `ENOENT` error is short for *Error NO ENTry*, meaning what it's looking for doesn't exist. Do you get any errors during npm/yarn installation? – Sven Aug 14 '17 at 21:07
  • no errors during npm. yes, the binaries are there and full access rights. – Peter Aug 14 '17 at 21:08
  • Thank you, it's likely that the issue is simply related to the path you supply the `setFfmpegPath` method with. If you change it to: `setFfmpegPath(path.join(path.dirname(require.resolve("ffmpeg-binaries")), "bin/ffmpeg"))`. It's untested, but play with `require.resolve` and the `path` module. – Sven Aug 14 '17 at 21:23
  • Thanks a lot. Getting - Cannot find module 'ffmpeg‌​-binaries' - now but at least it's a new error. Maybe the module needs to be registered correctly? I'll try to hunt this down... – Peter Aug 14 '17 at 21:45
  • For now, if you run `npm i -S ffmpeg-binaries` it should be able to resolve. – Sven Aug 14 '17 at 21:48
  • Actually, looking throught the [source](https://github.com/Hackzzila/node-ffmpeg-binaries/blob/de39352ace83866f6cb00b04b2e2f5bae9c4dc59/index.js#L8) for `ffmpeg-binaries` you should be able to simply run `var ffmpegPath = require("ffmpeg-binaries").ffmpegPath` to get the binary path of `ffmpeg`. – Sven Aug 14 '17 at 21:50
  • I tried: var ffmpegPath = require("ffmpeg-binaries").ffmpegPath; var command = new ffmpeg(tempFilePath); command.setFfmpegPath(ffmpegPath); but I am getting: Error: Cannot find ffmpeg . Seems that ffmpegPath returns a [function], not a string. I think we are on the right track, though. leaving office - I'll play with the path setting when I get home. thanks for your help. – Peter Aug 14 '17 at 22:12
  • 1
    Whoops, missed that part. You're right, you have to invoke the returned function: `var ffmpegPath = require("ffmpeg-binaries").ffmpegPath();` – Sven Aug 14 '17 at 22:37
  • Hey Svenskunganka. That worked perfectly! Thanks a lot. I will update the code in my example for anybody that needs a working audio parser function. If you want to create an answer out of your response, I'll mark it as answer. Otherwise, thanks a lot, I really appreciate it. – Peter Aug 15 '17 at 14:47
  • @Svenskunganka can you post this as an answer so op can accept it and close the question? Thanks! – Ahmed Fasih Aug 15 '17 at 15:12

1 Answers1

0

We solved the issue in the comments for the question, but I'll post an answer for any future users that might have the same issue. The problem is that the path supplied to the setFfmpegPath() method was relative, and should instead be absolute. ffmpeg-binaries module exports a couple helper-functions you can call to get the paths to its binaries:

var ffmpeg = require("fluent-ffmpeg")
var ffmpegPath = require("ffmpeg-binaries")

ffmpeg
  .setFfmpegPath(ffmpegPath)
  ...

Make sure you have ffmpeg-binaries installed with npm i -S ffmpeg-binaries.

Update 7th Nov 2018:

The ffmpeg-binaries package released a new breaking change in version 4.0.0 which removed all functions it exported, and instead just exports a string pointing to the directory ffmpeg is located. This was changed in commit 009e4d5.
I've updated the answer to reflect these changes.

Sven
  • 5,155
  • 29
  • 53
  • ffmpegBinaries.ffmpegPath() is not a function - Am getting a error like this – Vishnu Nov 07 '18 at 11:57
  • @Vishnu that's because since the time I wrote this answer, the `ffmpeg-binaries` released a new major version with a breaking change that changes their API. They no longer export functions but instead just export a string pointing to the directory ffmpeg is located. See my updated answer. – Sven Nov 07 '18 at 13:53
  • Thank you for your response. I have no bin directory what to do – Vishnu Nov 08 '18 at 04:43
  • Generating thumbnail But its taking long time to generate – Vishnu Nov 08 '18 at 05:33