【问题标题】:Can you call out to FFMPEG in a Firebase Cloud Function你能在 Firebase 云函数中调用 FFMPEG
【发布时间】:2017-08-04 01:14:03
【问题描述】:

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

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

【问题讨论】:

  • 请记住,您可以使用的临时磁盘空间和内存有限。事实上,临时磁盘存储在内存中的,所以如果你有一个大视频,你很容易耗尽内存。
  • 请注意,对于8,5GB per functions 所写的here 中的许多操作而言,内存是相当多的

标签: firebase ffmpeg google-cloud-functions


【解决方案1】:

建议 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

【讨论】:

    【解决方案2】:

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

    注意:您只有/tmp/ 的磁盘写入访问权限。

    选项 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)。

    【讨论】:

    • 如何在云firebase函数中引用文件,我的意思是在FFMPEG命令中写入的路径是什么来引用firebase存储中的文件?
    • @OmarHossamEldin 作为函数的一部分上传的所有内容都存储在服务器上的/user_code/ 目录中。
    • 这是一个救生员 - 谢谢!!让 Github 页面的 refs 非常有帮助。只是附带说明,我正在使用fluent-ffmpeg npm 包,并且需要将 ffmpeg 路径添加到 Docker 文件中的二进制文件作为 ENV 变量:ENV PATH="/usr/src/app/node_modules/ffmpeg-static/bin/linux/x64:${PATH}"
    • ffmpeg 现在包含在 Cloud Functions 环境中
    【解决方案3】:

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

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

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

    【讨论】:

    • 运行时环境版本太旧,甚至不支持 var_stream_map 标志 - 我将繁重的工作转移到 App Engine
    【解决方案4】:

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

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

    【讨论】:

    • 谢谢,只是想知道您是否看过任何带有代码模板的存储库,您可以在这些代码模板上构建此类任务?具体来说,您将如何触发 GAE 中的工作流程?
    • 您使用函数来触发工作流程,它们向 GAE 发出请求以完成繁重的工作 - 同样,当繁重的工作完成时 - GAE 可以向 HTTP 函数发出请求 - 或使用 PubSub 进行通信,因为函数也可以处理 PubSub
    【解决方案5】:
    /**
     * 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-static是直接返回路径,所以需要直接调用.setFfmpegPath(ffmpeg_static),不带.path
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 注意当前版本的ffmpeg-static是直接返回路径,所以需要直接调用.setFfmpegPath(ffmpeg_static),不带.path
      • 这个答案缺乏细节。 ffmpeg-static 很棒。您只需将其添加为依赖项,当某人(或系统)执行 npm 安装时,它会自动安装合适版本的 ffmpeg。您可以使用 ... const pathToFfmpeg = require('ffmpeg-static') ... 获取 ffmpeg bin 的路径,然后在使用例如 Node 的 execSync 函数执行命令时使用该路径 - 但是,我不是确定它在 Cloud Runtime 环境中的效果如何,并且配额问题可能仍然是一个问题 - 最好坚持使用 AppEngine - 但 +1 是个好主意
      猜你喜欢
      • 2019-01-18
      • 2021-09-13
      • 2020-01-28
      • 2020-11-24
      • 2018-05-17
      • 2020-10-20
      • 2019-07-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多