你能在 Firebase Cloud Function 中调出 FFMPEG 吗

Can you call out to FFMPEG in a Firebase Cloud Function

根据 Firebase Cloud Functions 文档,您可以在云函数中利用 ImageMagick:https://firebase.google.com/docs/functions/use-cases

是否可以做类似的事情但调用 FFMPEG 而不是 ImageMagick?虽然缩略图很棒,但我还希望能够将传入的图像附加到存储在 Firebase 存储中的视频文件中。

更新:ffmpeg 现已预装在 Cloud Functions 环境中。有关预装软件包的完整列表,请查看 https://cloud.google.com/functions/docs/reference/system-packages

注意: 您在 /tmp/.

上只有磁盘 write 访问权限

选项 1:使用 ffmpeg-fluent npm 模块

此模块通过易于使用的 Node.js 模块抽象了 ffmpeg 命令行选项。

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

let cmd = ffmpeg('example.mp4')
    .clone()
    .size('300x300')
    .save('/tmp/smaller-file.mp4')
    .on('end', () => {
      // Finished processing the video.
      console.log('Done');

      // E.g. return the resized video:
      res.sendFile('/tmp/smaller-file.mp4');
    });

Full code on GitHub

选项 2:直接调用 ffmpeg 二进制文件

因为 ffmpeg 已经安装,您可以通过 shell 进程调用二进制文件及其命令行选项。

const { exec } = require("child_process");

exec("ffmpeg -i example.mp4", (error, stdout, stderr) => {
  //ffmpeg logs to stderr, but typically output is in stdout.
  console.log(stderr);
});

Full code on GitHub

选项 3:上传您自己的二进制文件

如果您需要特定版本的 ffmpeg,您可以将 ffmpeg 二进制文件作为上传的一部分,然后 运行 使用 child_process.exec 之类的 shell 命令。您需要一个为目标平台 (Ubuntu) 编译的 ffmpeg 二进制文件。

带有预编译 ffmpeg 二进制文件的文件列表

./
../
index.js
ffmpeg

index.js

const { exec } = require("child_process");

exec("ffmpeg -i example.mp4", (error, stdout, stderr) => {
  //ffmpeg logs to stderr, but typically output is in stdout.
  console.log(stderr);
});

我已经包含 two full working examples on GitHub。这些示例适用于 Google Cloud Functions(不是专门针对 Firebase 的 Cloud Functions)。

使用库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()

虽然从技术上讲您可以 运行 Firebase Functions 实例上的 FFMPEG,但您很快就会达到小配额限制。

根据 ,您可以改用函数来触发对 GCP 更强大的 App Engine 或 Compute Engine 服务的请求。 App Engine 进程可以从同一个存储桶中抓取文件,处理转码,并将完成的文件上传回存储桶。如果您查看 link 中的其他答案,一位用户发布了一个示例存储库,它就是这样做的。

/**
 * 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

实际上,没有。 FFMPEG 处理 audio/video 个通常超过 Cloud Functions quotas(10MB 上传)的文件。

您需要 运行 Node.js on GCP's AppEngine.

ffmpeg 现已包含在 Cloud Functions 环境中,因此可以直接使用:

spawn(
  'ffmpeg',
  ['-i', 'video.mp4'] 
)

已安装软件包的完整列表:https://cloud.google.com/functions/docs/reference/nodejs-system-packages

建议使用 App Engine 的其他答案是正确的。但缺少的信息是什么是 App Engine?

基本上是举重运动员。它允许您编写后端并将其部署到云中。想一想您通常可能会开发的 Node Express 服务器。然后部署到云端。那是 App Engine。

Firebase / Cloud Functions 通常通过 HTTP 或 PubSub 与 App Engine 通信。

函数是为轻量级工作而设计的。它们会告诉您事件何时发生(例如,文件上传到存储桶),并且触发的“事件”具有有关该事件的有效载荷详细信息(例如,上传到存储桶的对象的详细信息)。

当该事件发生时,如果需要繁重的工作(或者 Node.js 运行时环境中缺少所需的软件),该函数会向 App Engine 发出 HTTP 请求,提供信息App Engine 需要做必要的处理。

App Engine 非常灵活。您定义一个 yaml 文件和一个可选的 Dockerfile。

这是一个例子:

runtime: custom # custom means it uses a Dockerfile
env: flex

manual_scaling:
  instances: 1
resources:
  cpu: 1
  memory_gb: 0.5
  disk_size_gb: 10

这里定义CPU个数,内存,磁盘大小等,与函数不同,磁盘是可写的(我被引导相信,我还在整合中)。

通过 Dockerfile,您可以准确定义要安装的软件。如果您不熟悉 Dockerfile,这里有一个很好的例子。

https://nodejs.org/en/docs/guides/nodejs-docker-webapp

您在本地开发,然后在完成后部署到云端:

gcloud app deploy

瞧,您的应用出现在云端。 gcloud 命令附带 Google Cloud SDK.

请注意,AppEngine 可以在处理完成后通过 HTTP 函数或 PubSub 返回函数。

他充满爱 :D