mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-06-13 10:42:12 +00:00
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
217 lines
5.3 KiB
Dart
217 lines
5.3 KiB
Dart
import 'dart:io';
|
|
import 'dart:ui';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
|
import 'package:image/image.dart' as img;
|
|
import 'package:pro_video_editor/pro_video_editor.dart';
|
|
import 'package:twonly/src/utils/log.dart';
|
|
|
|
Future<bool> createThumbnailsForVideo(
|
|
File sourceFile,
|
|
File destinationFile,
|
|
) async {
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
if (!sourceFile.existsSync() || sourceFile.lengthSync() == 0) {
|
|
Log.warn('Source video file does not exist or is empty.');
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
|
|
if (destinationFile.existsSync()) {
|
|
if (destinationFile.lengthSync() > 0) {
|
|
return true;
|
|
} else {
|
|
try {
|
|
destinationFile.deleteSync();
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
try {
|
|
final images = await ProVideoEditor.instance.getThumbnails(
|
|
ThumbnailConfigs(
|
|
video: EditorVideo.file(sourceFile),
|
|
outputFormat: ThumbnailFormat.webp,
|
|
timestamps: const [
|
|
Duration.zero,
|
|
],
|
|
outputSize: const Size(272, 153),
|
|
),
|
|
);
|
|
|
|
if (images.isNotEmpty && images.first.isNotEmpty) {
|
|
stopwatch.stop();
|
|
await destinationFile.writeAsBytes(images.first);
|
|
if (destinationFile.existsSync() && destinationFile.lengthSync() > 0) {
|
|
Log.info(
|
|
'It took ${stopwatch.elapsedMilliseconds}ms to create the video thumbnail.',
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Log.error('Error creating video thumbnail: $e');
|
|
}
|
|
|
|
Log.warn(
|
|
'Thumbnail creation failed for the video.',
|
|
);
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
|
|
Future<bool> createThumbnailsForImage(
|
|
File sourceFile,
|
|
File destinationFile,
|
|
) async {
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
if (!sourceFile.existsSync() || sourceFile.lengthSync() == 0) {
|
|
Log.warn('Source image file does not exist or is empty.');
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
|
|
if (destinationFile.existsSync()) {
|
|
if (destinationFile.lengthSync() > 0) {
|
|
return true;
|
|
} else {
|
|
try {
|
|
destinationFile.deleteSync();
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
try {
|
|
await FlutterImageCompress.compressAndGetFile(
|
|
sourceFile.absolute.path,
|
|
destinationFile.absolute.path,
|
|
minWidth: 300,
|
|
minHeight: 300,
|
|
quality: 100,
|
|
format: CompressFormat.webp,
|
|
);
|
|
stopwatch.stop();
|
|
|
|
if (destinationFile.existsSync() && destinationFile.lengthSync() > 0) {
|
|
Log.info(
|
|
'It took ${stopwatch.elapsedMilliseconds}ms to create the image thumbnail.',
|
|
);
|
|
return true;
|
|
} else {
|
|
Log.error('Compressed image thumbnail is empty or missing.');
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
Log.error('Error creating image thumbnail: $e');
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<bool> createThumbnailsForGif(
|
|
File sourceFile,
|
|
File destinationFile,
|
|
) async {
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
if (!sourceFile.existsSync() || sourceFile.lengthSync() == 0) {
|
|
Log.warn('Source GIF file does not exist or is empty.');
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
|
|
if (destinationFile.existsSync()) {
|
|
if (destinationFile.lengthSync() > 0) {
|
|
return true;
|
|
} else {
|
|
try {
|
|
destinationFile.deleteSync();
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
try {
|
|
// For GIFs, we decode the first frame and save it as WebP
|
|
final bytes = await sourceFile.readAsBytes();
|
|
final pngBytes = await compute(_processGifThumbnail, bytes);
|
|
if (pngBytes == null || pngBytes.isEmpty) {
|
|
Log.error('Could not decode GIF for thumbnail.');
|
|
return false;
|
|
}
|
|
|
|
final webp = await FlutterImageCompress.compressWithList(
|
|
pngBytes,
|
|
format: CompressFormat.webp,
|
|
quality: 85,
|
|
);
|
|
if (webp.isEmpty) {
|
|
Log.error('GIF thumbnail compression returned empty.');
|
|
return false;
|
|
}
|
|
|
|
await destinationFile.writeAsBytes(webp);
|
|
|
|
stopwatch.stop();
|
|
if (destinationFile.existsSync() && destinationFile.lengthSync() > 0) {
|
|
Log.info(
|
|
'It took ${stopwatch.elapsedMilliseconds}ms to create the GIF thumbnail.',
|
|
);
|
|
return true;
|
|
} else {
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
Log.error('Error creating GIF thumbnail: $e');
|
|
try {
|
|
if (destinationFile.existsSync()) {
|
|
destinationFile.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Uint8List? _processGifThumbnail(Uint8List bytes) {
|
|
final image = img.decodeGif(bytes);
|
|
if (image == null) return null;
|
|
|
|
final thumbnail = img.copyResize(
|
|
image,
|
|
width: image.width > image.height ? 400 : null,
|
|
height: image.height >= image.width ? 400 : null,
|
|
);
|
|
|
|
return img.encodePng(thumbnail);
|
|
}
|