Add Linux x64 FFmpeg native bundle

This commit is contained in:
gamer147
2026-07-31 09:38:01 -04:00
parent c33b21ef82
commit 35fd461720
7 changed files with 248 additions and 25 deletions

1
.gitattributes vendored
View File

@@ -2,3 +2,4 @@ docs/opcode-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol
docs/global-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol docs/global-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol
docs/engine-ctx-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol docs/engine-ctx-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol
tools/age_opcodes_himegari.py whitespace=trailing-space,space-before-tab,cr-at-eol tools/age_opcodes_himegari.py whitespace=trailing-space,space-before-tab,cr-at-eol
*.sh text eol=lf

View File

@@ -113,8 +113,8 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
│ └── Age.Engine.Text.Windows/ optional exact Windows GDI gray-4 reference backend; kept │ └── Age.Engine.Text.Windows/ optional exact Windows GDI gray-4 reference backend; kept
│ outside the platform-neutral VM/runtime core │ outside the platform-neutral VM/runtime core
├── native/ authored native runtime boundaries ├── native/ authored native runtime boundaries
│ └── age_movie_ffmpeg/ project-owned FFmpeg C ABI, immutable Windows dependency manifest, │ └── age_movie_ffmpeg/ project-owned FFmpeg C ABI, immutable Windows/Linux dependency manifests,
│ and bootstrap/build scripts (outputs stay under disposable build/) │ and target bootstrap/build scripts (outputs stay under disposable build/)
├── tools/frida/ runtime-capture + engine-dump scripts (see tools/frida/README.md) ├── tools/frida/ runtime-capture + engine-dump scripts (see tools/frida/README.md)
└── godot/ DELIVERABLE — the Godot/C# ADV front-end (references Age.Engine └── godot/ DELIVERABLE — the Godot/C# ADV front-end (references Age.Engine
plus the optional exact Windows text adapter). Its assembly embeds plus the optional exact Windows text adapter). Its assembly embeds

View File

@@ -27,10 +27,11 @@ The VM and content pipeline are already mostly platform-neutral:
The managed FFmpeg resolver derives reserved Windows, Linux, and macOS x64/arm64 RIDs from the actual The managed FFmpeg resolver derives reserved Windows, Linux, and macOS x64/arm64 RIDs from the actual
process and rejects unreserved architectures instead of silently looking in the wrong directory. process and rejects unreserved architectures instead of silently looking in the wrong directory.
The sole movie path now uses the project-owned FFmpeg C ABI rather than a Windows multimedia API, but only a The sole movie path now uses the project-owned FFmpeg C ABI rather than a Windows multimedia API. Reproducible
Windows-x64 native bundle is built and staged today. The accepted DirectShow fallback was deleted after the Windows-x64 and Linux-x64 native bundles are built and staged; the Linux ABI and complete installed corpus pass
corpus and audible LOGO/OP/CHAPTER gates passed. There are also softer Windows assumptions that should be tested under WSL, but an exported Linux Godot executable has not yet received window/audio/font/filesystem smoke tests.
or replaced before claiming portable exports. The accepted DirectShow fallback was deleted after the corpus and audible LOGO/OP/CHAPTER gates passed. There
are also softer Windows assumptions that should be tested or replaced before claiming portable exports.
## Dependency inventory ## Dependency inventory
@@ -38,8 +39,8 @@ or replaced before claiming portable exports.
|---|---|---|---| |---|---|---|---|
| SYS4INI per-game startup profile | `Sys4AssetCatalog` parses and retains the bounded ordered startup trailer; `Sys4LogicalCanvas` applies `SCREENX`/`SCREENY` with AGE's independent `640x480` fallbacks. Native SYS4INI also carries text, ADV input/skip, save ABI/path, audio, legacy renderer, and Windows registration settings | Godot uses the selected canvas for content scaling, backbuffers/compositor bounds, primary surfaces, layout/input fallbacks, and the default windowed client. `--window-width`/`--window-height` vary only the physical client while preserving the canvas and letterbox policy. The port still does not source `CancelMesSkipOnClick`, `CoexistMesSkip`, cursor/redraw policy, or wheel action ids from the trailer | Validate the existing presentation policy on Linux/macOS window managers. Apply further semantic keys explicitly, translate save roots through the host, and classify DirectDraw/fullscreen-bit and registration/key settings as native compatibility metadata. Canonical inventory and consumers: `sys4-format-notes.md` and `engine-re.md` | | SYS4INI per-game startup profile | `Sys4AssetCatalog` parses and retains the bounded ordered startup trailer; `Sys4LogicalCanvas` applies `SCREENX`/`SCREENY` with AGE's independent `640x480` fallbacks. Native SYS4INI also carries text, ADV input/skip, save ABI/path, audio, legacy renderer, and Windows registration settings | Godot uses the selected canvas for content scaling, backbuffers/compositor bounds, primary surfaces, layout/input fallbacks, and the default windowed client. `--window-width`/`--window-height` vary only the physical client while preserving the canvas and letterbox policy. The port still does not source `CancelMesSkipOnClick`, `CoexistMesSkip`, cursor/redraw policy, or wheel action ids from the trailer | Validate the existing presentation policy on Linux/macOS window managers. Apply further semantic keys explicitly, translate save roots through the host, and classify DirectDraw/fullscreen-bit and registration/key settings as native compatibility metadata. Canonical inventory and consumers: `sys4-format-notes.md` and `engine-re.md` |
| Retained graphics presentation | Backend-neutral `GfxState`; accepted default Godot `Sprite2D` GPU stage plus the retained software pixel oracle, using runtime `ImageTexture`, canvas transforms/materials, and no native graphics API | GPU backend caches static/color-key variants, updates dynamic surfaces, handles retained range transitions, and falls back whole-frame for the legacy host screen-transition path | Godot owns D3D/Vulkan/Metal/OpenGL selection; validate shader/blend/filter behavior per target rather than adding a platform renderer | | Retained graphics presentation | Backend-neutral `GfxState`; accepted default Godot `Sprite2D` GPU stage plus the retained software pixel oracle, using runtime `ImageTexture`, canvas transforms/materials, and no native graphics API | GPU backend caches static/color-key variants, updates dynamic surfaces, handles retained range transitions, and falls back whole-frame for the legacy host screen-transition path | Godot owns D3D/Vulkan/Metal/OpenGL selection; validate shader/blend/filter behavior per target rather than adding a platform renderer |
| AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED; `0x24d` movie masks) | `FfmpegMovieDecoder` is the sole factory over the project-owned `native/age_movie_ffmpeg` ABI | Windows-x64 passes the complete 213-payload installed video/audio corpus gate plus audible LOGO/OP/CHAPTER playback and real TEST.AGF green-mask decode | Add target-specific native builds and export packaging | | AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED; `0x24d` movie masks) | `FfmpegMovieDecoder` is the sole factory over the project-owned `native/age_movie_ffmpeg` ABI | Windows-x64 and Linux-x64 both pass the complete 213-payload installed video/audio corpus gate; Windows also passes audible LOGO/OP/CHAPTER playback and real TEST.AGF green-mask decode | Add macOS native builds, distributable source-offer packaging, and Linux/macOS Godot runtime smoke gates |
| Movie integration | Each surface owns a unique playback-instance id; `MovieRuntime` owns `IMovieDecoder` from an injected factory; video-only streams use monotonic pacing while audio-bearing streams use the Godot output clock. Opcode `0x24d` redirects decoded green bytes through the platform-neutral retained rasterizer and exact managed packed-alpha mask helper | Concurrent/restarted uses of one asset have independent frame/audio/completion/teardown state. Ordinary and mask movies share VFS/FFmpeg ownership; the mask result is a backend-neutral dynamic RGBA surface consumed by either Godot renderer. Managed code is no longer Windows-annotated, while only the win-x64 native bundle exists today | Add Linux/macOS native builds and smoke gates | | Movie integration | Each surface owns a unique playback-instance id; `MovieRuntime` owns `IMovieDecoder` from an injected factory; video-only streams use monotonic pacing while audio-bearing streams use the Godot output clock. Opcode `0x24d` redirects decoded green bytes through the platform-neutral retained rasterizer and exact managed packed-alpha mask helper | Concurrent/restarted uses of one asset have independent frame/audio/completion/teardown state. Ordinary and mask movies share VFS/FFmpeg ownership; the mask result is a backend-neutral dynamic RGBA surface consumed by either Godot renderer. Managed code is no longer Windows-annotated, and target-aware builds stage either the win-x64 or Linux-x64 native bundle | Add macOS native builds and Linux/macOS in-engine smoke gates |
| Movie audio | ABI v2 returns timestamped stereo float PCM; bounded managed buffering feeds a per-playback Godot `AudioStreamGenerator` and routes native movie flags to engine buses | All 29 installed audio-bearing streams decode with signal; synchronized LOGO/OP/CHAPTER playback is audibly accepted | Treat absent, distorted, or unsynchronized audio from an audio-bearing movie as a runtime bug | | Movie audio | ABI v2 returns timestamped stereo float PCM; bounded managed buffering feeds a per-playback Godot `AudioStreamGenerator` and routes native movie flags to engine buses | All 29 installed audio-bearing streams decode with signal; synchronized LOGO/OP/CHAPTER playback is audibly accepted | Treat absent, distorted, or unsynchronized audio from an audio-bearing movie as a runtime bug |
| ADV font discovery/raster fidelity | `Age.Engine.Text` remains OS-neutral. On compatible ACP-932 Windows, `auto` selects the separate exact `Age.Engine.Text.Windows` GDI gray-4 backend. Otherwise Godot TextServer supplies Unicode masks and metrics from ordered system-font substitutions in `godot/config/himegari-text-rendering.json`; this policy is explicitly not GDI pixel-exact. Immediate `0x204`/`0x205`, live ADV, retained History, and the wait atlas all enter ordinary RGBA surfaces/retained handles | Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle. Forced exact and forced portable self-tests cover immediate, live, and History rasterization, bounded caching, complete-line materialization, retained ordering, alpha/tint/fade, transforms, clipping, capture, HIDEWIN suspension, partial erase/republication, reset, and teardown. Gameplay `Label` pools and overlay projection are gone | Produce and smoke-test Linux/macOS exports, confirm installed family availability/fallback selection there, and keep the GDI project out of non-Windows deliverables | | ADV font discovery/raster fidelity | `Age.Engine.Text` remains OS-neutral. On compatible ACP-932 Windows, `auto` selects the separate exact `Age.Engine.Text.Windows` GDI gray-4 backend. Otherwise Godot TextServer supplies Unicode masks and metrics from ordered system-font substitutions in `godot/config/himegari-text-rendering.json`; this policy is explicitly not GDI pixel-exact. Immediate `0x204`/`0x205`, live ADV, retained History, and the wait atlas all enter ordinary RGBA surfaces/retained handles | Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle. Forced exact and forced portable self-tests cover immediate, live, and History rasterization, bounded caching, complete-line materialization, retained ordering, alpha/tint/fade, transforms, clipping, capture, HIDEWIN suspension, partial erase/republication, reset, and teardown. Gameplay `Label` pools and overlay projection are gone | Produce and smoke-test Linux/macOS exports, confirm installed family availability/fallback selection there, and keep the GDI project out of non-Windows deliverables |
| Filesystem semantics | Several filename and containment comparisons use `OrdinalIgnoreCase`; installed assets are conventionally uppercase | Needs validation on case-sensitive filesystems; may hide casing or containment mistakes | Add Linux/macOS tests with mixed-case synthetic roots and use filesystem-appropriate containment rules | | Filesystem semantics | Several filename and containment comparisons use `OrdinalIgnoreCase`; installed assets are conventionally uppercase | Needs validation on case-sensitive filesystems; may hide casing or containment mistakes | Add Linux/macOS tests with mixed-case synthetic roots and use filesystem-appropriate containment rules |
@@ -72,9 +73,9 @@ VM op 0x236 (non-modal) / op 0x20f (modal) / op 0x24d (green-mask transition)
Everything before and after the selected decoder is portable. The replacement decision is an in-process Everything before and after the selected decoder is portable. The replacement decision is an in-process
FFmpeg backend behind a project-owned C ABI, not raw FFmpeg structs in Godot/C# and not a subprocess. FFmpeg FFmpeg backend behind a project-owned C ABI, not raw FFmpeg structs in Godot/C# and not a subprocess. FFmpeg
`n8.1.2-29-g703dcc25b9` is pinned by immutable release URL and SHA-256 in `n8.1.2-29-g703dcc25b9` is pinned by immutable release URL and SHA-256 in each target manifest under
`native/age_movie_ffmpeg/dependency-win64.json`; changing that pin requires rerunning the full installed-movie `native/age_movie_ffmpeg/`; changing either pin requires rerunning the full installed-movie gate. The shim
gate. The shim dynamically links an LGPL build made without GPL or nonfree components and uses dynamically links an LGPL build made without GPL or nonfree components and uses
`libavformat`, `libavcodec`, `libavutil`, `libswscale`, and `libswresample`. Release artifacts must carry `libavformat`, `libavcodec`, `libavutil`, `libswscale`, and `libswresample`. Release artifacts must carry
the matching FFmpeg source/configuration and notices required by FFmpeg's the matching FFmpeg source/configuration and notices required by FFmpeg's
[license checklist](https://ffmpeg.org/legal.html). [license checklist](https://ffmpeg.org/legal.html).
@@ -167,11 +168,10 @@ DirectShow renderer to the existing D3D target without assigning an RGB key. The
DirectShow preroll/stream-origin inference are recorded in `docs/engine-re.md`. DirectShow preroll/stream-origin inference are recorded in `docs/engine-re.md`.
Native deliverables are RID-specific and bundled with the Godot export; the runtime must not discover an Native deliverables are RID-specific and bundled with the Godot export; the runtime must not discover an
arbitrary system FFmpeg. The first implementation gate is Windows x64 because that is the current runnable arbitrary system FFmpeg. Windows x64 and Linux x64 now have pinned builds; the C ABI and loader paths also reserve
target, but the C ABI and loader paths must reserve Windows x64, Linux x64, macOS x64, and macOS arm64 from the macOS x64 and macOS arm64. Builds use shared libraries, `$ORIGIN`/`@loader_path`-style local lookup on Unix
start. Builds use shared libraries, `$ORIGIN`/`@loader_path`-style local lookup on Unix targets, recorded source targets, recorded source hashes and configure arguments, and no committed original-game data. Packaging
hashes and configure arguments, and no committed original-game data. Packaging automation is part of completing automation is part of completing the backend, including the corresponding-source/configuration offer.
the backend, not a prerequisite for the first native decode spike.
The current movie ABI is version 3. Its position-seek entry point resets both independent demux/decoder The current movie ABI is version 3. Its position-seek entry point resets both independent demux/decoder
pipelines before managed keyframe/audio preroll, so opcode `0x241` has the same bounded, synchronized behavior pipelines before managed keyframe/audio preroll, so opcode `0x241` has the same bounded, synchronized behavior
@@ -188,8 +188,19 @@ packaging. `FfmpegMovieDecoder` now adds cancellable timestamp pacing and is the
copies the shim, five required shared libraries, and FFmpeg license beside `Himegari.dll`; a natural SYSTEM4 copies the shim, five required shared libraries, and FFmpeg license beside `Himegari.dll`; a natural SYSTEM4
smoke completed 7288 ms `LOGO.AGF`, opened 106919 ms `OP.AGF`, and published frames from both without loading smoke completed 7288 ms `LOGO.AGF`, opened 106919 ms `OP.AGF`, and published frames from both without loading
DirectShow; the user subsequently confirmed both opening movies work in normal windowed playback. DirectShow DirectShow; the user subsequently confirmed both opening movies work in normal windowed playback. DirectShow
was deleted after the later synchronized-audio gate. Full was deleted after the later synchronized-audio gate. Full export/source-offer packaging, Linux Godot runtime
export/source-offer packaging and non-Windows builds remain outstanding. smoke coverage, and macOS native builds remain outstanding.
The Linux-x64 bundle landed on 2026-07-31 from the matching pinned BtbN LGPL shared build. Its bootstrap verifies
the immutable archive hash and reported FFmpeg commit; its build emits an ELF64 x86-64 ABI v3 shim plus only the
five required SONAME libraries and license under disposable `build/native/linux-x64`. The shim records
`RUNPATH=$ORIGIN`, every FFmpeg dependency resolves beside it, and the bundle's newest required glibc symbol is
2.28, matching the provider baseline enforced by the build script. A self-contained Linux corpus-gate executable
under Ubuntu 20.04 decoded all 213 installed payloads with 0 failures in 10.696 seconds. A zero-warning
`ExportRelease` Linux-target assembly build staged the shim, five libraries, and license beside `Himegari.dll`
without the Windows GDI assembly or DLLs. These gates establish Linux loader/ABI/decoder and managed staging
compatibility; they do not yet establish an exported Godot window, live audio timing, font realization, or
case-sensitive install/profile behavior.
The Windows-x64 installed-corpus gate passed on 2026-07-22 with the pinned dependency: all 213 signature-selected The Windows-x64 installed-corpus gate passed on 2026-07-22 with the pinned dependency: all 213 signature-selected
MPEG payloads opened and reached EOF under a 30-second per-item bound. It decoded all 15,788 frames across twelve MPEG payloads opened and reached EOF under a 30-second per-item bound. It decoded all 15,788 frames across twelve

View File

@@ -221,22 +221,25 @@ script/offset/opcode, that frame chain, the hottest sites in the bounded final 1
final 16-instruction sequence. This makes the last ADV locator unnecessary for identifying a post-dialogue loop; final 16-instruction sequence. This makes the last ADV locator unnecessary for identifying a post-dialogue loop;
send either the `step-limit` console block or the generated JSON. send either the `step-limit` console block or the generated JSON.
## Native FFmpeg movie shim (Windows x64) ## Native FFmpeg movie shim (Windows and Linux x64)
These PowerShell tools build the selected Windows-x64 live movie backend. The dependency manifest pins an The target-specific dependency manifests pin immutable LGPL shared FFmpeg archives and SHA-256 hashes. The
immutable LGPL shared FFmpeg archive and SHA-256. Windows tools use PowerShell/MSVC; the Linux tools run in Bash with Python 3, curl, a C compiler, binutils, and
the ordinary archive/core utilities available.
| Tool | Purpose | Run | Reads → Writes | | Tool | Purpose | Run | Reads → Writes |
|---|---|---|---| |---|---|---|---|
| `native/age_movie_ffmpeg/bootstrap-win64.ps1` | Download, hash-check, extract, and version-check the pinned FFmpeg SDK. Returns the resolved SDK root. | `.\\native\\age_movie_ffmpeg\\bootstrap-win64.ps1 [-Destination <dir>]` | `dependency-win64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` | | `native/age_movie_ffmpeg/bootstrap-win64.ps1` | Download, hash-check, extract, and version-check the pinned FFmpeg SDK. Returns the resolved SDK root. | `.\\native\\age_movie_ffmpeg\\bootstrap-win64.ps1 [-Destination <dir>]` | `dependency-win64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
| `native/age_movie_ffmpeg/build-win64.ps1` | Discover the MSVC x64 toolchain, build `age_movie_ffmpeg.dll`, and stage its exact shared-library/license dependencies. | `.\\native\\age_movie_ffmpeg\\build-win64.ps1 -SdkRoot <bootstrap-output> [-OutputDirectory <dir>]` | C ABI source + FFmpeg SDK → disposable `build/native/win-x64/` by default | | `native/age_movie_ffmpeg/build-win64.ps1` | Discover the MSVC x64 toolchain, build `age_movie_ffmpeg.dll`, and stage its exact shared-library/license dependencies. | `.\\native\\age_movie_ffmpeg\\build-win64.ps1 -SdkRoot <bootstrap-output> [-OutputDirectory <dir>]` | C ABI source + FFmpeg SDK → disposable `build/native/win-x64/` by default |
| `native/age_movie_ffmpeg/bootstrap-linux-x64.sh` | Download, SHA-256-check, extract, and version-check the pinned Linux x64 FFmpeg SDK. Prints the resolved SDK root. | `native/age_movie_ffmpeg/bootstrap-linux-x64.sh [destination]` | `dependency-linux-x64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
| `native/age_movie_ffmpeg/build-linux-x64.sh` | Build the ELF64 x86-64 shim; stage the five exact FFmpeg SONAME libraries and license; reject stale `.so` files, unresolved/nonlocal FFmpeg dependencies, a missing `$ORIGIN` runpath, or a glibc requirement newer than the pinned 2.28 baseline. | `sdk_root="$(native/age_movie_ffmpeg/bootstrap-linux-x64.sh)"` then `native/age_movie_ffmpeg/build-linux-x64.sh "$sdk_root" [output-directory]` | C ABI source + FFmpeg SDK → disposable `build/native/linux-x64/` by default |
| `tools/movie-corpus-gate` | Discover every MPEG program stream stored under an `.AGF` catalog entry; decode every video frame and, when present, every audio block through the unpaced FFmpeg session; validate independent sequence dimensions, metadata, RGBA size, stereo finite PCM, monotonic video/audio timestamps, EOF, timeout, and teardown; then emit a per-asset JSON report. Audio fields include sample rate/channels, block and PCM-frame counts, first/last PTS, signal presence, and decode time. Exit 0 means the expected corpus count and every asset passed; exit 1 is a gate failure; exit 2 means the native shim is absent. | `dotnet run --project tools/movie-corpus-gate -- --output build/movie-corpus-ffmpeg.json --expected-count 213 --max-item-ms 30000` · optional `--native-dir <dir>` | `SYS4INI.BIN` + loose/ALF VFS assets + staged FFmpeg shim → stdout progress + disposable `build/movie-corpus-ffmpeg.json` | | `tools/movie-corpus-gate` | Discover every MPEG program stream stored under an `.AGF` catalog entry; decode every video frame and, when present, every audio block through the unpaced FFmpeg session; validate independent sequence dimensions, metadata, RGBA size, stereo finite PCM, monotonic video/audio timestamps, EOF, timeout, and teardown; then emit a per-asset JSON report. Audio fields include sample rate/channels, block and PCM-frame counts, first/last PTS, signal presence, and decode time. Exit 0 means the expected corpus count and every asset passed; exit 1 is a gate failure; exit 2 means the native shim is absent. | `dotnet run --project tools/movie-corpus-gate -- --output build/movie-corpus-ffmpeg.json --expected-count 213 --max-item-ms 30000` · optional `--native-dir <dir>` | `SYS4INI.BIN` + loose/ALF VFS assets + staged FFmpeg shim → stdout progress + disposable `build/movie-corpus-ffmpeg.json` |
The managed isolated probes load from `AGE_FFMPEG_NATIVE_DIR` when set, then application-local and The managed isolated probes load from `AGE_FFMPEG_NATIVE_DIR` when set, then application-local and
`runtimes/win-x64/native` locations. Build the shim before running the `FfmpegShim*` tests; no original-game `runtimes/<rid>/native` locations. Build the target shim before running the `FfmpegShim*` tests; no original-game
movie is copied into the repository or native output. When `build/native/win-x64/age_movie_ffmpeg.dll` exists, movie is copied into the repository or native output. A target-aware `dotnet build godot/Himegari.csproj`
`dotnet build godot/Himegari.csproj` also stages the shim, its five DLL dependencies, and `FFmpeg-LICENSE.txt` stages the matching shim, five shared-library dependencies, and `FFmpeg-LICENSE.txt` beside `Himegari.dll`
beside `Himegari.dll` for development playback. when the corresponding `build/native/win-x64/` or `build/native/linux-x64/` bundle exists.
The current native movie ABI is version 3. In addition to sequential video/audio decode, it exposes a The current native movie ABI is version 3. In addition to sequential video/audio decode, it exposes a
synchronous position seek for both independent demuxers; exact video-frame selection and audio trimming are synchronous position seek for both independent demuxers; exact video-frame selection and audio trimming are

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "$script_dir/../.." && pwd)"
destination="${1:-$repo_root/build/ffmpeg-sdk}"
manifest="$script_dir/dependency-linux-x64.json"
manifest_value() {
python3 -c 'import json, sys; print(json.load(open(sys.argv[1], encoding="utf-8"))[sys.argv[2]])' \
"$manifest" "$1"
}
for command in python3 curl sha256sum tar awk; do
command -v "$command" >/dev/null 2>&1 || {
echo "required command was not found: $command" >&2
exit 1
}
done
archive="$(manifest_value archive)"
url="$(manifest_value url)"
expected_hash="$(manifest_value sha256)"
expected_version="$(manifest_value ffmpeg_version)"
archive_root="${archive%.tar.xz}"
download_dir="$repo_root/build/downloads"
archive_path="$download_dir/$archive"
sdk_root="$destination/$archive_root"
mkdir -p -- "$download_dir" "$destination"
if [[ ! -f "$archive_path" ]]; then
curl --fail --location --retry 3 --output "$archive_path" "$url"
fi
actual_hash="$(sha256sum "$archive_path" | awk '{ print $1 }')"
if [[ "$actual_hash" != "$expected_hash" ]]; then
echo "FFmpeg archive SHA-256 mismatch: expected $expected_hash, got $actual_hash" >&2
exit 1
fi
if [[ ! -d "$sdk_root" ]]; then
tar -xJf "$archive_path" -C "$destination"
fi
for required in \
"$sdk_root/include/libavformat/avformat.h" \
"$sdk_root/lib/libavformat.so" \
"$sdk_root/bin/ffmpeg" \
"$sdk_root/LICENSE.txt"; do
if [[ ! -e "$required" ]]; then
echo "FFmpeg SDK file was not found after extraction: $required" >&2
exit 1
fi
done
reported="$(LD_LIBRARY_PATH="$sdk_root/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
"$sdk_root/bin/ffmpeg" -version)"
reported="${reported%%$'\n'*}"
version_without_date="${expected_version%-????????}"
if [[ "$reported" != *"$version_without_date"* ]]; then
echo "unexpected FFmpeg build: $reported" >&2
exit 1
fi
printf '%s\n' "$sdk_root"

View File

@@ -0,0 +1,131 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "usage: $0 <ffmpeg-sdk-root> [output-directory]" >&2
exit 2
fi
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "$script_dir/../.." && pwd)"
sdk_root="$(cd -- "$1" && pwd)"
output_directory_input="${2:-$repo_root/build/native/linux-x64}"
mkdir -p -- "$output_directory_input"
output_directory="$(cd -- "$output_directory_input" && pwd)"
include_directory="$sdk_root/include"
library_directory="$sdk_root/lib"
shim="$output_directory/libage_movie_ffmpeg.so"
manifest="$script_dir/dependency-linux-x64.json"
for command in cc readelf ldd python3 sort; do
command -v "$command" >/dev/null 2>&1 || {
echo "required command was not found: $command" >&2
exit 1
}
done
for required in \
"$include_directory/libavformat/avformat.h" \
"$library_directory/libavformat.so" \
"$library_directory/libavcodec.so" \
"$library_directory/libavutil.so" \
"$library_directory/libswscale.so" \
"$library_directory/libswresample.so" \
"$sdk_root/LICENSE.txt"; do
if [[ ! -e "$required" ]]; then
echo "missing FFmpeg SDK file: $required" >&2
exit 1
fi
done
cc \
-std=c11 -fPIC -fvisibility=hidden -O2 -Wall -Wextra \
-I "$include_directory" \
-shared -Wl,-z,defs -Wl,-z,origin -Wl,--enable-new-dtags -Wl,-rpath,'$ORIGIN' \
-o "$shim" "$script_dir/age_movie.c" \
-L "$library_directory" \
-lavformat -lavcodec -lavutil -lswscale -lswresample
runtime_libraries=(
libavformat.so.62
libavcodec.so.62
libavutil.so.60
libswscale.so.9
libswresample.so.6
)
for runtime in "${runtime_libraries[@]}"; do
source_library="$library_directory/$runtime"
if [[ ! -e "$source_library" ]]; then
echo "missing FFmpeg runtime library: $source_library" >&2
exit 1
fi
cp --dereference -- "$source_library" "$output_directory/$runtime"
done
cp -- "$sdk_root/LICENSE.txt" "$output_directory/FFmpeg-LICENSE.txt"
expected_outputs=(libage_movie_ffmpeg.so "${runtime_libraries[@]}")
shopt -s nullglob
for existing in "$output_directory"/*.so*; do
existing_name="$(basename -- "$existing")"
expected=false
for expected_name in "${expected_outputs[@]}"; do
if [[ "$existing_name" == "$expected_name" ]]; then
expected=true
break
fi
done
if [[ "$expected" != true ]]; then
echo "unexpected stale shared library in output directory: $existing" >&2
exit 1
fi
done
if ! readelf -h "$shim" | grep -Eq 'Class:[[:space:]]+ELF64'; then
echo "native movie shim is not an ELF64 binary: $shim" >&2
exit 1
fi
if ! readelf -h "$shim" | grep -Eq 'Machine:[[:space:]]+Advanced Micro Devices X86-64'; then
echo "native movie shim is not an x86-64 binary: $shim" >&2
exit 1
fi
if ! readelf -d "$shim" | grep -Fq '$ORIGIN'; then
echo "native movie shim does not have an \$ORIGIN runtime search path: $shim" >&2
exit 1
fi
ldd_output="$(ldd "$shim")"
if [[ "$ldd_output" == *"not found"* ]]; then
printf '%s\n' "$ldd_output" >&2
echo "native movie bundle has unresolved shared-library dependencies" >&2
exit 1
fi
for runtime in "${runtime_libraries[@]}"; do
resolution="$(printf '%s\n' "$ldd_output" | grep -F "$runtime =>" || true)"
if [[ "$resolution" != *"=> $output_directory/$runtime "* ]]; then
printf '%s\n' "$ldd_output" >&2
echo "$runtime did not resolve from the bundle directory" >&2
exit 1
fi
done
minimum_glibc="$(python3 -c \
'import json, sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["minimum_glibc"])' \
"$manifest")"
versioned_outputs=("$shim")
for runtime in "${runtime_libraries[@]}"; do
versioned_outputs+=("$output_directory/$runtime")
done
highest_glibc="$(readelf --version-info "${versioned_outputs[@]}" \
| grep -Eo 'GLIBC_[0-9]+(\.[0-9]+)*' | sort -Vu | tail -n 1 || true)"
if [[ -z "$highest_glibc" ]]; then
echo "could not determine the bundle's glibc symbol-version requirement" >&2
exit 1
fi
highest_glibc="${highest_glibc#GLIBC_}"
newest_glibc="$(printf '%s\n%s\n' "$minimum_glibc" "$highest_glibc" | sort -Vu | tail -n 1)"
if [[ "$newest_glibc" != "$minimum_glibc" ]]; then
echo "bundle requires glibc $highest_glibc, newer than the pinned $minimum_glibc baseline" >&2
exit 1
fi
printf '%s\n' "$shim"

View File

@@ -0,0 +1,12 @@
{
"provider": "BtbN/FFmpeg-Builds",
"release_tag": "autobuild-2026-07-21-13-38",
"archive": "ffmpeg-n8.1.2-29-g703dcc25b9-linux64-lgpl-shared-8.1.tar.xz",
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-21-13-38/ffmpeg-n8.1.2-29-g703dcc25b9-linux64-lgpl-shared-8.1.tar.xz",
"sha256": "d0026954f10d303d1fc6517ed6b2290c42323b3958f11baafb1b330dfc564b36",
"ffmpeg_version": "n8.1.2-29-g703dcc25b9-20260721",
"ffmpeg_commit": "703dcc25b9",
"variant": "linux64 LGPL shared 8.1",
"minimum_glibc": "2.28",
"minimum_linux_kernel": "4.18"
}