diff --git a/CHANGELOG.md b/CHANGELOG.md index c9cb3ba..641f7bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Feature: Show link in chat if the saved media file contains one Improve: Verification badge for groups +Improve: Huge reduction in app size +Fix: Crash on older devices when compressing a video + +## 0.0.94 + Fix: Problem with decrypting messages fixed ## 0.0.93 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3b9e45f..ae7f32b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -46,11 +46,6 @@ PODS: - SwiftyGif - emoji_picker_flutter (0.0.1): - Flutter - - ffmpeg_kit_flutter_new (1.0.0): - - ffmpeg_kit_flutter_new/full-gpl (= 1.0.0) - - Flutter - - ffmpeg_kit_flutter_new/full-gpl (1.0.0): - - Flutter - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter @@ -278,6 +273,8 @@ PODS: - Flutter - permission_handler_apple (9.3.0): - Flutter + - pro_video_editor (0.0.1): + - Flutter - PromisesObjC (2.4.0) - restart_app (0.0.1): - Flutter @@ -329,6 +326,8 @@ PODS: - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter + - video_compress (0.3.0): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - FlutterMacOS @@ -342,7 +341,6 @@ DEPENDENCIES: - cryptography_flutter_plus (from `.symlinks/plugins/cryptography_flutter_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`) - - ffmpeg_kit_flutter_new (from `.symlinks/plugins/ffmpeg_kit_flutter_new/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Firebase - firebase_core (from `.symlinks/plugins/firebase_core/ios`) @@ -368,6 +366,7 @@ DEPENDENCIES: - no_screenshot (from `.symlinks/plugins/no_screenshot/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - pro_video_editor (from `.symlinks/plugins/pro_video_editor/ios`) - restart_app (from `.symlinks/plugins/restart_app/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) @@ -376,6 +375,7 @@ DEPENDENCIES: - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) - SwiftProtobuf - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - video_compress (from `.symlinks/plugins/video_compress/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) SPEC REPOS: @@ -428,8 +428,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info_plus/ios" emoji_picker_flutter: :path: ".symlinks/plugins/emoji_picker_flutter/ios" - ffmpeg_kit_flutter_new: - :path: ".symlinks/plugins/ffmpeg_kit_flutter_new/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" firebase_core: @@ -470,6 +468,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info_plus/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + pro_video_editor: + :path: ".symlinks/plugins/pro_video_editor/ios" restart_app: :path: ".symlinks/plugins/restart_app/ios" sentry_flutter: @@ -484,6 +484,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + video_compress: + :path: ".symlinks/plugins/video_compress/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/darwin" @@ -498,7 +500,6 @@ SPEC CHECKSUMS: DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc - ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d firebase_core: ee30637e6744af8e0c12a6a1e8a9718506ec2398 @@ -540,6 +541,7 @@ SPEC CHECKSUMS: no_screenshot: 5e345998c43ffcad5d6834f249590483fcc037bd package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf SDWebImage: 1bb6a1b84b6fe87b972a102bdc77dd589df33477 @@ -554,6 +556,7 @@ SPEC CHECKSUMS: SwiftProtobuf: c901f00a3e125dc33cac9b16824da85682ee47da SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + video_compress: f2133a07762889d67f0711ac831faa26f956980e video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a PODFILE CHECKSUM: ae041999f13ba7b2285ff9ad9bc688ed647bbcb7 diff --git a/lib/src/model/json/userdata.dart b/lib/src/model/json/userdata.dart index 9945264..39bd078 100644 --- a/lib/src/model/json/userdata.dart +++ b/lib/src/model/json/userdata.dart @@ -31,9 +31,6 @@ class UserData { @JsonKey(defaultValue: false) bool isDeveloper = false; - @JsonKey(defaultValue: false) - bool disableVideoCompression = false; - @JsonKey(defaultValue: 0) int deviceId = 0; diff --git a/lib/src/model/json/userdata.g.dart b/lib/src/model/json/userdata.g.dart index 3dc2fbb..83ddff8 100644 --- a/lib/src/model/json/userdata.g.dart +++ b/lib/src/model/json/userdata.g.dart @@ -17,8 +17,6 @@ UserData _$UserDataFromJson(Map json) => UserData( ..appVersion = (json['appVersion'] as num?)?.toInt() ?? 0 ..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0 ..isDeveloper = json['isDeveloper'] as bool? ?? false - ..disableVideoCompression = - json['disableVideoCompression'] as bool? ?? false ..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0 ..subscriptionPlanIdStore = json['subscriptionPlanIdStore'] as String? ..lastImageSend = json['lastImageSend'] == null @@ -95,7 +93,6 @@ Map _$UserDataToJson(UserData instance) => { 'appVersion': instance.appVersion, 'avatarCounter': instance.avatarCounter, 'isDeveloper': instance.isDeveloper, - 'disableVideoCompression': instance.disableVideoCompression, 'deviceId': instance.deviceId, 'subscriptionPlan': instance.subscriptionPlan, 'subscriptionPlanIdStore': instance.subscriptionPlanIdStore, diff --git a/lib/src/services/mediafiles/compression.service.dart b/lib/src/services/mediafiles/compression.service.dart index d2e492b..da9f156 100644 --- a/lib/src/services/mediafiles/compression.service.dart +++ b/lib/src/services/mediafiles/compression.service.dart @@ -1,14 +1,14 @@ 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:pro_video_editor/pro_video_editor.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'; +import 'package:video_compress/video_compress.dart'; Future compressImage( File sourceFile, @@ -69,51 +69,47 @@ Future compressAndOverlayVideo(MediaFileService media) async { media.ffmpegOutputPath.deleteSync(); } - if (gUser.disableVideoCompression) { - media.originalPath.copySync(media.tempPath.path); - return; - } - - var overLayCommand = ''; - if (media.overlayImagePath.existsSync()) { - if (Platform.isAndroid) { - overLayCommand = - '-i "${media.overlayImagePath.path}" -filter_complex "[1:v]format=yuva420p[ovr_in];[0:v]format=yuv420p[base_in];[ovr_in][base_in]scale2ref=w=rw:h=rh[ovr_out][base_out];[base_out][ovr_out]overlay=0:0"'; - } else { - overLayCommand = - '-i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0"'; - } - } - final stopwatch = Stopwatch()..start(); - var additionalParams = ''; + try { + final task = VideoRenderData( + video: EditorVideo.file(media.originalPath), + // qualityPreset: VideoQualityPreset.p720High, + imageBytes: media.overlayImagePath.readAsBytesSync(), + enableAudio: !media.removeAudio, + ); - if (Platform.isAndroid) { - additionalParams += ' -c:v libx264'; - } + final result = await ProVideoEditor.instance.renderVideo(task); + media.ffmpegOutputPath.writeAsBytesSync(result); - var command = - '-i "${media.originalPath.path}" $overLayCommand -map "0:a?" $additionalParams -preset veryfast -crf 28 -c:a aac -b:a 64k "${media.ffmpegOutputPath.path}"'; + MediaInfo? mediaInfo; + try { + mediaInfo = await VideoCompress.compressVideo( + media.ffmpegOutputPath.path, + quality: VideoQuality.Res640x480Quality, + includeAudio: true, + ); + Log.info('Video has now size of ${mediaInfo!.filesize} bytes.'); + } catch (e) { + Log.error('during video compression: $e'); + } - if (media.removeAudio) { - command = - '-i "${media.originalPath.path}" $overLayCommand $additionalParams -preset veryfast -crf 28 -an "${media.ffmpegOutputPath.path}"'; - } + if (mediaInfo == null) { + Log.error('Could not compress video using original video.'); + // as a fall back use the non compressed version + media.ffmpegOutputPath.renameSync(media.tempPath.path); + } else { + mediaInfo.file!.renameSync(media.tempPath.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', + 'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from ${media.ffmpegOutputPath.statSync().size} to ${media.tempPath.statSync().size} bytes.', ); - } else { - Log.info(command); - Log.error('Compression failed for the video with exit code $returnCode.'); - Log.error(await session.getAllLogsAsString()); + } catch (e) { + Log.error(e); + // 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 diff --git a/lib/src/services/mediafiles/thumbnail.service.dart b/lib/src/services/mediafiles/thumbnail.service.dart index 53275b3..c6c0d81 100644 --- a/lib/src/services/mediafiles/thumbnail.service.dart +++ b/lib/src/services/mediafiles/thumbnail.service.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart'; -import 'package:ffmpeg_kit_flutter_new/return_code.dart'; +import 'dart:ui'; +import 'package:pro_video_editor/pro_video_editor.dart'; import 'package:twonly/src/utils/log.dart'; Future createThumbnailsForVideo( @@ -13,22 +13,26 @@ Future createThumbnailsForVideo( return; } - final command = - '-y -i "${sourceFile.path}" -ss 00:00:00 -vframes 1 -vf "scale=iw:ih:flags=lanczos" -c:v libwebp -q:v 100 -compression_level 6 "${destinationFile.path}"'; + final images = await ProVideoEditor.instance.getThumbnails( + ThumbnailConfigs( + video: EditorVideo.file(sourceFile), + outputFormat: ThumbnailFormat.webp, + timestamps: const [ + Duration.zero, + ], + outputSize: const Size(272, 153), + ), + ); - final session = await FFmpegKit.execute(command); - final returnCode = await session.getReturnCode(); - - if (ReturnCode.isSuccess(returnCode)) { + if (images.isNotEmpty) { stopwatch.stop(); + destinationFile.writeAsBytesSync(images.first); Log.info( 'It took ${stopwatch.elapsedMilliseconds}ms to create the thumbnail.', ); } else { - Log.info(command); Log.error( - 'Thumbnail creation failed for the video with exit code $returnCode.', + 'Thumbnail creation failed for the video with exit code.', ); - Log.error(await session.getAllLogsAsString()); } } diff --git a/lib/src/views/camera/share_image_editor.view.dart b/lib/src/views/camera/share_image_editor.view.dart index 9d12615..4e31b88 100644 --- a/lib/src/views/camera/share_image_editor.view.dart +++ b/lib/src/views/camera/share_image_editor.view.dart @@ -444,7 +444,7 @@ class _ShareImageEditorView extends State { setState(() {}); // Make a short delay, so the setState does have its effect... - await Future.delayed(const Duration(milliseconds: 10)); + await Future.delayed(const Duration(milliseconds: 80)); final image = await screenshotController.capture( pixelRatio: pixelRatio, diff --git a/lib/src/views/settings/developer/developer.view.dart b/lib/src/views/settings/developer/developer.view.dart index 37413cf..920eba9 100644 --- a/lib/src/views/settings/developer/developer.view.dart +++ b/lib/src/views/settings/developer/developer.view.dart @@ -29,14 +29,6 @@ class _DeveloperSettingsViewState extends State { setState(() {}); } - Future toggleVideoCompression() async { - await updateUserdata((u) { - u.disableVideoCompression = !u.disableVideoCompression; - return u; - }); - setState(() {}); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -75,17 +67,6 @@ class _DeveloperSettingsViewState extends State { } }, ), - ListTile( - title: const Text('Disable ffmpeg'), - subtitle: const Text( - 'If your smartphone crashes, you can disable ffmpeg. This will prevent your videos from being compressed and NO FILTER will be applied to the video! This is a workaround, until the root-cause in ffmpeg is found.', - ), - onTap: toggleVideoCompression, - trailing: Switch( - value: gUser.disableVideoCompression, - onChanged: (a) => toggleVideoCompression(), - ), - ), if (!kReleaseMode) ListTile( title: const Text('Automated Testing'), diff --git a/lib/src/views/settings/help/diagnostics.view.dart b/lib/src/views/settings/help/diagnostics.view.dart index a96cd1c..faa5bc0 100644 --- a/lib/src/views/settings/help/diagnostics.view.dart +++ b/lib/src/views/settings/help/diagnostics.view.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/loader.dart'; class DiagnosticsView extends StatefulWidget { @@ -150,14 +151,19 @@ class _LogViewerWidgetState extends State { } TextSpan _formatLineSpan(_LogEntry e) { - final tsStyle = - TextStyle(color: Colors.grey.shade500, fontFamily: 'monospace'); + final tsStyle = TextStyle( + color: isDarkMode(context) ? Colors.white : Colors.black, + fontFamily: 'monospace', + ); final levelStyle = TextStyle( color: Colors.blueGrey.shade600, fontWeight: FontWeight.bold, fontFamily: 'monospace', ); - const msgStyle = TextStyle(fontFamily: 'monospace'); + final msgStyle = TextStyle( + color: isDarkMode(context) ? Colors.white : Colors.black, + fontFamily: 'monospace', + ); return TextSpan( children: [ diff --git a/pubspec.lock b/pubspec.lock index 240bf52..3ee608d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -456,22 +456,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - ffmpeg_kit_flutter_new: - dependency: "direct main" - description: - name: ffmpeg_kit_flutter_new - sha256: d127635f27e93a7f21f0a14ce0a1a148e80919c402dac4a2118d73bfb17ce841 - url: "https://pub.dev" - source: hosted - version: "4.1.0" - ffmpeg_kit_flutter_platform_interface: - dependency: transitive - description: - name: ffmpeg_kit_flutter_platform_interface - sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee - url: "https://pub.dev" - source: hosted - version: "0.2.1" file: dependency: transitive description: @@ -1513,6 +1497,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + pro_video_editor: + dependency: "direct main" + description: + name: pro_video_editor + sha256: "0d985f7653c59e2b521d19db49351476eb74eb4001689b33fb8112ab1a9c4330" + url: "https://pub.dev" + source: hosted + version: "1.6.1" protobuf: dependency: "direct main" description: @@ -1996,6 +1988,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.1.0" + video_compress: + dependency: "direct main" + description: + name: video_compress + sha256: "31bc5cdb9a02ba666456e5e1907393c28e6e0e972980d7d8d619a7beda0d4f20" + url: "https://pub.dev" + source: hosted + version: "3.1.4" video_player: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 097f6c6..73a3b38 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.0.94+94 +version: 0.0.95+95 environment: sdk: ^3.6.0 @@ -83,7 +83,6 @@ dependencies: cached_network_image: ^3.4.1 cryptography_flutter_plus: ^2.3.4 cryptography_plus: ^2.7.0 - ffmpeg_kit_flutter_new: ^4.1.0 flutter_android_volume_keydown: ^1.0.1 flutter_image_compress: ^2.4.0 flutter_volume_controller: ^1.3.4 @@ -113,6 +112,8 @@ dependencies: flutter_sharing_intent: ^2.0.4 no_screenshot: ^0.3.1 google_mlkit_face_detection: ^0.13.1 + pro_video_editor: ^1.6.1 + video_compress: ^3.1.4 dependency_overrides: dots_indicator: