You can call FFMPEG in Firebase cloud function
In Firebase Cloud Functions documentation, you can use ImageMagick from cloud function: https://firebase.google.com/docs/functions/use-cases
Is it possible to do something like this, but call FFMPEG instead of ImageMagick? While thumbnailing the images is great, I would also like to add the incoming images to the video file stored in Firebase Storage.
source to share
ffmpeg
not preinstalled (pretty much just ImageMagick); to see exactly what is installed check the Dockerfile here: https://github.com/GoogleCloudPlatform/nodejs-docker/blob/master/runtime-image/Dockerfile .
However, you can load arbitrary binaries when you load your code with gcloud beta functions deploy
, because everything in the current directory (except node_modules
) is loaded .
Note. you only have write access to the disk in /tmp/
.
Option 1: use ffmpeg-static npm module
ffmpeg-static
is an npm module that builds the correct ffmpeg binary based on the current system at the time npm install
. Since the cloud functions build your code in the cloud, it will build the correct ffmpeg binary.
https://github.com/eugeneware/ffmpeg-static
You can see it in action in Cloud Functions for the Firebase Sample Repository .
const ffmpeg = require('fluent-ffmpeg');
const ffmpeg_static = require('ffmpeg-static');
var cmd = ffmpeg('/tmp/video.avi')
.setFfmpegPath(ffmpeg_static.path)
.videoBitrate(1024)
.videoCodec('divx')
.format('avi')
.on('end', () => {
// ...
})
.on('error', err => {
console.error(err);
})
.save('/tmp/file-out.avi');
(Thanks to Daniel Lesse for pointing this module out in his answer .)
Option 2: upload your own binary
You can include the ffmpeg binary as part of the download and then run a shell command using something like child_process.exec
. You will need the ffmpeg binary compiled for the target platform (Debian / jessie).
List of files with precompiled ffmpeg binary
./ ../ index.js ffmpeg
Then run for example gcloud beta functions deploy myFunc --trigger-http
index.js
var exec = require('child_process').exec;
var cmd = 'ffmpeg -i /tmp/myvideo.mp4 /tmp/image-%d.jpg';
exec(cmd, function(error, stdout, stderr) {
// command output is in stdout
});
source to share
Use lib https://github.com/eugeneware/ffmpeg-static
const ffmpeg = require('fluent-ffmpeg');
const ffmpeg_static = require('ffmpeg-static');
let cmd = ffmpeg.('filePath.mp4')
.setFfmpegPath(ffmpeg_static.path)
.setInputFormat('mp4')
.output('outputPath.mp4')
...
...
.run()
source to share
As long as you can technically run FFMPEG on a Firebase Functions instance, you will quickly reach small quota limits.
As per this answer, you can instead use functions to initiate a request to more powerful App Engine or Compute Engine services for GCP. The App Engine process can grab a file from the same bucket, handle the transcoding, and load the finished file back into the bucket. If you check the other answers at the link, one user posted a sample repo that does exactly that.
source to share
As far as I know ffmpeg is not pre-installed on containers.
When I had to transcode the media, I looked at the cross-compiled version of ffmpeg emscripten. It seemed to work well in local node environments, but I never intended to deploy it (since we didn't need to recode in the end).
source to share
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for t`he specific language governing permissions and
* limitations under the License.
*/
'use strict';
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');
/**
* 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().onChange(event => {
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.
if (!contentType.startsWith('audio/')) {
console.log('This is not an audio.');
return;
}
// 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;
}
// Exit if this is a move or deletion event.
if (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);
// We add a '_output.flac' suffix to target audio file name. That 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);
return bucket.file(filePath).download({
destination: tempFilePath
}).then(() => {
console.log('Audio downloaded locally to', tempFilePath);
// Convert the audio to mono channel using FFMPEG.
const command = ffmpeg(tempFilePath)
.setFfmpegPath(ffmpeg_static.path)
.audioChannels(1)
.audioFrequency(16000)
.format('flac')
.on('error', (err) => {
console.log('An error occurred: ' + err.message);
})
.on('end', () => {
console.log('Output audio created at', targetTempFilePath);
// Uploading the audio.
return bucket.upload(targetTempFilePath, {destination: targetStorageFilePath}).then(() => {
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);
console.log('Temporary files removed.', targetTempFilePath);
});
})
.save(targetTempFilePath);
});
});
https://github.com/firebase/functions-samples/blob/master/ffmpeg-convert-audio/functions/index.js
source to share