Prerequisites
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:
import { ffmpeg } from "@trigger.dev/build/extensions/core";
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
build: {
extensions: [ffmpeg()],
},
});
Build extensions 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.
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.
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
Task code
trigger/ffmpeg-compress-video.ts
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { logger, task } from "@trigger.dev/sdk/v3";
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";
const s3Client = new S3Client({
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;
const tempDirectory = os.tmpdir();
const outputPath = path.join(tempDirectory, `output_${Date.now()}.mp4`);
const response = await fetch(videoUrl);
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",
"-crf 28",
"-preset veryslow",
"-vf scale=iw/2:ih/2",
"-c:a aac",
"-b:a 64k",
"-ac 1",
])
.output(outputPath)
.on("end", resolve)
.on("error", reject)
.run();
});
const compressedVideo = await fs.readFile(outputPath);
const compressedSize = compressedVideo.length;
logger.log(`Compressed video size: ${compressedSize} bytes`);
logger.log(`Temporary compressed video file created`, { outputPath });
const r2Key = `processed-videos/${path.basename(outputPath)}`;
const uploadParams = {
Bucket: process.env.R2_BUCKET,
Key: r2Key,
Body: compressedVideo,
};
await s3Client.send(new PutObjectCommand(uploadParams));
logger.log(`Compressed video saved to your r2 bucket`, { r2Key });
await fs.unlink(outputPath);
logger.log(`Temporary compressed video file deleted`, { outputPath });
return {
Bucket: process.env.R2_BUCKET,
r2Key,
};
},
});
Testing your task
To test this task, use this payload structure:
{
"videoUrl": "<video-url>"
}
This task demonstrates how to use FFmpeg to extract audio from a video, convert it to WAV format, and upload it to R2 storage.
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
Task code
trigger/ffmpeg-extract-audio.ts
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { logger, task } from "@trigger.dev/sdk/v3";
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";
const s3Client = new S3Client({
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;
const tempDirectory = os.tmpdir();
const outputPath = path.join(tempDirectory, `audio_${Date.now()}.wav`);
const response = await fetch(videoUrl);
await new Promise((resolve, reject) => {
if (!response.body) {
return reject(new Error("Failed to fetch video"));
}
ffmpeg(Readable.from(response.body))
.outputOptions([
"-vn",
"-acodec pcm_s16le",
"-ar 44100",
"-ac 2",
])
.output(outputPath)
.on("end", resolve)
.on("error", reject)
.run();
});
const audioBuffer = await fs.readFile(outputPath);
const audioSize = audioBuffer.length;
logger.log(`Extracted audio size: ${audioSize} bytes`);
logger.log(`Temporary audio file created`, { outputPath });
const r2Key = `extracted-audio/${path.basename(outputPath)}`;
const uploadParams = {
Bucket: process.env.R2_BUCKET,
Key: r2Key,
Body: audioBuffer,
};
await s3Client.send(new PutObjectCommand(uploadParams));
logger.log(`Extracted audio saved to your R2 bucket`, { r2Key });
await fs.unlink(outputPath);
logger.log(`Temporary audio file deleted`, { outputPath });
return {
Bucket: process.env.R2_BUCKET,
r2Key,
};
},
});
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>"
}
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.
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
Task code
trigger/ffmpeg-generate-thumbnail.ts
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { logger, task } from "@trigger.dev/sdk/v3";
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";
const s3Client = new S3Client({
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;
const tempDirectory = os.tmpdir();
const outputPath = path.join(tempDirectory, `thumbnail_${Date.now()}.jpg`);
const response = await fetch(videoUrl);
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"],
})
.on("end", resolve)
.on("error", reject);
});
const thumbnail = await fs.readFile(outputPath);
const r2Key = `thumbnails/${path.basename(outputPath)}`;
const uploadParams = {
Bucket: process.env.R2_BUCKET,
Key: r2Key,
Body: thumbnail,
};
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 });
await fs.unlink(outputPath);
logger.log(`Thumbnail uploaded to S3: ${r2Url}`);
return {
thumbnailBuffer: thumbnail,
thumbnailPath: outputPath,
r2Url,
};
},
});
Testing your task
To test this task in the dashboard, you can use the following payload:
{
"videoUrl": "<video-url>"
}
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 .