> For the complete documentation index, see [llms.txt](https://wong-coupon.gitbook.io/flutter/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wong-coupon.gitbook.io/flutter/media/ffmpeg-flutter.md).

# FFmpeg 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](https://www.ffmpeg.org/) 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](https://pub.dev/packages/ffmpeg_kit_flutter_full_gpl) package brings the full capabilities of FFmpeg to Flutter. Additionally, you can explore other packages at [FFmpeg Kit Packages](https://github.com/arthenica/ffmpeg-kit/wiki/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:

```yaml
ffmpeg_kit_flutter_full_gpl: ^latest_version
```

Run the following command:

```bash
flutter pub get
```

### Android Configuration

Add the following to your `android/app/build.gradle` file:

```gradle
defaultConfig {
    ...
    minSdk = 24
    ...
}
```

Update permissions in `AndroidManifest.xml`:

```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:

```xml
<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:

```ruby
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.

```dart
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:

```dart
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:

<figure><img src="/files/C9cajeiyRclsbzlgMSts" alt=""><figcaption><p>Convert a Sequence of Images to Video</p></figcaption></figure>

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

```dart
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

```dart
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

```dart
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

```dart
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

```dart
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

```dart
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>.

[Buy Me a Coffee](https://buymeacoffee.com/ducmng12g) | [Support Me on Ko-fi](https://ko-fi.com/I2I81AEJG8)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wong-coupon.gitbook.io/flutter/media/ffmpeg-flutter.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
