import 'dart:async'; import 'dart:io'; import 'package:drift/drift.dart' show Value; import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart'; import 'package:ffmpeg_kit_flutter_new/return_code.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; Future compressImage( File sourceFile, File destinationFile, ) async { final stopwatch = Stopwatch()..start(); // // ffmpeg -i input.png -vcodec libwebp -lossless 1 -preset default output.webp try { var compressedBytes = await FlutterImageCompress.compressWithFile( sourceFile.path, format: CompressFormat.webp, quality: 90, ); if (compressedBytes == null) { throw Exception( 'Could not compress media file: $sourceFile. Sending original file.', ); } Log.info('Compressed images size in bytes: ${compressedBytes.length}'); if (compressedBytes.length >= 1 * 1000 * 1000) { // if the media file is over 1MB compress it with 60% final tmpCompressedBytes = await FlutterImageCompress.compressWithFile( sourceFile.path, format: CompressFormat.webp, quality: 60, ); if (tmpCompressedBytes != null) { Log.error( 'Could not compress media file with 60%: $sourceFile. Sending original 90% compressed file.', ); compressedBytes = tmpCompressedBytes; } } await destinationFile.writeAsBytes(compressedBytes); } catch (e) { Log.error('$e'); sourceFile.copySync(destinationFile.path); } stopwatch.stop(); Log.info( 'Compression of the image took: ${stopwatch.elapsedMilliseconds} milliseconds.', ); } Future compressAndOverlayVideo(MediaFileService media) async { if (media.tempPath.existsSync()) { media.tempPath.deleteSync(); } if (media.ffmpegOutputPath.existsSync()) { media.ffmpegOutputPath.deleteSync(); } final stopwatch = Stopwatch()..start(); var command = '-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -map "0:a?" -preset veryfast -crf 28 -c:a aac -b:a 64k "${media.ffmpegOutputPath.path}"'; if (media.removeAudio) { command = '-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -preset veryfast -crf 28 -an "${media.ffmpegOutputPath.path}"'; } final session = await FFmpegKit.execute(command); final returnCode = await session.getReturnCode(); if (ReturnCode.isSuccess(returnCode)) { media.ffmpegOutputPath.copySync(media.tempPath.path); stopwatch.stop(); Log.info( 'It took ${stopwatch.elapsedMilliseconds}ms to compress the video', ); } else { Log.info(command); Log.error('Compression failed for the video with exit code $returnCode.'); Log.error(await session.getAllLogsAsString()); // This should not happen, but in case "notify" the user that the video was not send... This is absolutely bad, but // better this way then sending an uncompressed media file which potentially is 100MB big :/ // Hopefully the user will report the strange behavior <3 await twonlyDB.messagesDao.updateMessagesByMediaId( media.mediaFile.mediaId, const MessagesCompanion(isDeletedFromSender: Value(true)), ); media.fullMediaRemoval(); await media.setUploadState(UploadState.uploaded); } }