Releases: a-schild/jave2
Release list
4.2.0 release
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
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.
- Usage, rewritten
- Examples, and Examples.md, which now carry the same content
- Encoding Attributes, regenerated from the source, every setter listed
- Custom ffmpeg arguments, a new page
- Supported formats, generated from the bundled ffmpeg 9.0.1
- Developers guide lines, expanded from seven lines
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
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-win32andjave-nativebin-linux32are 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 ofjave-all-deps, so nothing breaks today, but it will be removed in a later release. On apple silicon usejave-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 ofjave-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 calledsetOptionAtIndexthat 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:
setVolumekilled the encoding. It was passed as-vol, which ffmpeg has removed, so the run died onUnrecognized option 'vol'. It now uses the volume filter, with the value converted from the 256 based scale, so callers change nothing (#44).setVsyncfailed the same way.-vsyncwas 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()andgetSupportedDecodingFormats()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
sourceInfowas 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
New features
- Video rotation —
MultimediaInfo.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 callback —
EncoderProgressListener.done()is called once an encoding has completed successfully. It is adefaultmethod, so existing listeners keep working unchanged. Thanks to @JinLike (#275). - Stream looping —
EncodingAttributes.setStreamLoop(Integer)maps to ffmpeg-stream_loop, with-1repeating endlessly. Thanks to @supermoonie. - ffmpeg working directory —
ProcessWrapper.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 reference —
MultimediaInfo.getMultimediaObject()points back at the object the information was read from. Thanks to @supermoonie. - Timeout when reading media information —
MultimediaObject.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
chmodwas 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 asPermission deniedorCannot 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 aConcurrentModificationExceptionwhen it was changed while another thread was encoding, andVideoProcessorreported 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.mdandExamples.mdcalledattrs.setFormat(...), which does not exist onEncodingAttributes, the method issetOutputFormat(...).
Build and infrastructure
- Publishing moved to the Sonatype Central Portal. The OSSRH service at
oss.sonatype.orgthis 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
developtohttps://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
masteras well asdevelop. - 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.
@Disableddid 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
Added support for Tune video attribute, thanks to rayacode
3.4.0 release
- 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
- 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
- Upgraded slf4j libraries
- Added options to get/set options by index, thanks to HANXU2018
- Upgraded binaries to 4.4.1 release
OS-X releases from https://www.osxexperts.net/
Linux binaries from https://johnvansickle.com/ffmpeg/
-> Windows and arm32 bit builds still on 4.4.0 release - Moved development to it's own branch
- Implemented first support for apple m1 chip on OS-X (Needs to be tested)
- Added win 32 ffmpeg 4.4 static release https://www.notion.so/34dc4ddf501a4b98b46ea9fb4f3470af?v=878345c5d88f4d21a6520db752b5c29f
Release 3.2.0
- 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
- 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)