Custom ffmpeg arguments - a-schild/jave2 GitHub Wiki
Custom ffmpeg arguments
The typed API covers what most encodings need. ffmpeg has far more than that, and when you need an option this library does not model, there are two ways down a level.
Before reaching for them, check whether the option is already there. EncodingAttributes,
AudioAttributes and VideoAttributes between them cover codecs, bit rates, sampling
rates, channels, frame rates, sizes, quality, presets, tune, crf, pixel format, frame rate
mode and looping, and the filter classes cover scaling, cropping, overlays, text,
subtitles and concatenation.
Adding an argument to the global list
Encoder builds every command line from one list of arguments. You can add to it, and
what you add applies to every encoding from then on, in the whole JVM.
// -movflags +faststart, so an mp4 can start playing before it has fully downloaded
Encoder.addOptionAtIndex(
new ValueArgument(ArgType.OUTFILE, "-movflags", ea -> Optional.of("+faststart")),
10);
The pieces:
ArgTypedecides where the argument lands.GLOBALbefore everything,INFILEbefore-i,OUTFILEafter the inputs and before the target file. Getting this wrong is the usual reason an option is ignored: ffmpeg cares where an option sits relative to the file it applies to.ValueArgumentemits a flag and a value, and emits nothing when the function returns an emptyOptional.PredicateArgumentemits fixed text, one word or two, when a condition over theEncodingAttributesholds.SimpleArgumentemits fixed text unconditionally.- The index is where in the list it goes, which decides the order on the command line.
The list can also be read and changed:
EncodingArgument existing = Encoder.getOptionAtIndex(10);
Encoder.setOptionAtIndex(replacement, 10);
Encoder.removeOptionAtIndex(10);
This list is static, shared by every
Encoderin the JVM. Changing it from one thread while another is encoding is safe as of 4.0.0, the list is copy on write, but the change still affects everybody. Set it up once at startup rather than per encoding.
Driving ffmpeg directly
For a one-off, or something the encoder is not shaped for at all, use the process wrapper and build the whole command line yourself.
ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(source.getAbsolutePath());
ffmpeg.addArgument("-vf");
ffmpeg.addArgument("select='gt(scene,0.4)'");
ffmpeg.addArgument("-fps_mode");
ffmpeg.addArgument("vfr");
ffmpeg.addArgument(target.getAbsolutePath());
try {
ffmpeg.execute();
RBufferedReader reader =
new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream()));
String line;
while ((line = reader.readLine()) != null) {
// ffmpeg writes its progress and its complaints to stderr
}
if (ffmpeg.getProcessExitCode() != 0) {
// it failed, and the lines above say why
}
} finally {
ffmpeg.destroy();
}
Two things to know about doing it this way:
- Read the error stream. ffmpeg writes to stderr as it works, and a full pipe will stop the process dead, so a run that produces plenty of output will appear to hang if you do not drain it.
- Always
destroy(). Afinallyblock, or use it as a resource, sinceProcessWrapperisAutoCloseable.
Which ffmpeg is being used
The bundled binary is extracted to a temporary directory the first time it is needed. To
use your own instead, for a build with codecs the bundled one lacks, supply a
ProcessLocator:
Encoder encoder = new Encoder(() -> "/usr/local/bin/ffmpeg");
ProcessLocator has a single method, getExecutablePath(), so a lambda returning the path
is enough. It builds the ProcessWrapper for you.
Everything else works unchanged against it. Note that some fixes in 4.0.0 exist precisely
because ffmpeg changed its command line over the years: -vol was removed in favour of
the volume filter, and -vsync in favour of -fps_mode. The library asks the executable
in front of it which of those it accepts, so a custom binary of any vintage is handled.
A word on where the arguments come from
addArgument adds whatever it is given, which is what makes this page possible. It is
also why an application must never hand it a string a user controls: that is giving the
user the ffmpeg command line. The same goes for URLs, since ffmpeg will fetch them.
SECURITY.md sets out where that boundary sits.