# 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="https://2074953091-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F63bX4jdEctx3wiLiur0x%2Fuploads%2Fgit-blob-75fa56bc41a8e5277fd9c731f5d6a69497d87278%2Fcomprehensive-guide-to-video-processing-in-flutter-using-ffmpeg-image-1.jpeg?alt=media" 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)
