feat: publish av-tools 0.2.0

This commit is contained in:
2026-07-27 01:04:21 +02:00
parent fcc537b024
commit 0a4548851c
68 changed files with 4130 additions and 605 deletions

View File

@@ -0,0 +1,420 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-3.0-or-later
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
scripts/build-reviewed-ffmpeg-core.sh st|mt SOURCE_ARCHIVE EMSDK_ROOT [JOBS]
Builds one private, staged FFmpeg WebAssembly core from the exact
0.12.10 Corresponding Source archive. The only upstream source change is
ffmpeg.wasm commit b409e36475bc21f0451b5b1e1d126fa82871439a:
`-sSTACK_SIZE=5MB`, which fixes upstream issues #591 and #775.
Output remains below .core-build/reviewed-stack5m and is never copied into the
application automatically. Re-running resumes at verified component stamps.
EOF
}
if [[ $# -lt 3 || $# -gt 4 ]]; then
usage >&2
exit 2
fi
MODE=$1
SOURCE_ARCHIVE=$2
EMSDK_ROOT=$3
JOBS=${4:-2}
if [[ "$MODE" != "st" && "$MODE" != "mt" ]]; then
echo "Mode must be st or mt." >&2
exit 2
fi
if [[ ! "$JOBS" =~ ^[1-8]$ ]]; then
echo "JOBS must be an integer from 1 to 8." >&2
exit 2
fi
SCRIPT_DIRECTORY=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
REPOSITORY_ROOT=$(cd -- "$SCRIPT_DIRECTORY/.." && pwd -P)
SOURCE_ARCHIVE=$(realpath -- "$SOURCE_ARCHIVE")
EMSDK_ROOT=$(realpath -- "$EMSDK_ROOT")
EXPECTED_ARCHIVE_SHA256=2ab505f72be883d1257bc71cbb93880d1ade5bc029792621cb87b4ecc068399a
EXPECTED_EMCC_SHA256=b0c9551f9155fa9c028efa24591acdfd30f02f32fb247cf9266f5f0f2ede7589
EXPECTED_CMAKE_SHA256=ade9326f83f4996b17e2cb1bbfe4848df30a237ef5769cd3c00239b9f0d3ec58
EXPECTED_SOURCE_ROOT=ffmpeg-core-0.12.10-reviewed-stack5m.1-corresponding-source
SOURCE_DATE_EPOCH=1736208000
if [[ ! -f "$SOURCE_ARCHIVE" || -L "$SOURCE_ARCHIVE" ]]; then
echo "SOURCE_ARCHIVE must be a regular, non-symlink file." >&2
exit 2
fi
if [[ ! -d "$EMSDK_ROOT" || -L "$EMSDK_ROOT" ]]; then
echo "EMSDK_ROOT must be a real directory." >&2
exit 2
fi
actual_archive_sha256=$(sha256sum -- "$SOURCE_ARCHIVE" | cut -d' ' -f1)
if [[ "$actual_archive_sha256" != "$EXPECTED_ARCHIVE_SHA256" ]]; then
echo "Corresponding Source archive SHA-256 mismatch." >&2
exit 1
fi
# shellcheck disable=SC1091
source "$EMSDK_ROOT/emsdk_env.sh" >/dev/null
export PATH="$REPOSITORY_ROOT/scripts/build-tools:$PATH"
if [[ -z "${AV_REVIEWED_CMAKE_BIN:-}" ]]; then
echo "AV_REVIEWED_CMAKE_BIN must identify the exact CMake 3.27.9 binary directory." >&2
exit 2
fi
AV_REVIEWED_CMAKE_BIN=$(realpath -- "$AV_REVIEWED_CMAKE_BIN")
if [[ ! -d "$AV_REVIEWED_CMAKE_BIN" || -L "$AV_REVIEWED_CMAKE_BIN" ]]; then
echo "AV_REVIEWED_CMAKE_BIN must be a real directory." >&2
exit 2
fi
if [[ $(sha256sum -- "$AV_REVIEWED_CMAKE_BIN/cmake" | cut -d' ' -f1) != "$EXPECTED_CMAKE_SHA256" ]]; then
echo "CMake binary SHA-256 mismatch; exact PyPI CMake 3.27.9 is required." >&2
exit 1
fi
export PATH="$AV_REVIEWED_CMAKE_BIN:$PATH"
EMCC=$(realpath -- "$EMSDK_ROOT/upstream/emscripten/emcc")
actual_emcc_sha256=$(sha256sum -- "$EMCC" | cut -d' ' -f1)
if [[ "$actual_emcc_sha256" != "$EXPECTED_EMCC_SHA256" ]]; then
echo "Emscripten compiler SHA-256 mismatch; activate exact version 3.1.40." >&2
exit 1
fi
if ! "$EMCC" --version | head -1 | grep -Fq \
"3.1.40 (5c27e79dd0a9c4e27ef2326841698cdd4f6b5784)"; then
echo "Emscripten identity mismatch; exact version 3.1.40 is required." >&2
exit 1
fi
if [[ $(cmake --version | head -1) != "cmake version 3.27.9" ]]; then
echo "CMake identity mismatch; exact version 3.27.9 is required." >&2
exit 1
fi
BUILD_PARENT="$REPOSITORY_ROOT/.core-build/reviewed-stack5m"
MODE_ROOT="$BUILD_PARENT/$MODE"
EXTRACT_ROOT="$MODE_ROOT/source"
SOURCE_ROOT="$EXTRACT_ROOT/$EXPECTED_SOURCE_ROOT/sources"
INSTALL_DIR="$MODE_ROOT/install"
OUTPUT_DIR="$MODE_ROOT/output"
STAMP_DIR="$MODE_ROOT/stamps"
case "$MODE_ROOT" in
"$REPOSITORY_ROOT"/.core-build/reviewed-stack5m/st | \
"$REPOSITORY_ROOT"/.core-build/reviewed-stack5m/mt) ;;
*)
echo "Refusing an unexpected build directory: $MODE_ROOT" >&2
exit 1
;;
esac
mkdir -p -- "$MODE_ROOT" "$STAMP_DIR"
if [[ ! -f "$STAMP_DIR/source-prepared" ]]; then
if [[ -e "$EXTRACT_ROOT" ]]; then
echo "Incomplete source staging already exists at $EXTRACT_ROOT." >&2
echo "Remove only that private mode directory to restart it." >&2
exit 1
fi
mkdir -p -- "$EXTRACT_ROOT"
tar -xf "$SOURCE_ARCHIVE" -C "$EXTRACT_ROOT"
lock="$EXTRACT_ROOT/$EXPECTED_SOURCE_ROOT/SOURCE_LOCK.json"
node -e '
const fs = require("node:fs");
const lock = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const expected = new Map([
["ffmpeg.wasm", "71aa99d37c02a7b4c435275ca9ef50e612f6efa1"],
["FFmpeg", "4729204c17f756e186d622060088371d10b34f7e"],
["Opus", "e85ed7726db5d677c9c0677298ea0cb9c65bdd23"],
]);
for (const [name, revision] of expected) {
const entry = lock.sources.find((candidate) => candidate.name === name);
if (!entry || entry.revision !== revision) {
throw new Error(`Unexpected ${name} source identity.`);
}
}
' "$lock"
wasm_build_script="$SOURCE_ROOT/ffmpeg.wasm/build/ffmpeg-wasm.sh"
if grep -Fq -- "-sSTACK_SIZE=" "$wasm_build_script"; then
echo "Base archive unexpectedly already selects a stack size." >&2
exit 1
fi
sed -i \
'/-sUSE_SDL=2/a\ -sSTACK_SIZE=5MB # upstream b409e364: support libopus' \
"$wasm_build_script"
if [[ $(grep -Fc -- "-sSTACK_SIZE=5MB" "$wasm_build_script") -ne 1 ]]; then
echo "Could not apply the reviewed one-line stack patch exactly once." >&2
exit 1
fi
touch "$STAMP_DIR/source-prepared"
fi
# The upstream container uses unbounded `make -j`. Keep local builds bounded
# without changing compiled source, and allow a resumed build to select a new
# bounded job count.
while IFS= read -r -d '' build_script; do
sed -E -i \
-e "s/ -j([1-8])?$/ -j$JOBS/" \
-e "s/ -j([1-8])? / -j$JOBS /g" \
"$build_script"
done < <(find "$SOURCE_ROOT/ffmpeg.wasm/build" -type f -name '*.sh' -print0)
# The upstream container does not provide native NASM. Some development hosts
# do; allowing CMake to discover it would produce native x86 objects that
# cannot be linked into WebAssembly. Make the container's effective choice
# explicit and deterministic.
X265_BUILD_SCRIPT="$SOURCE_ROOT/ffmpeg.wasm/build/x265.sh"
if ! grep -Fq -- "-DENABLE_ASSEMBLY=OFF" "$X265_BUILD_SCRIPT"; then
sed -i \
'/-DENABLE_LIBNUMA=OFF/a\ -DENABLE_ASSEMBLY=OFF' \
"$X265_BUILD_SCRIPT"
fi
if [[ $(grep -Fc -- "-DENABLE_ASSEMBLY=OFF" "$X265_BUILD_SCRIPT") -ne 1 ]]; then
echo "Could not disable native x265 assembly exactly once." >&2
exit 1
fi
# Upstream builds each dependency in an isolated container stage. A cumulative
# local prefix would otherwise let FreeType discover the already-built zlib and
# let HarfBuzz discover FreeType. Their resulting private dependencies are not
# present in FFmpeg's pkg-config probes and, on a developer workstation, optional
# libraries such as libpng may even resolve to host-native /usr objects. Make the
# isolated-stage dependency choices explicit.
FREETYPE_BUILD_SCRIPT="$SOURCE_ROOT/ffmpeg.wasm/build/freetype2.sh"
if ! grep -Fq -- "--with-zlib=no" "$FREETYPE_BUILD_SCRIPT"; then
sed -i \
'/--without-harfbuzz/a\ --with-zlib=no\n --with-bzip2=no\n --with-png=no\n --with-brotli=no' \
"$FREETYPE_BUILD_SCRIPT"
fi
for freetype_flag in \
"--without-harfbuzz" \
"--with-zlib=no" \
"--with-bzip2=no" \
"--with-png=no" \
"--with-brotli=no"; do
if [[ $(grep -Fc -- "$freetype_flag" "$FREETYPE_BUILD_SCRIPT") -ne 1 ]]; then
echo "Could not select isolated FreeType dependency flags exactly once." >&2
exit 1
fi
done
HARFBUZZ_BUILD_SCRIPT="$SOURCE_ROOT/ffmpeg.wasm/build/harfbuzz.sh"
for harfbuzz_flag in \
"--with-freetype=no" \
"--with-glib=no" \
"--with-gobject=no" \
"--with-cairo=no" \
"--with-icu=no" \
"--with-graphite2=no" \
"--with-uniscribe=no" \
"--with-directwrite=no" \
"--with-coretext=no"; do
if ! grep -Fq -- "$harfbuzz_flag" "$HARFBUZZ_BUILD_SCRIPT"; then
sed -i \
"/--enable-static/a\\ $harfbuzz_flag" \
"$HARFBUZZ_BUILD_SCRIPT"
fi
if [[ $(grep -Fc -- "$harfbuzz_flag" "$HARFBUZZ_BUILD_SCRIPT") -ne 1 ]]; then
echo "Could not select isolated HarfBuzz dependency flags exactly once." >&2
exit 1
fi
done
mkdir -p -- "$INSTALL_DIR" "$OUTPUT_DIR"
export INSTALL_DIR
export EM_PKG_CONFIG_PATH="$EMSDK/upstream/emscripten/system/lib/pkgconfig:$INSTALL_DIR/lib/pkgconfig"
export EM_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
export PKG_CONFIG_PATH="$INSTALL_DIR/lib/pkgconfig:$EM_PKG_CONFIG_PATH"
export LANG=C
export LC_ALL=C
export TZ=UTC
export SOURCE_DATE_EPOCH
export ZERO_AR_DATE=1
MODE_FLAGS="-O3 -msimd128"
if [[ "$MODE" == "st" ]]; then
export FFMPEG_ST=yes
unset FFMPEG_MT || true
else
MODE_FLAGS="$MODE_FLAGS -sUSE_PTHREADS -pthread"
# HarfBuzz's pinned upstream build script reads this value directly under
# `set -u`, while the other scripts use its presence to disable threading.
# An exported empty value keeps both behaviours correct for the MT build.
export FFMPEG_ST=
export FFMPEG_MT=yes
fi
export CFLAGS="-I$INSTALL_DIR/include $MODE_FLAGS"
export CXXFLAGS="$CFLAGS"
export LDFLAGS="-L$INSTALL_DIR/lib $CFLAGS"
FFMPEG_WASM="$SOURCE_ROOT/ffmpeg.wasm"
build_component() {
local stamp=$1
local directory=$2
local script=$3
if [[ -f "$STAMP_DIR/$stamp" ]]; then
echo "[reviewed-core:$MODE] reuse $stamp"
return
fi
echo "[reviewed-core:$MODE] build $stamp"
(
cd -- "$SOURCE_ROOT/$directory"
if [[ "$stamp" == "libvpx" ]]; then
# libvpx embeds its configure command in the linked library. A relative
# prefix keeps that shipped identity independent of the checkout path
# while still installing into this mode's private prefix.
INSTALL_DIR=../../../../install \
CFLAGS="-I../../../../install/include $MODE_FLAGS" \
CXXFLAGS="-I../../../../install/include $MODE_FLAGS" \
LDFLAGS="-L../../../../install/lib -I../../../../install/include $MODE_FLAGS" \
bash "$FFMPEG_WASM/build/$script"
else
bash "$FFMPEG_WASM/build/$script"
fi
)
touch "$STAMP_DIR/$stamp"
}
if [[ ! -f "$STAMP_DIR/sdl2" ]]; then
if [[ "$MODE" == "st" ]]; then
embuilder build sdl2
else
embuilder build sdl2-mt
fi
touch "$STAMP_DIR/sdl2"
fi
build_component x264 x264 x264.sh
build_component x265 x265 x265.sh
build_component libvpx libvpx libvpx.sh
build_component lame lame lame.sh
build_component ogg ogg ogg.sh
build_component theora theora theora.sh
build_component opus opus opus.sh
build_component vorbis vorbis vorbis.sh
build_component zlib zlib zlib.sh
build_component libwebp libwebp libwebp.sh
build_component freetype2 freetype2 freetype2.sh
build_component fribidi fribidi fribidi.sh
build_component harfbuzz harfbuzz harfbuzz.sh
build_component libass libass libass.sh
build_component zimg zimg zimg.sh
# Configuration probes from Autotools projects must never smuggle host-native
# include/library paths into the reviewed WebAssembly link.
if grep -R -E -n -- \
'/usr/(lib|include)|/lib/(x86_64|i[3-6]86)-linux-gnu' \
"$INSTALL_DIR/lib/pkgconfig" "$INSTALL_DIR/lib"/*.la; then
echo "A staged dependency recorded a host-native build path." >&2
exit 1
fi
if emnm "$INSTALL_DIR/lib/libfreetype.a" |
grep -F -- " U png_" >/dev/null; then
echo "FreeType unexpectedly retained an unbundled libpng dependency." >&2
exit 1
fi
if emnm "$INSTALL_DIR/lib/libfreetype.a" |
grep -E -- ' U (inflate|BZ2_|Brotli)' >/dev/null; then
echo "FreeType unexpectedly retained a cross-stage compression dependency." >&2
exit 1
fi
FFMPEG_SOURCE="$SOURCE_ROOT/FFmpeg"
# FFmpeg records --extra-cflags in `ffmpeg -buildconf`. Use a path relative to
# its source root so the shipped runtime identity is independent of the
# maintainer's checkout location. Dependency builds above retain their
# absolute private staging prefix; only the final FFmpeg configuration is
# embedded in the distributed core.
FFMPEG_INSTALL_RELATIVE=../../../../install
export CFLAGS="-I$FFMPEG_INSTALL_RELATIVE/include $MODE_FLAGS"
export CXXFLAGS="$CFLAGS"
export LDFLAGS="-L$INSTALL_DIR/lib $CFLAGS"
if [[ ! -f "$STAMP_DIR/ffmpeg" ]]; then
echo "[reviewed-core:$MODE] build FFmpeg"
(
cd -- "$FFMPEG_SOURCE"
bash "$FFMPEG_WASM/build/ffmpeg.sh" \
--enable-gpl \
--enable-libx264 \
--enable-libx265 \
--enable-libvpx \
--enable-libmp3lame \
--enable-libtheora \
--enable-libvorbis \
--enable-libopus \
--enable-zlib \
--enable-libwebp \
--enable-libfreetype \
--enable-libfribidi \
--enable-libass \
--enable-libzimg
)
touch "$STAMP_DIR/ffmpeg"
fi
if [[ ! -f "$STAMP_DIR/core" ]]; then
echo "[reviewed-core:$MODE] link ffmpeg-core with 5 MiB stack"
mkdir -p -- "$FFMPEG_SOURCE/src"
cp -a -- "$FFMPEG_WASM/src/bind" "$FFMPEG_SOURCE/src/"
cp -a -- "$FFMPEG_WASM/src/fftools" "$FFMPEG_SOURCE/src/"
cp -- "$FFMPEG_WASM/build/ffmpeg-wasm.sh" "$FFMPEG_SOURCE/build-reviewed-core.sh"
(
cd -- "$FFMPEG_SOURCE"
bash ./build-reviewed-core.sh \
-lx264 \
-lx265 \
-lvpx \
-lmp3lame \
-logg \
-ltheora \
-lvorbis \
-lvorbisenc \
-lvorbisfile \
-lopus \
-lz \
-lwebpmux \
-lwebp \
-lsharpyuv \
-lfreetype \
-lfribidi \
-lharfbuzz \
-lass \
-lzimg \
-sEXPORT_ES6 \
-o "$OUTPUT_DIR/ffmpeg-core.js"
)
touch "$STAMP_DIR/core"
fi
expected_files=(ffmpeg-core.js ffmpeg-core.wasm)
if [[ "$MODE" == "mt" ]]; then
expected_files+=(ffmpeg-core.worker.js)
fi
for expected_file in "${expected_files[@]}"; do
if [[ ! -s "$OUTPUT_DIR/$expected_file" || -L "$OUTPUT_DIR/$expected_file" ]]; then
echo "Missing staged output: $OUTPUT_DIR/$expected_file" >&2
exit 1
fi
if grep -aFq -- "$REPOSITORY_ROOT" "$OUTPUT_DIR/$expected_file" ||
grep -aFq -- "$EMSDK_ROOT" "$OUTPUT_DIR/$expected_file"; then
echo "Staged output leaks a maintainer-local build path: $expected_file" >&2
exit 1
fi
done
(
cd -- "$OUTPUT_DIR"
sha256sum -- "${expected_files[@]}" >SHA256SUMS
)
echo "[reviewed-core:$MODE] staged outputs"
cat "$OUTPUT_DIR/SHA256SUMS"