Last active
February 6, 2024 16:58
-
-
Save ampersanda/e8c9efaa6670ce590d6f1cb31ce05e93 to your computer and use it in GitHub Desktop.
Native drag and drop video splitter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:io'; | |
import 'package:flutter/cupertino.dart'; | |
import 'package:flutter/scheduler.dart'; | |
import 'package:macos_ui/macos_ui.dart'; | |
import 'package:process_run/process_run.dart'; | |
import 'package:super_clipboard/src/formats_base.dart'; | |
import 'package:super_clipboard/src/reader.dart'; | |
import 'package:super_drag_and_drop/super_drag_and_drop.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
// This widget is the root of your application. | |
@override | |
Widget build(BuildContext context) { | |
return MacosApp( | |
title: 'Video Splitter', | |
theme: MacosThemeData.light(), | |
darkTheme: MacosThemeData.dark(), | |
debugShowCheckedModeBanner: false, | |
home: const MainScreen(), | |
); | |
} | |
} | |
class MainScreen extends StatefulWidget { | |
const MainScreen({super.key}); | |
@override | |
State<MainScreen> createState() => _MainScreenState(); | |
} | |
class _MainScreenState extends State<MainScreen> { | |
bool _splitting = false; | |
@override | |
void initState() { | |
super.initState(); | |
SchedulerBinding.instance.addPostFrameCallback((_) async { | |
final bool ffmpegInstalled = await _checkIsFfmpegInstalled(); | |
if (!ffmpegInstalled && mounted) { | |
showMacosAlertDialog( | |
context: context, | |
builder: (_) { | |
bool installing = false; | |
return StatefulBuilder( | |
builder: (BuildContext context, | |
void Function(void Function()) setState) { | |
return MacosAlertDialog( | |
appIcon: const MacosIcon( | |
CupertinoIcons.checkmark_circle_fill, | |
color: CupertinoColors.inactiveGray, | |
size: 56.0, | |
), | |
title: Text( | |
'ffmpeg is required!', | |
style: MacosTheme.of(context).typography.headline, | |
), | |
message: const Text( | |
'Please install ffmpeg before continue using this app', | |
textAlign: TextAlign.center, | |
), | |
primaryButton: PushButton( | |
controlSize: ControlSize.large, | |
onPressed: installing | |
? null | |
: () async { | |
setState(() { | |
installing = true; | |
}); | |
final bool installed = await _installFfmpeg(); | |
if (installed && mounted) { | |
Navigator.pop(context); | |
} | |
setState(() { | |
installing = false; | |
}); | |
}, | |
child: Text(installing | |
? 'Installing...' | |
: 'Install using Homebrew'), | |
), | |
); | |
}, | |
); | |
}, | |
); | |
} | |
}); | |
} | |
Future<bool> _installFfmpeg() async { | |
final Shell shell = Shell(); | |
try { | |
final List<ProcessResult> results = | |
await shell.run('brew install ffmpeg'); | |
return results.firstOrNull?.exitCode == 0; | |
} catch (e) { | |
return false; | |
} | |
} | |
Future<bool> _checkIsFfmpegInstalled() async { | |
final Shell shell = Shell(); | |
try { | |
final List<ProcessResult> results = await shell.run('which ffmpeg'); | |
return results.firstOrNull?.exitCode == 0; | |
} catch (e) { | |
return false; | |
} | |
} | |
Future<bool> _splitVideo(Uri uri) async { | |
final Shell shell = Shell(); | |
final File file = File.fromUri(uri); | |
final String folder = file.parent.path; | |
final String? fileName = uri.pathSegments.lastOrNull; | |
final String? extension = fileName?.split('.').lastOrNull; | |
if (fileName == null || extension == null) { | |
return false; | |
} | |
try { | |
final Directory splitFolder = | |
await Directory('$folder/$fileName.splitted').create(); | |
final List<ProcessResult> results = await shell.run( | |
'ffmpeg -i "${file.path}" -c copy -map 0 -segment_time 60 -f segment -reset_timestamps 1 "${splitFolder.path}/target_%d.$extension"'); | |
return results.firstOrNull?.exitCode == 0; | |
} catch (e) { | |
return false; | |
} finally { | |
setState(() { | |
_splitting = false; | |
}); | |
} | |
} | |
static const List<SimpleFileFormat> _acceptedFormats = <SimpleFileFormat>[ | |
Formats.mp4, | |
Formats.mov, | |
Formats.webm | |
]; | |
@override | |
Widget build(BuildContext context) { | |
return MacosScaffold( | |
children: <Widget>[ | |
ContentArea( | |
builder: (BuildContext context, ScrollController scrollController) { | |
return ColoredBox( | |
color: CupertinoColors.darkBackgroundGray, | |
child: SizedBox.expand( | |
child: DropRegion( | |
formats: _acceptedFormats, | |
hitTestBehavior: HitTestBehavior.opaque, | |
onDropOver: (DropOverEvent event) { | |
if (_splitting) { | |
return DropOperation.none; | |
} | |
// You can inspect local data here, as well as formats of each item. | |
// However on certain platforms (mobile / web) the actual data is | |
// only available when the drop is accepted (onPerformDrop). | |
final DropItem item = event.session.items.first; | |
// This drop region only supports copy operation. | |
if (event.session.allowedOperations | |
.contains(DropOperation.link)) { | |
if (_acceptedFormats.any((SimpleFileFormat format) => | |
item.canProvide(format))) { | |
return DropOperation.link; | |
} | |
} | |
return DropOperation.none; | |
}, | |
onPerformDrop: (PerformDropEvent event) async { | |
final DropItem item = event.session.items.first; | |
final DataReader reader = item.dataReader!; | |
if (_acceptedFormats.any( | |
(SimpleFileFormat format) => item.canProvide(format))) { | |
reader.getValue(Formats.fileUri, (Uri? uri) { | |
if (uri == null) { | |
return; | |
} | |
_splitVideo(uri); | |
}); | |
} | |
}, | |
child: Padding( | |
padding: const EdgeInsets.all(15.0), | |
child: Center( | |
child: Text( | |
_splitting ? 'Splitting...' : 'Drop Video Here')), | |
), | |
), | |
), | |
); | |
}, | |
) | |
], | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: videosplit | |
description: "Split video into chunks using ffmpeg" | |
publish_to: 'none' # Remove this line if you wish to publish to pub.dev | |
version: 1.0.0+1 | |
environment: | |
sdk: '>=3.2.5 <4.0.0' | |
dependencies: | |
flutter: | |
sdk: flutter # 3.16.8 | |
cupertino_icons: ^1.0.2 | |
super_drag_and_drop: ^0.8.4 | |
process_run: ^0.14.1+3 | |
local_hero: ^0.3.0 | |
macos_ui: ^2.0.5 | |
dev_dependencies: | |
flutter_test: | |
sdk: flutter | |
flutter_lints: ^2.0.0 | |
flutter: | |
uses-material-design: true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment