可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have problems executing this ffmpeg
command in my java code:
ffmpeg -i sample.mp4 -i ad.mp4 -filter_complex "[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]" -map "[out]" output.mp4
I used the getRuntime()
method below, but that doesn't work for me. Even if I simply remove the "
, still it doesn't work. When I simply copy-paste the equivalent string in terminal, it works.
String c1=" -i "+dir+"sample.mp4 "+"-i "+dir+"ad.mp4 -filter_complex [0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out] -map [out] "+dir+"output.mp4";
RunCommand("ffmpeg"+c1);
Using this method:
private static void RunCommand(String command) throws InterruptedException {
try {
// Execute command
Process proc = Runtime.getRuntime().exec(command);
System.out.println(proc.exitValue());
// Get output stream to write from it
// Read the output
BufferedReader reader =
new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line = "";
while((line = reader.readLine()) != null) {
System.out.print(line + "\n");
// System.out.println(ads.get(0));
}
proc.waitFor();
} catch (IOException e) {
}
}
This one doesn't work also, and printing the exit value shows this:
Exception in thread "main" java.lang.IllegalThreadStateException: process hasn't exited
at java.lang.UNIXProcess.exitValue(UNIXProcess.java:423)
at parser.Parser.RunCommand(Parser.java:106)
at parser.Parser.commandGenerator2(Parser.java:79)
at parser.Parser.main(Parser.java:44)
If I move the proc.waitFor();
before printing the exit value, it is 1
.
What is the problem? Why it doesn't run in Java code?
回答1:
There is some issue on your code, First, use thread to stream in and err of inner process to the console
Create a pipe stream class like :
class PipeStream extends Thread {
InputStream is;
OutputStream os;
public PipeStream(InputStream is, OutputStream os) {
this.is = is;
this.os = os;
}
public void run() {
byte[] buffer=new byte[1024];
int len;
try {
while ((len=is.read(buffer))>=0){
os.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Then adapt the runtime part to:
System.out.println("Launching command: "+command);
ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", command);
Process proc=pb.start();
PipeStream out=new PipeStream(proc.getInputStream(), System.out);
PipeStream err=new PipeStream(proc.getErrorStream(), System.err);
out.start();
err.start();
proc.waitFor();
System.out.println("Exit value is: "+proc.exitValue());
It will show the command that will be run, the logs and potentially the error.
You will be able to copy paste the command to check on a terminal what is going on if needed.
EDIT: This is very funny. You code was missing some escape char AND there is not visible char in your code. I saw them when I copy paste the line of code. Copy paste the following line in your code, it will remove the error :
String command="ffmpeg -i "+dir+"sample.mp4 -i "+dir+"ad.mp4 -filter_complex '[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]' -map '[out]' "+dir+"output.mp4";
回答2:
Several issues to fix (most are addressed by other answers individually, with various degrees of explanation, but you need to fix all):
- you need to pass arguments to
ffmpeg
exactly as the shell would, that means you either need to build a command as string array with individual arguments, or, to make it easier (and identical to shell behavior), quote your arguments: add pair of \"
around the big filter argument. Then you should be able to pass command to runCommand
exactly as you write it in a shell.
- but java cannot parse those quotes(*) and isolate arguments that will be passed to
ffmpeg
, but /bin/sh
can do that for you: wrap your command with /bin/sh -c ...
(for that I will use ProcessBuilder
below)
- you need to consume output, or your process might block eternally.
ProcessBuilder
to the rescue: redirect stderr
to stdout
to only get a single stream to consume, then redirect stdout
wherever you want (below I inherit from parent process, so it goes to the output of your java
process itself.
- you need to wait for process to complete before getting its exit value (below I use
waitFor()
, which waits as long as necessary, but there are other options)
- [Added after @wargre caught that] not to steal, but for completeness sake you need to make sure the command is not infested with invisible characters ;) For example you were actually passing
-fi......lter_complex
(dots in hex are e2 80 8c e2 80 8b
), but there are more scattered in your command.
Thus:
String c1 = " -i " + dir + "sample.mp4 -i " + dir
+ "ad.mp4 -filter_complex \"[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]\" -map \"[out]\" "
+ dir + "output.mp4";
// Notice additional quotes around filter & map above
runCommand("ffmpeg" + c1);
// ...
static void runCommand(String command) throws IOException, InterruptedException {
Process p = new ProcessBuilder("/bin/sh", "-c", command)
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.start();
p.waitFor();
System.out.println("Exit value: " + p.exitValue());
}
(*) In a shell, if you want to print a b
you do echo "a b"
, but in java this does not work:
Runtime.getRuntime().exec("echo \"a b\"");
What it does is naively split around the spaces, and it will pass 2 arguments instead of 1 to echo
: "a
then b"
. Not what you want.
Alternative: pass arguments individually.
runCommandAsVarargs(
"ffmpeg",
"-i",
dir + "sample.mp4",
"-i",
dir + "ad.mp4",
"-filter_complex",
"[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]",
"-map",
"[out]",
dir + "output.mp4"
);
// ...
static void runCommandAsVarargs(String... command) throws IOException, InterruptedException {
Process p = new ProcessBuilder(command)
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.start();
p.waitFor();
System.out.println("Exit value: " + p.exitValue());
}
回答3:
It seems like you missed quotes around argument for -filter_complex
parameter. Java will run something like this:
ffmpeg -i ./sample.mp4 -i ./ad.mp4 -filter_complex [0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out] -map [out] output.mp4
It doesn't work since ;
means end of command in bash.
Putting quotes back in java code should fix command (make sure to escape them properly).
String c1=" -i "+dir+"sample.mp4 "+"-i "+dir+"ad.mp4 -filter_complex \"[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]\" -map [out] "+dir+"output.mp4";
RunCommand("ffmpeg"+c1);