DEV Community

Javid Jamae
Javid Jamae

Posted on • Originally published at ffmpeg-micro.com

FFmpeg on Firebase Cloud Functions: Why It Breaks (and the Fix)

Originally published at ffmpeg-micro.com

You're adding video processing to a Firebase app. You install a static FFmpeg binary, write a Cloud Function that triggers on upload, deploy it, and watch it die. The function times out, the binary bloats your deployment, and GCP bills you for memory you didn't actually need.

The Timeout Wall

Firebase Cloud Functions v1 default to a 60-second timeout. You can push that to 540 seconds (9 minutes) on v2 functions, and that's a hard cap.

Transcoding a 5-minute 1080p video to H.264 takes 8-15 minutes on Cloud Functions CPU allocation. The 540-second ceiling doesn't come close.

The Memory and CPU Trap

Cloud Functions v2 gives you up to 32 GB of memory, but CPU allocation is tied to memory. At 2 GB memory, you get 1 vCPU. FFmpeg encoding is CPU-bound, not memory-bound. You end up over-provisioning memory just to get more CPU.

Binary Bundling Problems

Getting FFmpeg into a Cloud Function means either bundling via npm (ffmpeg-static, 70-100 MB), including the binary directly, or downloading at runtime. Cloud Functions has a 100 MB compressed deployment limit.

The Fix: Call an API Instead

Keep your function small and offload the encoding:

import { onObjectFinalized } from "firebase-functions/v2/storage";
import { defineSecret } from "firebase-functions/params";
import { getStorage } from "firebase-admin/storage";
import { initializeApp } from "firebase-admin/app";

initializeApp();
const ffmpegMicroKey = defineSecret("FFMPEG_MICRO_API_KEY");

export const processUploadedVideo = onObjectFinalized(
  {
    bucket: "my-app-videos",
    secrets: [ffmpegMicroKey],
    memory: "256MiB",
    timeoutSeconds: 60,
  },
  async (event) => {
    const { name, contentType } = event.data;
    if (!contentType?.startsWith("video/")) return;

    const bucket = getStorage().bucket(event.data.bucket);
    const [signedUrl] = await bucket.file(name!).getSignedUrl({
      action: "read",
      expires: Date.now() + 60 * 60 * 1000,
    });

    const response = await fetch(
      "https://api.ffmpeg-micro.com/v1/transcodes",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${ffmpegMicroKey.value()}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          inputs: [{ url: signedUrl }],
          outputFormat: "mp4",
          preset: { quality: "high", resolution: "1080p" },
        }),
      }
    );

    const job = await response.json();
    console.log(`Transcode job ${job.id} started for ${name}`);
  }
);
Enter fullscreen mode Exit fullscreen mode

This function deploys in seconds, cold-starts in under a second, and needs only 256 MB of memory.

Read the full post with detailed analysis

Top comments (0)