#!/usr/bin/env bash # Build a minimal, LGPL-2.1-or-later, statically linked FFmpeg (ffmpeg.exe + # ffprobe.exe) for Windows x64 for Audio Converter. # # Run inside an MSYS2 MINGW64 shell (MSYSTEM=MINGW64), from anywhere: # bash tools/ffmpeg/build-ffmpeg.sh # Re-runnable: it reuses out/ffmpeg-src (re-checking out the pinned tag) and # rebuilds in out/ffmpeg-build. Output goes to tools/bin/ffmpeg/. # # Licence rules: no --enable-gpl, no --enable-nonfree, no --enable-version3. set -euo pipefail FFMPEG_TAG="n9.0.2" FFMPEG_SHA="946fcce07b6dcd0331c8cc609192aeff5e1924f8" FFMPEG_URL="https://github.com/FFmpeg/FFmpeg" if [ "${MSYSTEM:-}" != "MINGW64" ]; then echo "error: run this from an MSYS2 MINGW64 shell (MSYSTEM=MINGW64)" >&2 exit 1 fi HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO="$(cd "$HERE/../.." && pwd)" SRC="$REPO/out/ffmpeg-src" BUILD="$REPO/out/ffmpeg-build" DEST="$REPO/tools/bin/ffmpeg" JOBS="${JOBS:-$(nproc)}" # --- Dependencies (MSYS2 packages; static .a libraries are used) ----------- PKGS=(mingw-w64-x86_64-toolchain mingw-w64-x86_64-lame mingw-w64-x86_64-libogg mingw-w64-x86_64-libvorbis mingw-w64-x86_64-opus mingw-w64-x86_64-zlib make pkgconf nasm git diffutils) if [ "${SKIP_PACMAN:-0}" != "1" ]; then pacman -S --noconfirm --needed "${PKGS[@]}" fi for lib in libmp3lame.a libogg.a libvorbis.a libvorbisenc.a libopus.a libz.a; do [ -f "/mingw64/lib/$lib" ] || { echo "error: missing static /mingw64/lib/$lib" >&2; exit 1; } done # --- Source ----------------------------------------------------------------- if [ ! -d "$SRC/.git" ]; then git clone --depth 1 --branch "$FFMPEG_TAG" "$FFMPEG_URL" "$SRC" fi git -c safe.directory="$SRC" -C "$SRC" -c advice.detachedHead=false checkout -q "$FFMPEG_TAG" 2>/dev/null || { git -c safe.directory="$SRC" -C "$SRC" fetch --depth 1 origin "refs/tags/$FFMPEG_TAG:refs/tags/$FFMPEG_TAG" git -c safe.directory="$SRC" -C "$SRC" -c advice.detachedHead=false checkout -q "$FFMPEG_TAG" } HEAD_SHA="$(git -c safe.directory="$SRC" -C "$SRC" rev-parse HEAD)" if [ "$HEAD_SHA" != "$FFMPEG_SHA" ]; then echo "error: $FFMPEG_TAG resolved to $HEAD_SHA, expected $FFMPEG_SHA" >&2 exit 1 fi # --- Component selection ---------------------------------------------------- join() { local IFS=,; echo "$*"; } # Print a command line readably (quote only args that need it). showcmd() { local a out=(); for a in "$@"; do if [[ "$a" =~ [[:space:]\"\'] ]]; then out+=("'$a'"); else out+=("$a"); fi; done; echo "${out[*]}"; } DECODERS=( mp3float mp3 aac aac_fixed aac_latm alac flac vorbis opus wmav1 wmav2 wmapro wmalossless amrnb amrwb ac3 ac3_fixed eac3 gsm adpcm_ima_wav adpcm_ms pcm_s8 pcm_u8 pcm_s16le pcm_s16be pcm_u16le pcm_u16be pcm_s24le pcm_s24be pcm_u24le pcm_u24be pcm_s32le pcm_s32be pcm_u32le pcm_u32be pcm_s64le pcm_s64be pcm_f16le pcm_f24le pcm_f32le pcm_f32be pcm_f64le pcm_f64be pcm_s16le_planar pcm_s24le_planar pcm_s32le_planar pcm_s8_planar pcm_alaw pcm_mulaw mjpeg png bmp wrapped_avframe ) ENCODERS=( libmp3lame libopus libvorbis aac flac alac wmav2 pcm_s16le pcm_s24le pcm_s32le pcm_f32le pcm_s16be pcm_s24be pcm_s32be pcm_u8 mjpeg ) # lavfi (sine/testsrc inputs for tests) is an input device in libavdevice: see --enable-indev. DEMUXERS=( mov matroska ogg wav w64 aiff flac mp3 aac asf avi amr amrnb amrwb caf ac3 eac3 mpegts image2 image2pipe image_jpeg_pipe image_png_pipe image_bmp_pipe mjpeg pcm_s16le pcm_s16be pcm_s24le pcm_f32le gsm ) MUXERS=( mp3 ipod mp4 mov wav w64 flac ogg opus aiff asf adts null image2 mjpeg pcm_s16le ) PARSERS=( mpegaudio aac aac_latm flac vorbis opus ac3 amr gsm mjpeg png bmp ) BSFS=( aac_adtstoasc opus_metadata null ) FILTERS=( abuffer abuffersink buffer buffersink aresample aformat atrim asetpts volume loudnorm anull apad sine anullsrc format null trim setpts scale testsrc testsrc2 ) CONFIGURE=( "$SRC/configure" --arch=x86_64 --target-os=mingw32 --pkg-config-flags=--static --extra-cflags=-static --extra-ldflags=-static --disable-shared --enable-static --disable-autodetect --disable-everything --disable-programs --enable-ffmpeg --enable-ffprobe --disable-doc --disable-debug --disable-network --disable-devices --disable-hwaccels --enable-indev=lavfi --enable-small --enable-zlib --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-protocol=file,pipe --enable-decoder="$(join "${DECODERS[@]}")" --enable-encoder="$(join "${ENCODERS[@]}")" --enable-demuxer="$(join "${DEMUXERS[@]}")" --enable-muxer="$(join "${MUXERS[@]}")" --enable-parser="$(join "${PARSERS[@]}")" --enable-bsf="$(join "${BSFS[@]}")" --enable-filter="$(join "${FILTERS[@]}")" ) echo "=== configure line ===" showcmd "${CONFIGURE[@]}" echo "======================" rm -rf "$BUILD" mkdir -p "$BUILD" "$DEST" cd "$BUILD" "${CONFIGURE[@]}" || { tail -50 ffbuild/config.log; exit 1; } # Licence guard: refuse to ship anything that is not LGPL-2.1-or-later. grep -q '^#define CONFIG_GPL 0' config.h grep -q '^#define CONFIG_NONFREE 0' config.h grep -q '^#define CONFIG_VERSION3 0' config.h grep -q 'FFMPEG_LICENSE "LGPL version 2.1 or later"' config.h make -j"$JOBS" ffmpeg.exe ffprobe.exe # make already strips ffmpeg.exe/ffprobe.exe (the *_g.exe files keep symbols); # strip again to be explicit. Only the stripped exes are shipped. rm -f "$DEST"/*-unstripped.exe strip -o "$DEST/ffmpeg.exe" ffmpeg.exe strip -o "$DEST/ffprobe.exe" ffprobe.exe echo "--- sizes (bytes): unstripped *_g.exe vs shipped" stat -c '%n %s' ffmpeg_g.exe ffprobe_g.exe "$DEST/ffmpeg.exe" "$DEST/ffprobe.exe" # Imports must be Windows system DLLs only. for exe in ffmpeg.exe ffprobe.exe; do echo "--- imports of $exe" objdump -p "$DEST/$exe" | grep 'DLL Name' | tee "$BUILD/$exe.imports" if grep -Eiq 'libgcc|libwinpthread|libstdc|libmp3lame|libogg|libvorbis|libopus|zlib1|libz' "$BUILD/$exe.imports"; then echo "error: $exe imports a non-system DLL" >&2; exit 1 fi done # --- Licence and provenance files -------------------------------------------- cp "$SRC/COPYING.LGPLv2.1" "$DEST/LICENSE.txt" "$DEST/ffmpeg.exe" -hide_banner -buildconf > "$DEST/buildconf.txt" "$DEST/ffmpeg.exe" -hide_banner -L | grep -q 'GNU Lesser General Public' if grep -Eq -- '--enable-(gpl|nonfree|version3)' "$DEST/buildconf.txt"; then echo "error: buildconf contains a non-LGPL-2.1 flag" >&2; exit 1 fi { echo "Audio Converter - bundled FFmpeg (LGPL-2.1-or-later)" echo echo "FFmpeg $FFMPEG_TAG" echo " commit: $HEAD_SHA" echo " source: $FFMPEG_URL/tree/$FFMPEG_TAG" echo " tarball: https://ffmpeg.org/releases/ffmpeg-${FFMPEG_TAG#n}.tar.xz" echo " licence: LGPL-2.1-or-later (see LICENSE.txt, COPYING.LGPLv2.1 from the source tree)" echo echo "Statically linked libraries (MSYS2 MINGW64 packages, https://packages.msys2.org/):" for p in mingw-w64-x86_64-lame mingw-w64-x86_64-libogg mingw-w64-x86_64-libvorbis \ mingw-w64-x86_64-opus mingw-w64-x86_64-zlib mingw-w64-x86_64-gcc \ mingw-w64-x86_64-crt-git mingw-w64-x86_64-winpthreads; do v="$(pacman -Q "$p" 2>/dev/null || echo "$p (not installed)")" echo " $v" done echo " upstream: LAME (LGPL-2.0-or-later) https://lame.sourceforge.io/" echo " libogg, libvorbis (BSD-3-Clause) https://xiph.org/downloads/" echo " libopus (BSD-3-Clause) https://opus-codec.org/" echo " zlib (zlib licence) https://zlib.net/" echo " MinGW-w64 runtime / winpthreads (ZPL/MIT/BSD), libgcc (GPL + runtime exception)" echo " package sources: https://github.com/msys2/MINGW-packages" echo echo "Configure line:" showcmd "${CONFIGURE[@]}" | sed "s#$SRC#./ffmpeg-src#" } > "$DEST/SOURCE.txt" ls -l "$DEST" echo "BUILD OK"