#!/bin/zsh
# Builds the FFmpeg libraries of b3Player Pro: FFmpeg 7.1.2 as LGPL-2.1-or-later shared libraries for macOS 14 or later
# on Apple silicon (arm64). This is the script b3LAB uses. It builds from the unmodified upstream tarball.
#
#   - shared libraries (the application links them dynamically), no ffmpeg/ffprobe programs
#   - no external libraries: only the system's zlib, bzip2 and iconv and Apple's AudioToolbox
#   - no encoders, muxers, filters, devices or network
#   - no FFmpeg decoders for H.264, HEVC, AAC and ProRes (macOS handles them); the DNxHD/DNxHR decoder is included
#
# Layout: put this script into a folder named "Scripts" inside an empty working directory. The parent of that folder is
# the root: the build files go to <root>/build/ffmpeg-build, the result to <root>/ThirdParty/ffmpeg
# (include, lib, CONFIGURE.txt). The script downloads the tarball from ffmpeg.org, or uses
# <root>/build/ffmpeg-build/src/ffmpeg-7.1.2.tar.xz if you put it there. Needs the Xcode command line tools, curl, shasum.
#
# Usage:
#   Scripts/build_ffmpeg_lgpl.sh          # build (a few minutes)
#   Scripts/build_ffmpeg_lgpl.sh --check  # only check the existing build
#   Scripts/build_ffmpeg_lgpl.sh --clean  # remove build/ffmpeg-build and build again
set -euo pipefail

FFMPEG_VERSION="7.1.2"
FFMPEG_SHA256="089bc60fb59d6aecc5d994ff530fd0dcb3ee39aa55867849a2bbc4e555f9c304"

ROOT=${0:A:h:h}
BUILD_ROOT="$ROOT/build/ffmpeg-build"
SRC_DIR="$BUILD_ROOT/src"
SOURCE="$SRC_DIR/ffmpeg-$FFMPEG_VERSION"
OUT="$ROOT/ThirdParty/ffmpeg"

log() { print -P "%F{cyan}==>%f $*" }
die() { print -P "%F{red}XX%f $*" >&2; exit 1 }

CONFIGURE_FLAGS=(
  # Neutral prefix: FFmpeg embeds its configuration line, and the product must not carry this machine's paths.
  --prefix=/opt/b3ffmpeg
  --install-name-dir=@rpath
  --enable-shared
  --disable-static
  --disable-programs
  --disable-doc
  --disable-debug
  --disable-gpl
  --disable-nonfree
  --disable-version3
  --disable-autodetect
  --enable-zlib
  --enable-bzlib
  --enable-audiotoolbox
  --disable-videotoolbox
  --disable-avdevice
  --disable-avfilter
  --disable-postproc
  --disable-network
  --disable-encoders
  --disable-muxers
  --disable-devices
  --disable-filters
  --disable-hwaccels
  --disable-protocols
  --enable-protocol=file
  --enable-protocol=pipe
  # Apple decodes these, FFmpeg's own decoders stay out of the product.
  --disable-decoder=h264
  --disable-decoder=hevc
  --disable-decoder=aac
  --disable-decoder=aac_fixed
  --disable-decoder=aac_latm
  --disable-decoder=prores
  --cc=clang
  --extra-cflags=-mmacosx-version-min=14.0
  --extra-ldflags=-mmacosx-version-min=14.0
)

check() {
  local lib="$OUT/lib/libavcodec.dylib"
  [[ -f "$lib" ]] || die "Missing $lib"
  local configuration
  configuration=$(<"$OUT/CONFIGURE.txt")
  for token in --enable-gpl --enable-nonfree --enable-version3; do
    [[ "$configuration" == *"$token"* ]] && die "Forbidden flag in the configuration: $token"
  done
  # The license the libraries report about themselves.
  local license
  license=$(strings "$lib" | grep -m1 "^libavcodec license: " | sed 's/^libavcodec license: //' || true)
  [[ "$license" == "LGPL version 2.1 or later" ]] || die "libavcodec reports the license \"$license\""
  # The decoders Apple handles must be off, and no encoder may be on.
  local components="$SOURCE/config_components.h"
  if [[ -f "$components" ]]; then
    for decoder in H264 HEVC AAC AAC_FIXED AAC_LATM PRORES; do
      grep -q "^#define CONFIG_${decoder}_DECODER 0" "$components" || die "The $decoder decoder is not disabled"
    done
    # DNxHD/DNxHR is played by FFmpeg itself, so this one has to be on.
    grep -q "^#define CONFIG_DNXHD_DECODER 1" "$components" || die "The DNXHD decoder is not enabled"
    grep -q -E "^#define CONFIG_[A-Z0-9_]+_ENCODER 1" "$components" && die "An encoder is enabled"
  fi
  for name in avcodec avformat avutil swscale swresample; do
    strings "$OUT/lib"/lib$name.*.*.*.dylib | grep -q "/Users/" && die "lib$name carries a user path"
  done
  local file
  for name in avcodec avformat avutil swscale swresample; do
    file=$(/bin/ls "$OUT/lib"/lib$name.*.dylib | head -1)
    otool -D "$file" | tail -1 | grep -q "^@rpath/" || die "$file has no @rpath install name"
    # Nothing outside the system may be linked.
    if otool -L "$file" | tail -n +2 | grep -v -E "@rpath/|/usr/lib/|/System/Library/" | grep -q .; then
      otool -L "$file" >&2
      die "$file links a library outside the system"
    fi
  done
  log "OK: FFmpeg $FFMPEG_VERSION, $license, shared, only system libraries"
}

if [[ "${1:-}" == "--check" ]]; then check; exit 0; fi
if [[ "${1:-}" == "--clean" ]]; then rm -rf "$BUILD_ROOT"; fi

mkdir -p "$SRC_DIR"
ARCHIVE="$SRC_DIR/ffmpeg-$FFMPEG_VERSION.tar.xz"
if [[ ! -d "$SOURCE" ]]; then
  if [[ ! -f "$ARCHIVE" ]]; then
    log "Downloading FFmpeg $FFMPEG_VERSION"
    curl -fSL --retry 3 -o "$ARCHIVE" "https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VERSION.tar.xz"
  fi
  [[ "$(shasum -a 256 "$ARCHIVE" | awk '{print $1}')" == "$FFMPEG_SHA256" ]] || die "SHA-256 of the tarball does not match"
  tar -xJf "$ARCHIVE" -C "$SRC_DIR"
fi

log "Configuring"
cd "$SOURCE"
make distclean > /dev/null 2>&1 || true
./configure "${CONFIGURE_FLAGS[@]}" > "$BUILD_ROOT/configure.log" 2>&1 || { tail -30 "$BUILD_ROOT/configure.log"; die "configure failed" }
grep -q "^License: LGPL version 2.1 or later" "$BUILD_ROOT/configure.log" || die "configure did not report LGPL 2.1"

log "Building"
make -j"$(sysctl -n hw.ncpu)" > "$BUILD_ROOT/make.log" 2>&1 || { tail -30 "$BUILD_ROOT/make.log"; die "make failed" }
STAGE="$BUILD_ROOT/stage"
rm -rf "$STAGE" "$OUT/include" "$OUT/lib"
make install DESTDIR="$STAGE" > "$BUILD_ROOT/install.log" 2>&1 || die "make install failed"
mkdir -p "$OUT"
mv "$STAGE/opt/b3ffmpeg/include" "$STAGE/opt/b3ffmpeg/lib" "$OUT/"
rm -rf "$STAGE" "$OUT/share" "$OUT/lib/pkgconfig"

# The exact configuration, for the LGPL notice and the source offer.
{
  print "FFmpeg $FFMPEG_VERSION, https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VERSION.tar.xz (SHA-256 $FFMPEG_SHA256)"
  print "Unmodified source. Configured with:"
  print "./configure ${CONFIGURE_FLAGS[*]}"
} > "$OUT/CONFIGURE.txt"

check
