在Linux中用Java代码执行FFmpeg命令的问题

时间:2017-08-07 19:37:43

标签: java linux video terminal ffmpeg

我在java代码中执行此ffmpeg命令时遇到问题:

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

我使用了下面的getRuntime()方法,但这对我不起作用。即使我只删除",它仍然无法正常工作。当我只是在终端中复制粘贴等效字符串时,它就可以工作。

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);

使用此方法:

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) {
    }
}

这个也不起作用,打印退出值显示:

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)

如果我在打印退出值之前移动proc.waitFor();,则为1

有什么问题?为什么它不能在Java代码中运行?

3 个答案:

答案 0 :(得分:9)

您的代码存在一些问题,首先,使用线程流入并将内部进程的错误传递给控制台

创建一个管道流类,如:

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();  
        }
    }
}

然后将运行时部分调整为:

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());

它将显示将要运行的命令,日志以及可能的错误。

您将能够复制粘贴命令以检查终端在需要时发生的事情。

编辑:这很有趣。您的代码缺少一些转义字符并且您的代码中没有可见的字符。当我复制粘贴代码行时,我看到了它们。复制粘贴代码中的以下行,它将删除错误:

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";

答案 1 :(得分:3)

需要解决的几个问题(大多数问题都是由其他答案单独解决,有不同程度的解释,但您需要解决所有问题):

  • 你需要将参数传递给ffmpeg,就像shell一样,这意味着您需要使用单独的参数构建一个带有字符串数组的命令,或者为了使它更容易(并且与shell行为相同),引用你的参数:在big filter参数周围添加\"对。然后,您应该能够将命令传递给runCommand,就像在shell中编写命令一样。
  • 但java无法解析那些引号(*)并隔离将传递给ffmpeg的参数,但/bin/sh可以为您执行此操作:使用/bin/sh -c ...包装命令(为此我将使用下面的ProcessBuilder
  • 您需要消耗输出,否则您的过程可能会永久阻止。 ProcessBuilder救援:将stderr重定向到stdout只获取一个要使用的流,然后将stdout重定向到您想要的任何地方(下面我从父进程继承,所以它转到java进程本身的输出。
  • 你需要等待进程完成才能获得退出值(下面我使用waitFor(),只要有必要等待,但还有其他选项)
  • [在@wargre抓住后添加] 不要偷,但为了完整起见,你需要确保命令没有被不可见的字符侵扰;)例如你实际上是在传递-fi......lter_complex(十六进制的点是e2 80 8c e2 80 8b),但你的命令中有更多的分散。

因此:

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());
}

(*)在shell中,如果要打{{1​​}},则执行a b,但在java中这不起作用:

echo "a  b"

它做的是在空格周围天真分裂,它会将2个参数而不是1传递给Runtime.getRuntime().exec("echo \"a b\""); echo然后"a。不是你想要的。

替代方案:单独传递参数。

b"

答案 2 :(得分:1)

好像你错过了-filter_complex参数参数的引号。 Java将运行如下:

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

它不起作用,因为;表示bash中的命令结束。 将引号放回java代码中应该修复命令(确保正确地转义它们)。

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);