#!/bin/zsh
# Our own FFmpeg for PyAV: LGPL, no x264 or x265 (docs/SPEC-LGPL-FFMPEG.md).
#
#   tools/build_ffmpeg_lgpl.sh <work dir> <python>
#
# <work dir>/src must hold these sources; each is checked against its hash below before anything is
# built (FFmpeg's tarball was first checked against FFmpeg's release key FCF986EA15E6E293A5644F10B4322F04D67658D8
# with its .asc; LAME, Opus, libvpx and dav1d against Homebrew's formula hashes).
# <python> is a venv for the build; the pinned build tools (BUILD_TOOLS) are installed into it.
# It builds into <work dir>/prefix, then a self-contained PyAV 18.1.0 wheel into <work dir>/wheels: the FFmpeg
# libraries are copied into the wheel (delocate) and nothing outside macOS's own libraries may be linked.
# Needs: Xcode's clang, nasm, pkg-config. Licences: FFmpeg LGPL 2.1+, LAME LGPL, Opus, libvpx, dav1d BSD.
set -euo pipefail
W=${1:?work dir}
PY=${2:?python}
SRC=$W/src
PREFIX=$W/prefix
BUILD=$W/build
JOBS=$(sysctl -n hw.ncpu)
BUILD_TOOLS=("cython==3.3.0" "setuptools==80.9.0" "wheel==0.45.1" "meson==1.9.1" "ninja==1.13.0" "delocate==0.13.0")
mkdir -p $PREFIX $BUILD $W/wheels
export MACOSX_DEPLOYMENT_TARGET=12.0
export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig
export LDFLAGS="-Wl,-headerpad_max_install_names"          # delocate rewrites the library paths
export PATH="$(dirname $PY):$PATH"                          # meson and ninja from the build venv

# ---- the sources, checked ------------------------------------------------------------------------
(cd $SRC && shasum -a 256 -c - <<'SUMS'
7138d28c96d9d3e3af4ee3d8cad72741f8ffb40da90c1112235dea3ecd3178a3  ffmpeg-8.1.3.tar.xz
3df5124d5ad3a98312ffd7ba6a9b36230e4f8a3e66d3ce0f425e336c32d216eb  lame-4.0.tar.gz
6ffcb593207be92584df15b32466ed64bbec99109f007c82205f0194572411a1  opus-1.6.1.tar.gz
1020f184046187baa2985dbde38e0691f49c44088bca7a1842b0236c6081dc0a  libvpx-1.17.0.tar.gz
2abfb0c89212e6e4733a54e0ae509ec00a5b845a6360946f918806e14aedb011  dav1d-1.5.4.tar.bz2
SUMS
)
"$PY" -m pip install -q "${BUILD_TOOLS[@]}"

unpack() {   # name tarball
  rm -rf $BUILD/$1 && mkdir -p $BUILD/$1 && tar -xf $SRC/$2 -C $BUILD/$1 --strip-components 1
}

# LAME (LGPL): the podcast MP3 ("mp3" is libmp3lame in FFmpeg). No decoder: it would link mpg123.
if [[ ! -f $PREFIX/lib/libmp3lame.dylib ]] || otool -L $PREFIX/lib/libmp3lame.dylib | grep -q mpg123; then
  unpack lame lame-4.0.tar.gz
  (cd $BUILD/lame && ./configure --prefix=$PREFIX --disable-static --enable-shared --disable-frontend --disable-gtktest \
     --disable-decoder --enable-nasm && make -j$JOBS && make install)
fi

# Opus and libvpx (BSD): browsers record dictation and remote calls as WebM/Ogg with them; FFmpeg's own
# decoders read those, the tests make such clips with these encoders.
if [[ ! -f $PREFIX/lib/libopus.dylib ]]; then
  unpack opus opus-1.6.1.tar.gz
  (cd $BUILD/opus && ./configure --prefix=$PREFIX --disable-static --enable-shared --disable-doc --disable-extra-programs \
     && make -j$JOBS && make install)
fi
if [[ ! -f $PREFIX/lib/libvpx.dylib ]]; then
  unpack libvpx libvpx-1.17.0.tar.gz
  (cd $BUILD/libvpx && ./configure --prefix=$PREFIX --enable-shared --disable-static --disable-examples --disable-tools \
     --disable-docs --disable-unit-tests --enable-vp8 --enable-vp9 --as=nasm && make -j$JOBS && make install)
fi
# libvpx names itself without a path; everything that links it must find it in the prefix.
install_name_tool -id $PREFIX/lib/libvpx.12.dylib $PREFIX/lib/libvpx.12.dylib

# dav1d (BSD): AV1 decoding (some phones and browsers record AV1), as the stock wheel has it.
if [[ ! -f $PREFIX/lib/libdav1d.dylib ]]; then
  unpack dav1d dav1d-1.5.4.tar.bz2
  (cd $BUILD/dav1d && meson setup build --prefix=$PREFIX --libdir=lib --buildtype=release --default-library=shared \
     -Denable_tools=false -Denable_tests=false && ninja -C build install)
fi

# FFmpeg 8.1.3: LGPL v2.1+ (no --enable-gpl, no --enable-version3, no --enable-nonfree), shared libraries, and
# nothing found by itself (--disable-autodetect): only what is named here, so no Homebrew library is linked.
# The same list is written into FFmpeg-CONFIGURE.txt for the source offer (docs/SPEC-OPEN-SOURCE.md).
FFMPEG_FLAGS=(--enable-shared --disable-static
  --disable-programs --disable-doc --disable-debug
  --disable-gpl --disable-nonfree --disable-autodetect
  --enable-videotoolbox --enable-audiotoolbox --enable-zlib --enable-bzlib --enable-iconv
  --enable-libmp3lame --enable-libopus --enable-libvpx --enable-libdav1d)
if [[ ! -f $PREFIX/lib/libavcodec.dylib ]] || ! otool -L $PREFIX/lib/libavcodec.dylib | grep -q dav1d \
   || otool -L $PREFIX/lib/libavcodec.dylib | grep -q /usr/local/opt; then
  unpack ffmpeg ffmpeg-8.1.3.tar.xz
  (cd $BUILD/ffmpeg && ./configure --prefix=$PREFIX "${FFMPEG_FLAGS[@]}" \
     --extra-cflags="-I$PREFIX/include" --extra-ldflags="-L$PREFIX/lib -Wl,-headerpad_max_install_names" \
     --extra-libs="-liconv" \
     && make -j$JOBS && make install)
fi

# PyAV 18.1.0 from its source, against this FFmpeg; then the libraries into the wheel.
[[ -f $BUILD/pyav-src/av-18.1.0.tar.gz ]] || "$PY" -m pip download --no-binary :all: --no-deps -d $BUILD/pyav-src "av==18.1.0"
echo "PyAV sdist: $(shasum -a 256 $BUILD/pyav-src/av-18.1.0.tar.gz)"
rm -rf $W/wheels/* $W/wheels-raw && mkdir -p $W/wheels-raw
"$PY" -m pip wheel --no-cache-dir --no-deps --no-build-isolation -w $W/wheels-raw $BUILD/pyav-src/av-18.1.0.tar.gz 2>&1 | tail -3
"$(dirname $PY)/delocate-wheel" -w $W/wheels -v $W/wheels-raw/av-*.whl 2>&1 | tail -3

# Nothing outside macOS may be linked by what the wheel carries.
CHECK=$W/wheel-check && rm -rf $CHECK && mkdir -p $CHECK && (cd $CHECK && unzip -q $W/wheels/av-*.whl)
BAD=$(find $CHECK \( -name '*.dylib' -o -name '*.so' \) | while read f; do
  own=$(otool -D "$f" | tail -n +2)                         # a library's own name is not something it links
  otool -L "$f" | tail -n +2 | awk '{print $1}' | { grep -vxF "${own:-/nothing}" || true; } \
    | { grep -vE '^(/usr/lib/|/System/|@loader_path/|@rpath/)' || true; } | sed "s|^|$(basename $f): |"; done)
if [[ -n "$BAD" ]]; then echo "Linked outside macOS and the wheel:"; echo "$BAD"; exit 1; fi
echo "Self-contained: $(ls $W/wheels)"

# The licences the app must carry with these libraries (FFmpeg LGPL 2.1, LAME LGPL, Opus, libvpx and dav1d BSD).
LIC=$W/licenses && rm -rf $LIC && mkdir -p $LIC
cp $BUILD/ffmpeg/COPYING.LGPLv2.1 $LIC/FFmpeg-COPYING.LGPLv2.1 && cp $BUILD/ffmpeg/LICENSE.md $LIC/FFmpeg-LICENSE.md
cp $BUILD/lame/COPYING $LIC/LAME-COPYING
cp $BUILD/opus/COPYING $LIC/Opus-COPYING
cp $BUILD/libvpx/LICENSE $LIC/libvpx-LICENSE && cp $BUILD/libvpx/PATENTS $LIC/libvpx-PATENTS
cp $BUILD/dav1d/COPYING $LIC/dav1d-COPYING
cp $0 $LIC/build_ffmpeg_lgpl.sh                      # how the libraries were built, for the source offer
# The exact sources, for the LGPL's source offer (published per release at studiotake.app/open-source:
# docs/SPEC-OPEN-SOURCE.md).
cat > $LIC/SOURCES.txt <<SRC
The media libraries in Studiotake are built from these unmodified sources, by build_ffmpeg_lgpl.sh in this folder.
FFmpeg 8.1.3 (LGPL 2.1 or later)  https://ffmpeg.org/releases/ffmpeg-8.1.3.tar.xz
  sha256 7138d28c96d9d3e3af4ee3d8cad72741f8ffb40da90c1112235dea3ecd3178a3
LAME 4.0 (LGPL)  https://downloads.sourceforge.net/project/lame/lame/4.0/lame-4.0.tar.gz
  sha256 3df5124d5ad3a98312ffd7ba6a9b36230e4f8a3e66d3ce0f425e336c32d216eb
Opus 1.6.1 (BSD)  https://ftp.osuosl.org/pub/xiph/releases/opus/opus-1.6.1.tar.gz
  sha256 6ffcb593207be92584df15b32466ed64bbec99109f007c82205f0194572411a1
libvpx 1.17.0 (BSD)  https://github.com/webmproject/libvpx/archive/refs/tags/v1.17.0.tar.gz
  sha256 1020f184046187baa2985dbde38e0691f49c44088bca7a1842b0236c6081dc0a
dav1d 1.5.4 (BSD)  https://code.videolan.org/videolan/dav1d/-/archive/1.5.4/dav1d-1.5.4.tar.bz2
  sha256 2abfb0c89212e6e4733a54e0ae509ec00a5b845a6360946f918806e14aedb011
PyAV 18.1.0 (BSD)  https://pypi.org/project/av/18.1.0/
  sha256 $(shasum -a 256 $BUILD/pyav-src/av-18.1.0.tar.gz | awk '{print $1}')
SRC
# The configure settings, as run (the build folder written $PREFIX), and as the built library reports them.
{
  print -r -- "FFmpeg 8.1.3, configured by build_ffmpeg_lgpl.sh (MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET):"
  print -r -- "./configure --prefix=\$PREFIX ${FFMPEG_FLAGS[*]} --extra-cflags=\"-I\$PREFIX/include\" --extra-ldflags=\"-L\$PREFIX/lib -Wl,-headerpad_max_install_names\" --extra-libs=\"-liconv\""
  print -r -- ""
  print -r -- "As libavcodec reports it:"
  sed -n 's/^#define FFMPEG_CONFIGURATION "\(.*\)"$/\1/p' $BUILD/ffmpeg/config.h | sed "s|$PREFIX|\$PREFIX|g"
} > $LIC/FFmpeg-CONFIGURE.txt
# The checked sources themselves, for the release's source archive (tools/source_offer.py); nothing is fetched then.
SOURCE=$W/source && rm -rf $SOURCE && mkdir -p $SOURCE
cp $SRC/ffmpeg-8.1.3.tar.xz $SRC/lame-4.0.tar.gz $SRC/opus-1.6.1.tar.gz $SRC/libvpx-1.17.0.tar.gz $SRC/dav1d-1.5.4.tar.bz2 \
   $BUILD/pyav-src/av-18.1.0.tar.gz $SOURCE/
echo "Sources for the offer: $(ls $SOURCE | tr '\n' ' ')"
echo "Licences: $(ls $LIC | tr '\n' ' ')"
