# Video processing with FFmpeg

> Source: https://trigger.dev/docs/guides/examples/ffmpeg-video-processing

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#prerequisites)

Prerequisites
----------------------------------------------------------------------------------------------------

*   A project with [Trigger.dev initialized](https://trigger.dev/docs/quick-start)
    
*   [FFmpeg](https://www.ffmpeg.org/download.html)
     installed on your machine

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#adding-the-ffmpeg-build-extension)

Adding the FFmpeg build extension

To use these example tasks, you’ll first need to add our FFmpeg extension to your project configuration like this:

trigger.config.ts

    import { ffmpeg } from "@trigger.dev/build/extensions/core";
    import { defineConfig } from "@trigger.dev/sdk";
    
    export default defineConfig({
      project: "<project ref>",
      // Your other config settings...
      build: {
        extensions: [ffmpeg()],
      },
    });
    

[Build extensions](https://trigger.dev/docs/config/extensions/overview)
 allow you to hook into the build system and customize the build process or the resulting bundle and container image (in the case of deploying). You can use pre-built extensions or create your own.

You’ll also need to add `@trigger.dev/build` to your `package.json` file under `devDependencies` if you don’t already have it there. If you are modifying this example and using popular FFmpeg libraries like `fluent-ffmpeg` you’ll also need to add them to [`external`](https://trigger.dev/docs/config/config-file#external)
 in your `trigger.config.ts` file.

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#compress-a-video-using-ffmpeg)

Compress a video using FFmpeg
------------------------------------------------------------------------------------------------------------------------------------

This task demonstrates how to use FFmpeg to compress a video, reducing its file size while maintaining reasonable quality, and upload the compressed video to R2 storage.

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#key-features)

Key Features

*   Fetches a video from a given URL
*   Compresses the video using FFmpeg with various compression settings
*   Uploads the compressed video to R2 storage

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#task-code)

Task code

trigger/ffmpeg-compress-video.ts

    import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
    import { logger, task } from "@trigger.dev/sdk";
    import ffmpeg from "fluent-ffmpeg";
    import fs from "fs/promises";
    import fetch from "node-fetch";
    import { Readable } from "node:stream";
    import os from "os";
    import path from "path";
    
    // Initialize S3 client
    const s3Client = new S3Client({
      // How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/
      region: "auto",
      endpoint: process.env.R2_ENDPOINT,
      credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
        secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
      },
    });
    
    export const ffmpegCompressVideo = task({
      id: "ffmpeg-compress-video",
      run: async (payload: { videoUrl: string }) => {
        const { videoUrl } = payload;
    
        // Generate temporary file names
        const tempDirectory = os.tmpdir();
        const outputPath = path.join(tempDirectory, `output_${Date.now()}.mp4`);
    
        // Fetch the video
        const response = await fetch(videoUrl);
    
        // Compress the video
        await new Promise((resolve, reject) => {
          if (!response.body) {
            return reject(new Error("Failed to fetch video"));
          }
    
          ffmpeg(Readable.from(response.body))
            .outputOptions([\
              "-c:v libx264", // Use H.264 codec\
              "-crf 28", // Higher CRF for more compression (28 is near the upper limit for acceptable quality)\
              "-preset veryslow", // Slowest preset for best compression\
              "-vf scale=iw/2:ih/2", // Reduce resolution to 320p width (height auto-calculated)\
              "-c:a aac", // Use AAC for audio\
              "-b:a 64k", // Reduce audio bitrate to 64k\
              "-ac 1", // Convert to mono audio\
            ])
            .output(outputPath)
            .on("end", resolve)
            .on("error", reject)
            .run();
        });
    
        // Read the compressed video
        const compressedVideo = await fs.readFile(outputPath);
        const compressedSize = compressedVideo.length;
    
        // Log compression results
        logger.log(`Compressed video size: ${compressedSize} bytes`);
        logger.log(`Temporary compressed video file created`, { outputPath });
    
        // Create the r2Key for the extracted audio, using the base name of the output path
        const r2Key = `processed-videos/${path.basename(outputPath)}`;
    
        const uploadParams = {
          Bucket: process.env.R2_BUCKET,
          Key: r2Key,
          Body: compressedVideo,
        };
    
        // Upload the video to R2 and get the URL
        await s3Client.send(new PutObjectCommand(uploadParams));
        logger.log(`Compressed video saved to your r2 bucket`, { r2Key });
    
        // Delete the temporary compressed video file
        await fs.unlink(outputPath);
        logger.log(`Temporary compressed video file deleted`, { outputPath });
    
        // Return the compressed video buffer and r2 key
        return {
          Bucket: process.env.R2_BUCKET,
          r2Key,
        };
      },
    });
    

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#testing-your-task)

Testing your task

To test this task, use this payload structure:

    {
      "videoUrl": "<video-url>" // Replace <a-video-url> with the URL of the video you want to upload
    }
    

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#extract-audio-from-a-video-using-ffmpeg)

Extract audio from a video using FFmpeg
--------------------------------------------------------------------------------------------------------------------------------------------------------

This task demonstrates how to use FFmpeg to extract audio from a video, convert it to WAV format, and upload it to R2 storage.

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#key-features-2)

Key Features

*   Fetches a video from a given URL
*   Extracts the audio from the video using FFmpeg
*   Converts the extracted audio to WAV format
*   Uploads the extracted audio to R2 storage

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#task-code-2)

Task code

trigger/ffmpeg-extract-audio.ts

    import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
    import { logger, task } from "@trigger.dev/sdk";
    import ffmpeg from "fluent-ffmpeg";
    import fs from "fs/promises";
    import fetch from "node-fetch";
    import { Readable } from "node:stream";
    import os from "os";
    import path from "path";
    
    // Initialize S3 client
    const s3Client = new S3Client({
      // How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/
      region: "auto",
      endpoint: process.env.R2_ENDPOINT,
      credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
        secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
      },
    });
    
    export const ffmpegExtractAudio = task({
      id: "ffmpeg-extract-audio",
      run: async (payload: { videoUrl: string }) => {
        const { videoUrl } = payload;
    
        // Generate temporary file names
        const tempDirectory = os.tmpdir();
        const outputPath = path.join(tempDirectory, `audio_${Date.now()}.wav`);
    
        // Fetch the video
        const response = await fetch(videoUrl);
    
        // Extract the audio
        await new Promise((resolve, reject) => {
          if (!response.body) {
            return reject(new Error("Failed to fetch video"));
          }
    
          ffmpeg(Readable.from(response.body))
            .outputOptions([\
              "-vn", // Disable video output\
              "-acodec pcm_s16le", // Use PCM 16-bit little-endian encoding\
              "-ar 44100", // Set audio sample rate to 44.1 kHz\
              "-ac 2", // Set audio channels to stereo\
            ])
            .output(outputPath)
            .on("end", resolve)
            .on("error", reject)
            .run();
        });
    
        // Read the extracted audio
        const audioBuffer = await fs.readFile(outputPath);
        const audioSize = audioBuffer.length;
    
        // Log audio extraction results
        logger.log(`Extracted audio size: ${audioSize} bytes`);
        logger.log(`Temporary audio file created`, { outputPath });
    
        // Create the r2Key for the extracted audio, using the base name of the output path
        const r2Key = `extracted-audio/${path.basename(outputPath)}`;
    
        const uploadParams = {
          Bucket: process.env.R2_BUCKET,
          Key: r2Key,
          Body: audioBuffer,
        };
    
        // Upload the audio to R2 and get the URL
        await s3Client.send(new PutObjectCommand(uploadParams));
        logger.log(`Extracted audio saved to your R2 bucket`, { r2Key });
    
        // Delete the temporary audio file
        await fs.unlink(outputPath);
        logger.log(`Temporary audio file deleted`, { outputPath });
    
        // Return the audio file path, size, and R2 URL
        return {
          Bucket: process.env.R2_BUCKET,
          r2Key,
        };
      },
    });
    

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#testing-your-task-2)

Testing your task

To test this task, use this payload structure:

Make sure to provide a video URL that contains audio. If the video does not have audio, the task will fail.

    {
      "videoUrl": "<video-url>" // Replace <a-video-url> with the URL of the video you want to upload
    }
    

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#generate-a-thumbnail-from-a-video-using-ffmpeg)

Generate a thumbnail from a video using FFmpeg
----------------------------------------------------------------------------------------------------------------------------------------------------------------------

This task demonstrates how to use FFmpeg to generate a thumbnail from a video at a specific time and upload the generated thumbnail to R2 storage.

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#key-features-3)

Key Features

*   Fetches a video from a given URL
*   Generates a thumbnail from the video at the 5-second mark
*   Uploads the generated thumbnail to R2 storage

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#task-code-3)

Task code

trigger/ffmpeg-generate-thumbnail.ts

    import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
    import { logger, task } from "@trigger.dev/sdk";
    import ffmpeg from "fluent-ffmpeg";
    import fs from "fs/promises";
    import fetch from "node-fetch";
    import { Readable } from "node:stream";
    import os from "os";
    import path from "path";
    
    // Initialize S3 client
    const s3Client = new S3Client({
      // How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/
      region: "auto",
      endpoint: process.env.R2_ENDPOINT,
      credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
        secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
      },
    });
    
    export const ffmpegGenerateThumbnail = task({
      id: "ffmpeg-generate-thumbnail",
      run: async (payload: { videoUrl: string }) => {
        const { videoUrl } = payload;
    
        // Generate output file name
        const tempDirectory = os.tmpdir();
        const outputPath = path.join(tempDirectory, `thumbnail_${Date.now()}.jpg`);
    
        // Fetch the video
        const response = await fetch(videoUrl);
    
        // Generate the thumbnail
        await new Promise((resolve, reject) => {
          if (!response.body) {
            return reject(new Error("Failed to fetch video"));
          }
          ffmpeg(Readable.from(response.body))
            .screenshots({
              count: 1,
              folder: "/tmp",
              filename: path.basename(outputPath),
              size: "320x240",
              timemarks: ["5"], // 5 seconds
            })
            .on("end", resolve)
            .on("error", reject);
        });
    
        // Read the generated thumbnail
        const thumbnail = await fs.readFile(outputPath);
    
        // Create the r2Key for the extracted audio, using the base name of the output path
        const r2Key = `thumbnails/${path.basename(outputPath)}`;
    
        const uploadParams = {
          Bucket: process.env.R2_BUCKET,
          Key: r2Key,
          Body: thumbnail,
        };
    
        // Upload the thumbnail to R2 and get the URL
        await s3Client.send(new PutObjectCommand(uploadParams));
        const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`;
        logger.log("Thumbnail uploaded to R2", { url: r2Url });
    
        // Delete the temporary file
        await fs.unlink(outputPath);
    
        // Log thumbnail generation results
        logger.log(`Thumbnail uploaded to S3: ${r2Url}`);
    
        // Return the thumbnail buffer, path, and R2 URL
        return {
          thumbnailBuffer: thumbnail,
          thumbnailPath: outputPath,
          r2Url,
        };
      },
    });
    

### 

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#testing-your-task-3)

Testing your task

To test this task in the dashboard, you can use the following payload:

    {
      "videoUrl": "<video-url>" // Replace <a-video-url> with the URL of the video you want to upload
    }
    

[​](https://trigger.dev/docs/guides/examples/ffmpeg-video-processing#local-development)

Local development
------------------------------------------------------------------------------------------------------------

To test this example task locally, be sure to install any packages from the build extensions you added to your `trigger.config.ts` file to your local machine. In this case, you need to install ffmpeg.

Was this page helpful?

YesNo

[Previous](https://trigger.dev/docs/guides/examples/fal-ai-realtime)
[Firecrawl URL crawlThis example demonstrates how to crawl a URL using Firecrawl with Trigger.dev.\
\
Next](https://trigger.dev/docs/guides/examples/firecrawl-url-crawl)

Ctrl+I

Assistant

Responses are generated using AI and may contain mistakes.
