Usage - a-schild/jave2 GitHub Wiki
Check the latest release for the current version. The examples below use 4.2.0.
jave-all-deps pulls in the library and the binary for every supported platform. It is
the simplest choice and the largest download.
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>4.2.0</version>
</dependency>Take jave-core plus the package for the platform you deploy to. This is much smaller,
and is what you want in a container image.
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-linux64</artifactId>
<version>4.2.0</version>
</dependency>| Operating system | Architecture | Package |
|---|---|---|
| Windows | x64 | jave-nativebin-win64 |
| Windows | ARM 64 bit | jave-nativebin-win-arm64 |
| macOS | Apple silicon | jave-nativebin-osxm1 |
| macOS | Intel x64 |
jave-nativebin-osx64 — deprecated, Apple ends intel support with macOS 27 |
| Linux | x64 | jave-nativebin-linux64 |
| Linux | ARM 64 bit | jave-nativebin-linux-arm64 |
| Linux | ARM 32 bit | jave-nativebin-linux-arm32 |
The 32 bit x86 packages, jave-nativebin-win32 and jave-nativebin-linux32, were
removed in 4.0.0. Stay on 3.6.0 if you need them.
If you pick packages yourself, import the bill of materials and leave the versions off. A core and a native binary from different releases is the usual way this goes wrong.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-bom</artifactId>
<version>4.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-linux64</artifactId>
</dependency>
</dependencies>The javadoc is at javadoc.io.
Two things in 4.1.0 change behaviour rather than only adding to it.
Encoder.abortEncoding() now actually stops the encoding. It used to close the
streams before killing the process, and closing a pipe does not wake a thread already
blocked reading it, so the call waited for ffmpeg to finish of its own accord and then
returned as though it had aborted. The same applied to the deadline in
MultimediaObject.getInfo(long). Both take effect immediately now. If you built waiting
or timeout logic around the old behaviour, it is no longer needed.
slf4j-api moved from 1.7.36 to 2.0.18. slf4j 2 finds its binding through the
ServiceLoader rather than through StaticLoggerBinder, so if your project still uses a
1.7 binding, logback-classic 1.2.x or slf4j-simple 1.7.x, and picks 2.0.18 up from
here, you will see "No SLF4J providers were found" and lose your logging until the binding
is moved to a 2.x one. Only the api is used, so pinning slf4j-api to 1.7.36 in your own
build is also a valid answer.
Works from 4.2.0 onwards with nothing to configure. The jave-nativebin-* packages carry
the reachability metadata that registers the bundled ffmpeg, and native-image picks that up
by itself.
Before 4.2.0 the executable was silently dropped from the image, because native-image
discards resources unless something registers them, and the first encoding failed with
Could not find ffmpeg platform executable in resources. If you see that, the version is
the thing to check first.
Three things are worth knowing when building an image:
-
Depend on one platform package, not
jave-all-deps. An image is built for a single platform, so pulling in every binary embeds every binary, several hundred megabytes of ffmpeg that the image can never use. -
The temporary directory has to be writable, and it has to allow execution. The binary
is extracted at run time and then run, so a container with a read only
/tmp, or one mountednoexec, fails at the point of extraction or immediately after. That is equally true on the jvm, but it catches people more often in a minimal native image container. -
A custom
ProcessLocatorsidesteps all of it. If you point the encoder at an ffmpeg you install into the image yourself, nothing is extracted and none of the above applies.
This is checked in CI on every change that could affect it: a native image is built and run, and it extracts the binary, encodes a file and reads the result back.
The class you will use most is ws.schild.jave.Encoder.
File source = new File("source.mov");
File target = new File("target.webm");
AudioAttributes audio = new AudioAttributes();
audio.setCodec("libvorbis");
audio.setBitRate(192000);
audio.setSamplingRate(44100);
audio.setChannels(2);
VideoAttributes video = new VideoAttributes();
video.setCodec("libvpx");
video.setBitRate(250000);
video.setFrameRate(25);
EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat("webm");
attrs.setAudioAttributes(audio);
attrs.setVideoAttributes(video);
new Encoder().encode(new MultimediaObject(source), target, attrs);encode() blocks until the transcoding has finished or failed. To follow its progress,
see monitoring below.
EncodingAttributes carries everything the encoder needs.
| Method | What it does |
|---|---|
setOutputFormat(String) |
The container to write. Must be one the ffmpeg in use can write, see Encoder.getSupportedEncodingFormats()
|
setAudioAttributes(AudioAttributes) |
Audio settings. Leave it unset and the result has no audio |
setVideoAttributes(VideoAttributes) |
Video settings. Leave it unset and the result has no video, including album art, which is carried as a video stream |
setOffset(Float) |
Start this many seconds into the source |
setDuration(Float) |
Encode only this many seconds |
setStreamLoop(Integer) |
Repeat the input this many extra times, -1 for endlessly |
The method for the container was called
setFormatin very old versions. It has beensetOutputFormatfor a long time, and examples using the old name will not compile.
See Encoding Attributes for the audio and video settings, and Supported formats for what the bundled ffmpeg can read and write.
Pass an EncoderProgressListener and the encoder will call it as it goes.
new Encoder().encode(new MultimediaObject(source), target, attrs,
new EncoderProgressListener() {
@Override
public void sourceInfo(MultimediaInfo info) {
// Called once, before the work starts, when the source could be read.
// Not called when several sources are concatenated, since there is no
// single source to describe.
}
@Override
public void progress(int permil) {
// 0 to 1000, or PROGRESS_UNKNOWN when the source declares no duration
// and there is nothing to be a proportion of.
if (permil == EncoderProgressListener.PROGRESS_UNKNOWN) {
System.out.println("working"); // an indeterminate progress bar
} else {
System.out.println(permil / 10.0 + "%");
}
}
@Override
public void message(String message) {
// Usually a warning from ffmpeg
}
@Override
public void done() {
// Called once the run has finished successfully. This is a default
// method, so listeners written before 3.6.0 need not implement it.
}
});A percentage needs a total, and some sources do not declare one: live streams, and webm
files from browser recorders, which commonly carry no duration in the header. Several
sources concatenated have no single duration either. In all those cases progress() is
called with EncoderProgressListener.PROGRESS_UNKNOWN, which is -1.
Before 4.1.0 those cases divided by the missing duration anyway and handed the listener a large negative number that looked like a permil, which is what made progress bars jump about on webm input (#269).
encode() throws EncoderException, or InputFormatException when the source could not
be understood. The message carries what ffmpeg reported, including its exit code.
try {
new Encoder().encode(new MultimediaObject(source), target, attrs);
} catch (InputFormatException e) {
// the source is not something ffmpeg could read
} catch (EncoderException e) {
// the run itself failed, e.getMessage() has ffmpeg's complaint
}Encoder.getUnhandledMessages() returns the ffmpeg output lines the library did not
recognise, which is often where the real reason is.
MultimediaInfo info = new MultimediaObject(new File("source.mp4")).getInfo();
info.getFormat(); // container
info.getDuration(); // milliseconds, -1 when unknown
info.getVideo().getSize(); // VideoSize, null when there is no video
info.getAudio().getBitRate();
info.getRotate(); // degrees clockwise, from the stream metadataA source that never answers can hold the call open, so there is a form that gives up:
// stop waiting after five seconds
MultimediaInfo info = new MultimediaObject(url).getInfo(5000L);- Examples — converting between particular formats
- Custom ffmpeg arguments — reaching options this API does not model
- Encoding Attributes — every audio and video setting
- Supported formats — what the bundled ffmpeg reads and writes