Skip to content

Releases: a-schild/jave2

4.2.0 release

Choose a tag to compare

@a-schild a-schild released this 20 Aug 12:41

One fix, and it is the whole point of the release: jave now works when compiled ahead of time by GraalVM native-image.

The bundled ffmpeg is unchanged at 9.0.1, the published packages are unchanged, and there are no API or behaviour changes. Upgrading from 4.1.0 is a version number and nothing else.

GraalVM native image (#276)

Supported, with nothing to configure.

The bundled ffmpeg is a resource inside the jave-nativebin-* jars, extracted at run time by DefaultFFMPEGLocator. native-image discards resources unless something registers them, so an image built against jave contained no binary at all, and the first encoding failed with

Could not find ffmpeg platform executable in resources for <ws/schild/jave/nativebin/ffmpeg-amd64>

which is why it worked when run normally and not after packaging. Nothing to do with Spring Boot, despite where it was usually noticed.

Each jave-nativebin-* jar now ships

META-INF/native-image/ws.schild/<artifactId>/resource-config.json

which native-image discovers on its own. There is nothing to add to your project, no build flags, and no effect on ordinary JVM use. The metadata is generated from the actual contents of each module rather than written by hand, so the registration cannot drift away from the file it is meant to match.

Verified, not assumed

A new workflow installs GraalVM, builds a native image of a smoke test and runs it, with --no-fallback so it cannot quietly produce a JVM image instead:

GraalVM Runtime Environment Oracle GraalVM 21.0.12+7.1
Finished generating 'jave-graalvm-smoke' in 1m 16s.
==> locating the bundled ffmpeg
    extracted /tmp/jave/ffmpeg-amd64 (61892072 bytes)
==> asking it what it can encode
    79 audio encoders
==> encoding something
    wrote smoke-target.mp3 (4640 bytes)
==> reading it back
    format mp3, mp3 (mp3float)

It covers extracting the binary and running the process, not just linking, and it runs on every change that could affect it.

Building an image

Three things are worth knowing.

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 the image can never use.

<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>

The temporary directory has to be writable, and it has to allow execution. The binary is extracted and then run, so a read only /tmp, or one mounted noexec, fails at that point. That is equally true on the JVM, but it catches people more often in a minimal native image container.

A custom ProcessLocator sidesteps all of it. Point the encoder at an ffmpeg you install into the image yourself and nothing is extracted.

Documented in Usage.

Also

.gitignore now ignores target directories at any depth. The rule matched only one level down, so build output from anything nested deeper was offered up for committing.

Upgrading

Nothing to do beyond the version. If you are coming from 4.0.0 rather than 4.1.0, note the two behaviour changes in 4.1.0: abortEncoding() now actually aborts instead of waiting for ffmpeg to finish, and slf4j-api moved to 2.0.18, which stops an slf4j 1.7 binding from being found.

Full changelog: 4.1.0...4.2.0

4.1.0 release

Choose a tag to compare

@a-schild a-schild released this 20 Aug 09:38

Two new features, three fixes, and a large clean up of the build and the documentation.

The bundled ffmpeg is unchanged at 9.0.1, and the set of published packages is unchanged from 4.0.0.

Read this before upgrading

Two changes alter behaviour rather than only adding to it.

abortEncoding() now actually aborts

ProcessWrapper.destroy() closed the process streams before killing the process. Closing a pipe does not wake a thread that is already blocked reading it, so control never reached the kill until the reader returned by itself, which for a process that had not finished meant not at all.

Reading ffmpeg's output while it runs is the normal case, so in practice Encoder.abortEncoding() waited for the encoding to finish and then reported success, and MultimediaObject.getInfo(long) did the same with its deadline. Both looked correct from the outside, which is presumably why this went unnoticed for so long. A test that took 120 seconds to report a 250 millisecond timeout now takes 0.3.

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. If your project still uses an slf4j 1.7 binding, logback-classic 1.2.x or slf4j-simple 1.7.x, and picks 2.0.18 up transitively 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 equally valid.

New

Two pass encoding (#156)

VideoAttributes video = new VideoAttributes();
video.setCodec("libx264");
video.setBitRate(1200000);       // the budget the two passes exist to spend well

EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat("mp4");
attrs.setAudioAttributes(audio);
attrs.setVideoAttributes(video);
attrs.setTwoPass(true);

ffmpeg is run twice over the same input. The first run encodes the video only to measure it, writing what it learns to a statistics file and discarding the pictures; the second reads those measurements back and decides where the bitrate is worth spending. On material that is not uniformly difficult the same budget buys noticeably better quality, at roughly twice the time.

A video bitrate is required, since that is the budget being planned, and validate() says so rather than running ffmpeg twice for nothing. It is the wrong tool alongside setCrf, which asks for a quality rather than a size. The statistics file is unique to each encoding, lives in the temporary directory, and is removed afterwards. The two passes are reported to an EncoderProgressListener as one encoding, each taking half of the 0..1000 range, with sourceInfo called once at the start and done() once at the end.

Every channel layout is recognised (#45)

AudioInfo.getChannels() matched only mono, stereo and quad, so a 5.1 or 7.1 file reported -1, meaning unavailable.

Layouts whose name carries the count are now worked out arithmetically, so 5.1 is 6 and 7.1.4 is 12, and a layout ffmpeg adds later needs no change here. Only the names that say nothing about their size are tabulated. A bracketed qualifier such as 5.1(side) says which channels, not how many. ffmpeg's unnamed fallback, 6 channels, is handled too.

The new test checks this against ffmpeg -layouts itself rather than against a list written by hand, so the two cannot drift apart. All 40 standard layouts pass.

Fixed

Nonsense progress on sources with no duration (#269)

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. Concatenated sources have no single duration either.

The permil was calculated anyway, dividing by -1 or by 0, and the only clamp was an upper one, so a large negative number went straight to the listener looking like progress. That is what made progress bars misbehave on webm input.

progress() is now called with the new EncoderProgressListener.PROGRESS_UNKNOWN when no proportion can be calculated, and a real permil is clamped at both ends. Listeners are still called at the same points, so code that only wanted to know work was happening is unaffected, and anything drawing a bar now has a documented value to switch to an indeterminate one on.

An intermittent test failure

DefaultFFMPEGLocatorTest cleared the shared temporary directory so the locator had to extract a binary, but that directory belongs to the whole build and windows will not delete an executable a process has just finished with, so the cleanup threw and took the test with it. Cleanup is now best effort, and the test asserts on what the locator returns instead.

Build and dependencies

selenium-java 2.44.0 and com.opera:operadriver 1.5 were test dependencies that nothing referenced. They dated from 2014 and pulled about forty transitive artifacts of the same vintage onto the test classpath, commons-collections 3.2.1, guava 14.0, httpclient 4.3.4, xalan 2.7.1 among them, which is a lot for any vulnerability scanner to complain about in exchange for nothing at all. The test dependency tree drops from 56 artifacts to 13. commons-lang3 was reaching EncoderTest through that accident and is now declared properly.

jave-core-test depended on logback-classic, which nothing configured, while the api resolved to slf4j 1.7. The binding never matched the api, so the library's own debug logging silently produced nothing while tests ran. It works now.

Build plugins to their latest stable releases: compiler 3.15.0, resources 3.5.0, surefire 3.5.6, javadoc 3.12.0, jar 3.5.1, source 3.4.0, gpg 3.2.8, deploy 3.1.4, release 3.3.1, scm-provider-gitexe 2.2.1, buildnumber 3.3.0, templating 3.1.0. JUnit moves to 5.14.4 rather than to 6.x, which requires Java 17 and would raise the floor for the test modules.

Documentation

The wiki had drifted a long way from the library. Usage still documented version 2.4.2, attrs.setFormat() and the retired jave-native-* artifact names, and every example on the Examples page used setFormat, which has not existed for years, so none of them compiled. Supported formats predated this fork entirely, listing codecs ffmpeg dropped over a decade ago while omitting libopus, libvpx-vp9, libx265 and libaom-av1.

The usage examples have moved out of the README, so there is one place to keep current instead of three. Every Java snippet in the documentation was compiled against jave-core before publishing, which caught six wrong API calls.

Closes #45, #59, #155, #156, #233, #269.

Full changelog: 4.0.0...4.1.0

4.0.0 release

Choose a tag to compare

@a-schild a-schild released this 19 Aug 16:18

The first release built on the new publishing and binary pipeline. Every bundled ffmpeg moves from 4.4.1 to 9.0.x, five major versions, and the linux binaries are now built from source rather than taken from a publisher.

There are two breaking changes, both listed first.

Breaking

  • The 32 bit x86 packages are gone. jave-nativebin-win32 and jave-nativebin-linux32 are no longer published. ffmpeg itself stopped publishing builds for 32 bit Windows, so that binary could not be brought past 4.4.1 by any route, and 32 bit x86 Linux goes with it. Stay on 3.6.0 if you need either. 32 bit ARM is not affected and remains supported.
  • jave-nativebin-osx64, for intel macs, is deprecated. Apple ends support for intel hardware with macOS 27. It is still built, still published and still part of jave-all-deps, so nothing breaks today, but it will be removed in a later release. On apple silicon use jave-nativebin-osxm1.

ffmpeg 9.0.1 everywhere

Package ffmpeg
jave-nativebin-win64 9.0.1
jave-nativebin-win-arm64 9.0.1 — new
jave-nativebin-osx64 9.0.1 — deprecated
jave-nativebin-osxm1 9.0.x
jave-nativebin-linux64 9.0.1
jave-nativebin-linux-arm64 9.0.1
jave-nativebin-linux-arm32 9.0.1

The linux binaries are built from source now. The static builds this project always used are no longer reachable, and every remaining publisher links dynamically against glibc 2.28 or newer, which would have dropped older distributions and musl based images. Ours are compiled against musl and linked fully statically, so they carry no interpreter and no libc dependency at all and run anywhere the old ones did, and in Alpine containers besides, which the old ones could not. Each build is checked before it is accepted: that it is static, that it runs on glibc, that https and every codec the test suite uses are present, that it can transcode, and that it has not lost an encoder against the binary it replaces.

New

  • jave-nativebin-win-arm64, for windows on arm. Part of jave-all-deps, and needs no code change on your side.
  • jave-bom, a bill of materials. Import it and declare the jave artifacts without versions, so a project picking its own platform packages cannot end up with a core and a native binary from different releases (#273).
  • Encoder.getOptionAtIndex(). Reading an option was already possible, through a method called setOptionAtIndex that returns one, which is presumably why nobody found it. The old name still works and is deprecated (#180).

Fixed

Several of these broke against any modern ffmpeg, not just the bundled one, so they affected anyone pointing JAVE at their own binary:

  • setVolume killed the encoding. It was passed as -vol, which ffmpeg has removed, so the run died on Unrecognized option 'vol'. It now uses the volume filter, with the value converted from the 256 based scale, so callers change nothing (#44).
  • setVsync failed the same way. -vsync was removed in favour of -fps_mode, and the two never overlap, so the option is now chosen from what the ffmpeg in front of it actually accepts.
  • getSupportedEncodingFormats() and getSupportedDecodingFormats() returned nothing at all on ffmpeg 5 and newer, silently. They looked for a header ffmpeg renamed.
  • Concatenating sources threw a NullPointerException in any progress listener that read what it was handed, because the source information is read from a single input and sourceInfo was called with null regardless (#178).
  • AMR encoding works (#265), and animated webp (#253), both verified against the shipped binary.

Documented

  • SECURITY.md sets out where the boundary lies between this library and the application calling it, and answers CVE-2023-48909 with the two properties that make its claim untrue: no shell is ever spawned, and paths are absolutised so they cannot be read as ffmpeg options. Both are covered by tests that fail if either stops being true.
  • Album art survives an audio conversion if you ask for the video stream and ask for it unchanged. Cover art is a video stream, so an audio only encoding drops it by definition. In Examples.md, with tests (#266).

Upgrading

If you use jave-all-deps and are not on 32 bit x86, change the version and nothing else.

<dependency>
    <groupId>ws.schild</groupId>
    <artifactId>jave-all-deps</artifactId>
    <version>4.0.0</version>
</dependency>

If you pick platform packages yourself, consider the BOM:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-bom</artifactId>
            <version>4.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Full changelog: https://github.com/a-schild/jave2/blob/master/Changelog.md

3.6.0 release

Choose a tag to compare

@a-schild a-schild released this 17 Aug 07:25

New features

  • Video rotationMultimediaInfo.getRotate() reports the angle, in degrees clockwise, that cameras record in the stream metadata when filming in a non native orientation. It is 0 when the file carries no rotation. Thanks to @JinLike (#275).
  • Finished callbackEncoderProgressListener.done() is called once an encoding has completed successfully. It is a default method, so existing listeners keep working unchanged. Thanks to @JinLike (#275).
  • Stream loopingEncodingAttributes.setStreamLoop(Integer) maps to ffmpeg -stream_loop, with -1 repeating endlessly. Thanks to @supermoonie.
  • ffmpeg working directoryProcessWrapper.setExecFolder(File) sets the directory the ffmpeg process runs in, which matters for paths ffmpeg resolves itself, such as the file list of the concat demuxer. Thanks to @supermoonie.
  • Source back referenceMultimediaInfo.getMultimediaObject() points back at the object the information was read from. Thanks to @supermoonie.
  • Timeout when reading media informationMultimediaObject.getInfo(long timeoutMillis) gives up after the time you allow, so an unreachable or stalled source can no longer block the calling thread forever (#264).

Fixed

  • The extracted ffmpeg binary could be run before it was ready (#281, #236). Two races: the chmod was started but never waited for, and the binary was copied straight onto its final path, so a second process could pick up a file that was not executable yet or only half written. Both showed up as Permission denied or Cannot run program. The executable bit is now set in process, and the copy is staged in a temporary file that is made executable and only then moved into place atomically.
  • Thread safety of shared state (#179). Encoder's global option list threw a ConcurrentModificationException when it was changed while another thread was encoding, and VideoProcessor reported itself enabled because some earlier instance had been, then failed on a null encoder.
  • An unparsable source header no longer aborts an encoding. ffmpeg 4.4.x describes some containers in a way the parser does not accept. That information is only used to express progress as a percentage, so it is now a warning. Thanks to @jhsea3do (#209).
  • Documentation examples did not compile (#189). README.md and Examples.md called attrs.setFormat(...), which does not exist on EncodingAttributes, the method is setOutputFormat(...).

Build and infrastructure

  • Publishing moved to the Sonatype Central Portal. The OSSRH service at oss.sonatype.org this project published through has been retired by Sonatype. Releases and snapshots now go through the Central Portal, and the version badges in the README read from it again.
  • Snapshots are published from develop to https://central.sonatype.com/repository/maven-snapshots/.
  • All published artifacts now carry sources and javadoc jars, which the Central Portal requires.
  • CI fixed. The workflow built with JDK 11 while one module targets release 12, so it had been failing before it reached a single test. It now builds with JDK 17 and runs on master as well as develop.
  • JUnit 5 annotations now take effect. The test modules had no engine on the classpath and no pinned surefire version, so Maven fell back to a provider that ignores the annotations. @Disabled did nothing and seven tests had never run at all. Thanks to @Stickerifier for the test rework this uncovered.

Upgrading

No source changes are required. EncoderProgressListener.done() is a default method, so existing implementations compile unchanged.

<dependency>
    <groupId>ws.schild</groupId>
    <artifactId>jave-all-deps</artifactId>
    <version>3.6.0</version>
</dependency>

Full changelog: https://github.com/a-schild/jave2/blob/master/Changelog.md

3.5.0 release

Choose a tag to compare

@a-schild a-schild released this 15 Feb 17:50

Added support for Tune video attribute, thanks to rayacode

3.4.0 release

Choose a tag to compare

@a-schild a-schild released this 22 Nov 15:28
  • Added PresetEnum to API
  • Added quit encoding to api, thanks to sam80180
  • Added module name for java 9+ compatibility, thanks to Stickerifier
  • Use proper class for semaphore, thanks to leeychee
  • Updated maven build environment
    Thanks for the different contributions

3.3.1 release

Choose a tag to compare

@a-schild a-schild released this 05 Apr 15:20
  • Fixed naming of binary for OSX M1 platform
  • Windows 64bit + 32bit binary on 4.4.1 too
    -> Still missing 4.4.1 binaries for arm32 build

3.3.0 release

Choose a tag to compare

@a-schild a-schild released this 01 Apr 11:58
547c668

Release 3.2.0

Choose a tag to compare

@a-schild a-schild released this 06 Sep 09:32
  • Modified quoting for command line arguments
  • Implemented subtitle ass video filter
  • Added constructor for scaling filter which allows string expressions
  • Added constructor for scaling filter which does not require the ForceOriginalAspectRatio parameter
  • Added CropFilter
  • Added constructor for color filter which allows string expressions
  • Added support for multiple video filters in one conversion pass
  • Added enhanced meta data detection in MultiMedia object
  • Implement critical section in executable location+creation to prevent race condition (Issue #163)
  • Upgraded to ffmpeg v 4.4
  • Binaries from https://github.com/eugeneware/ffmpeg-static
  • The 32bit windows binaries remain at v4.2 since ffmpeg no longer supports the 32bit architekture
    32bit support will be removed later

3.1.1 release

Choose a tag to compare

@a-schild a-schild released this 12 Apr 15:49
  • 3.1.1
    • Modified quoting for command line arguments
  • 3.1.0
    • Added support for arm32 bit (Thanks to jmformenti)
    • Added option to use a specific quote character for command line
      options. (Thanks to topcatv)
    • Added support for multimedia metdata (Thanks to jmformenti)
    • Corrected typo in setURL method of MultimediaObject (Thanks to Pyjou)