https://github.com/brutalcoding/video_trimmer
https://github.com/brutalcoding/video_trimmer
Last synced: 12 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/brutalcoding/video_trimmer
- Owner: BrutalCoding
- License: mit
- Created: 2020-09-27T10:50:02.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2020-09-27T23:12:40.000Z (over 5 years ago)
- Last Synced: 2025-03-28T16:04:47.546Z (about 1 year ago)
- Language: Dart
- Size: 1.95 MB
- Stars: 0
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
A Flutter package for trimming videos
### Features
* Customizable video trimmer
* Video playback control
* Retrieving and storing video file
Also, supports conversion to **GIF**.
TRIM EDITOR
EXAMPLE APP
CUSTOMIZABLE VIDEO EDITOR
## Usage
* Add the dependency `video_trimmer` to your **pubspec.yaml** file.
### Android
* Go to `/android/app/build.gradle` and set the proper `minSdkVersion`, **24** for **Main Release** or **16** for **LTS Release**.
> Refer to the [FFmpeg Release](#ffmpeg-release) section.
```gradle
minSdkVersion
```
* Go to `/android/build.gradle` and add the following line:
```gradle
ext.flutterFFmpegPackage = ''
```
> Replace the `` with a proper package name from the [Packages List](#packages-list) section.
### iOS
* Add the following keys to your **Info.plist** file, located in `/ios/Runner/Info.plist`:
```
NSCameraUsageDescription
Used to demonstrate image picker plugin
NSMicrophoneUsageDescription
Used to capture audio for image picker plugin
NSPhotoLibraryUsageDescription
Used to demonstrate image picker plugin
```
* Set the platform version in `ios/Podfile`, **12.1** for **Main Release** or **9.3** for **LTS Release**.
> Refer to the [FFmpeg Release](#ffmpeg-release) section.
```
platform :ios, ''
```
* Replace with the following in the `# Plugin Pods` section of the `ios/Podfile`:
```
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
if name == 'flutter_ffmpeg'
pod name+'/', :path => File.join(symlink, 'ios')
else
pod name, :path => File.join(symlink, 'ios')
end
end
```
> Replace the `` with a proper package name from the [Packages List](#packages-list) section.
### FFmpeg Release
In reference to the releases specified in the [flutter_ffmpeg](https://pub.dev/packages/flutter_ffmpeg) package.
Main Release
LTS Release
Android API Level
24
16
Android Camera Access
Yes
-
Android Architectures
arm-v7a-neon
arm64-v8a
x86
x86-64
arm-v7a
arm-v7a-neon
arm64-v8a
x86
x86-64
Xcode Support
10.1
7.3.1
iOS SDK
12.1
9.3
iOS Architectures
arm64
arm64e
x86-64
armv7
arm64
i386
x86-64
### Packages List
The following **FFmpeg Packages** List is in reference to the [flutter_ffmpeg](https://pub.dev/packages/flutter_ffmpeg) package.
| Package | Main Release | LTS Release |
| :----: | :----: | :----: |
| min | min | min-lts |
| min-gpl | min-gpl | min-gpl-lts |
| https | https | https-lts |
| https-gpl | https-gpl | https-gpl-lts |
| audio | audio | audio-lts |
| video | video | video-lts |
| full | full | full-lts |
| full-gpl | full-gpl | full-gpl-lts |
## Functionalities
### Loading input video file
```dart
final Trimmer _trimmer = Trimmer();
await _trimmer.loadVideo(videoFile: file);
```
### Saving trimmed video
Returns a string to indicate whether the saving operation was successful.
```dart
await _trimmer
.saveTrimmedVideo(startValue: _startValue, endValue: _endValue)
.then((value) {
setState(() {
_value = value;
});
});
```
### Video playback state
Returns the video playback state. If **true** then the video is playing, otherwise it is paused.
```dart
await _trimmer.videPlaybackControl(
startValue: _startValue,
endValue: _endValue,
);
```
### Advanced Command
You can use an advanced **FFmpeg** command if you require more customization. Just define your FFmpeg command using the `ffmpegCommand` property and set an output video format using `customVideoFormat`.
Refer to the [Official FFmpeg Documentation](https://ffmpeg.org/documentation.html) for more information.
> **NOTE:** Passing a wrong video format to the `customVideoFormat` property may result in a crash.
```dart
// Example of defining a custom command
// This is already used for creating GIF by
// default, so you do not need to use this.
await _trimmer
.saveTrimmedVideo(
startValue: _startValue,
endValue: _endValue,
ffmpegCommand:
'-vf "fps=10,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0',
customVideoFormat: '.gif')
.then((value) {
setState(() {
_value = value;
});
});
```
## Widgets
### Display a video playback area
```dart
VideoViewer()
```
### Display the video trimmer area
```dart
TrimEditor(
viewerHeight: 50.0,
viewerWidth: MediaQuery.of(context).size.width,
onChangeStart: (value) {
_startValue = value;
},
onChangeEnd: (value) {
_endValue = value;
},
onChangePlaybackState: (value) {
setState(() {
_isPlaying = value;
});
},
)
```
## Example
```dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:video_trimmer/trim_editor.dart';
import 'package:video_trimmer/video_trimmer.dart';
import 'package:video_trimmer/video_viewer.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Trimmer',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
final Trimmer _trimmer = Trimmer();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Video Trimmer"),
),
body: Center(
child: Container(
child: RaisedButton(
child: Text("LOAD VIDEO"),
onPressed: () async {
File file = await ImagePicker.pickVideo(
source: ImageSource.gallery,
);
if (file != null) {
await _trimmer.loadVideo(videoFile: file);
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return TrimmerView(_trimmer);
}));
}
},
),
),
),
);
}
}
class TrimmerView extends StatefulWidget {
final Trimmer _trimmer;
TrimmerView(this._trimmer);
@override
_TrimmerViewState createState() => _TrimmerViewState();
}
class _TrimmerViewState extends State {
double _startValue = 0.0;
double _endValue = 0.0;
bool _isPlaying = false;
bool _progressVisibility = false;
Future _saveVideo() async {
setState(() {
_progressVisibility = true;
});
String _value;
await widget._trimmer
.saveTrimmedVideo(startValue: _startValue, endValue: _endValue)
.then((value) {
setState(() {
_progressVisibility = false;
_value = value;
});
});
return _value;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Video Trimmer"),
),
body: Builder(
builder: (context) => Center(
child: Container(
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.black,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Visibility(
visible: _progressVisibility,
child: LinearProgressIndicator(
backgroundColor: Colors.red,
),
),
RaisedButton(
onPressed: _progressVisibility
? null
: () async {
_saveVideo().then((outputPath) {
print('OUTPUT PATH: $outputPath');
final snackBar = SnackBar(content: Text('Video Saved successfully'));
Scaffold.of(context).showSnackBar(snackBar);
});
},
child: Text("SAVE"),
),
Expanded(
child: VideoViewer(),
),
Center(
child: TrimEditor(
viewerHeight: 50.0,
viewerWidth: MediaQuery.of(context).size.width,
onChangeStart: (value) {
_startValue = value;
},
onChangeEnd: (value) {
_endValue = value;
},
onChangePlaybackState: (value) {
setState(() {
_isPlaying = value;
});
},
),
),
FlatButton(
child: _isPlaying
? Icon(
Icons.pause,
size: 80.0,
color: Colors.white,
)
: Icon(
Icons.play_arrow,
size: 80.0,
color: Colors.white,
),
onPressed: () async {
bool playbackState =
await widget._trimmer.videPlaybackControl(
startValue: _startValue,
endValue: _endValue,
);
setState(() {
_isPlaying = playbackState;
});
},
)
],
),
),
),
),
);
}
}
```
## License
Copyright (c) 2020 Souvik Biswas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.