3

I am trying to learn how to write a firebase cloud function with ffmpeg-fluent and am referring to this sample code. I have copied the code, only changing the initialisation of gcs to const gcs = admin.storage();. The deployment on Node 10 was successful, but upon uploading an mp3 file to test the function, it gave me the following error.

Error: Cannot find ffmpeg at /srv/node_modules/fluent-ffmpeg/lib/processor.js:136:22 
at FfmpegCommand.proto._getFfmpegPath (/srv/node_modules/fluent-ffmpeg/lib/capabilities.js:90:14) 
at FfmpegCommand.proto._spawnFfmpeg (/srv/node_modules/fluent-ffmpeg/lib/processor.js:132:10) 

at FfmpegCommand.proto.availableFormats.proto.getAvailableFormats (/srv/node_modules/fluent-ffmpeg/lib/capabilities.js:517:10) 
at /srv/node_modules/fluent-ffmpeg/lib/capabilities.js:568:14 
at nextTask (/srv/node_modules/async/dist/async.js:4578:27) 
at Object.waterfall (/srv/node_modules/async/dist/async.js:4589:9) 
at Object.awaitable(waterfall) [as waterfall] (/srv/node_modules/async/dist/async.js:208:32) 
at FfmpegCommand.proto._checkCapabilities (/srv/node_modules/fluent-ffmpeg/lib/capabilities.js:565:11) 
at /srv/node_modules/fluent-ffmpeg/lib/processor.js:298:14

Can someone enlighten me what installation steps have I missed out?

Here is the code segment from the repository earlier.

index.js

const functions = require('firebase-functions');
const gcs = require('@google-cloud/storage')();
const path = require('path');
const os = require('os');
const fs = require('fs');
const ffmpeg = require('fluent-ffmpeg');
const ffmpeg_static = require('ffmpeg-static');

// Makes an ffmpeg command return a promise.
function promisifyCommand(command) {
  return new Promise((resolve, reject) => {
    command.on('end', resolve).on('error', reject).run();
  });
}

/**
 * When an audio is uploaded in the Storage bucket We generate a mono channel audio automatically using
 * node-fluent-ffmpeg.
 */
exports.generateMonoAudio = functions.storage.object().onFinalize(async (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.

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

  // Get the file name.
  const fileName = path.basename(filePath);
  // Exit if the audio is already converted.
  if (fileName.endsWith('_output.flac')) {
    console.log('Already a converted audio.');
    return null;
  }

  // Download file from bucket.
  const bucket = gcs.bucket(fileBucket);
  const tempFilePath = path.join(os.tmpdir(), fileName);
  // We add a '_output.flac' suffix to target audio file name. That's where we'll upload the converted audio.
  const targetTempFileName = fileName.replace(/\.[^/.]+$/, '') + '_output.flac';
  const targetTempFilePath = path.join(os.tmpdir(), targetTempFileName);
  const targetStorageFilePath = path.join(path.dirname(filePath), targetTempFileName);

  await bucket.file(filePath).download({destination: tempFilePath});
  console.log('Audio downloaded locally to', tempFilePath);
  // Convert the audio to mono channel using FFMPEG.

  let command = ffmpeg(tempFilePath)
      .setFfmpegPath(ffmpeg_static.path)
      .audioChannels(1)
      .audioFrequency(16000)
      .format('flac')
      .output(targetTempFilePath);

  await promisifyCommand(command);
  console.log('Output audio created at', targetTempFilePath);
  // Uploading the audio.
  await bucket.upload(targetTempFilePath, {destination: targetStorageFilePath});
  console.log('Output audio uploaded to', targetStorageFilePath);

  // Once the audio has been uploaded delete the local file to free up disk space.
  fs.unlinkSync(tempFilePath);
  fs.unlinkSync(targetTempFilePath);

  return console.log('Temporary files removed.', targetTempFilePath);
});

package.json

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint .",
    "serve": "firebase emulators:start --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "10"
  },
  "dependencies": {
    "@google-cloud/storage": "^5.0.1",
    "child-process-promise": "^2.2.1",
    "ffmpeg-static": "^4.2.5",
    "firebase-admin": "^8.10.0",
    "firebase-functions": "^3.6.1",
    "fluent-ffmpeg": "^2.1.2",
    "fs-extra": "^8.1.0",
    "sharp": "^0.25.3"
  },
  "devDependencies": {
    "eslint": "^5.12.0",
    "eslint-plugin-promise": "^4.0.1",
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}
Prashin Jeevaganth
  • 1,223
  • 1
  • 18
  • 42
  • Can you add the `package.json` as well? Are you using the same as in the repository you have linked? – llompalles Jun 30 '20 at 15:01
  • I have updated the post with my own `package.json` and it deploys successfully, please tell me if you need more information – Prashin Jeevaganth Jun 30 '20 at 15:05
  • It seems you are having the error when importing the `ffmpeg` libraries. I would recommend to deploy a minimal simplified version of you function's code with just the import and the constructor `var command = ffmpeg();` to narrow down the issue. – llompalles Jul 01 '20 at 13:43
  • I have tried cutting down the code to narrow down the issue, and I agree that the issue occurs with the imports. The logs only show the error when I try to upload a file that will invoke the cloud function. Since this code snippet was made public for a rather long time, I hope that someone who has successfully got this code importing correctly to share their experiences with me, because I believe I followed the documentation closely. – Prashin Jeevaganth Jul 01 '20 at 13:50
  • for me using `ffmpeg_static` without `.path` works, ex: `ffmpeg("filepath").setFfmpegPath(ffmpeg_static)`, not `ffmpeg("filepath").setFfmpegPath(ffmpeg_static.path)` – shmibbles May 28 '21 at 16:47

1 Answers1

3

Here the ffmpeg_static.path path is undefined

let command = ffmpeg(tempFilePath)
      .setFfmpegPath(ffmpeg_static.path)
      .audioChannels(1)
      .audioFrequency(16000)
      .format('flac')
      .output(targetTempFilePath);

what you should do instead is install "ffmpeg-installer/ffmpeg". You can find it here: https://www.npmjs.com/package/@ffmpeg-installer/ffmpeg

Then set the correct path like:

const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
const ffmpeg = require('fluent-ffmpeg');

let command = ffmpeg(tempFilePath)
      .setFfmpegPath(ffmpegPath)
      .audioChannels(1)
      .audioFrequency(16000)
      .format('flac')
      .output(targetTempFilePath);