在 Linux 中用 Java 代码执行 FFmpeg 命令的问题
Posted
技术标签:
【中文标题】在 Linux 中用 Java 代码执行 FFmpeg 命令的问题【英文标题】:Issues in executing FFmpeg command in Java code in Linux 【发布时间】:2018-01-15 04:59:23 【问题描述】:我在我的 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 -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);
使用这种方法:
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 代码中运行?
【问题讨论】:
【参考方案1】:需要解决的几个问题(大多数问题都由其他答案单独解决,有不同程度的解释,但您需要解决所有问题):
您需要将参数传递给ffmpeg
,就像 shell 一样,这意味着您需要将命令构建为带有单个参数的字符串数组,或者,为了更容易(并且与 shell 行为相同),引用您的参数:在大过滤器参数周围添加一对\"
。然后你应该能够将命令传递给runCommand
,就像你在 shell 中写的一样。
但 java 无法解析那些引号 (*) 并隔离将传递给 ffmpeg
的参数,但 /bin/sh
可以为你做到这一点:用 /bin/sh -c ...
包装你的命令(为此我将使用 ProcessBuilder
下面)
您需要消耗输出,否则您的进程可能会永远阻塞。 ProcessBuilder
进行救援:将stderr
重定向到stdout
以仅获取单个流来使用,然后将stdout
重定向到任何你想要的地方(下面我从父进程继承,所以它转到你的@987654332 的输出@ 进程本身。
您需要等待进程完成才能获取其退出值(下面我使用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 中,如果你想打印 a b
,你可以使用 echo "a b"
,但在 java 中这不起作用:
Runtime.getRuntime().exec("echo \"a b\"");
它所做的只是天真地分割空格,它会将 2 个参数而不是 1 传递给 echo
:"a
然后 b"
。不是你想要的。
替代方案:单独传递参数。
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());
【讨论】:
它不起作用。但是,至少我们看到了输出错误:Unrecognized option 'filter_complex'. Error splitting the argument list: Option not found Exit value: 1
Unrecognized option 'filter_complex'
真的出乎意料...您确定您在与您在 shell 中尝试过的系统相同的系统上运行它吗?使用相同的ffmpeg版本? -- 无论如何,我一开始就修复了我的命令中的一个错误,您还需要在"[out]"
周围加上引号...请参阅编辑。我还提供了一种替代方法,您可以逐个显式传递参数以避免歧义和引用问题。
啊啊,那个无法识别的选项是因为@wargre 发现的不可见字符...您实际上传递了-fi......lter_complex
(十六进制中的点是e2 80 8c e2 80 8b
),但是您的命令中还有更多分散的。
谢谢!现在可以了。老实说,我对所有这些差异感到困惑。【参考方案2】:
您的代码存在一些问题,首先,使用线程流入和内部进程的错误到控制台
创建一个管道流类,如:
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";
【讨论】:
但是当我在终端中简单地复制粘贴等效字符串时,它可以工作。 我的代码的结果是什么?你有执行的痕迹吗?有什么错误?Unrecognized option 'filter_complex'. Error splitting the argument list: Option not found Exit value: 1
添加了一行行之有效的。问题是代码中的不可见字符 ( / )
PipeStream out=new PipeStream(proc.getInputStream(), System.out);
和 out.start();
应该将输出从进程重定向到控制台。如果要检查输出是什么,请将 System.out 更改为 ByteArrayOutputStream
,然后通过 toByteArray
或 toString
获取内容。【参考方案3】:
您似乎错过了 -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 -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);
【讨论】:
所以你的意思是我在代码中使用\"
,就好像我在终端中运行一样?您可以通过修改我的代码来更新您的答案吗?
所以我在代码中更改了 c1 并在字符串中添加了\"
,如下所示,但不起作用。
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";
@TinaJ 已更新。如果它不起作用,您能否添加System.out.println(c1);
的输出?
没有仍然没有工作:-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] 输出。mp4以上是关于在 Linux 中用 Java 代码执行 FFmpeg 命令的问题的主要内容,如果未能解决你的问题,请参考以下文章
windows和ubuntu虚拟机设置共享文件夹,在windows中用source insight阅读和编写代码,在linux中用交叉编译工具编译。
在Linux中#!/usr/bin/python之后把后面的代码当成程序来执行。 但是在windows中用IDLE编程的话#后面的都是注释,之后的代码都被当成文本了。 该怎么样才能解决这个问题呢?