如何从Java中读取FFmpeg的响应并将其用于创建进度条?

9

我正在研究如何在Java中创建FFmpeg的进度条。为此,我需要执行一个命令,然后读取所有进度:

String[] command = {"gnome-terminal", "-x", "/bin/sh", "-c","ffmpeg -i /home/tmp/F.webm /home/tmp/converted1.mp4"};

Process process = Runtime.getRuntime().exec(command);

这个程序运行得非常好。但我需要捕获所有的进度来制作一个进度条。那么我该如何从Java中读取这些数据呢?


1
我得到了类似于“frame= 2822 fps=493 q=19.1 Lsize=4082kB time=117.66 bitrate= 284.2kbits/s”的输出,你计划如何提取百分比进度? - aioobe
首先,它显示了开始的持续时间...我希望它被优先读取并转换为秒并存储,然后提取所有行,例如frame= 2822 fps=493 q=19.1 Lsize=4082kB time=117.66 bitrate= 284.2kbits/s,从这一行中提取time=value,并通过公式(time/duration)*100计算进度。 - shalki
我明白了。让我试一试 :-) - aioobe
嗨,我也在使用FFMPEG Java的Rapper JJPEG。我想问这里有两个选项,要么通过Java应用程序使用FFMPEG CMD命令,要么直接使用JJMPEG。请问您能指导我哪个选项更好吗? - Bilal Ahmed Yaseen
3个回答

21

这里有一个完整的例子可以帮助你入门

import java.io.*;
import java.util.Scanner;
import java.util.regex.Pattern;

class Test {
  public static void main(String[] args) throws IOException {
    ProcessBuilder pb = new ProcessBuilder("ffmpeg","-i","in.webm","out.mp4");
    final Process p = pb.start();

    new Thread() {
      public void run() {

        Scanner sc = new Scanner(p.getErrorStream());

        // Find duration
        Pattern durPattern = Pattern.compile("(?<=Duration: )[^,]*");
        String dur = sc.findWithinHorizon(durPattern, 0);
        if (dur == null)
          throw new RuntimeException("Could not parse duration.");
        String[] hms = dur.split(":");
        double totalSecs = Integer.parseInt(hms[0]) * 3600
                         + Integer.parseInt(hms[1]) *   60
                         + Double.parseDouble(hms[2]);
        System.out.println("Total duration: " + totalSecs + " seconds.");

        // Find time as long as possible.
        Pattern timePattern = Pattern.compile("(?<=time=)[\\d.]*");
        String match;
        while (null != (match = sc.findWithinHorizon(timePattern, 0))) {
          double progress = Double.parseDouble(match) / totalSecs;
          System.out.printf("Progress: %.2f%%%n", progress * 100);
        }
      }
    }.start();

  }
}

输出:

Total duration: 117.7 seconds.
Progress: 7.71%
Progress: 16.40%
Progress: 25.00%
Progress: 33.16%
Progress: 42.67%
Progress: 51.35%
Progress: 60.57%
Progress: 69.07%
Progress: 78.02%
Progress: 86.49%
Progress: 95.94%
Progress: 99.97%

你还可以考虑使用一些类似jjmpeg这样的Java绑定程序来处理ffmpeg,这可能以更可靠的方式提供所需的内容。

编辑

使用ffmpeg 2.0时,时间输出为HH:mm:ss.S,因此timePattern需要包含一个:

Pattern timePattern = Pattern.compile("(?<=time=)[\\d:.]*");

此外,dur需要按:拆分并将其总和。

String[] matchSplit;
while (null != (match = sc.findWithinHorizon(timePattern, 0))) {
    matchSplit = match.split(":")
    double progress = Integer.parseInt(matchSplit[0]) * 3600 +
        Integer.parseInt(matchSplit[1]) * 60 +
        Double.parseDouble(matchSplit[2]) / totalSecs;
//...

请注意,如果文件已经存在,则整个程序将在问题“文件'out.mp4'已经存在。要覆盖吗?[y/N]”上冻结。 - aioobe
是的...我知道那个...这就是为什么我给出了这个命令:String[] command = {"gnome-terminal", "-x", "/bin/sh", "-c","ffmpeg -i /home/tmp/F.webm /home/tmp/converted1.mp4"}; 这样它在执行此过程时会打开一个终端,但是当我这样做时,我无法获取ffmpeg进程的错误流...这就是我卡住的地方,现在还卡住了...我只能通过上面的代码中给出的ffmpeg命令来获取输出..但如果文件已经存在,它就会冻结... - shalki
看起来ffmpeg的输出已经改变了,这些模式不再适用。 - John Giotta
@JohnGiotta,请随意在我的答案中添加更正后的模式。 - aioobe
上面的代码中有一个小错别字,应该是:double progress = (Integer.parseInt(matchSplit[0]) * 3600 + Integer.parseInt(matchSplit[1]) * 60 + Double.parseDouble(matchSplit[2])) / totalSecs; - Hyndrix
显示剩余3条评论

1

您可以尝试解析ffmpeg的输出并以某种方式了解已完成的工作。但这很困难,也不稳定。无论是我们(ffmpeg用户)还是ffmpeg本身都不知道并且不能在时间上知道处理需要多长时间。

根据我的经验,最简单的方法是实现一种启发式算法。假设处理时间与文件大小成线性关系。这种方法是“错误”的,但足够好而且非常简单。现在使用与实际生活中使用的相同选项运行处理,使用几个不同大小的文件创建大小到时间的映射。进行统计分析并创建公式,例如time = something + coef * size

现在您可以创建进度条。像大多数进度条一样,它应该达到约95%,然后等待进程的真正终止。

这非常简单,效果不比任何其他更复杂的解决方案差。


嘿,这不是必要的...我的做法是正确的吗?我的方法是获取输入视频的持续时间,当您执行此ffmpeg命令时,它将显示出来,并且使用time=value显示进度,而该时间告诉有多少视频已被转换。例如,如果我的输入视频持续时间为00:04:30.00,并且在特定时间点我得到了frame= 2822 fps=493 q=19.1 Lsize=4082kB time=117.66 bitrate= 284.2kbits/s,那么这意味着我的00:01:57.66视频已被转换...因此,通过这种方式,我可以获得进展情况。 - shalki
但问题是我该如何从我的Java程序中读取它...而FFmpeg将其发送到stderr...我可以将其重定向到文件...但我需要即时了解正在发生的进展...因为我想在进程进行时制作进度条。 - shalki
要获取进程的错误流,您可以使用 p.getErrorStream() - aioobe

0

我已经成功地使用以下代码为ffmpeg命令显示了进度条。

  try {
                Scanner sc = new Scanner(process.getErrorStream());

                // Find duration
                Pattern durPattern = Pattern.compile("(?<=Duration: )[^,]*");
                String dur = sc.findWithinHorizon(durPattern, 0);
                Log.e("duration"+dur);
                String givenDateString = dur;
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.S");
                sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
                try {
                    Date mDate = sdf.parse(givenDateString);
                    totalDuration = mDate.getTime();
                    System.out.println("Duration in milli :: " + totalDuration);
                } catch (ParseException e) {
                    e.printStackTrace();
                }

                // Find time as long as possible.
                Pattern timePattern = Pattern.compile("(?<=time=)[\\d:.]*");
                String match;
                String[] matchSplit;
                while (null != (match = sc.findWithinHorizon(timePattern, 0))) {
                    if (isCancelled()) {
                        return;
                    }
                    Log.e("match"+match);
                    String givenDateString1 = match;
                    SimpleDateFormat sdf1 = new SimpleDateFormat("HH:mm:ss.S");
                    sdf1.setTimeZone(TimeZone.getTimeZone("GMT"));
                    try {
                        Date mDate = sdf1.parse(givenDateString1);
                        currentDuration = mDate.getTime();
                        System.out.println("Time in milli :: " + currentDuration);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    Double percentage = (double) 0;

                    long currentSeconds = (int) (currentDuration);
                    long totalSeconds = (int) (totalDuration);

                    // calculating percentage
                     percentage =(((double)currentSeconds)/totalSeconds)*100;


                    Log.e("Progress"+percentage);
                    publishProgress(""+percentage);
                }
            }catch (Exception e){
                e.printStackTrace();
            }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接