Reduce video file size in Flutter / Dart

10k views Asked by At

I am working on an application where users can upload videos to our server. They can select videos from their gallery, so I would like to reduce them before uploading.

I would like to know if there is any way to reduce the video file size with Flutter/ Dart. Thanks.

7

There are 7 answers

1
TruongSinh On BEST ANSWER

Update: since my original answer, there's another package https://pub.dev/packages/flutter_video_compress with friendlier API

https://pub.dartlang.org/packages/flutter_ffmpeg is pretty good, and has well-documented instruction

import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';

 final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg();

 _flutterFFmpeg.execute("-i file1.mp4 -c:v mpeg4 file2.mp4").then((rc) => print("FFmpeg process exited with rc $rc"));

Check the rc code, and if it's successful, open file2.mp4, which is the compressed/processed file.

0
Roger Cuesta On

You can try to search in https://pub.dartlang.org if exist any package that have that function or similar .

0
wahid anvary On

I am using flutter_ffmpeg plugin detailed:

static Future<String> ReduceSizeAndType(videoPath, outDirPath) async {
    assert(File(videoPath).existsSync());

    final arguments = '-y -i $videoPath ' +
        '-preset ultrafast -g 48 -sc_threshold 0 ' +
        '-c:v libx264 -b:v 720k ' +
        '-c:a copy ' +
        '"$outDirPath/file2.mp4"';

    final int rc = await _encoder.execute(arguments);
    assert(rc == 0);

    return outDirPath;
  }

You can change the quality and size of your video with increasing and decreasing 720k quantity. Returned of "$outDirPath" is your output video path.

1
Kab Agouda On

Use flutter_video_compress package

Installing:

add flutter_video_compress as a dependency in your pubspec.yaml file.

Usage :

Create an instance

final _flutterVideoCompress = FlutterVideoCompress();

Get thumbnail from video path

final uint8list = await _flutterVideoCompress.getThumbnail(
  file.path,
  quality: 50, // default(100)
  position: -1 // default(-1)
);

Get thumbnail file from video path

final thumbnailFile = await _flutterVideoCompress.getThumbnailWithFile(
  file.path,
  quality: 50, // default(100)
  position: -1 // default(-1)
);

Convert video to a gif

final file = await _flutterVideoCompress.convertVideoToGif(
  videoFile.path,
  startTime: 0, // default(0)
  duration: 5, // default(-1)
  // endTime: -1 // default(-1)
);
print(file.path);

Compression Video

final info = await _flutterVideoCompress.compressVideo(
  file.path,
  quality: VideoQuality.DefaultQuality, // default(VideoQuality.DefaultQuality)
  deleteOrigin: false, // default(false)
);
print(info.toJson().toString());

You can learn more about this package here

0
Holofox On

Use ffmpeg_kit_flutter package:

pubspec.yaml

ffmpeg_kit_flutter_min_gpl: ^5.1.0

ffmpeg_service.dart

import 'dart:async';
import 'dart:io' as io;

import 'package:ffmpeg_kit_flutter_min_gpl/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_min_gpl/return_code.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

enum FfmpegCompressScale {
  none(null),
  half(4),
  oneThird(6),
  oneQuarter(8),
  oneFifth(10);

  const FfmpegCompressScale(this.value);

  final int? value;
}

enum FfmpegPreset {
  ultrafast,
  superfast,
  veryfast,
  faster,
  fast,
  medium,
  slow,
  slower,
  veryslow,
}

class FfmpegService {
  const FfmpegService();

  Future<io.Directory> get _tempVideoDir async {
    final temp = await getTemporaryDirectory();
    return await io.Directory(p.join(temp.path, 'video')).create();
  }

  Future<String?> compressVideo(
    String path, {
    FfmpegPreset preset = FfmpegPreset.medium,
    FfmpegCompressScale scale = FfmpegCompressScale.none,
    int crf = 28,
  }) async {
    try {
      final name = p.basenameWithoutExtension(path);
      final epoch = DateTime.now().millisecondsSinceEpoch;
      final outputPath = [(await _tempVideoDir).path, '${name}_$epoch.mp4']
          .join(io.Platform.pathSeparator);
      final command = [
        '-y -i $path -preset ${preset.name}',
        if (scale.value != null)
          '-vf scale="trunc(iw/${scale.value})*2:trunc(ih/${scale.value})*2"',
        '-c:v libx264 -crf $crf',
        '-c:a copy $outputPath',
      ].join(' ');
      final completer = Completer<String?>();
      await FFmpegKit.executeAsync(
        command,
        (session) async {
          final code = await session.getReturnCode();
          if (ReturnCode.isSuccess(code)) {
            return completer.complete(outputPath);
          } else if (ReturnCode.isCancel(code)) {
            return completer.complete(null);
          }
          final state = await session.getState();
          return completer.completeError(Exception(
            'FFmpeg process exited with state ${state.name} '
            'and return code $code.',
          ));
        },
        (log) => debugPrint(log.getMessage()),
      );
      return await completer.future;
    } catch (e, s) {
      debugPrint('Failed to compress video $e $s');
      rethrow;
    }
  }
}

Usage

final path = await const FfmpegService().compressVideo(
  file.path,
  preset: FfmpegPreset.veryfast,
  scale: FfmpegCompressScale.half,
);

You can work with the constant rate factor and scaling of the video depending on your preferences. You also need to remember that the lower the processing speed, the more the file is compressed.

I use the H.264 codec for more device compatibility, if you want you can use H.265 for more compression.

0
Eslam Hanafy On

Based on @holofox's answer You will need to add the ffmpeg_kit_flutter in your Pubspec file like this

ffmpeg_kit_flutter_min_gpl: ^6.0.3

Also, you may need to add this code in the android/app/build.gradle

packagingOptions {
        pickFirst '**/libc++_shared.so'
    }

and change the minSdkVersion to 24

and this is an updated code adding the progress callback to the compress function

import 'dart:async';
import 'dart:io' as io;

import 'package:ffmpeg_kit_flutter_min_gpl/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_min_gpl/return_code.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_video_info/flutter_video_info.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

enum FfmpegCompressScale {
  none(null),
  half(4),
  oneThird(6),
  oneQuarter(8),
  oneFifth(10);

  const FfmpegCompressScale(this.value);

  final int? value;
}

enum FfmpegPreset {
  ultrafast,
  superfast,
  veryFast,
  faster,
  fast,
  medium,
  slow,
  slower,
  verySlow,
}

class FfmpegService {
  const FfmpegService();

  Future<io.Directory> get _tempVideoDir async {
    final temp = await getTemporaryDirectory();
    return await io.Directory(p.join(temp.path, 'video')).create();
  }

  Future<String?> compressVideo(
    String path, {
    FfmpegPreset preset = FfmpegPreset.veryFast,
    FfmpegCompressScale scale = FfmpegCompressScale.none,
    int crf = 28,
    void Function(int)? progressCallback,
  }) async {
    try {
      final name = p.basenameWithoutExtension(path);
      final epoch = DateTime.now().millisecondsSinceEpoch;
      final flutterVideoInfo = FlutterVideoInfo();
      final videoInfo = await flutterVideoInfo.getVideoInfo(path);

      final outputPath = [(await _tempVideoDir).path, '${name}_$epoch.mp4']
          .join(io.Platform.pathSeparator);
      final command = [
        '-y -i $path -preset ${preset.name}',
        if (scale.value != null)
          '-vf scale="trunc(iw/${scale.value})*2:trunc(ih/${scale.value})*2"',
        '-c:v libx264 -crf $crf',
        '-c:a copy $outputPath',
      ].join(' ');
      final completer = Completer<String?>();
      await FFmpegKit.executeAsync(
          command,
          (session) async {
            final code = await session.getReturnCode();
            if (ReturnCode.isSuccess(code)) {
              return completer.complete(outputPath);
            } else if (ReturnCode.isCancel(code)) {
              return completer.complete(null);
            }
            final state = await session.getState();
            return completer.completeError(Exception(
              'FFmpeg process exited with state ${state.name} '
              'and return code $code.',
            ));
          },
          (log) => debugPrint(log.getMessage()),
          (statistics) {
            if (progressCallback == null) return;
            final time = statistics.getTime();
            if (time > 0 && videoInfo?.duration != null) {
              final progress = (time / videoInfo!.duration! * 100).toInt();
              progressCallback(progress);
            }
          });
      return await completer.future;
    } catch (e, s) {
      debugPrint('Failed to compress video $e $s');
      rethrow;
    }
  }
}

don't forget to add flutter_video_info flutter_video_info: ^1.3.1 to your Pubspec file to use this updated code

Finally, Thanks @holofox for your great answer.

0
KHALED On

@eslam

When adding ffmpeg_kit_flutter_min_gpl: ^6.0.3 and packagingOptions { pickFirst '**/libc++_shared.so' } it is giving an error about Duplicated classes such as com.arthenica.ffmpegkit.StreamInformation found in modules jetified-ffmpeg-kit-https-6.0-2-runtime (com.arthenica:ffmpeg-kit-https:6.0-2) and jetified-ffmpeg-kit-min-gpl-6.0-2-runtime. what other configurations are missing? @es