mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-04 04:04:18 +00:00 
			
		
		
		
	Generate video thumbnails
This commit is contained in:
		@@ -312,6 +312,9 @@ class ConversationsHelper {
 | 
			
		||||
    // Check for file
 | 
			
		||||
    if (message.hasFile) request.addBytesFile("file", message.file);
 | 
			
		||||
 | 
			
		||||
    if (message.hasThumbnail)
 | 
			
		||||
      request.addBytesFile("thumbnail", message.thumbnail);
 | 
			
		||||
 | 
			
		||||
    //Send the message
 | 
			
		||||
    APIResponse response;
 | 
			
		||||
    if (!message.hasFile)
 | 
			
		||||
 
 | 
			
		||||
@@ -54,10 +54,16 @@ class ServerConfigurationHelper {
 | 
			
		||||
        conversationsPolicy: ConversationsPolicy(
 | 
			
		||||
          minMessageLen: conversationsPolicy["min_message_len"],
 | 
			
		||||
          maxMessageLen: conversationsPolicy["max_message_len"],
 | 
			
		||||
          allowedFilesType: conversationsPolicy["allowed_files_type"].cast<String>(),
 | 
			
		||||
          allowedFilesType:
 | 
			
		||||
              conversationsPolicy["allowed_files_type"].cast<String>(),
 | 
			
		||||
          filesMaxSize: conversationsPolicy["files_max_size"],
 | 
			
		||||
          writingEventInterval: conversationsPolicy["writing_event_interval"],
 | 
			
		||||
          writingEventLifetime: conversationsPolicy["writing_event_lifetime"],
 | 
			
		||||
          maxMessageImageWidth: conversationsPolicy["max_message_image_width"],
 | 
			
		||||
          maxMessageImageHeight:
 | 
			
		||||
              conversationsPolicy["max_message_image_height"],
 | 
			
		||||
          maxThumbnailWidth: conversationsPolicy["max_thumbnail_width"],
 | 
			
		||||
          maxThumbnailHeight: conversationsPolicy["max_thumbnail_height"],
 | 
			
		||||
        ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -72,4 +78,4 @@ class ServerConfigurationHelper {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Shortcut for server configuration
 | 
			
		||||
ServerConfig get srvConfig => ServerConfigurationHelper.config;
 | 
			
		||||
ServerConfig get srvConfig => ServerConfigurationHelper.config;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,19 @@ class NewConversationMessage {
 | 
			
		||||
  final int conversationID;
 | 
			
		||||
  final String message;
 | 
			
		||||
  final BytesFile file;
 | 
			
		||||
  final BytesFile thumbnail;
 | 
			
		||||
 | 
			
		||||
  NewConversationMessage({
 | 
			
		||||
    @required this.conversationID,
 | 
			
		||||
    @required this.message,
 | 
			
		||||
    this.file,
 | 
			
		||||
    this.thumbnail,
 | 
			
		||||
  })  : assert(conversationID != null),
 | 
			
		||||
        assert(file != null || message != null);
 | 
			
		||||
 | 
			
		||||
  bool get hasMessage => message != null;
 | 
			
		||||
 | 
			
		||||
  bool get hasFile => file != null;
 | 
			
		||||
 | 
			
		||||
  bool get hasThumbnail => thumbnail != null;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,10 @@ class ConversationsPolicy {
 | 
			
		||||
  final int filesMaxSize;
 | 
			
		||||
  final int writingEventInterval;
 | 
			
		||||
  final int writingEventLifetime;
 | 
			
		||||
  final int maxMessageImageWidth;
 | 
			
		||||
  final int maxMessageImageHeight;
 | 
			
		||||
  final int maxThumbnailWidth;
 | 
			
		||||
  final int maxThumbnailHeight;
 | 
			
		||||
 | 
			
		||||
  const ConversationsPolicy({
 | 
			
		||||
    @required this.minMessageLen,
 | 
			
		||||
@@ -72,12 +76,20 @@ class ConversationsPolicy {
 | 
			
		||||
    @required this.filesMaxSize,
 | 
			
		||||
    @required this.writingEventInterval,
 | 
			
		||||
    @required this.writingEventLifetime,
 | 
			
		||||
    @required this.maxMessageImageWidth,
 | 
			
		||||
    @required this.maxMessageImageHeight,
 | 
			
		||||
    @required this.maxThumbnailWidth,
 | 
			
		||||
    @required this.maxThumbnailHeight,
 | 
			
		||||
  })  : assert(minMessageLen != null),
 | 
			
		||||
        assert(maxMessageLen != null),
 | 
			
		||||
        assert(allowedFilesType != null),
 | 
			
		||||
        assert(filesMaxSize != null),
 | 
			
		||||
        assert(writingEventInterval != null),
 | 
			
		||||
        assert(writingEventLifetime != null);
 | 
			
		||||
        assert(writingEventLifetime != null),
 | 
			
		||||
        assert(maxMessageImageWidth != null),
 | 
			
		||||
        assert(maxMessageImageHeight != null),
 | 
			
		||||
        assert(maxThumbnailWidth != null),
 | 
			
		||||
        assert(maxThumbnailHeight != null);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ServerConfig {
 | 
			
		||||
 
 | 
			
		||||
@@ -77,8 +77,8 @@ Future<BytesFile> showPickFileDialog({
 | 
			
		||||
  @required BuildContext context,
 | 
			
		||||
  int maxFileSize,
 | 
			
		||||
  List<String> allowedMimeTypes,
 | 
			
		||||
  double imageMaxWidth,
 | 
			
		||||
  double imageMaxHeight,
 | 
			
		||||
  int imageMaxWidth,
 | 
			
		||||
  int imageMaxHeight,
 | 
			
		||||
}) async {
 | 
			
		||||
  assert(allowedMimeTypes != null);
 | 
			
		||||
 | 
			
		||||
@@ -113,8 +113,8 @@ Future<BytesFile> showPickFileDialog({
 | 
			
		||||
        source: choice == _FileChoices.PICK_IMAGE
 | 
			
		||||
            ? ImageSource.gallery
 | 
			
		||||
            : ImageSource.camera,
 | 
			
		||||
        maxWidth: imageMaxWidth,
 | 
			
		||||
        maxHeight: imageMaxHeight,
 | 
			
		||||
        maxWidth: imageMaxWidth.toDouble(),
 | 
			
		||||
        maxHeight: imageMaxHeight.toDouble(),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (image == null) return null;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import 'package:comunic/helpers/server_config_helper.dart';
 | 
			
		||||
import 'package:comunic/helpers/users_helper.dart';
 | 
			
		||||
import 'package:comunic/lists/conversation_messages_list.dart';
 | 
			
		||||
import 'package:comunic/lists/users_list.dart';
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:comunic/models/conversation.dart';
 | 
			
		||||
import 'package:comunic/models/conversation_message.dart';
 | 
			
		||||
import 'package:comunic/models/new_conversation_message.dart';
 | 
			
		||||
@@ -15,11 +16,14 @@ import 'package:comunic/ui/tiles/conversation_message_tile.dart';
 | 
			
		||||
import 'package:comunic/ui/tiles/server_conversation_message_tile.dart';
 | 
			
		||||
import 'package:comunic/ui/widgets/safe_state.dart';
 | 
			
		||||
import 'package:comunic/ui/widgets/scroll_watcher.dart';
 | 
			
		||||
import 'package:comunic/utils/files_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/intl_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/list_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/log_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/ui_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/video_utils.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:mime/mime.dart';
 | 
			
		||||
 | 
			
		||||
/// Conversation screen
 | 
			
		||||
///
 | 
			
		||||
@@ -227,16 +231,23 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
        context: context,
 | 
			
		||||
        maxFileSize: srvConfig.conversationsPolicy.filesMaxSize,
 | 
			
		||||
        allowedMimeTypes: srvConfig.conversationsPolicy.allowedFilesType,
 | 
			
		||||
        imageMaxWidth: srvConfig.conversationsPolicy.maxMessageImageWidth,
 | 
			
		||||
        imageMaxHeight: srvConfig.conversationsPolicy.maxMessageImageHeight,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (file == null) return;
 | 
			
		||||
 | 
			
		||||
      BytesFile thumbnail;
 | 
			
		||||
 | 
			
		||||
      if (isVideo(lookupMimeType(file.filename)))
 | 
			
		||||
        thumbnail = await generateVideoThumbnail(videoFile: file);
 | 
			
		||||
 | 
			
		||||
      await _submitMessage(
 | 
			
		||||
        NewConversationMessage(
 | 
			
		||||
          conversationID: widget.conversationID,
 | 
			
		||||
          message: null,
 | 
			
		||||
          file: file,
 | 
			
		||||
        ),
 | 
			
		||||
            conversationID: widget.conversationID,
 | 
			
		||||
            message: null,
 | 
			
		||||
            file: file,
 | 
			
		||||
            thumbnail: thumbnail),
 | 
			
		||||
      );
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,9 @@ class ConversationMessageTile extends StatelessWidget {
 | 
			
		||||
 | 
			
		||||
          // Update message content
 | 
			
		||||
          PopupMenuItem(
 | 
			
		||||
            enabled: message.isOwner,
 | 
			
		||||
            enabled: message.isOwner &&
 | 
			
		||||
                message.message != null &&
 | 
			
		||||
                message.message.content.isNotEmpty,
 | 
			
		||||
            value: _MenuChoices.REQUEST_UPDATE_CONTENT,
 | 
			
		||||
            child: Text(tr("Update")),
 | 
			
		||||
          ),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								lib/utils/video_utils.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/utils/video_utils.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:comunic/utils/log_utils.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:path/path.dart' as path;
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
import 'package:random_string/random_string.dart';
 | 
			
		||||
import 'package:video_thumbnail/video_thumbnail.dart';
 | 
			
		||||
 | 
			
		||||
/// Video utilities
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
/// Generate a thumbnail for a video. In case of failure, return null
 | 
			
		||||
Future<BytesFile> generateVideoThumbnail({
 | 
			
		||||
  @required BytesFile videoFile,
 | 
			
		||||
  int maxWidth,
 | 
			
		||||
}) async {
 | 
			
		||||
  File file;
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    final tempDir = await getTemporaryDirectory();
 | 
			
		||||
    if (tempDir == null) return null;
 | 
			
		||||
    file = File(path.join(tempDir.path, randomString(15, from: 65, to: 90)));
 | 
			
		||||
 | 
			
		||||
    await file.writeAsBytes(videoFile.bytes);
 | 
			
		||||
 | 
			
		||||
    return BytesFile(
 | 
			
		||||
      "thumb.png",
 | 
			
		||||
      await VideoThumbnail.thumbnailData(
 | 
			
		||||
          video: file.absolute.path, maxWidth: maxWidth),
 | 
			
		||||
    );
 | 
			
		||||
  } catch (e, s) {
 | 
			
		||||
    logError(e, s);
 | 
			
		||||
    return null;
 | 
			
		||||
  } finally {
 | 
			
		||||
    if (file != null && await file.exists()) await file.delete();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -686,6 +686,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.1.4+1"
 | 
			
		||||
  video_thumbnail:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: video_thumbnail
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.2.5+1"
 | 
			
		||||
  wakelock:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
 
 | 
			
		||||
@@ -109,6 +109,9 @@ dependencies:
 | 
			
		||||
  # Determine file mime type
 | 
			
		||||
  mime: ^0.9.7
 | 
			
		||||
 | 
			
		||||
  # Create video thumbnails
 | 
			
		||||
  video_thumbnail: ^0.2.5+1
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
    sdk: flutter
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user