FFmpeg Flutter
Handling Video with FFmpeg in Flutter
Video processing is one of the most resource-intensive tasks, especially in terms of RAM and CPU usage. Without proper optimization, your application can easily crash. During development, I encountered issues when using third-party libraries from Pub.dev to:
Convert videos into GIFs.
Convert a list of images into a video.
These libraries often take a long time to process and produce results that do not meet the desired quality. However, with FFmpeg, you can efficiently solve these issues.
Introduction to FFmpeg
FFmpeg is a powerful open-source library designed for audio and video processing. FFmpeg supports various tasks such as:
Format conversion (MP4, MKV, MP3, etc.).
Video and audio processing: Adding watermarks, trimming, merging, and compressing.
Creating GIFs from videos.
Streaming and media optimization.
The ffmpeg_kit_flutter_full_gpl package brings the full capabilities of FFmpeg to Flutter. Additionally, you can explore other packages at FFmpeg Kit Packages.
Installing the FFmpeg Toolkit
Note: This guide is based on Flutter 3.24.5 and ffmpeg_kit_flutter_full_gpl
6.0.3. It appears that newer versions of Flutter may not work well with this library.
Installation
Add the dependency to your pubspec.yaml
file:
ffmpeg_kit_flutter_full_gpl: ^latest_version
Run the following command:
flutter pub get
Android Configuration
Add the following to your android/app/build.gradle
file:
defaultConfig {
...
minSdk = 24
...
}
Update permissions in AndroidManifest.xml
:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
iOS Configuration
Add the following to your Info.plist
file:
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library to pick files.</string>
<key>NSDocumentDirectoryUsageDescription</key>
<string>We need access to your documents to pick files.</string>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
Ensure that your ios/Podfile
specifies the platform version:
platform :ios, '13.0'
Convert a Sequence of Images to Video
Below is a function to convert a list of images into a video. My idea is to store all images in a directory, and FFmpegKit will read them to create a video.
Future<({bool isSuccess, String message})> convertImageDirectoryToVideo({
required String imagesPath,
required String outputVideoPath,
int framerate = 24,
int? fps,
int? quality,
}) async {
var isSuccess = false;
var message = "";
final command = '-framerate $framerate -i $imagesPath/%d.png '
'${fps != null ? "-r $fps" : ""} '
'${quality != null ? "-crf $quality -preset slow" : ""} '
'-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" '
'-c:v libx264 -pix_fmt yuv420p -movflags +faststart $outputVideoPath';
await FFmpegKit.execute(command).then((session) async {
final returnCode = await session.getReturnCode();
if (returnCode?.isValueSuccess() ?? false) {
message = 'Video conversion successful!';
isSuccess = true;
} else {
final output = await session.getOutput();
message = 'Video conversion failed: $output';
}
});
return (isSuccess: isSuccess, message: message);
}
Example Usage:
final result = convertImageDirectoryToVideo(
imagesPath: "path/to/images",
outputVideoPath: "path/to/videoOutput.mp4",
);
Ensure that the directory path/to/images
contains images named with sequential numbers and in .png
format, as illustrated in the example:

The output file path/to/videoOutput.mp4
must have a unique name to avoid errors during FFmpeg processing.
In the command above, the images will be automatically resized to match an even-numbered resolution. You can modify the command as needed.
Add a Watermark to a Video
Function to Add Watermark
Future<({bool isSuccess, String message})> addWatermarkToVideo({
required String videoPath,
required String watermarkPath,
required String outputPath,
required int x,
required int y,
int? width,
int? height,
}) async {
var isSuccess = false;
var message = "";
final scaleFilter = (width != null && height != null)
? "[1:v]scale=$width:$height[wm];[0:v][wm]overlay=$x:$y"
: "overlay=$x:$y";
final command =
'-i $videoPath -i $watermarkPath -filter_complex "$scaleFilter" -codec:a copy $outputPath';
await FFmpegKit.execute(command).then((session) async {
final returnCode = await session.getReturnCode();
if (returnCode?.isValueSuccess() ?? false) {
message = "Watermark added successfully!";
isSuccess = true;
} else {
message = "Failed to add watermark.";
}
});
return (isSuccess: isSuccess, message: message);
}
Example Usage
final result = addWatermarkToVideo(
videoPath: videoPath!,
watermarkPath: watermarkPath!,
outputPath: outputPath,
x: 20,
y: 30,
width: 200,
height: 200,
);
x, y
: Coordinates of the watermark.width, height
: Dimensions of the watermark (optional).
Reduce Video Quality
Function to Reduce Video Quality
Future<({bool isSuccess, String message})> reduceVideoQualityByPercentage({
required String inputPath,
required String outputPath,
required double qualityPercentage
}) async {
final crfValue = (51 - 18) * (1 - qualityPercentage / 100) + 18;
final command =
'-i $inputPath -crf ${crfValue.toInt()} -preset fast -codec:a copy $outputPath';
var isSuccess = false;
var message = "";
await FFmpegKit.execute(command).then((session) async {
final returnCode = await session.getReturnCode();
if (returnCode?.isValueSuccess() ?? false) {
message = "Quality reduced successfully!";
isSuccess = true;
} else {
message = "Failed to reduce quality.";
}
});
return (isSuccess: isSuccess, message: message);
}
Example Usage
final result = reduceVideoQualityByPercentage(
inputPath: videoPath!,
outputPath: outputPath,
qualityPercentage: 50,
);
qualityPercentage
: The percentage of the original quality to retain. A lower percentage means higher compression.crfValue
: Controls the quality, where lower values mean better quality and higher values mean more compression.
Create GIF from Video
Function to Create GIF
Future<({bool isSuccess, String message})> createGifFromVideo({
required String inputPath,
required String outputPath,
required double fps,
required int quality,
int scale = 320,
}) async {
final command =
'-i $inputPath -vf "fps=$fps,scale=$scale:-1:flags=lanczos" -q:v $quality $outputPath';
var isSuccess = false;
var message = "";
await FFmpegKit.execute(command).then((session) async {
final returnCode = await session.getReturnCode();
if (returnCode?.isValueSuccess() ?? false) {
message = "GIF created successfully!";
isSuccess = true;
} else {
message = "Failed to create GIF.";
}
});
return (isSuccess: isSuccess, message: message);
}
Example Usage
final result = createGifFromVideo(
inputPath: videoPath!,
outputPath: pathGif,
fps: 10,
quality: 10,
scale: 500,
);
fps
: Frame rate of the GIF (higher values make the GIF smoother but increase file size).quality
: Lower values mean better quality but larger file size.scale
: The width of the GIF; the height is adjusted automatically to maintain the aspect ratio.
âš Note:
The larger the
scale
, the closer the GIF quality is to the original video.Be mindful of RAM usage when using this function, as GIF creation can be memory-intensive.
If you want a ready-to-use solution, try my plugin: https://pub.dev/packages/my_maker_video.
Last updated