bramp / ffmpeg-cli-wrapper

Java wrapper around the FFmpeg command line tool
BSD 2-Clause "Simplified" License
1.67k stars 412 forks source link

Incorrect order of arguments for input #124

Closed kresic-ivan-nsoft closed 3 weeks ago

kresic-ivan-nsoft commented 7 years ago

Hi there,

I'm trying to add some text to an input video for a new output video. However, I'm getting errors no matter what order of commands I try.

The command need to be something like ffmpeg -i input.mp4 -vf "drawtext=...", however, I cannot accomplish that order of arguments, which needs to be respected.

I've tried

however, the result is always incorrect order of the arguments (either -vf -i -f -r..., or -i -f -r -vf, both of which cause errors because -vf isn't immediately following the -i).

Any help here? Is there a way to define the order or the args or explicitly say the args should be applied after the input?

bobmarks commented 7 years ago

I had the same issue with extra args. The extra args is a great idea as the wrapper will always be playing catch up with ffmpeg. Unfortunately it isn't well implemented currently and this wrapper can only do 80 of the 80 / 20. Really the extra args should be added at any stage with the order preserved to mimic ANY scenario of ffmpeg. This will then cover both the 80 (via fluid API methods) and 20 scenarios (via manually strings in extra args).

kresic-ivan-nsoft commented 7 years ago

Thanks @bobmarks for the comment. I somehow managed to hack it to work losing a few days of coding unfortunately. Now I need to add smaller images on top of the large one, which is the video background. The same issue with arguments order, again.

bobmarks commented 7 years ago

If you look at https://github.com/bramp/ffmpeg-cli-wrapper/blob/master/src/main/java/net/bramp/ffmpeg/builder/FFmpegBuilder.java you'll see: -

  ...

  final List<String> inputs = new ArrayList<>();
  final Map<String, FFmpegProbeResult> inputProbes = new TreeMap<>();

  final List<String> extra_args = new ArrayList<>();

  // Output
  final List<FFmpegOutputBuilder> outputs = new ArrayList<>();

  // Filters
  String complexFilter;
  String audioFilter;
  String videoFilter;

  ...

I think the problem is really that EVERY object should have the ability to have extra_args so that ANY order of ffmpeg arguments can be constructed. This could be achieved by refactoring ffmpeg commands from e.g. String to a base object e.g. FfmpegCommand which has an extra_args list as a parameter.

In the end I simply wrote my own tiny ffmpeg wrapper.

I wanted 2 things (1) parameters which this wrapper didn't support (even via extra_args) and (2) progress updater (which this wrapper does very nicely). The wrapper wasn't hard and I used this article for inspiration:

https://codedump.io/share/JXwbHVigF2SB/1/how-to-read-ffmpeg-response-from-java-and-use-it-to-create-a-progress-bar

e.g.

private String ffmpeg;     // location of ffmpeg (e.g. inject in using Spring etc).

...

String [] ffmpegArgs = {
                ffmpeg,
                "-y",                                 // Override file
                "-i", input.getAbsolutePath(),        // input video
                "-c:v", "libvpx-vp9",                 // Copy video using `libvpx-vp9` video encoder - see https://trac.ffmpeg.org/wiki/Encode/VP9
                "-crf", "20",                         // "constant quality" of 20 (see above URL)
                "-b:v", "0",                          // Video bit-rate limit of 0 (see above URL)
                "-c:a", "libvorbis",                  // Copy audio using `libvorbis` audio encoder (see https://xiph.org/vorbis/)
                "-threads", "4",                      // Use 4 threads.
                output.getAbsolutePath()
        };
ProcessBuilder pb = new ProcessBuilder(ffmpegArgs);
Process process = pb.start();

FfmpegProgressExtractor progressExtractor = new FfmpegProgressExtractor (
                     process, progressService);   // progressService is an interface which is called when progress is updated

Thread progressThread = new Thread(progressExtractor);
progressThread.setName(output.getName() + "-" + progressSection);
progressThread.start();

int retcode = process.waitFor();

and

public class FfmpegProgressExtractor implements Runnable {

    // Injected fields

    private Process process;
    private ProgressService progressService;  // simple interface

    // Other fields

    private double totalDurationSeconds = -1;

    public FfmpegProgressExtractor (Process process, ProgressService progressService) {
        this.process = process;
        this.progressService = progressService;
    }

    @Override
    public void run() {
        Scanner sc = new Scanner(process.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(":");
        this.totalDurationSeconds = Integer.parseInt(hms[0]) * 3600
                + Integer.parseInt(hms[1]) * 60
                + Double.parseDouble(hms[2]);

        // Start progress of this section
        this.progressService.update(totalDurationSeconds, 0);

        // Find time as long as possible.

        Pattern timePattern = Pattern.compile("(?<=time=)[\\d:.]*");
        String[] matchSplit;
        String match;
        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]) / totalDurationSeconds;
            double percentage = progress * 100;

            // Update section progress
            this.progressService.update(-1, percentage);
        }

        this.progressService.update(-1, 100);   // 100 percent finished
    }
}

and

public interface ProgressService {

    void update(double durationSeconds, double percentage);

}

Obviously the implementation the ProgressService will vary from app to app so no point in putting my implementation in here (which talks to a DAO i.e. updates a database).

kresic-ivan-nsoft commented 7 years ago

Awesome @bobmarks, I'll take a look. Thanks a bunch!

bobmarks commented 7 years ago

No worries. This is easily the best wrapper I've found and it actually helped me learn FFmpeg - if they crack the extra_args it could also future proof it into future version of ffmpeg as well.

Euklios commented 3 weeks ago

Closed thanks to #339