video_editor
video_editor copied to clipboard
CropGridViewer.preview cutting video frames from edges vertically
CropGridViewer.preview cutting video frames from edges vertically but if i use CropGridViewer.edit it working fine please.
here is my code
import 'dart:io';
import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart'; import 'package:ffmpeg_kit_flutter_full_gpl/log.dart'; import 'package:ffmpeg_kit_flutter_full_gpl/return_code.dart'; import 'package:ffmpeg_kit_flutter_full_gpl/statistics.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:intl/intl.dart'; import 'package:media_scanner/media_scanner.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_editor/video_editor.dart'; import 'package:video_player/video_player.dart'; import 'package:videocutter/Tab/TabBarViewWidget.dart'; import 'package:videocutter/Trim/video_trim.dart';
class Trim extends StatefulWidget { const Trim({Key? key, required this.file}) : super(key: key);
final File file;
@override State<Trim> createState() => _TrimState(); }
class _TrimState extends State<Trim> { late final VideoEditorController _controller; late VideoPlayerController _videoPlayerController;
int duration = 0;
String formatter(Duration duration) => [ duration.inMinutes.remainder(60).toString().padLeft(2, '0'), duration.inSeconds.remainder(60).toString().padLeft(2, '0') ].join(":");
final double height = 60;
ValueNotifier
@override void initState() { super.initState(); videoPlayerController = VideoPlayerController.file(widget.file) ..initialize().then(() { setState(() { duration = _videoPlayerController.value.duration.inSeconds; }); });
_controller = VideoEditorController.file(
File(widget.file.path),
minDuration: const Duration(seconds: 10),
maxDuration: Duration(
seconds: 60), // Default value, can be updated later
);
_controller.initialize(aspectRatio: 16 / 9).then((_) {
setState(() {
// Update maxDuration after _controller has been initialized
_controller.maxDuration =
Duration(seconds: duration > 0 ? duration : 60);
});
}).catchError((error) {
// handle minimum duration bigger than video duration error
Navigator.pop(context);
});
}
@override void dispose() { _controller.dispose(); _videoPlayerController.dispose(); super.dispose(); }
/// Basic export video function /// Basic export video function
bool istrim = true; int progress = 0; String? fileName;
Future
try {
Directory mainFolder = Directory('${(await getApplicationDocumentsDirectory()).path}/trim/cut/');
final String outputPath= '${mainFolder.path}/$fileName.mp4';
//final String outputPath = '/storage/emulated/0/Download/trim/cut/$fileName.mp4';
final String inputPath = widget.file.path;
final Duration start = _controller.startTrim;
final Duration end = _controller.endTrim;
final command =
'-ss ${formatter(start)} -t ${formatter(end - start)} -noaccurate_seek -i "$inputPath" -codec copy -avoid_negative_ts 1 "$outputPath"';
int totalVideoDuration = (end - start).inMilliseconds;
int totalProgress = 0;
// Execute FFmpeg command
await FFmpegKit.executeAsync(command, (session) async {
final returnCode = await session.getReturnCode();
print('The command is executed: $command');
if (ReturnCode.isSuccess(returnCode)) {
print('working fine $returnCode');
progressNotifier.value = 100;
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => TabBarViewWidget(initialIndex: 0, showAppBar: true)),
);
}
}, (Log log) {
// Log callback
}, (Statistics statistics) {
if (statistics == null) {
return;
}
if (statistics.getTime() > 0) {
// Calculate the percentage of completion
totalProgress = (statistics.getTime() * 100) ~/ totalVideoDuration;
progress = totalProgress;
print('Progress: $progress%');
progressNotifier.value=progress;
}
});
// MediaScanner.loadMedia(path: outputPath);
print('Execution completed successfully');
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => TabBarViewWidget(initialIndex: 1),
// ),
// );
} catch (e) {
print('Error trimming video: $e');
}
}
Future _showFileNameDialog(BuildContext context) async { TextEditingController fileNameController = TextEditingController();
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Enter Name'),
content: TextField(
controller: fileNameController,
decoration: InputDecoration(hintText: '${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}'),
),
actions: <Widget>[
TextButton(
onPressed: () async {
Navigator.pop(context, fileNameController.text.trim());
if(fileNameController.text==null||fileNameController.text.isEmpty){
fileName= DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
print('The file name is $fileName');
}else{
fileName=fileNameController.text.trim();
print('The file name is $fileName');
}
trimVideos();
},
child: Text('OK'),
),
TextButton(
onPressed: () {
Navigator.pop(context, null); // Cancel
},
child: Text('Cancel'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return Scaffold(
appBar: AppBar(
actions: [
Padding(
padding: EdgeInsets.only(right: 30),
)
],
centerTitle: true,
title: Text('Trim video'),
),
body: FutureBuilder(
future: _controller.initialize(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return _buildTrimWidget();
} else {
return Center(child:
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircularProgressIndicator(),
],
));
}
},
),
);
}
Widget _buildTrimWidget() {
if (_controller.initialized) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Stack(
alignment: Alignment.center,
children: [
Container(
color: Colors.black,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height, // Adjust height as desired
child: CropGridViewer.edit(
controller: _controller,
),
),
AnimatedBuilder(
animation: _controller.video,
builder: (_, __) =>
AnimatedOpacity(
opacity: _controller.isPlaying ? 0 : 1,
duration: kThemeAnimationDuration,
child: GestureDetector(
onTap: _controller.video.play,
child: Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: const Icon(
Icons.play_arrow,
color: Colors.black,
),
),
),
),
),
],
),
),
ElevatedButton(
onPressed: (){
_showFileNameDialog(context);
},
child: Text('Trim Now')
),
ValueListenableBuilder<int>(
valueListenable: progressNotifier,
builder: (context, value, child) {
return value < 100
? Column(
children: [
Text('Progress: $value%'),
LinearProgressIndicator(
value: value / 100,
minHeight: 10,
backgroundColor: Colors.grey,
valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
),
],
)
: Container(); // Empty container when progress is finished
}),
..._trimSlider(),
],
);
} else {
return const Center(child: CircularProgressIndicator());
}
}
List<Widget> _trimSlider() {
return [
AnimatedBuilder(
animation: Listenable.merge([
_controller,
_controller.video,
]),
builder: (_, __) {
final int duration = _controller.videoDuration.inSeconds;
final double pos = _controller.trimPosition * duration;
return Padding(
padding: EdgeInsets.symmetric(horizontal: height / 4),
child: Row(
children: [
Text('${formatter(Duration(seconds: pos.toInt()))}'),
const Expanded(child: SizedBox()),
AnimatedOpacity(
opacity: _controller.isTrimming ? 1 : 0,
duration: kThemeAnimationDuration,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(formatter(_controller.startTrim)),
const SizedBox(width: 10),
Text(formatter(_controller.endTrim)),
],
),
),
],
),
);
},
),
Container(
width: MediaQuery
.of(context)
.size
.width,
margin: EdgeInsets.symmetric(vertical: height / 4),
child: TrimSlider(
controller: _controller,
height: height,
horizontalMargin: height / 4,
child: TrimTimeline(
controller: _controller,
padding: const EdgeInsets.only(top: 10),
),
),
),
];
}
}