diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01cfb59..79d74e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,6 +106,11 @@ jobs: if: steps.jars.outputs.cache-hit != 'true' run: ./src/FFmpegKit.Android/Jars/FetchJars.sh "${{ steps.native.outputs.version }}" + # Runs on cache hits too: the fetch already verifies fresh downloads, but a restored + # cache is only as trustworthy as its last write. Seconds of hashing per build. + - name: Verify FFmpegKit binaries + run: ./src/FFmpegKit.Android/Jars/FetchJars.sh --verify "${{ steps.native.outputs.version }}" + - name: Pack all variants run: ./src/FFmpegKit.Android/BuildNugets.sh "${{ inputs.version }}" "${{ steps.native.outputs.version }}" diff --git a/Directory.Build.props b/Directory.Build.props index 3d5683a..bd0a72f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,6 +16,13 @@ 8.1.7 $(FFmpegVersion) + + 0.2.1 + Video diff --git a/README.md b/README.md index 31fac9f..f8aa191 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![NuGet](https://img.shields.io/nuget/v/FFmpegKit.Net.Video.Android?label=nuget)](https://www.nuget.org/packages/FFmpegKit.Net.Video.Android) [![release](https://github.com/sbokatuk/FFmpegKit.Android/actions/workflows/release.yml/badge.svg)](https://github.com/sbokatuk/FFmpegKit.Android/actions/workflows/release.yml) -[![Targets: net8.0 | net9.0 | net10.0](https://img.shields.io/badge/targets-net8.0%20%7C%20net9.0%20%7C%20net10.0-512BD4)](#packages) +[![Targets: net8.0 | net9.0 | net10.0](https://img.shields.io/badge/targets-net8.0%20%7C%20net9.0%20%7C%20net10.0-512BD4)](#installation) [![ffmpeg 8.1.2](https://img.shields.io/badge/ffmpeg-8.1.2-632CA6)](#about) -[![Licence: MIT AND LGPL-3.0 or GPL-3.0](https://img.shields.io/badge/licence-MIT%20AND%20LGPL--3.0%20or%20GPL--3.0-orange)](#licence) +[![Licence: MIT AND LGPL-3.0 or GPL-3.0](https://img.shields.io/badge/licence-MIT%20AND%20LGPL--3.0%20or%20GPL--3.0-orange)](#license) .NET for Android and .NET MAUI bindings for the native **FFmpegKit** library. @@ -34,6 +34,8 @@ So the `.aar` files come from the community fork, via Maven Central — see [`Fe The version is set by `FFmpegKitNativeVersion` in [`Directory.Build.props`](Directory.Build.props), which `FetchJars.sh` reads, so the download and the `.aar` the project expects cannot drift apart. +Every download is verified against a SHA-256 baseline committed at [`build/checksums/`](build/checksums) — one file per FFmpegKit line, recorded once with [`build/update-checksums.sh`](build/update-checksums.sh) (from Maven Central's own `.sha256` sidecars where published) and enforced on every fetch, locally and in CI. A corrupted or substituted artifact fails the fetch instead of ending up inside a shipped package. + The fork currently publishes three lines, each with all eight variants: | FFmpegKit | Bundled FFmpeg | ABIs | minSdk | @@ -46,6 +48,8 @@ The fork currently publishes three lines, each with all eight variants: None of them ship 32-bit binaries, so dropping back to an older line does not restore `armeabi-v7a` or `x86` support. +Both constraints are surfaced at build time in consuming apps by a small `.targets` file every package carries: **`FFMPEGKIT001`** warns when `SupportedOSPlatformVersion` is below 24, **`FFMPEGKIT002`** when the build includes 32-bit runtime identifiers (`android-arm`, `android-x86`) — each otherwise only fails on a device, when `libffmpegkit.so` does not load. Both are ordinary MSBuild warnings, suppressible per project via `NoWarn` for apps that gate FFmpegKit usage at runtime. + ### Releasing an older line Tags name the FFmpeg version: **`v6.1.6.4` publishes `6.1.6.4`** and builds against the FFmpegKit release that packages FFmpeg 6.1.6, looked up in [`build/native-versions.tsv`](build/native-versions.tsv). The fourth component is the binding revision and any prerelease suffix is ignored when resolving (`v8.1.2.4-rc.1` → FFmpeg `8.1.2`). A tag naming a version that is not in the mapping fails the release with the list of known builds, so an old-style tag like `v8.1.7.4` cannot be published by mistake. No branch or `Directory.Build.props` edit is needed. @@ -148,6 +152,12 @@ Execute your FFmpeg command FFmpegKit.Execute("-i input.mov -c:v libx264 output.mp4"); ``` +> If your app's own root namespace starts with `FFmpegKit` (say, `FFmpegKit.MyApp`), the bare +> `FFmpegKit` above resolves to your namespace instead of the class and fails to compile — add +> `using FFmpeg = Ffmpegkit.Droid.FFmpegKit;` and call `FFmpeg.Execute(...)`, as the sample and +> the device tests do. The [migration notes](#installation) explain why the namespace is not +> `FFmpegKit.*` itself. + More examples and usage can be found in the [original FFmpegKit wiki](https://github.com/arthenica/ffmpeg-kit/wiki/Android). That repository is archived, but the Java API it documents is the one these bindings expose, so it remains the reference. ### Beyond the generated binding @@ -195,6 +205,8 @@ stream.IsVideo / IsAudio FFmpegKitConfig.EnableLogCallback(log => Debug.WriteLine(log.Message)); ``` +Clear either hook by name — `FFmpegKitConfig.DisableLogCallback()` / `DisableStatisticsCallback()` — instead of the `EnableLogCallback((ILogCallback)null)` incantation. The delegates are held by FFmpegKit until cleared, so anything they capture stays alive too. + **Use managed enums.** `SessionState` and `Level` are Java enums, so they cannot be used in a `switch`, and comparing them with `==` compares managed peer references rather than the underlying constants. `ToManaged()` converts at the boundary. ### Working with user-picked files @@ -239,7 +251,7 @@ Python 3 is also needed, for the package merge step. ./src/FFmpegKit.Android/BuildNugets.sh 8.1.2.4-rc.1 # ...or with an explicit package version ``` -`FetchJars.sh` reads the FFmpegKit version from `FFmpegKitNativeVersion` in `Directory.Build.props`, the same property the `.csproj` uses to pick the `.aar`, so the two cannot drift apart. Pass a version to override it (`./FetchJars.sh 8.2.0`) when trying a newer upstream build before committing to it. +`FetchJars.sh` reads the FFmpegKit version from `FFmpegKitNativeVersion` in `Directory.Build.props`, the same property the `.csproj` uses to pick the `.aar`, so the two cannot drift apart. Pass a version to override it (`./FetchJars.sh 8.2.0`) when trying a newer upstream build before committing to it — record its checksum baseline first with `./build/update-checksums.sh 8.2.0`, since the fetch refuses to run against a line that has no committed baseline. `./FetchJars.sh --verify` re-checks the files already on disk without downloading anything. ### A single variant diff --git a/build/checksums/6.0.3.sha256 b/build/checksums/6.0.3.sha256 new file mode 100644 index 0000000..fe0648b --- /dev/null +++ b/build/checksums/6.0.3.sha256 @@ -0,0 +1,10 @@ +c6df4ed69ab9e4717fc487afe86223033cb56ec81e492e45c9258a7e647f6426 ffmpeg-kit-audio-6.0.3.aar +21240a3e9c4004fba6fa71adcab0da8fd70be1e2407ff1127dbea30c745ddd7d ffmpeg-kit-full-6.0.3.aar +80b069ac31afe428f50f8f6332aef88ced20afc4d03daf78582e62a327ab8e7b ffmpeg-kit-full-gpl-6.0.3.aar +c77da985b4ae175fefbd6ea3fd2a7048081f0e4859b9649e9707d34b32ae28f9 ffmpeg-kit-https-6.0.3.aar +3022679a5911860343bd2070203660bad30c56ab5ab670ce72e7cb44cd3fd0c6 ffmpeg-kit-https-gpl-6.0.3.aar +0cd6912de6ab35b958847bfd6ffa0b214ec1d11cd5020e4244e058f6761c8c55 ffmpeg-kit-min-6.0.3.aar +f23d29e3f70eb1d59721ba16e57c01d9bc50a45f19e16a027a2a719a62cde0fc ffmpeg-kit-min-gpl-6.0.3.aar +9522f17d5323e9f6333c85b3717e6777d1f9c205c29932c19ecc62bc3d3e16d0 ffmpeg-kit-video-6.0.3.aar +1cad0fb4dfa01755a014331b5ed199281d2c3fab5aca5c9d7abd0b41d0ec3f7b smart-exception-common-0.2.1.jar +5b96aaa5f191dedbef72fb0c38f1a2b01807920afc0d92a75a2acd6e0cc7703c smart-exception-java-0.2.1.jar diff --git a/build/checksums/7.1.6.sha256 b/build/checksums/7.1.6.sha256 new file mode 100644 index 0000000..f90f4a5 --- /dev/null +++ b/build/checksums/7.1.6.sha256 @@ -0,0 +1,10 @@ +870d2c110b05a280bf28c430e92650fa82e00bb77de5d5a85bc7e94c9d1ebad6 ffmpeg-kit-audio-7.1.6.aar +149fe79f8009d0b849818ccf370d9c4637a1198bbef4d5b2e63204dab5e2401d ffmpeg-kit-full-7.1.6.aar +0aa3e5f568274950a5524f336d9c141c539bc07a296ba5ec7fd42f098cd3a244 ffmpeg-kit-full-gpl-7.1.6.aar +e80e5ee52248a3dcb1dda0524dd784fe4bbb212dcd4d575e82e15d21a002d860 ffmpeg-kit-https-7.1.6.aar +afa1e52f817cc8d555f9ec7d6f4611347bcf686027b7a4f0325613759c8f4f92 ffmpeg-kit-https-gpl-7.1.6.aar +f6ce02f57dadd8e6ccc407f0ed0c916567714c0db8b170eda33d9b6654ac14e1 ffmpeg-kit-min-7.1.6.aar +fcf7fb115c7c930a907cc173441a445e0bae025e914579002ee9cc0245b3b773 ffmpeg-kit-min-gpl-7.1.6.aar +c2e53d6c33e195a663152b4f135a3bae96d6bf0232ef62a532eadb827f9dc803 ffmpeg-kit-video-7.1.6.aar +1cad0fb4dfa01755a014331b5ed199281d2c3fab5aca5c9d7abd0b41d0ec3f7b smart-exception-common-0.2.1.jar +5b96aaa5f191dedbef72fb0c38f1a2b01807920afc0d92a75a2acd6e0cc7703c smart-exception-java-0.2.1.jar diff --git a/build/checksums/8.1.7.sha256 b/build/checksums/8.1.7.sha256 new file mode 100644 index 0000000..4366356 --- /dev/null +++ b/build/checksums/8.1.7.sha256 @@ -0,0 +1,10 @@ +9b277de25934594635a495e4fda164b902309219a2f538827f5ecc7ed77229d6 ffmpeg-kit-audio-8.1.7.aar +c3cbc81d498175fd2aa69ee2dfe7dafbf519052a96283c2568fe5b3b16618456 ffmpeg-kit-full-8.1.7.aar +374d3734755fd4a4f2241da23b93ab0b3bcae9bd39f01cf3bf29a5731914658b ffmpeg-kit-full-gpl-8.1.7.aar +07d321247016c6a46cc81ad2c1cfb58c2503d575e6d3daebe310ff7efa538630 ffmpeg-kit-https-8.1.7.aar +48425b8deb0b885fe2f6bfdb4fc027029e072789d181935d60b538d03b5fa1b2 ffmpeg-kit-https-gpl-8.1.7.aar +591ddcf3698028e838b854dc775e60aedaf43fffd59689cb59b8af632aba3d76 ffmpeg-kit-min-8.1.7.aar +3698fe62678cbc6c7d3c2b124ce28f4f064d0f18e1ecb414a513ed9909eee73e ffmpeg-kit-min-gpl-8.1.7.aar +d37050bc77f3a2dac30c27a0ce464d499cabbb71634fb71704aa447f39db9309 ffmpeg-kit-video-8.1.7.aar +1cad0fb4dfa01755a014331b5ed199281d2c3fab5aca5c9d7abd0b41d0ec3f7b smart-exception-common-0.2.1.jar +5b96aaa5f191dedbef72fb0c38f1a2b01807920afc0d92a75a2acd6e0cc7703c smart-exception-java-0.2.1.jar diff --git a/build/update-checksums.sh b/build/update-checksums.sh new file mode 100755 index 0000000..0dbc561 --- /dev/null +++ b/build/update-checksums.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +set -e + +# Records the SHA-256 baseline that Jars/FetchJars.sh verifies every download against, +# one file per FFmpegKit line: build/checksums/.sha256. +# +# For the .aar files the digest is taken from Maven Central's .sha256 sidecar when one is +# published, so the recorded value comes from repository metadata rather than whatever this +# machine happened to download. When no sidecar exists the artifact itself is downloaded and +# hashed - trust-on-first-use, which still pins every later fetch to today's bytes. The +# smart-exception jars come from GitHub releases, which publish no digests, so they are +# always download-and-hash. +# +# Run this once when adopting a new upstream line (a new native-versions.tsv row), review the +# diff, and commit the result. It is deliberately not run by CI: the whole point is that CI +# verifies against a baseline a human recorded. +# +# Usage: +# build/update-checksums.sh # version from Directory.Build.props +# build/update-checksums.sh 7.1.6 # an older line, e.g. when adding a native-versions.tsv row + +cd "$(dirname "$0")/.." + +PROPS="Directory.Build.props" +MAVEN_BASE="https://repo1.maven.org/maven2/dev/ffmpegkit-maintained" + +read_property() { + sed -n "s:.*<$1>\(.*\).*:\1:p" "$PROPS" | head -1 +} + +require_version() { + if [ -z "$2" ]; then + echo "error: could not read $1 from $PROPS" >&2 + exit 1 + fi + case "$2" in + *[!A-Za-z0-9._-]*) + echo "error: invalid $1 '$2'" >&2 + exit 1 + ;; + esac +} + +FFMPEG_KIT_VERSION="$1" +if [ -z "$FFMPEG_KIT_VERSION" ]; then + FFMPEG_KIT_VERSION=$(read_property FFmpegKitNativeVersion) +fi +require_version FFmpegKitNativeVersion "$FFMPEG_KIT_VERSION" + +SMART_EXCEPTION_VERSION=$(read_property SmartExceptionVersion) +require_version SmartExceptionVersion "$SMART_EXCEPTION_VERSION" + +OUT="build/checksums/$FFMPEG_KIT_VERSION.sha256" +mkdir -p build/checksums + +STAGING=$(mktemp -d) +trap 'rm -rf "$STAGING"' EXIT + +sha256_of() { + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + else + sha256sum "$1" | awk '{print $1}' + fi +} + +# $1 = url, $2 = file name. Prints the digest. +digest_of() { + # A sidecar is a few dozen bytes; only fall back to the full artifact when it is absent + # or malformed. + sidecar=$(curl -fsL --retry 3 --retry-delay 2 "$1.sha256" 2>/dev/null | awk '{print $1}' | tr '[:upper:]' '[:lower:]') || sidecar="" + case "$sidecar" in + *[!0-9a-f]* | "") ;; + *) + if [ ${#sidecar} -eq 64 ]; then + echo "$sidecar" + return + fi + ;; + esac + + curl -fL --retry 3 --retry-delay 2 -o "$STAGING/$2" "$1" >&2 + sha256_of "$STAGING/$2" +} + +: > "$STAGING/baseline" + +for artifact in common java; do + name="smart-exception-$artifact-$SMART_EXCEPTION_VERSION.jar" + url="https://github.com/tanersener/smart-exception/releases/download/v$SMART_EXCEPTION_VERSION/$name" + printf '%s %s\n' "$(digest_of "$url" "$name")" "$name" >> "$STAGING/baseline" + echo "==> $name" +done + +for variant in audio full full-gpl https https-gpl min min-gpl video; do + name="ffmpeg-kit-$variant-$FFMPEG_KIT_VERSION.aar" + url="$MAVEN_BASE/ffmpeg-kit-$variant/$FFMPEG_KIT_VERSION/$name" + printf '%s %s\n' "$(digest_of "$url" "$name")" "$name" >> "$STAGING/baseline" + echo "==> $name" +done + +sort -k2 "$STAGING/baseline" > "$OUT" +echo "==> wrote $OUT" diff --git a/docs/release-notes/8.1.2.5.md b/docs/release-notes/8.1.2.5.md new file mode 100644 index 0000000..7cd04d3 --- /dev/null +++ b/docs/release-notes/8.1.2.5.md @@ -0,0 +1,23 @@ +## What's changed + +Binding revision 5 of FFmpeg `8.1.2` (FFmpegKit `8.1.7`). The native binaries are unchanged; everything below is binding, packaging and pipeline work. + +### Build-time constraint checks + +Every package now ships a small `build`/`buildTransitive` `.targets` file imported into consuming **apps**: + +- **`FFMPEGKIT001`** warns when `SupportedOSPlatformVersion` is below 24 — ahead of the manifest merger's java-worded `AMM0000` error, and still there if that error is silenced with `tools:overrideLibrary`. +- **`FFMPEGKIT002`** warns when the build includes 32-bit runtime identifiers (`android-arm`, `android-x86`). There are no 32-bit native libraries in any variant or version; without this warning the failure is `libffmpegkit.so` not loading on a 32-bit device. + +Both are ordinary MSBuild warnings with their own codes, so either can be suppressed per project via `NoWarn` by apps that gate FFmpegKit usage at runtime. Library projects are not affected. + +### API + +- The hand-written additions — async wrappers, typed media information, lambda callbacks, SAF helpers — now carry **nullable reference annotations** (`#nullable enable` per file). The generated binding surface remains unannotated, faithfully to the Java API. +- **`FFmpegKitConfig.DisableLogCallback()`** and **`DisableStatisticsCallback()`** replace the `EnableLogCallback((ILogCallback)null!)` incantation as the way to clear the global callbacks. + +### Supply chain + +- Every native download is now **verified against a SHA-256 baseline** committed at `build/checksums/`, one file per FFmpegKit line, recorded from Maven Central's own `.sha256` sidecars where published (`build/update-checksums.sh`). A corrupted or substituted artifact fails the fetch — locally and in CI, which also re-verifies restored caches. +- The `smart-exception` runtime jars' version now lives once in `Directory.Build.props`; the fetch script and the project file both read it, so the pairing can no longer drift and fail only on device. +- Downloads retry on transient network errors. diff --git a/samples/FFmpegKit.Android.Example/MainPage.xaml b/samples/FFmpegKit.Android.Example/MainPage.xaml index f1dd9e0..063ccad 100644 --- a/samples/FFmpegKit.Android.Example/MainPage.xaml +++ b/samples/FFmpegKit.Android.Example/MainPage.xaml @@ -27,6 +27,13 @@ Clicked="OnConvertClicked" HorizontalOptions="Fill" /> +