Per the Firebase Cloud Functions documentation, you can leverage ImageMagick from within a cloud function: https://firebase.google.com/docs/functions/use-cases
Is i
Practically, no. FFMPEG processes audio/video files which typically exceed the Cloud Functions quotas (10MB uploads).
You would need to run Node.js on GCP's AppEngine.
ffmpeg
is not preinstalled (pretty much just ImageMagick); to see exactly what's installed check out the Dockerfile here: https://github.com/GoogleCloudPlatform/nodejs-docker/blob/master/runtime-image/Dockerfile.
However, you can upload arbitrary binaries when you upload your code using gcloud beta functions deploy
because everything in the current directory (except node_modules
) is uploaded.
Note: you only have disk write access at /tmp/
.
ffmpeg-static
is an npm module that builds the correct ffmpeg binary based on the current system during npm install
. Since Cloud Functions builds your code in the cloud, it'll build the correct ffmpeg binary.
https://github.com/eugeneware/ffmpeg-static
You can see it in action in the Cloud Functions for Firebase examples repo.
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 Daniel Lessa for pointing out this module in his answer.)
You could include an ffmpeg binary as part of the upload and then run a shell command using something like child_process.exec. You'll need the ffmpeg binary that's compiled for the target platform (Debian/jessie).
./
../
index.js
ffmpeg
Then run for example gcloud beta functions deploy myFunc --trigger-http
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
});
ffmpeg
is now included in the Cloud Functions environment so it can be used directly:
spawn(
'ffmpeg',
['-i', 'video.mp4']
)
Full list of installed packages: https://cloud.google.com/functions/docs/reference/nodejs-system-packages
Use the 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()
While you technically can run FFMPEG on a Firebase Functions instance, you will quickly hit the small quota limits.
As per this answer, you can instead use Functions to trigger a request to GCP's more powerful App Engine or Compute Engine services. The App Engine process can grab the file from the same bucket, handle the transcoding, and upload the finished file back to the bucket. If you check the other answers at the link, one user posted a sample repo that does just that.
/**
* 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'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);
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