**Major Features Added:** - **Inline Reply System**: Replace compose screen with inline reply boxes - **Thread Navigation**: Parent/child navigation with jump functionality - **Chain Flow UI**: Reply counts, expand/collapse animations, visual hierarchy - **Enhanced Animations**: Smooth transitions, hover effects, micro-interactions **Frontend Changes:** - **ThreadedCommentWidget**: Complete rewrite with animations and navigation - **ThreadNode Model**: Added parent references and descendant counting - **ThreadedConversationScreen**: Integrated navigation handlers - **PostDetailScreen**: Replaced with threaded conversation view - **ComposeScreen**: Added reply indicators and context - **PostActions**: Fixed visibility checks for chain buttons **Backend Changes:** - **API Route**: Added /posts/:id/thread endpoint - **Post Repository**: Include allow_chain and visibility fields in feed - **Thread Handler**: Support for fetching post chains **UI/UX Improvements:** - **Reply Context**: Clear indication when replying to specific posts - **Character Counting**: 500 character limit with live counter - **Visual Hierarchy**: Depth-based indentation and styling - **Smooth Animations**: SizeTransition, FadeTransition, hover states - **Chain Navigation**: Parent/child buttons with visual feedback **Technical Enhancements:** - **Animation Controllers**: Proper lifecycle management - **State Management**: Clean separation of concerns - **Navigation Callbacks**: Reusable navigation system - **Error Handling**: Graceful fallbacks and user feedback This creates a Reddit-style threaded conversation experience with smooth animations, inline replies, and intuitive navigation between posts in a chain.
58 lines
2.2 KiB
Dart
58 lines
2.2 KiB
Dart
import 'dart:io';
|
|
import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
|
|
import 'package:ffmpeg_kit_flutter_new/ffmpeg_session.dart';
|
|
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
class VideoStitchingService {
|
|
/// Stitches multiple video files into a single video file using FFmpeg.
|
|
///
|
|
/// Returns the stitched file, or null if stitching failed or input is empty.
|
|
static Future<File?> stitchVideos(List<File> segments) async {
|
|
if (segments.isEmpty) return null;
|
|
if (segments.length == 1) return segments.first;
|
|
|
|
try {
|
|
// 1. Create a temporary file listing all segments for FFmpeg concat demuxer
|
|
final tempDir = await getTemporaryDirectory();
|
|
final listFile = File('${tempDir.path}/segments_list.txt');
|
|
|
|
final buffer = StringBuffer();
|
|
for (final segment in segments) {
|
|
// FFmpeg requires safe paths (escaping special chars might be needed, but usually basic paths are fine)
|
|
// IMPORTANT: pathways in list file for concat demuxer must be absolute.
|
|
buffer.writeln("file '${segment.path}'");
|
|
}
|
|
await listFile.writeAsString(buffer.toString());
|
|
|
|
// 2. Define output path
|
|
final outputFile = File('${tempDir.path}/stitched_${DateTime.now().millisecondsSinceEpoch}.mp4');
|
|
|
|
// 3. Execute FFmpeg command
|
|
// -f concat: format
|
|
// -safe 0: allow unsafe paths (required for absolute paths)
|
|
// -i listFile: input list
|
|
// -c copy: stream copy (fast, no re-encoding)
|
|
final command = "-f concat -safe 0 -i '${listFile.path}' -c copy '${outputFile.path}'";
|
|
|
|
final session = await FFmpegKit.execute(command);
|
|
final returnCode = await session.getReturnCode();
|
|
|
|
if (ReturnCode.isSuccess(returnCode)) {
|
|
return outputFile;
|
|
} else {
|
|
print("Stitching failed with return code: $returnCode");
|
|
// Fallback: return the last segment or first one to at least save something?
|
|
// For strict correctness, return null or throw.
|
|
// Let's print logs.
|
|
final logs = await session.getOutput();
|
|
print("FFmpeg Logs: $logs");
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
print("Stitching Error: $e");
|
|
return null;
|
|
}
|
|
}
|
|
}
|