From 7ba7aab9de4bfeddfb32ebd547ee87274f3e9627 Mon Sep 17 00:00:00 2001 From: Dimitar Klaturov Date: Fri, 18 Oct 2024 01:31:52 +0300 Subject: [PATCH] combine videos video codes --- Custom Widgets/img-local-remote.dart | 2 +- Packages/ffmpeg/combine-videos.dart | 264 ++++++++---------- Packages/ffmpeg/other/combine-videos.dart | 168 +++++++++++ Packages/ffmpeg/{ => other}/thumbnail.dart | 2 +- .../image_gallery_saver/copy-to-gallery.dart | 2 +- Packages/path/get-full-path.dart | 28 ++ .../video_player/player-local-remote.dart | 2 +- 7 files changed, 321 insertions(+), 147 deletions(-) create mode 100644 Packages/ffmpeg/other/combine-videos.dart rename Packages/ffmpeg/{ => other}/thumbnail.dart (97%) create mode 100644 Packages/path/get-full-path.dart diff --git a/Custom Widgets/img-local-remote.dart b/Custom Widgets/img-local-remote.dart index 4803753..19385e0 100644 --- a/Custom Widgets/img-local-remote.dart +++ b/Custom Widgets/img-local-remote.dart @@ -1,5 +1,5 @@ // YouTube channel - https://www.youtube.com/@flutterflowexpert -// video - no +// paid video - https://www.youtube.com/watch?v=LfAwHZndeWQ // Join the Klaturov army - https://www.youtube.com/@flutterflowexpert/join // Support my work - https://github.com/sponsors/bulgariamitko // Website - https://bulgariamitko.github.io/flutterflowtutorials/ diff --git a/Packages/ffmpeg/combine-videos.dart b/Packages/ffmpeg/combine-videos.dart index fdb27f2..0dadcc3 100644 --- a/Packages/ffmpeg/combine-videos.dart +++ b/Packages/ffmpeg/combine-videos.dart @@ -1,5 +1,5 @@ // YouTube channel - https://www.youtube.com/@flutterflowexpert -// video - no +// paid video - https://www.youtube.com/watch?v=LfAwHZndeWQ // Join the Klaturov army - https://www.youtube.com/@flutterflowexpert/join // Support my work - https://github.com/sponsors/bulgariamitko // Website - https://bulgariamitko.github.io/flutterflowtutorials/ @@ -9,160 +9,138 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; -import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart'; -import 'package:ffmpeg_kit_flutter/return_code.dart'; -import 'package:firebase_storage/firebase_storage.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:flutter/foundation.dart' - show consolidateHttpClientResponseBytes; - -Future finalVideoCreator( - List videoUrls, - String videoId, - String userId, -) async { - FFAppState().update(() { - FFAppState().finalVideoIsProcessing = true; - FFAppState().finalVideoStatus = 'Starting video processing...'; - }); - - try { - final tempDir = await getTemporaryDirectory(); - final List intermediateVideos = []; - - // Download and process each video - for (int i = 0; i < videoUrls.length; i++) { - FFAppState().update(() { - FFAppState().finalVideoStatus = - 'Processing video ${i + 1} of ${videoUrls.length}'; - }); - - final videoPath = '${tempDir.path}/video_$i.mp4'; - await _downloadFile(videoUrls[i].url, videoPath); - - final intermediatePath = '${tempDir.path}/intermediate_$i.mp4'; - await _createIntermediateVideo( - videoPath, intermediatePath, videoUrls[i].length); - intermediateVideos.add(intermediatePath); +import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart'; +import 'package:ffmpeg_kit_flutter_full_gpl/return_code.dart'; +import 'package:path/path.dart' as path; +import 'package:video_thumbnail/video_thumbnail.dart'; +import 'package:video_player/video_player.dart'; +import 'package:http/http.dart' as http; + +class FileHandler { + static Future getAppDirectory() async { + if (Platform.isIOS) { + return (await getApplicationDocumentsDirectory()).path; + } else if (Platform.isAndroid) { + return (await getExternalStorageDirectory() ?? + await getApplicationDocumentsDirectory()) + .path; + } else { + throw UnsupportedError('Unsupported platform'); } + } - // Concatenate videos - FFAppState().update(() { - FFAppState().finalVideoStatus = 'Concatenating videos...'; - }); - - final finalVideoPath = '${tempDir.path}/final_video.mp4'; - await _concatenateVideos(intermediateVideos, finalVideoPath); - - // Upload final video - FFAppState().update(() { - FFAppState().finalVideoStatus = 'Uploading final video...'; - }); - - final finalVideoUrl = await _uploadVideo(finalVideoPath, userId, videoId); + static Future getFilePath(String fileName) async { + final appDir = await getAppDirectory(); + return path.join(appDir, fileName); + } - // Update Firestore - await FirebaseFirestore.instance - .collection('projects') - .doc(videoId) - .update({'finalVideo': finalVideoUrl}); + static String getFileNameFromPath(String fullPath) { + return path.basename(fullPath); + } - FFAppState().update(() { - FFAppState().finalVideoStatus = - 'Video processing completed successfully!'; - }); - } catch (e) { - FFAppState().update(() { - FFAppState().finalVideoStatus = 'Error: ${e.toString()}'; - }); - } finally { - FFAppState().update(() { - FFAppState().finalVideoIsProcessing = false; - }); + static Future getFullPath(String fileName) async { + return await getFilePath(fileName); } } -Future _downloadFile(String url, String destPath) async { - final response = await HttpClient().getUrl(Uri.parse(url)); - final HttpClientResponse httpResponse = await response.close(); - final bytes = await consolidateHttpClientResponseBytes(httpResponse); - await File(destPath).writeAsBytes(bytes); +Future generateThumbnail(String videoPath) async { + try { + final thumbnailFileName = '${DateTime.now().millisecondsSinceEpoch}.png'; + final thumbnailPath = await FileHandler.getFilePath(thumbnailFileName); + + // Calculate the middle of the video duration + final videoPlayerController = VideoPlayerController.file(File(videoPath)); + await videoPlayerController.initialize(); + final videoDuration = videoPlayerController.value.duration; + final middlePosition = videoDuration.inMilliseconds ~/ 2; + await videoPlayerController.dispose(); + + final thumbnail = await VideoThumbnail.thumbnailFile( + video: videoPath, + thumbnailPath: thumbnailPath, + imageFormat: ImageFormat.PNG, + maxHeight: 640, + maxWidth: 480, + quality: 100, + timeMs: middlePosition, + ); + + return thumbnail != null ? FileHandler.getFileNameFromPath(thumbnail) : ''; + } catch (e) { + print('Error generating thumbnail: $e'); + return ''; + } } -Future _createIntermediateVideo( - String inputPath, String outputPath, int lengthInSeconds) async { - final command = - '''-y -i "$inputPath" -t $lengthInSeconds -c:v h264 -c:a aac "$outputPath"'''; - print("Executing FFmpeg command: $command"); - - final session = await FFmpegKit.execute(command); - final returnCode = await session.getReturnCode(); - final log = await session.getLogs(); - - log.forEach((log) { - print(log.getMessage()); - }); - - if (ReturnCode.isSuccess(returnCode)) { - print("FFmpeg process completed successfully."); - print("Return code: ${returnCode?.getValue()}"); - print("Duration: ${await session.getDuration()}"); - } else { - final failStackTrace = await session.getFailStackTrace(); - print("FFmpeg process failed. Return code: ${returnCode?.getValue()}"); - print("Fail stack trace: $failStackTrace"); - throw Exception( - "FFmpeg process failed with return code ${returnCode?.getValue()}"); +Future> combineVideos( + String video1Url, + String video2Url, +) async { + try { + print("Starting combineVideos function"); + + // Download videos if they are URLs + final video1Path = await _downloadVideoIfNeeded(video1Url); + final video2Path = await _downloadVideoIfNeeded(video2Url); + + // Prepare output file + final String outputFileName = + 'combined_${DateTime.now().millisecondsSinceEpoch}.mp4'; + final String outputPath = await FileHandler.getFilePath(outputFileName); + + // Create input list file + final String inputListPath = + await FileHandler.getFilePath('input_list.txt'); + final File inputListFile = File(inputListPath); + await inputListFile.writeAsString("file '$video1Path'\nfile '$video2Path'"); + + // FFmpeg command to combine videos + String command = '-y -f concat -safe 0 -i "$inputListPath" ' + '-c:v libx264 -preset slow -crf 22 ' + '-c:a aac -b:a 192k -ar 44100 ' + '-vsync 1 -max_muxing_queue_size 1024 ' + '-fflags +genpts ' + '"$outputPath"'; + + print("Executing FFmpeg command: $command"); + final session = await FFmpegKit.execute(command); + final returnCode = await session.getReturnCode(); + + if (ReturnCode.isSuccess(returnCode)) { + print("Video combination complete. Output file: $outputFileName"); + + // Generate thumbnail + final thumbnailFileName = await generateThumbnail(outputPath); + + // Clean up temporary files + await inputListFile.delete(); + + return [outputFileName, thumbnailFileName]; + } else { + print("Failed to combine videos. Return code: $returnCode"); + final logs = await session.getLogsAsString(); + print("FFmpeg logs: $logs"); + throw Exception('Failed to combine videos'); + } + } catch (e) { + print("Error in combineVideos: $e"); + throw Exception('Failed to combine videos: $e'); } } -Future _concatenateVideos( - List inputPaths, String outputPath) async { - final tempDir = await getTemporaryDirectory(); - final inputFile = '${tempDir.path}/input.txt'; - await File(inputFile).writeAsString( - inputPaths.map((p) => "file '${p.replaceAll("'", "\\'")}'").join('\n')); - - final command = - '''-y -f concat -safe 0 -i "$inputFile" -c copy "$outputPath"'''; - print("Executing FFmpeg command: $command"); - - final session = await FFmpegKit.execute(command); - final returnCode = await session.getReturnCode(); - final log = await session.getLogs(); - - log.forEach((log) { - print(log.getMessage()); - }); - - if (ReturnCode.isSuccess(returnCode)) { - print("FFmpeg process completed successfully."); - print("Return code: ${returnCode?.getValue()}"); - print("Duration: ${await session.getDuration()}"); +Future _downloadVideoIfNeeded(String videoUrl) async { + if (videoUrl.startsWith('http://') || videoUrl.startsWith('https://')) { + // Download the video + final response = await http.get(Uri.parse(videoUrl)); + final bytes = response.bodyBytes; + final String fileName = + 'downloaded_${DateTime.now().millisecondsSinceEpoch}.mp4'; + final String filePath = await FileHandler.getFilePath(fileName); + final File file = File(filePath); + await file.writeAsBytes(bytes); + return filePath; } else { - final failStackTrace = await session.getFailStackTrace(); - print("FFmpeg process failed. Return code: ${returnCode?.getValue()}"); - print("Fail stack trace: $failStackTrace"); - throw Exception( - "FFmpeg process failed with return code ${returnCode?.getValue()}"); + // It's already a local path + return videoUrl; } } - -Future checkFfmpegCapabilities() async { - final encodersSession = await FFmpegKit.execute('-encoders'); - final encodersOutput = await encodersSession.getOutput(); - print("Available encoders:\n$encodersOutput"); - - final formatsSession = await FFmpegKit.execute('-formats'); - final formatsOutput = await formatsSession.getOutput(); - print("Supported formats:\n$formatsOutput"); -} - -Future _uploadVideo( - String videoPath, String userId, String videoId) async { - final fileName = 'users/$userId/uploads/final-video/$videoId/final-video.mp4'; - final ref = FirebaseStorage.instance.ref().child(fileName); - final uploadTask = ref.putFile(File(videoPath)); - final snapshot = await uploadTask.whenComplete(() {}); - return await snapshot.ref.getDownloadURL(); -} diff --git a/Packages/ffmpeg/other/combine-videos.dart b/Packages/ffmpeg/other/combine-videos.dart new file mode 100644 index 0000000..c687d65 --- /dev/null +++ b/Packages/ffmpeg/other/combine-videos.dart @@ -0,0 +1,168 @@ +// YouTube channel - https://www.youtube.com/@flutterflowexpert +// paid video - https://www.youtube.com/watch?v=LfAwHZndeWQ +// Join the Klaturov army - https://www.youtube.com/@flutterflowexpert/join +// Support my work - https://github.com/sponsors/bulgariamitko +// Website - https://bulgariamitko.github.io/flutterflowtutorials/ +// You can book me as FF mentor - https://calendly.com/bulgaria_mitko +// GitHub repo - https://github.com/bulgariamitko/flutterflowtutorials +// Discord channel - https://discord.gg/G69hSUqEeU + +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; +import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart'; +import 'package:ffmpeg_kit_flutter/return_code.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/foundation.dart' + show consolidateHttpClientResponseBytes; + +Future finalVideoCreator( + List videoUrls, + String videoId, + String userId, +) async { + FFAppState().update(() { + FFAppState().finalVideoIsProcessing = true; + FFAppState().finalVideoStatus = 'Starting video processing...'; + }); + + try { + final tempDir = await getTemporaryDirectory(); + final List intermediateVideos = []; + + // Download and process each video + for (int i = 0; i < videoUrls.length; i++) { + FFAppState().update(() { + FFAppState().finalVideoStatus = + 'Processing video ${i + 1} of ${videoUrls.length}'; + }); + + final videoPath = '${tempDir.path}/video_$i.mp4'; + await _downloadFile(videoUrls[i].url, videoPath); + + final intermediatePath = '${tempDir.path}/intermediate_$i.mp4'; + await _createIntermediateVideo( + videoPath, intermediatePath, videoUrls[i].length); + intermediateVideos.add(intermediatePath); + } + + // Concatenate videos + FFAppState().update(() { + FFAppState().finalVideoStatus = 'Concatenating videos...'; + }); + + final finalVideoPath = '${tempDir.path}/final_video.mp4'; + await _concatenateVideos(intermediateVideos, finalVideoPath); + + // Upload final video + FFAppState().update(() { + FFAppState().finalVideoStatus = 'Uploading final video...'; + }); + + final finalVideoUrl = await _uploadVideo(finalVideoPath, userId, videoId); + + // Update Firestore + await FirebaseFirestore.instance + .collection('projects') + .doc(videoId) + .update({'finalVideo': finalVideoUrl}); + + FFAppState().update(() { + FFAppState().finalVideoStatus = + 'Video processing completed successfully!'; + }); + } catch (e) { + FFAppState().update(() { + FFAppState().finalVideoStatus = 'Error: ${e.toString()}'; + }); + } finally { + FFAppState().update(() { + FFAppState().finalVideoIsProcessing = false; + }); + } +} + +Future _downloadFile(String url, String destPath) async { + final response = await HttpClient().getUrl(Uri.parse(url)); + final HttpClientResponse httpResponse = await response.close(); + final bytes = await consolidateHttpClientResponseBytes(httpResponse); + await File(destPath).writeAsBytes(bytes); +} + +Future _createIntermediateVideo( + String inputPath, String outputPath, int lengthInSeconds) async { + final command = + '''-y -i "$inputPath" -t $lengthInSeconds -c:v h264 -c:a aac "$outputPath"'''; + print("Executing FFmpeg command: $command"); + + final session = await FFmpegKit.execute(command); + final returnCode = await session.getReturnCode(); + final log = await session.getLogs(); + + log.forEach((log) { + print(log.getMessage()); + }); + + if (ReturnCode.isSuccess(returnCode)) { + print("FFmpeg process completed successfully."); + print("Return code: ${returnCode?.getValue()}"); + print("Duration: ${await session.getDuration()}"); + } else { + final failStackTrace = await session.getFailStackTrace(); + print("FFmpeg process failed. Return code: ${returnCode?.getValue()}"); + print("Fail stack trace: $failStackTrace"); + throw Exception( + "FFmpeg process failed with return code ${returnCode?.getValue()}"); + } +} + +Future _concatenateVideos( + List inputPaths, String outputPath) async { + final tempDir = await getTemporaryDirectory(); + final inputFile = '${tempDir.path}/input.txt'; + await File(inputFile).writeAsString( + inputPaths.map((p) => "file '${p.replaceAll("'", "\\'")}'").join('\n')); + + final command = + '''-y -f concat -safe 0 -i "$inputFile" -c copy "$outputPath"'''; + print("Executing FFmpeg command: $command"); + + final session = await FFmpegKit.execute(command); + final returnCode = await session.getReturnCode(); + final log = await session.getLogs(); + + log.forEach((log) { + print(log.getMessage()); + }); + + if (ReturnCode.isSuccess(returnCode)) { + print("FFmpeg process completed successfully."); + print("Return code: ${returnCode?.getValue()}"); + print("Duration: ${await session.getDuration()}"); + } else { + final failStackTrace = await session.getFailStackTrace(); + print("FFmpeg process failed. Return code: ${returnCode?.getValue()}"); + print("Fail stack trace: $failStackTrace"); + throw Exception( + "FFmpeg process failed with return code ${returnCode?.getValue()}"); + } +} + +Future checkFfmpegCapabilities() async { + final encodersSession = await FFmpegKit.execute('-encoders'); + final encodersOutput = await encodersSession.getOutput(); + print("Available encoders:\n$encodersOutput"); + + final formatsSession = await FFmpegKit.execute('-formats'); + final formatsOutput = await formatsSession.getOutput(); + print("Supported formats:\n$formatsOutput"); +} + +Future _uploadVideo( + String videoPath, String userId, String videoId) async { + final fileName = 'users/$userId/uploads/final-video/$videoId/final-video.mp4'; + final ref = FirebaseStorage.instance.ref().child(fileName); + final uploadTask = ref.putFile(File(videoPath)); + final snapshot = await uploadTask.whenComplete(() {}); + return await snapshot.ref.getDownloadURL(); +} diff --git a/Packages/ffmpeg/thumbnail.dart b/Packages/ffmpeg/other/thumbnail.dart similarity index 97% rename from Packages/ffmpeg/thumbnail.dart rename to Packages/ffmpeg/other/thumbnail.dart index 5a25460..b712b20 100644 --- a/Packages/ffmpeg/thumbnail.dart +++ b/Packages/ffmpeg/other/thumbnail.dart @@ -1,5 +1,5 @@ // YouTube channel - https://www.youtube.com/@flutterflowexpert -// video - no +// paid video - https://www.youtube.com/watch?v=LfAwHZndeWQ // Join the Klaturov army - https://www.youtube.com/@flutterflowexpert/join // Support my work - https://github.com/sponsors/bulgariamitko // Website - https://bulgariamitko.github.io/flutterflowtutorials/ diff --git a/Packages/image_gallery_saver/copy-to-gallery.dart b/Packages/image_gallery_saver/copy-to-gallery.dart index 014a882..b0e4eec 100644 --- a/Packages/image_gallery_saver/copy-to-gallery.dart +++ b/Packages/image_gallery_saver/copy-to-gallery.dart @@ -1,5 +1,5 @@ // YouTube channel - https://www.youtube.com/@flutterflowexpert -// video - no +// paid video - https://www.youtube.com/watch?v=LfAwHZndeWQ // Join the Klaturov army - https://www.youtube.com/@flutterflowexpert/join // Support my work - https://github.com/sponsors/bulgariamitko // Website - https://bulgariamitko.github.io/flutterflowtutorials/ diff --git a/Packages/path/get-full-path.dart b/Packages/path/get-full-path.dart new file mode 100644 index 0000000..253ea9a --- /dev/null +++ b/Packages/path/get-full-path.dart @@ -0,0 +1,28 @@ +// YouTube channel - https://www.youtube.com/@flutterflowexpert +// paid video - https://www.youtube.com/watch?v=LfAwHZndeWQ +// Join the Klaturov army - https://www.youtube.com/@flutterflowexpert/join +// Support my work - https://github.com/sponsors/bulgariamitko +// Website - https://bulgariamitko.github.io/flutterflowtutorials/ +// You can book me as FF mentor - https://calendly.com/bulgaria_mitko +// GitHub repo - https://github.com/bulgariamitko/flutterflowtutorials +// Discord channel - https://discord.gg/G69hSUqEeU + +import 'dart:io'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +Future getFullPath() async { + String documentsPath = ''; + if (Platform.isIOS) { + documentsPath = (await getApplicationDocumentsDirectory()).path; + } else if (Platform.isAndroid) { + documentsPath = (await getExternalStorageDirectory() ?? + await getApplicationDocumentsDirectory()) + .path; + } else { + throw UnsupportedError('Unsupported platform'); + } + + return documentsPath; + // return p.join(documentsPath, fileName); +} diff --git a/Packages/video_player/player-local-remote.dart b/Packages/video_player/player-local-remote.dart index 9a17ec7..b5e9efd 100644 --- a/Packages/video_player/player-local-remote.dart +++ b/Packages/video_player/player-local-remote.dart @@ -1,5 +1,5 @@ // YouTube channel - https://www.youtube.com/@flutterflowexpert -// video - no +// paid video - https://www.youtube.com/watch?v=LfAwHZndeWQ // Join the Klaturov army - https://www.youtube.com/@flutterflowexpert/join // Support my work - https://github.com/sponsors/bulgariamitko // Website - https://bulgariamitko.github.io/flutterflowtutorials/