9

I am working on creating a progress bar for ffmpeg in java. So for that I need to execute a command, then read all of the progress:

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

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

This runs perfectly. However, I need to capture the all of the progress to make a progress bar. So how can I read that data from java?

Leigh
  • 28,765
  • 10
  • 55
  • 103
shalki
  • 890
  • 3
  • 14
  • 31
  • 1
    I get output like `frame= 2822 fps=493 q=19.1 Lsize=4082kB time=117.66 bitrate= 284.2kbits/s` how exactly do you plan to extract the %-progress? – aioobe Jun 07 '12 at 07:52
  • see first of all it shows the Duration in the starting... I want that to be read first and will convert it into seconds and store and then all the lines like `frame= 2822 fps=493 q=19.1 Lsize=4082kB time=117.66 bitrate= 284.2kbits/s` and from this line extract time=value and through this i will find progress by the formula `(time/duration)*100` – shalki Jun 07 '12 at 07:59
  • I see. Let me give it a try :-) – aioobe Jun 07 '12 at 08:01
  • Hi, I am also working FFMPEG Java's Rapper JJPEG. I want to ask that here I have two options either to use FFMPEG CMD command through Java application or use JJMPEG directly. Can you please guide me which options will be better for me ? – Bilal Ahmed Yaseen Jan 14 '15 at 07:58

3 Answers3

21

Here's a complete example for you which should get you started

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

  }
}

Output:

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%

You may also consider using some kind of Java bindings for ffmpeg such as jjmpeg which may provide what you need in a more robust way.

EDIT

With ffmpeg 2.0, time output is HH:mm:ss.S so the timePattern needs a to incorporate a :

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

In addition, the dur will need to be split on : and summed together

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;
//...
John Giotta
  • 16,432
  • 7
  • 52
  • 82
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • Note that the whole program freezes on the question `File 'out.mp4' already exists. Overwrite ? [y/N]` if the file exists. – aioobe Jun 07 '12 at 08:26
  • yes... I know that... that is why I was giving the command: `String[] command = {"gnome-terminal", "-x", "/bin/sh", "-c","ffmpeg -i /home/tmp/F.webm /home/tmp/converted1.mp4"};` So that it opens up a terminal while doing this process but when i do this i can't get the errorstream of ffmpeg process... that is where then I got stucked and even stucked now... I was getting the output with only the ffmpeg command as u have given in above code.. but then it will freeze if the file already exists... – shalki Jun 07 '12 at 09:18
  • Aha. Well, you can't get the ffmpeg output if it's running inside a gnome-terminal. Check if the file exists using `File.exists` before launching `ffmpeg`. – aioobe Jun 07 '12 at 09:42
  • It looks like ffmpeg output has changed to where these patterns no longer work. – John Giotta Aug 14 '13 at 21:11
  • @JohnGiotta, feel free to append the corrected Patterns to my answer. – aioobe Aug 15 '13 at 08:18
  • There is a small typo in the code above for ffmpeg 2.x: ``double progress = (Integer.parseInt(matchSplit[0]) * 3600 + Integer.parseInt(matchSplit[1]) * 60 + Double.parseDouble(matchSplit[2])) / totalSecs;``. – Hyndrix May 16 '14 at 14:01
  • it always throw new RuntimeException("Could not parse duration."); – Ahmad Arslan Nov 20 '17 at 11:52
  • Well, "scraping" stdin like this is bound to be unstable. You'll have to adjust the code so that it matches the output of your particular version of ffmpeg. – aioobe Nov 20 '17 at 13:17
1

You can try to parse ffmpeg output and somehow understand what work is already done. But this is hard and not stable anyway. Neither we (the ffmpeg users) nor ffmpeg itself does not know and cannot know in terms of time how long the processing will take.

According to my experience the easiest way is to implement a kind of heuristics. Assume that the time of processing linearly depends on the file size. This approach is "wrong" but good enough and very simple. Now run your processing with exactly the same options you are using in real life with several files of different size. Create mapping of size-to-time. Do statistical analysis and create formula like time = something + coef * size.

Now you can create you process bar. As most process bars it should arrive to ~95% and then wait for real termination of the process.

It is very simple and works not worse than any other more sophisticated solution.

AlexR
  • 114,158
  • 16
  • 130
  • 208
  • Hey this is not needed.... What i m doing is correct?? My approach is to get the Duration of input video which will be displayed when u execute this ffmpeg command... and it will even show the progress using time=value and that time is the time which tells how much of video has been converted... for example if my input video duration is 00:04:30.00 and at particular point of time i get `frame= 2822 fps=493 q=19.1 Lsize=4082kB time=117.66 bitrate= 284.2kbits/s` then it means that my 00:01:57.66 video has been converted... So by this i can get the progress.. – shalki Jun 07 '12 at 08:15
  • but the problem is how am i going to read it from my java program.... and ffmpeg sends that to stderr... i can redirect it to a file.. but i need immediate progress that's happening.. as i want to make a progress bar when the process is going on.. – shalki Jun 07 '12 at 08:18
  • To get the error stream of the process you do `p.getErrorStream()`. – aioobe Jun 07 '12 at 08:28
0

*I have Successfully Display ProgressBar for ffmpeg command using Following Code.

  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();
            }
Robert
  • 5,278
  • 43
  • 65
  • 115
Chirag Darji
  • 281
  • 3
  • 5