If you are looking for a package that crops and trims videos in Flutter, you must have already come across the video_trimmer Flutter package. This package can trim videos but does not provide video cropping (at least not out-of-the-box). In fact, none of the packages on pub.dev, as of today, allow cropping a video in Flutter. If you have dug deeper, you might have come across FFmpeg — a powerful video editing command line tool, that is not the easiest to get started with. In this article we will use the FFmpeg library to crop and trim a video in a Flutter project.

Here is a list of packages that we will be using; ffmpeg_kit_flutter package for cropping and trimming videos, the path_provider package to get the path to the application or external directory where the video files will be stored, and the video_player package to play the video preview.

This article will not cover building any UI for cropping and trimming. But, it will discuss in brief how to crop the video using the video_trimmer library as well. Also, you can try out the code used in this article from this GitHub repository.

Get Started

The end result of this tutorial will be a simple app that would look like the following screenshot.

Create a Flutter app to trim and crop videos easily.

When you tap the Save Video, the preview will refresh with the cropped and trimmed video replacing the original video. Follow the instructions in this article, and you will be able to develop a similar Flutter app.

First, let us start by adding dependencies to a new Flutter project.

Add Dependencies

In your project’s pubspec.yaml file add the following 3 dependencies.

dependencies:
  ...  
  ffmpeg_kit_flutter: ^4.5.1
  path_provider: ^2.0.11
  video_player: ^2.4.6

Then, execute flutter pub get command from the project folder root.

Set Minimum SDK Version and Platform Version

The ffmpeg_kit_flutter plugin runs on Android SDK API level 24+ and iOS SDK 12.1+. Therefore, modify the module level build.gradle file to declare the minSdkVersion.

android {
  defaultConfig {
    ...
    minSdkVersion 24
    ...
  }
}

Then, modify the Podfile to declare the minimum global platform.

# Uncomment this line to define a global platform for your project
platform :ios, '12.1'

Add a Video Asset

For simplicity, we will be using a video asset instead of implementing a file picker. Choose a video file you would like to work with or download this sample video file. Next, copy the video file to a new directory named assets at the root of your project folder.

Copy your video of choice to your assets folder at the root of your project folder.

And then reference the video file from the pubspec.yaml file.

flutter:
  assets:
    - assets/file1.mp4

Execute flutter pub get again.

Having added the required dependencies, let us move on to coding.

Implement Video Crop and Trim

Replace the code in your main.dart file with the following code in the code block. This will be our starter code.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Crop and Trim Demo',
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter Crop and Trim"),
      ),
      body: Column(
        children: [

        ],
      ),
    );
  }
}

Initialize the Input Player

Add the following code to the _MyHomePageState class.

import 'dart:io';

...
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';

...

class _MyHomePageState extends State<MyHomePage> {
  late String inputPath;
  VideoPlayerController? controller;
  String outputPath = "";
	
  ...

  @override
  void initState() {
    super.initState();
    copyVideoToApplicationDirectory().then((path) async {
      inputPath = path;
      controller = VideoPlayerController.file(File(inputPath));
      await controller?.initialize();
      await controller?.play();
      setState(() {});
      outputPath = await getOutputPath();
    });
  }

  ///Copy input file to ApplicationStorage Directory
  ///returns path to copied video
  Future<String> copyVideoToApplicationDirectory() async {
    const filename = "file1.mp4";
    var bytes = await rootBundle.load("assets/file1.mp4");
    String dir = (await getApplicationDocumentsDirectory()).path;
    writeToFile(bytes, '$dir/$filename');

    return '$dir/$filename';
  }

  ///Write to Path.
  Future<void> writeToFile(ByteData data, String path) {
    final buffer = data.buffer;
    return File(path).writeAsBytes(
        buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
  }
}

The copyVideoToApplicationDirectory method is copying the video from Assets to the Application directory on the phone’s file system. The resulting path is then stored in the inputPath variable which is then supplied to the FFmpeg command.

Next, add the VideoPlayer widget to the Column widget.

body: Column(
  children: [
    (controller != null)
        ? AspectRatio(
        aspectRatio: controller!.value.aspectRatio,
        child: VideoPlayer(controller!))
        : const SizedBox(),
  ],
),

Run the app and the video should start playing.

You can now play your video.

In the next step, we will add the FFmpeg command, but before that add the following 2 more methods to the _MyHomePageState class.

...

import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/log.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';

...

class _MyHomePageState extends State<MyHomePage> {
  ...

  /// Output path with a file name where the result will be stored.
  Future<String> getOutputPath() async {
    final appDirectory = Platform.isAndroid
        ? await getExternalStorageDirectory()
        : await getApplicationDocumentsDirectory();
    final externalPath = '${appDirectory?.path}/out_file.mp4';
    return externalPath;
  }

  ///Executes the FFMPEG [command]
  ///Note: Green bar on the right is a Flutter issue. <https://github.com/flutter/engine/pull/24888>
  ///Should get fixed in a 3.1.0+ stable release <https://github.com/flutter/engine/pull/24888#issuecomment-1212374010>
  Future<void> ffmpegExecute(String command) async {

    final session = await FFmpegKit.execute(command);

    final returnCode = await session.getReturnCode();
    if (ReturnCode.isSuccess(returnCode)) {
      
      print("Success");
      //Replace the preview video
      await controller?.pause();
      await controller?.dispose();
      controller = VideoPlayerController.file(File(outputPath));
      await controller?.initialize();
      await controller?.play();
      setState(() {});

    } else if (ReturnCode.isCancel(returnCode)) {

      print("Cancel");

    } else {

      print("Error");
      final failStackTrace = await session.getFailStackTrace();
      print(failStackTrace);
      List<Log> logs = await session.getLogs();
      for (var element in logs) {
        print(element.getMessage());
      }

    }
  }
}

The getOutputPath method provides the path where the resulting video will be saved. The outputPath will be passed to the FFmpeg command whereas the ffmpegExecute method is where the FFmpeg magic takes place.

The ffmpegExecute method expects a valid FFmpeg String command. The String command is then passed to the FFmpegKit.execute method which returns an instance of FFMpegSession . It will tell us whether our command was executed successfully or not. If it was not executed successfully, we can extract error logs from it.

FFmpeg Execute Command

Add a TextButton from where the FFmpeg command shall be sent.

TextButton(
  child: Text('Save Video'),
  onPressed: () async {
    //TODO: Call FFMPEG Execute
  },
),

Next, call the following ffmpegExecute method from the onPressed property.

ffmpegExecute('-ss 0:00:15 -to 0:00:45 -y -i $inputPath -filter:v "crop=320:150" -c:a copy $outputPath');

Run the app and tap the Save Video button. You will notice that the player is now playing the new cropped and trimmed video.

You can now play your cropped and trimmed video!

If you were able to successfully follow the instructions up till here, it is time to dig a little deeper into the FFmpeg command that we just ran earlier.

Understanding the FFmpeg Command

Because command FFmpeg is a command line tool, it expects string-only commands. For this reason, we do not have any dart Classes, Methods, or Parameters to work with that we usually get when working with a dart plugin. Let us dissect the above command line command that is helping us crop and trim a video.

Command Description
-ss 0:00:15 Seeks to position on the input video. The trim starts from this position.
-to 0:00:45 Stops reading at the position in the input video. The trim stops at this position. The total length of the video is 0:02:05.
-y Overwrite output files. Helpful when the Save Video button is tapped more than once.
-i $inputPath Input file location. This is the file on which the crop and trim are applied.
-filter:v Apply a filter to the video stream.
"crop=320:150" Apply a crop filter from the center of the video that is 320 pixels wide and 150 pixels tall. The original dimensions of the video were 320 x 240.
-c:a copy $outputPath Specifies the codec with which the output file must be encoded. Here, copy is a special value to indicate the stream is not to be re-encoded. The a after the colon is a stream specifier for the audio stream.

That will be either the application directory or the external directory of the application, depending on whether the app is running on iOS or Android.

You can check out the app’s code and play with it by downloading it from this GitHub Repository. This project allows you to tweak the duration of the trim and dimensions of the crop, so it has more code, but at the heart of it, it still uses the above FFmpeg command.

Download this app code from the GitHub Repository.

Cropping Using the video_trimmer Plugin

As a bonus, here is a tip on how to use the video_trimmer plugin for cropping videos.

The video_trimmer plugin also uses FFmpeg at its core and allows us to pass on an FFmpeg command while saving the video. For this reason, it is an easy task for us to apply the crop to a video using the video_trimmer plugin.

To do this, pass the following command to the saveTrimmedVideo method’s ffmpegCommand parameter as shown in the code block below. The video_trimmer package expects the customVideoFormat parameter argument when the ffmpegCommand parameter is used.

await _trimmer
    .saveTrimmedVideo(
        startValue: _startValue,
        endValue: _endValue,
        ffmpegCommand:
            '-filter:v "crop=320:150"',
        customVideoFormat: '.mp4')
    .then((value) {
  setState(() {
    _value = value;
  });
});

Limitations of this Approach

The crop that we are applying is from the center of the frame. The present command would also need starting coordinates to crop a non-center frame.

The project will need a refined UI for cropping and trimming videos to offer a complete app experience to users. The current approach is miles away from creating that experience.

But perhaps the most significant limitation is FFmpeg’s licensing. FFmpeg is available with both LGPL and GPL licenses, so you must ensure your project is compatible with those licenses. For many commercial projects, this is a non-starter.

Commercial Alternative

VideoEditor SDK (VE.SDK) from IMG.LY provides powerful video editing features, including cropping and trimming videos in a Flutter project. You will receive staples of video editing, including straightening videos, filters, brightness, color adjustments, and more.

Conclusion

While still a complex topic, video manipulation is more attainable to implement on Flutter than on native Android. Nevertheless, FFmpeg is the only open-source, free-to-use option to edit videos on Flutter right now.