Skip to content
This repository has been archived by the owner on Oct 14, 2021. It is now read-only.

Commit

Permalink
add support for single quotes and double quotes in command strings, fix
Browse files Browse the repository at this point in the history
  • Loading branch information
tanersener committed Jul 17, 2019
1 parent bbb25b0 commit 8e8b52e
Show file tree
Hide file tree
Showing 73 changed files with 1,152 additions and 142 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.2.6
- Adds support for single quotes and double quotes in command strings

## 0.2.5
- Implements side data information parsing

Expand Down
22 changes: 5 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# flutter_ffmpeg

![GitHub release](https://img.shields.io/badge/release-v0.2.5-blue.svg)
![GitHub release](https://img.shields.io/badge/release-v0.2.6-blue.svg)
![](https://img.shields.io/pub/v/flutter_ffmpeg.svg)

FFmpeg plugin for Flutter. Supports iOS and Android.
Expand Down Expand Up @@ -70,7 +70,7 @@ FFmpeg plugin for Flutter. Supports iOS and Android.
Add `flutter_ffmpeg` as a dependency in your `pubspec.yaml file`.
```
dependencies:
flutter_ffmpeg: ^0.2.5
flutter_ffmpeg: ^0.2.6
```

#### 2.1 Packages
Expand All @@ -83,7 +83,7 @@ Installation of `FlutterFFmpeg` using `pub` enables the default package, which i
flutter_ffmpeg:
git:
url: git://github.com/tanersener/flutter-ffmpeg.git
ref: v0.2.5
ref: v0.2.6
path: packages/flutter_ffmpeg_<package_name>
```
Expand Down Expand Up @@ -307,17 +307,10 @@ In order to install the `LTS` variant, install the `flutter_ffmpeg_https_lts` pa

### 4. Tips

- You should not use double quotes (") to define your complex filters or map definitions.
```
-filter_complex [0:v]scale=1280:-1[v] -map [v]
```

- If your commands include unnecessary quotes or space characters, your command will fail with `No such filter: ' '` errors. Please check your command and remove them.

- `flutter_ffmpeg` uses file system paths, it does not know what an assets folder or project folder is. So you can't use resources on those folders directly, you need to provide full paths of those resources.
- `flutter_ffmpeg` uses file system paths, it does not know what an `assets` folder or a `project` folder is. So you can't use resources on those folders directly, you need to provide full paths of those resources.

- `flutter_ffmpeg` requires ios deployment target to be at least `9.3`.
So if you don't specify a deployment target or set a value smaller than `9.3` then your build will fail with the following error.
If you don't specify a deployment target or set a value smaller than `9.3` then your build will fail with the following error.

```
Resolving dependencies of `Podfile`
Expand Down Expand Up @@ -349,11 +342,6 @@ In order to install the `LTS` variant, install the `flutter_ffmpeg_https_lts` pa

<img src="https://github.com/tanersener/flutter-ffmpeg/raw/development/doc/assets/tip_runner_deployment_target.png" width="480">

- `execute` method is overloaded and has an optional delimiter parameter. Delimiter defines how the command string will be split into arguments.
When delimiter is not specified the space character is used as the default delimiter.
Based on this, if one or more of your command arguments include a space character, in filename path or in `-filter_complex` block, then your command string will be split into invalid arguments and execution will fail.
You can fix this error by splitting your command string into array yourself and calling `executeWithArguments` method or using a different delimiter character in your command string and specifying it in the `execute` call.

- Enabling `ProGuard` on releases older than `v0.2.4` causes linking errors. Please add the following rule inside your `proguard-rules.pro` file to preserve necessary method names and prevent linking errors.

```
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'com.arthenica.flutter.ffmpeg'
version '0.2.5'
version '0.2.6'

buildscript {
repositories {
Expand Down
153 changes: 135 additions & 18 deletions example/lib/flutter_ffmpeg_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,17 @@ class VideoUtil {
}

static String generateEncodeVideoScript(String image1Path, String image2Path, String image3Path, String videoFilePath, String videoCodec, String customOptions) {
return "-hide_banner -y -loop 1 -i " +
return "-hide_banner -y -loop 1 -i '" +
image1Path +
" " +
"-loop 1 -i " +
"' " +
"-loop 1 -i \"" +
image2Path +
" " +
"-loop 1 -i " +
"\" " +
"-loop 1 -i \"" +
image3Path +
" " +
"\" " +
"-filter_complex " +
"[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];" +
"\"[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];" +
"[1:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream2out1][stream2out2];" +
"[2:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream3out1][stream3out2];" +
"[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" +
Expand All @@ -88,7 +88,7 @@ class VideoUtil {
"[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" +
"[stream2starting][stream1ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream2blended];" +
"[stream3starting][stream2ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream3blended];" +
"[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]" +
"[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]\"" +
" -map [video] -vsync 2 -async 1 " +
customOptions +
"-c:v " +
Expand Down Expand Up @@ -147,10 +147,21 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
VideoUtil.copyFileAssets('assets/tajmahal.jpg', ASSET_3).then((path) => print('Loaded asset $path.'));
}

void testParseArguments() {
testParseSimpleCommand();
testParseSingleQuotesInCommand();
testParseDoubleQuotesInCommand();
testParseDoubleQuotesAndEscapesInCommand();
}

void testRunCommand() {
getLastReturnCode().then((rc) => print("Last rc: $rc"));
getLastCommandOutput().then((output) => print("Last command output: $output"));

print("Testing ParseArguments.");

testParseArguments();

print("Testing COMMAND.");

// ENABLE LOG CALLBACK ON EACH CALL
Expand All @@ -176,9 +187,8 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
// disableLogs();
// enableLogs();

// execute(_commandController.text).then((rc) => print("FFmpeg process exited with rc $rc"));
// executeWithDelimiter(_commandController.text, "_").then((rc) => print("FFmpeg process exited with rc $rc") );
executeWithArguments(_commandController.text.split(" ")).then((rc) => print("FFmpeg process exited with rc $rc"));
execute(_commandController.text).then((rc) => print("FFmpeg process exited with rc $rc"));
// executeWithArguments(_commandController.text.split(" ")).then((rc) => print("FFmpeg process exited with rc $rc"));

setState(() {});
}
Expand Down Expand Up @@ -275,9 +285,6 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
}
});

// COMMENT OPTIONAL TESTS
// execute(VideoUtil.generateEncodeVideoScript(image1Path, image2Path, image3Path, videoPath, _currentCodec, "")).timeout(Duration(milliseconds: 1300), onTimeout: () { cancel(); });

// resetStatistics();

getLastReceivedStatistics().then((lastStatistics) {
Expand Down Expand Up @@ -336,10 +343,6 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
return await _flutterFFmpeg.execute(command);
}

Future<int> executeWithDelimiter(String command, String delimiter) async {
return await _flutterFFmpeg.execute(command, delimiter);
}

Future<void> cancel() async {
return await _flutterFFmpeg.cancel();
}
Expand Down Expand Up @@ -641,4 +644,118 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
super.dispose();
_commandController.dispose();
}

void testParseSimpleCommand() {
var argumentArray = _flutterFFmpeg.parseArguments("-hide_banner -loop 1 -i file.jpg -filter_complex [0:v]setpts=PTS-STARTPTS[video] -map [video] -vsync 2 -async 1 video.mp4");

assert(argumentArray != null);
assert(argumentArray.length == 14);

assert("-hide_banner" == argumentArray[0]);
assert("-loop" == argumentArray[1]);
assert("1" == argumentArray[2]);
assert("-i" == argumentArray[3]);
assert("file.jpg" == argumentArray[4]);
assert("-filter_complex" == argumentArray[5]);
assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray[6]);
assert("-map" == argumentArray[7]);
assert("[video]" == argumentArray[8]);
assert("-vsync" == argumentArray[9]);
assert("2" == argumentArray[10]);
assert("-async" == argumentArray[11]);
assert("1" == argumentArray[12]);
assert("video.mp4" == argumentArray[13]);
}

void testParseSingleQuotesInCommand() {
var argumentArray = _flutterFFmpeg.parseArguments("-loop 1 'file one.jpg' -filter_complex '[0:v]setpts=PTS-STARTPTS[video]' -map [video] video.mp4 ");

assert(argumentArray != null);
assert(argumentArray.length == 8);

assert("-loop" == argumentArray[0]);
assert("1" == argumentArray[1]);
assert("file one.jpg" == argumentArray[2]);
assert("-filter_complex" == argumentArray[3]);
assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray[4]);
assert("-map" == argumentArray[5]);
assert("[video]" == argumentArray[6]);
assert("video.mp4" == argumentArray[7]);
}

void testParseDoubleQuotesInCommand() {
var argumentArray = _flutterFFmpeg.parseArguments("-loop 1 \"file one.jpg\" -filter_complex \"[0:v]setpts=PTS-STARTPTS[video]\" -map [video] video.mp4 ");

assert(argumentArray != null);
assert(argumentArray.length == 8);

assert("-loop" == argumentArray[0]);
assert("1" == argumentArray[1]);
assert("file one.jpg" == argumentArray[2]);
assert("-filter_complex" == argumentArray[3]);
assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray[4]);
assert("-map" == argumentArray[5]);
assert("[video]" == argumentArray[6]);
assert("video.mp4" == argumentArray[7]);

argumentArray = _flutterFFmpeg.parseArguments(" -i file:///tmp/input.mp4 -vcodec libx264 -vf \"scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black\" -acodec copy -q:v 0 -q:a 0 video.mp4");

assert(argumentArray != null);
assert(argumentArray.length == 13);

assert("-i" == argumentArray[0]);
assert("file:///tmp/input.mp4" == argumentArray[1]);
assert("-vcodec" == argumentArray[2]);
assert("libx264" == argumentArray[3]);
assert("-vf" == argumentArray[4]);
assert("scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black" == argumentArray[5]);
assert("-acodec" == argumentArray[6]);
assert("copy" == argumentArray[7]);
assert("-q:v" == argumentArray[8]);
assert("0" == argumentArray[9]);
assert("-q:a" == argumentArray[10]);
assert("0" == argumentArray[11]);
assert("video.mp4" == argumentArray[12]);
}

void testParseDoubleQuotesAndEscapesInCommand() {
var argumentArray = _flutterFFmpeg.parseArguments(" -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\'FontSize=16,PrimaryColour=&HFFFFFF&\'\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");

assert(argumentArray != null);
assert(argumentArray.length == 13);

assert("-i" == argumentArray[0]);
assert("file:///tmp/input.mp4" == argumentArray[1]);
assert("-vf" == argumentArray[2]);
assert("subtitles=file:///tmp/subtitles.srt:force_style='FontSize=16,PrimaryColour=&HFFFFFF&'" == argumentArray[3]);
assert("-vcodec" == argumentArray[4]);
assert("libx264" == argumentArray[5]);
assert("-acodec" == argumentArray[6]);
assert("copy" == argumentArray[7]);
assert("-q:v" == argumentArray[8]);
assert("0" == argumentArray[9]);
assert("-q:a" == argumentArray[10]);
assert("0" == argumentArray[11]);
assert("video.mp4" == argumentArray[12]);

argumentArray = _flutterFFmpeg.parseArguments(" -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");

assert(argumentArray != null);
assert(argumentArray.length == 13);

assert("-i" == argumentArray[0]);
assert("file:///tmp/input.mp4" == argumentArray[1]);
assert("-vf" == argumentArray[2]);
assert("subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"" == argumentArray[3]);
assert("-vcodec" == argumentArray[4]);
assert("libx264" == argumentArray[5]);
assert("-acodec" == argumentArray[6]);
assert("copy" == argumentArray[7]);
assert("-q:v" == argumentArray[8]);
assert("0" == argumentArray[9]);
assert("-q:a" == argumentArray[10]);
assert("0" == argumentArray[11]);
assert("video.mp4" == argumentArray[12]);
}

}
8 changes: 4 additions & 4 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ packages:
flutter_ffmpeg:
dependency: "direct main"
description:
name: flutter_ffmpeg
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.5"
path: ".."
relative: true
source: path
version: "0.2.6"
flutter_test:
dependency: "direct dev"
description: flutter
Expand Down
3 changes: 2 additions & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ dependencies:
cupertino_icons: ^0.1.2
path: ^1.6.2
path_provider: ^0.5.0+1
flutter_ffmpeg: ^0.2.5
flutter_ffmpeg:
path: ../

dev_dependencies:
flutter_test:
Expand Down
2 changes: 1 addition & 1 deletion ios/flutter_ffmpeg.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'flutter_ffmpeg'
s.version = '0.2.5'
s.version = '0.2.6'
s.summary = 'FFmpeg plugin for Flutter.'
s.description = 'FFmpeg plugin based on mobile-ffmpeg for Flutter.'
s.homepage = 'https://github.com/tanersener/flutter-ffmpeg'
Expand Down
59 changes: 56 additions & 3 deletions lib/flutter_ffmpeg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ class FlutterFFmpeg {
}
}

/// Executes FFmpeg [command] provided. Command is split into arguments using provided [delimiter].
Future<int> execute(String command, [String delimiter = ' ']) async {
/// Executes FFmpeg [command] provided.
Future<int> execute(String command) async {
try {
final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod(
'execute', {'command': command, 'delimiter': delimiter});
'executeWithArguments', {'arguments': parseArguments(command)});
return result['rc'];
} on PlatformException catch (e) {
print("Plugin error: ${e.message}");
Expand Down Expand Up @@ -379,4 +379,57 @@ class FlutterFFmpeg {
return null;
}
}

/// Parses the given [command] into arguments.
List<String> parseArguments(String command) {
List<String> argumentList = new List();
StringBuffer currentArgument = new StringBuffer();

bool singleQuoteStarted = false;
bool doubleQuoteStarted = false;

for (int i = 0; i < command.length; i++) {
var previousChar;
if (i > 0) {
previousChar = command.codeUnitAt(i - 1);
} else {
previousChar = null;
}
var currentChar = command.codeUnitAt(i);

if (currentChar == ' '.codeUnitAt(0)) {
if (singleQuoteStarted || doubleQuoteStarted) {
currentArgument.write(String.fromCharCode(currentChar));
} else if (currentArgument.length > 0) {
argumentList.add(currentArgument.toString());
currentArgument = new StringBuffer();
}
} else if (currentChar == '\''.codeUnitAt(0) && (previousChar == null || previousChar != '\\'.codeUnitAt(0))) {
if (singleQuoteStarted) {
singleQuoteStarted = false;
} else if (doubleQuoteStarted) {
currentArgument.write(String.fromCharCode(currentChar));
} else {
singleQuoteStarted = true;
}
} else if (currentChar == '\"'.codeUnitAt(0) && (previousChar == null || previousChar != '\\'.codeUnitAt(0))) {
if (doubleQuoteStarted) {
doubleQuoteStarted = false;
} else if (singleQuoteStarted) {
currentArgument.write(String.fromCharCode(currentChar));
} else {
doubleQuoteStarted = true;
}
} else {
currentArgument.write(String.fromCharCode(currentChar));
}
}

if (currentArgument.length > 0) {
argumentList.add(currentArgument.toString());
}

return argumentList;
}

}
2 changes: 1 addition & 1 deletion packages/flutter_ffmpeg_audio/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'com.arthenica.flutter.ffmpeg'
version '0.2.5'
version '0.2.6'

buildscript {
repositories {
Expand Down
Loading

0 comments on commit 8e8b52e

Please sign in to comment.