Issues in executing FFmpeg command in Java code in

2020-08-11 10:08发布

问题:

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 -fi‌​lter_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 -fi‌​lter_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);