#!/usr/bin/env bash # SPDX-License-Identifier: MIT # Copyright (c) 2026 AArts Lab # # Builds the LGPL FFmpeg the Linux packages of Bernard Review bundle -- FFmpeg 8.1.3 (libavcodec 62, # libavformat 62, libavutil 60, libswscale 9, libswresample 6), LGPL version 2.1 or later -- into the same # prefix as the OpenEXR 3.4 / OpenImageIO 3.1 stack. The distributions' FFmpeg is GPL (Ubuntu 26.04's # libavcodec reports "GPL version 2 or later", RPM Fusion's ffmpeg-libs 5.1 on Rocky 9 "GPL version 3 or # later"), so the packages no longer depend on it. tools/linux/build_deps_exr34.sh runs this as its last # stage; on its own it adds FFmpeg to a prefix that script already built (a few minutes). No root. # # Usage: tools/linux/build_ffmpeg_lgpl.sh [--check] [work_dir] # --check only check the build tools and -dev packages, build nothing (exit 0 or 3) # the dependency prefix (e.g. ~/deps-exr34, /opt/deps-exr34); FFmpeg lands in its lib/, include/ # [work_dir] where the tarballs are downloaded and built (default: -src) # JOBS= parallel jobs (default: nproc) # # Sources, pinned with the SHA-256 of each tarball as downloaded on 2026-09-28 (a mismatch stops the script): # ffmpeg.org's release tarball (its GPG signature was checked against the FFmpeg release signing key, # FCF9 86EA 15E6 E293 A564 4F10 B432 2F04 D676 58D8), the nv-codec-headers tag (MIT, header-only; NVENC / # NVDEC / CUVID load the NVIDIA driver at run time, Linux driver 550.54.14 or newer) and, when the machine has # no nasm, nasm's release tarball, built in /tools for this build only (never installed or shipped). # The FFmpeg tarball is archived in /share/bernard-sources, and /share/bernard-licenses gets # FFmpeg's COPYING.LGPLv2.1 and LICENSE.md, the nv-codec-headers notices and FFmpeg-BUILD.txt (version, # source URL, SHA-256, configure line), which the packages install with the other licence files. # # Configure: LGPL (no --enable-gpl, --enable-nonfree or --enable-version3: nothing used needs them), shared # libraries only, no programs, no documentation, no libavdevice / libavfilter (the player links neither). # --disable-autodetect, then only what is named: zlib, iconv, the NVIDIA interfaces, and VAAPI with libdrm # when libva's -dev headers are there (otherwise the build goes on without VAAPI and says so). No external # codec library at all: the decoders, demuxers and parsers are FFmpeg's own (all of them), and so are the # encoders the export uses (prores_ks, dnxhd, mjpeg, png, aac, pcm_*). Every library gets the RUNPATH $ORIGIN. # The shipped libraries must not carry the builder's home or name: FFmpeg records its configure line # (avcodec_configuration()) and source paths (__FILE__), so it is configured with the neutral prefix # /tmp/bernard-deps (a symbolic link to ) and unpacked and built under /tmp/bernard-ffmpeg-build; the # pkg-config files are then pointed back at , and the libraries are checked to hold no /home/ path. # The last step compiles a check against the installed libavcodec: avcodec_license() must say LGPL. # # Exit status: 0 built; 1 a check after the build failed; 2 usage or download error; 3 a required tool or # -dev package is missing (nothing is built; the message names the packages to install). set -euo pipefail CHECK_ONLY=0 if [ "${1:-}" = "--check" ]; then CHECK_ONLY=1; shift; fi if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then sed -n '4,40p' "$0" | sed 's/^# \{0,1\}//' exit 2 fi PREFIX=$(mkdir -p "$1" && cd "$1" && pwd) WORK=${2:-${PREFIX}-src} mkdir -p "$WORK/tarballs" WORK=$(cd "$WORK" && pwd) JOBS=${JOBS:-$(nproc)} FFMPEG_VERSION=8.1.3 FFMPEG_URL=https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz FFMPEG_SHA256=7138d28c96d9d3e3af4ee3d8cad72741f8ffb40da90c1112235dea3ecd3178a3 NVCODEC_TAG=n12.2.72.0 NVCODEC_URL=https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/${NVCODEC_TAG}.tar.gz NVCODEC_SHA256=dbeaec433d93b850714760282f1d0992b1254fc3b5a6cb7d76fc1340a1e47563 NASM_VERSION=2.16.03 NASM_URL=https://www.nasm.us/pub/nasm/releasebuilds/${NASM_VERSION}/nasm-${NASM_VERSION}.tar.xz NASM_SHA256=1412a1c760bbd05db026b6c0d1657affd6631cd0a63cddb6f73cc6d4aa616148 log() { printf '\n=== %s\n' "$*"; } DISTRO=other if [ -r /etc/os-release ]; then . /etc/os-release case " ${ID:-} ${ID_LIKE:-} " in *" debian "*|*" ubuntu "*) DISTRO=deb ;; *" rhel "*|*" fedora "*|*" centos "*|*" rocky "*) DISTRO=rpm ;; esac fi has_header() { local d for d in "$PREFIX/include" /usr/local/include /usr/include /usr/include/x86_64-linux-gnu; do [ -e "$d/$1" ] && return 0 done return 1 } has_cmd() { command -v "$1" >/dev/null 2>&1; } pkg() { if [ "$DISTRO" = rpm ]; then echo "$2"; else echo "$1"; fi; } MISSING_REQUIRED=() has_cmd make || MISSING_REQUIRED+=("$(pkg make make)") has_cmd cc || MISSING_REQUIRED+=("$(pkg gcc gcc)") has_cmd pkg-config || MISSING_REQUIRED+=("$(pkg pkg-config pkgconf-pkg-config)") has_cmd curl || MISSING_REQUIRED+=("$(pkg curl curl)") has_cmd xz || MISSING_REQUIRED+=("$(pkg xz-utils xz)") has_header zlib.h || MISSING_REQUIRED+=("$(pkg zlib1g-dev zlib-devel)") if [ ${#MISSING_REQUIRED[@]} -gt 0 ]; then echo "FFmpeg: missing tools or -dev packages, nothing built: ${MISSING_REQUIRED[*]}" >&2 exit 3 fi # VAAPI (libva with its DRM backend, and libdrm): optional, and a system dependency of the packages when built. WITH_VAAPI=0 MISSING_OPTIONAL=() if has_header va/va.h && has_header va/va_drm.h && has_header xf86drm.h; then WITH_VAAPI=1 else has_header va/va_drm.h || MISSING_OPTIONAL+=("$(pkg libva-dev libva-devel)") has_header xf86drm.h || MISSING_OPTIONAL+=("$(pkg libdrm-dev libdrm-devel)") echo "FFmpeg: VAAPI headers not found, FFmpeg is built without VAAPI (hardware decode and h264_vaapi /" \ "hevc_vaapi): install ${MISSING_OPTIONAL[*]} and run this again to have it." >&2 fi NASM_NOTE="" if ! has_cmd nasm; then NASM_NOTE="nasm not found ($(pkg nasm nasm)): nasm ${NASM_VERSION} is built from its release tarball in $WORK/tools, for this build only" echo "FFmpeg: $NASM_NOTE" >&2 fi if [ $CHECK_ONLY = 1 ]; then exit 0; fi # fetch : downloaded once into /tarballs, checked every time. fetch() { local file="$WORK/tarballs/$1" url=$2 sum=$3 if [ ! -f "$file" ]; then log "download $url" curl -fL --retry 3 -o "$file.part" "$url" mv "$file.part" "$file" fi if ! echo "$sum $file" | sha256sum -c --status; then echo "SHA-256 mismatch for $file: expected $sum, got $(sha256sum "$file" | cut -d' ' -f1)" >&2 exit 2 fi } T_ALL=$SECONDS LIC="$PREFIX/share/bernard-licenses" mkdir -p "$LIC" "$PREFIX/share/bernard-sources" # nasm: FFmpeg's x86 SIMD is assembled by it; without it FFmpeg would be built without its assembly. if ! has_cmd nasm; then fetch "nasm-${NASM_VERSION}.tar.xz" "$NASM_URL" "$NASM_SHA256" rm -rf "${WORK:?}/nasm-${NASM_VERSION}" tar -xJf "$WORK/tarballs/nasm-${NASM_VERSION}.tar.xz" -C "$WORK" log "nasm ${NASM_VERSION}: build (build tool only, in $WORK/tools)" (cd "$WORK/nasm-${NASM_VERSION}" && ./configure --prefix="$WORK/tools" >/dev/null && make -j "$JOBS" >/dev/null && make install >/dev/null) export PATH="$WORK/tools/bin:$PATH" fi echo "nasm: $(command -v nasm) ($(nasm -v))" # nv-codec-headers: headers and ffnvcodec.pc into the prefix (nothing to compile, nothing shipped but notices). fetch "nv-codec-headers-${NVCODEC_TAG}.tar.gz" "$NVCODEC_URL" "$NVCODEC_SHA256" rm -rf "${WORK:?}/nv-codec-headers-${NVCODEC_TAG}" tar -xzf "$WORK/tarballs/nv-codec-headers-${NVCODEC_TAG}.tar.gz" -C "$WORK" make -C "$WORK/nv-codec-headers-${NVCODEC_TAG}" PREFIX="$PREFIX" LIBDIR=lib install >/dev/null # The MIT notice at the top of each header (there is no separate licence file). : > "$LIC/nv-codec-headers-LICENSE.txt" for h in "$WORK/nv-codec-headers-${NVCODEC_TAG}"/include/ffnvcodec/*.h; do { echo "== nv-codec-headers ${NVCODEC_TAG}, include/ffnvcodec/$(basename "$h") ==" sed -n '1,/\*\//p' "$h" echo; } >> "$LIC/nv-codec-headers-LICENSE.txt" done # FFmpeg, from the neutral paths (see the top): NEUTRAL -> , sources and build under BUILD_ROOT. NEUTRAL=/tmp/bernard-deps BUILD_ROOT=/tmp/bernard-ffmpeg-build if [ -e "$NEUTRAL" ] && [ ! -L "$NEUTRAL" ]; then echo "$NEUTRAL exists and is not a symbolic link: remove it (it is the neutral prefix FFmpeg is configured with)" >&2 exit 2 fi ln -sfn "$PREFIX" "$NEUTRAL" fetch "ffmpeg-${FFMPEG_VERSION}.tar.xz" "$FFMPEG_URL" "$FFMPEG_SHA256" SRC="$BUILD_ROOT/ffmpeg-${FFMPEG_VERSION}" BLD="$BUILD_ROOT/build" rm -rf "${BUILD_ROOT:?}" mkdir -p "$BLD" tar -xJf "$WORK/tarballs/ffmpeg-${FFMPEG_VERSION}.tar.xz" -C "$BUILD_ROOT" HW_ARGS=(--enable-ffnvcodec --enable-cuda --enable-cuvid --enable-nvdec --enable-nvenc) if [ $WITH_VAAPI = 1 ]; then HW_ARGS+=(--enable-vaapi --enable-libdrm); fi CONFIGURE_ARGS=( --prefix="$NEUTRAL" --libdir="$NEUTRAL/lib" --shlibdir="$NEUTRAL/lib" --incdir="$NEUTRAL/include" --enable-shared --disable-static --disable-programs --disable-doc --disable-debug --disable-avdevice --disable-avfilter --disable-autodetect --enable-zlib --enable-iconv "${HW_ARGS[@]}" ) log "FFmpeg ${FFMPEG_VERSION}: configure ${CONFIGURE_ARGS[*]}" (cd "$BLD" && PKG_CONFIG_PATH="$NEUTRAL/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" \ "$SRC/configure" "${CONFIGURE_ARGS[@]}") if grep -qE '^#define CONFIG_(GPL|GPLV3|NONFREE) 1' "$BLD/config.h"; then echo "FFmpeg configured GPL or nonfree -- check the configure line above" >&2 exit 1 fi # RUNPATH $ORIGIN, given to make rather than on the configure line (which avcodec_configuration() records): # ffbuild/library.mak expands the link line twice, hence $$$$, and the quotes keep it from the shell. printf '%s\n' "LDSOFLAGS += -Wl,-rpath,'\$\$\$\$ORIGIN'" >> "$BLD/ffbuild/config.mak" log "FFmpeg ${FFMPEG_VERSION}: build ($JOBS jobs)" make -C "$BLD" -j "$JOBS" make -C "$BLD" install # The pkg-config files name the neutral prefix: point them back at (the link is repointed by the # next run, and /tmp does not outlive a reboot). for pc in "$PREFIX"/lib/pkgconfig/lib{avcodec,avformat,avutil,swscale,swresample}.pc; do sed -i "s#$NEUTRAL#$PREFIX#g" "$pc" done cp "$WORK/tarballs/ffmpeg-${FFMPEG_VERSION}.tar.xz" "$PREFIX/share/bernard-sources/" cp "$SRC/COPYING.LGPLv2.1" "$LIC/FFmpeg-COPYING.LGPLv2.1" cp "$SRC/LICENSE.md" "$LIC/FFmpeg-LICENSE.md" # The check: the installed libraries, as the player will load them. log "FFmpeg ${FFMPEG_VERSION}: check" cat > "$BLD/bernard_licence_check.c" <<'EOF' #include #include #include #include #include #include int main(void) { const char *lic = avcodec_license(); printf("avcodec_version: %u.%u.%u\n", avcodec_version() >> 16, (avcodec_version() >> 8) & 0xff, avcodec_version() & 0xff); printf("avcodec_license: %s\n", lic); printf("avformat_license: %s\nswscale_license: %s\nswresample_license: %s\n", avformat_license(), swscale_license(), swresample_license()); printf("avcodec_configuration: %s\n", avcodec_configuration()); const char *need[] = {"prores_ks", "dnxhd", "mjpeg", "png", "aac", "pcm_s24le", "h264_nvenc", "hevc_nvenc", 0}; int bad = strstr(lic, "LGPL") == NULL; for (int i = 0; need[i]; ++i) if (!avcodec_find_encoder_by_name(need[i])) { printf("missing encoder %s\n", need[i]); bad = 1; } const char *dec[] = {"h264", "hevc", "prores", "dnxhd", "mjpeg", "png", "aac", "pcm_s24le", "vp9", 0}; for (int i = 0; dec[i]; ++i) if (!avcodec_find_decoder_by_name(dec[i])) { printf("missing decoder %s\n", dec[i]); bad = 1; } return bad; } EOF cc -o "$BLD/bernard_licence_check" "$BLD/bernard_licence_check.c" \ -I"$PREFIX/include" -L"$PREFIX/lib" -Wl,-rpath,"$PREFIX/lib" -lavcodec -lavformat -lswscale -lswresample -lavutil CHECK_OUT=$("$BLD/bernard_licence_check") || { echo "$CHECK_OUT"; echo "FFmpeg check failed" >&2; exit 1; } echo "$CHECK_OUT" for f in "$PREFIX"/lib/lib{avcodec,avformat,avutil,swscale,swresample}.so.*; do [ -L "$f" ] && continue rp=$(readelf -d "$f" | sed -n 's/.*(\(RPATH\|RUNPATH\)).*\[\(.*\)\]/\2/p') echo "$(basename "$f"): RUNPATH '$rp', needs: $(readelf -d "$f" | sed -n 's/.*(NEEDED).*\[\(.*\)\]/\1/p' | tr '\n' ' ')" if [ "$rp" != '$ORIGIN' ]; then echo "$f: RUNPATH '$rp', \$ORIGIN expected" >&2 exit 1 fi # Nothing of the build machine in what ships: /home/, the real prefix or work folder, the user's name. leaks=$(strings -a "$f" | grep -F -e /home/ -e "$PREFIX" -e "$WORK" -e "$HOME" || true) user=$(id -un) if [ "$user" != root ]; then leaks+=$(strings -a "$f" | grep -F -e "$user" || true); fi if [ -n "$leaks" ]; then echo "$f holds paths of the build machine:" >&2; echo "$leaks" | head -5 >&2 exit 1 fi done rm -rf "${BUILD_ROOT:?}" CONFIGURATION=$(echo "$CHECK_OUT" | sed -n 's/^avcodec_configuration: //p') LICENCE=$(echo "$CHECK_OUT" | sed -n 's/^avcodec_license: //p') cat > "$LIC/FFmpeg-BUILD.txt" </share/bernard-sources. Configuration (avcodec_configuration()): ${CONFIGURATION} nv-codec-headers ${NVCODEC_TAG} (MIT, headers only): ${NVCODEC_URL}, SHA-256 ${NVCODEC_SHA256} EOF VFILE="$LIC/VERSIONS.txt.part"; [ -f "$VFILE" ] || VFILE="$LIC/VERSIONS.txt" touch "$VFILE" sed -i '/^FFmpeg /d;/^nv-codec-headers /d' "$VFILE" echo "FFmpeg ${FFMPEG_VERSION} (${LICENCE}$([ $WITH_VAAPI = 1 ] && echo ", VAAPI" || echo ", no VAAPI"), NVENC/NVDEC)" >> "$VFILE" echo "nv-codec-headers ${NVCODEC_TAG} (headers)" >> "$VFILE" log "FFmpeg ${FFMPEG_VERSION}: done in $((SECONDS - T_ALL)) s, ${LICENCE} -> $PREFIX" if [ ${#MISSING_OPTIONAL[@]} -gt 0 ]; then echo "FFmpeg built without VAAPI (their -dev packages are missing): ${MISSING_OPTIONAL[*]}" >&2 fi [ -n "$NASM_NOTE" ] && echo "FFmpeg: $NASM_NOTE" >&2 exit 0