saveTrimmedVideo method

Future<void> saveTrimmedVideo({
  1. required double startValue,
  2. required double endValue,
  3. required dynamic onSave(
    1. String outputPath
    ),
  4. bool applyVideoEncoding = false,
  5. FileFormat? outputFormat,
  6. String? ffmpegCommand,
  7. String? customVideoFormat,
  8. int? fpsGIF,
  9. int? scaleGIF,
  10. String? videoFolderName,
  11. String? videoFileName,
  12. StorageDir? storageDir,
})

Saves the trimmed video to file system.

The required parameters are startValue, endValue & onSave.

The optional parameters are videoFolderName, videoFileName, outputFormat, fpsGIF, scaleGIF, applyVideoEncoding.

The @required parameter startValue is for providing a starting point to the trimmed video. To be specified in milliseconds.

The @required parameter endValue is for providing an ending point to the trimmed video. To be specified in milliseconds.

The @required parameter onSave is a callback Function that helps to retrieve the output path as the FFmpeg processing is complete. Returns a String.

The parameter videoFolderName is used to pass a folder name which will be used for creating a new folder in the selected directory. The default value for it is Trimmer.

The parameter videoFileName is used for giving a new name to the trimmed video file. By default the trimmed video is named as <original_file_name>_trimmed.mp4.

The parameter outputFormat is used for providing a file format to the trimmed video. This only accepts value of FileFormat type. By default it is set to FileFormat.mp4, which is for mp4 files.

The parameter storageDir can be used for providing a storage location option. It accepts only StorageDir values. By default it is set to applicationDocumentsDirectory. Some of the storage types are:

  • temporaryDirectory (Only accessible from inside the app, can be cleared at anytime)

  • applicationDocumentsDirectory (Only accessible from inside the app)

  • externalStorageDirectory (Supports only Android, accessible externally)

The parameters fpsGIF & scaleGIF are used only if the selected output format is FileFormat.gif.

  • fpsGIF for providing a FPS value (by default it is set to 10)

  • scaleGIF for proving a width to output GIF, the height is selected by maintaining the aspect ratio automatically (by default it is set to 480)

  • applyVideoEncoding for specifying whether to apply video encoding (by default it is set to false).

ADVANCED OPTION:

If you want to give custom FFmpeg command, then define ffmpegCommand & customVideoFormat strings. The input path, output path, start and end position is already define.

NOTE: The advanced option does not provide any safety check, so if wrong video format is passed in customVideoFormat, then the app may crash.

Implementation

Future<void> saveTrimmedVideo({
  required double startValue,
  required double endValue,
  required Function(String outputPath) onSave,
  bool applyVideoEncoding = false,
  FileFormat? outputFormat,
  String? ffmpegCommand,
  String? customVideoFormat,
  int? fpsGIF,
  int? scaleGIF,
  String? videoFolderName,
  String? videoFileName,
  StorageDir? storageDir,
}) async {
  final String _videoPath = currentVideoFile.path;
  final String _videoName = basename(_videoPath).split('.')[0];

  String _command;

  // Formatting Date and Time
  String dateTime = DateFormat.yMMMd()
      .addPattern('-')
      .add_Hms()
      .format(DateTime.now())
      .toString();

  // String _resultString;
  String _outputPath;
  String? _outputFormatString;
  String formattedDateTime = dateTime.replaceAll(' ', '');
  String formattedDateTime2 = formattedDateTime.replaceAll(',', '');

  debugPrint("DateTime: $dateTime");
  debugPrint("Formatted: $formattedDateTime");

  videoFolderName ??= "Trimmer";

  videoFileName ??= "${_videoName}_trimmed:$formattedDateTime2";

  videoFileName = videoFileName.replaceAll(' ', '_');

  String path = await _createFolderInAppDocDir(
    videoFolderName,
    storageDir,
  ).whenComplete(
        () => debugPrint("Retrieved Trimmer folder"),
  );

  print("path hasil ==> ${path}");

  Duration startPoint = Duration(milliseconds: startValue.toInt());
  Duration endPoint = Duration(milliseconds: endValue.toInt());

  // Checking the start and end point strings
  debugPrint("Start: ${startPoint.toString()} & End: ${endPoint.toString()}");

  // debugPrint(path);

  if (outputFormat == null) {
    outputFormat = FileFormat.mp4;
    _outputFormatString = outputFormat.toString();
    // debugPrint('OUTPUT: $_outputFormatString');
  } else {
    _outputFormatString = outputFormat.toFormatString();
  }

  String _trimLengthCommand = ' -ss $startPoint -i "$_videoPath" -t ${endPoint - startPoint} -avoid_negative_ts make_zero -movflags faststart';
  //-s 540x960

  if (ffmpegCommand == null) {
    _command = '$_trimLengthCommand -c:a copy ';

    if (!applyVideoEncoding) {
      _command += '-c:v copy ';
    }

    if (outputFormat == FileFormat.gif) {
      fpsGIF ??= 10;
      scaleGIF ??= 480;
      _command =
      '$_trimLengthCommand -vf "fps=$fpsGIF,scale=$scaleGIF:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ';
    }
  } else {
    _command = '$_trimLengthCommand $ffmpegCommand ';
    _outputFormatString = customVideoFormat;
  }

  _outputPath = '$path$videoFileName$_outputFormatString';

  _command += '"$_outputPath"';

  print("command ${_command}");

  FFmpegKit.executeAsync(_command, (session) async {
    final state =
    FFmpegKitConfig.sessionStateToString(await session.getState());
    final returnCode = await session.getReturnCode();
    debugPrint("FFmpeg process exited with state $state and rc $returnCode");

    if (ReturnCode.isSuccess(returnCode)) {
      debugPrint("FFmpeg processing completed successfully.");
      debugPrint('Video successfuly saved');
      onSave(_outputPath);
    } else {
      debugPrint("FFmpeg processing failed.");
      debugPrint('Couldn\'t save the video');
      onSave("");
    }
  });

  // return _outputPath;
}