Issue
I have been building an application to merge together videos based on data I got from the web. I was lucky enough to find this great tool ffmpeg that has saved me a lot of time and effort, but unfortunately I seem to be stuck on the following issue:
Im attempting to execute a command for merging a video with audio (in the future I will also be adding hardcoded .ASS subtitles and .PNG images). My application dynamically generates me a command based on the files its suppposed to use which works just as intended, as the command that I get works inside of my windows console (cmd). But when I try to run it through my Java application, the code seems to "freeze" permanently and while the placeholder file gets created on my desktop with the filename and correct file format, it just has the default windows video icon and cant be opened (as it has not finished being created, duh). Im also using Eclipse, perhaps thats worth noting.
This is the part of the code where it freezes:
protected void runFFMPEG(Multimap<String,String> args, String outputFile, String fileFormat) throws IOException
{
try
{
Runtime.getRuntime().exec(setCommand(args, outputFile, fileFormat)).waitFor();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
as previously said, the command itself is generated correctly, but heres the code nonetheless:
private String[] setCommand(Multimap<String, String> args, String outputFile, String fileFormat)
{
String[] command = new String[args.size()*2+2];
command[0] = ffmpegLocation;
int[] i = new int[1];
i[0] = 1;
args.forEach((key, value) -> {
command[i[0]++] = key;
command[i[0]++] = value;
});
command[i[0]] = outputFile + fileFormat;
return command;
}
Note: I used a normal string previously for command, but I saw another thread saying that using an array instead fixxed their issue (which it didnt for me, else i wouldnt be here...).
This is an example command that gets generated:
ffmpeg.exe -ss 00:00:00 -to 00:00:15 -i C:\Users\Test.mp4 -i C:\Users\FINAL.mp3 -map 0 -map 1:a -c:v copy C:/Users/User/TestVideo.mp4
I also have a different command that concats the audio together into one FINAL.mp3 file, so its not like ffmpeg entirely fails to work in my application. My guess is that it has something to do with the Runtime i get with Runtime.getRunTime(), but I cant seem to find out what the problem is. Can someone tell me what I did wrong? Thanks in advance!
Solution
To reliably start a sub-process you need to handle the output streams or the process will block. Use ProcessBuilder
to have better control of where output streams go - including redirect to File
.
Here is a simple example of how to handle launch which could be used in place of Runtime.exec()
and sends STDOUT(/STDERR) to any stream you pass in:
// Example: start(yourCmd, null, System.out, null);
// or start(yourCmd, null, System.out, System.err);
// Don't forget to close streams you pass in - if appropriate
public static int start(String[] cmd, byte[] stdin, OutputStream stdout, OutputStream stderr)
throws IOException, InterruptedException
{
Objects.requireNonNull(cmd);
Objects.requireNonNull(stdout);
System.out.println("start "+Arrays.toString(cmd));
// Launch and wait:
ProcessBuilder pb = new ProcessBuilder(cmd);
if (stderr == null) {
pb.redirectErrorStream(true); // No STDERR => merge to STDOUT
}
Process p = pb.start();
// Consumes STDERR at same time as STDOUT, not doing this large streams can block I/O in the sub-process
Thread bg = null;
if (stderr != null) {
Runnable task = () -> {
try(var from = p.getErrorStream()) {
from.transferTo(stderr);
} catch(IOException io) {
throw new UncheckedIOException(io);
}
};
bg = new Thread(task, "STDERR");
bg.start();
}
// Send STDIN if required, and close STDIN stream
// NOTE!!! a huge input stream can lock up STDOUT/STDERR readers, you may need a background thread here too
try(OutputStream os = p.getOutputStream()) {
if (stdin != null) os.write(stdin);
}
// Move STDOUT to the output stream
try(var stdo = p.getInputStream()) {
stdo.transferTo(stdout);
}
int rc = p.waitFor();
if (bg != null) {
bg.join();
}
System.out.println("start "+Arrays.toString(cmd));
System.out.println("Exit "+p.pid()+" CODE "+rc +' '+(rc == 0 ? "OK":"**** ERROR ****")+" "+(stderr == null ? "STDERR>OUT":""));
return rc;
}
Answered By - DuncG
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.