#!/bin/bash
#
# Build a minimal universal-binary (arm64 + x86_64) FFmpeg for Tagtool's
# bundled-subprocess recording paths (HAP, HAP Alpha, ProRes 422 HQ,
# ProRes 4444, Animation/qtrle). Designed to run on the Tagtool Mac mini
# build runner (Apple Silicon, macOS 14+, Xcode command-line tools present).
#
# Output: ~/ffmpeg-build/ffmpeg   (universal Mach-O, ~5–8 MB stripped)
#
# Configuration mirrors the Windows cross-compile in StreamingAssets/ffmpeg/README.md
# so HAP files produced on Mac and Windows are byte-for-byte identical.
#
# Usage:
#   chmod +x build-macos-ffmpeg.sh
#   ./build-macos-ffmpeg.sh
#   # Then copy the result into the Tagtool repo:
#   #   cp ~/ffmpeg-build/ffmpeg  /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/ffmpeg
#
# Total runtime ~5–10 minutes on M1/M2 hardware (FFmpeg builds twice — once per
# arch — because libavcodec doesn't support universal in a single configure pass).
#
# LGPL 2.1 compliance: built WITHOUT --enable-gpl and WITHOUT --enable-nonfree.
# The bundled binary may be redistributed in commercial closed-source apps so
# long as the source + configure flags are made available alongside the binary
# (already documented in ../LICENSES.txt and ../README.md).

set -euo pipefail

# Apple Silicon Homebrew installs to /opt/homebrew, which is NOT in the PATH of
# non-interactive SSH sessions (only ~/.zshrc sets it up). Add it explicitly so
# the cmake / nasm / etc the user installed via brew are reachable.
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:${PATH}"

WORK_DIR="${HOME}/ffmpeg-build"
PREFIX_ARM64="${WORK_DIR}/prefix-arm64"
PREFIX_X86="${WORK_DIR}/prefix-x86_64"
FFMPEG_TAG="n7.1"
SNAPPY_TAG="1.2.1"
MIN_MACOS="11.0"

echo "==> Tagtool FFmpeg builder for macOS"
echo "    Output dir: ${WORK_DIR}"
echo "    FFmpeg tag: ${FFMPEG_TAG}"
echo "    Snappy tag: ${SNAPPY_TAG}"
echo "    Min macOS:  ${MIN_MACOS}"
echo

# --- Tool checks ---
for tool in git make xcrun lipo strip python3; do
  if ! command -v "${tool}" >/dev/null; then
    echo "ERROR: required tool '${tool}' not found in PATH."
    echo "       Install Xcode command-line tools: xcode-select --install"
    exit 1
  fi
done

if ! command -v cmake >/dev/null; then
  echo "ERROR: cmake not found in PATH."
  echo ""
  echo "On Apple Silicon Macs, Homebrew installs cmake to /opt/homebrew/bin."
  echo "This script prepends that to PATH at the top, so cmake should be"
  echo "reachable if it's installed via Homebrew. If you're seeing this,"
  echo "either Homebrew is broken or cmake was never installed."
  echo ""
  echo "Quickest fix: install the CMake.app .dmg from https://cmake.org/download/"
  echo "  and run /Applications/CMake.app/Contents/MacOS/CMake --install"
  exit 1
fi

if ! xcrun --find clang >/dev/null 2>&1; then
  echo "ERROR: clang not available via xcrun. Install Xcode command-line tools."
  exit 1
fi

mkdir -p "${WORK_DIR}"
cd "${WORK_DIR}"

# --- Fetch sources ---
if [ ! -d "snappy" ]; then
  echo "==> Cloning Snappy ${SNAPPY_TAG}"
  git clone --depth 1 --branch "${SNAPPY_TAG}" https://github.com/google/snappy.git
  ( cd snappy && git submodule update --init --recursive --depth 1 )
fi

if [ ! -d "ffmpeg-src" ]; then
  echo "==> Cloning FFmpeg ${FFMPEG_TAG}"
  # NOTE: clone into `ffmpeg-src/` (not `FFmpeg/`) to avoid a case-insensitive
  # filesystem collision with the final `ffmpeg` binary in the same dir.
  # macOS' default APFS volume is case-insensitive, so `FFmpeg/` and `ffmpeg`
  # resolve to the same path and `cp` thinks the binary is a directory.
  git clone --depth 1 --branch "${FFMPEG_TAG}" https://github.com/FFmpeg/FFmpeg.git ffmpeg-src
fi

# --- Build Snappy as a universal static library ---
# CMake supports building both archs in one pass via CMAKE_OSX_ARCHITECTURES,
# which produces a universal libsnappy.a we can link from either FFmpeg slice.

build_snappy() {
  local arch="$1"
  local prefix="$2"
  local build_dir="snappy/build-${arch}"

  if [ -f "${prefix}/lib/libsnappy.a" ]; then
    echo "==> Snappy ${arch}: already built, skipping"
    return
  fi

  echo "==> Building Snappy for ${arch}"
  rm -rf "${build_dir}"
  mkdir -p "${build_dir}"  # explicit: cmake would create it, but shell redirect '>' resolves first
  cmake -S snappy -B "${build_dir}" \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX="${prefix}" \
    -DCMAKE_OSX_ARCHITECTURES="${arch}" \
    -DCMAKE_OSX_DEPLOYMENT_TARGET="${MIN_MACOS}" \
    -DBUILD_SHARED_LIBS=OFF \
    -DSNAPPY_BUILD_TESTS=OFF \
    -DSNAPPY_BUILD_BENCHMARKS=OFF \
    -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
    > "${build_dir}/configure.log" 2>&1
  cmake --build "${build_dir}" --target install -j "$(sysctl -n hw.ncpu)" \
    > "${build_dir}/build.log" 2>&1
}

build_snappy arm64  "${PREFIX_ARM64}"
build_snappy x86_64 "${PREFIX_X86}"

# --- Build FFmpeg twice, once per arch ---

build_ffmpeg() {
  local arch="$1"
  local prefix="$2"
  # Names are deliberately all-lowercase + uniquely-prefixed so they don't
  # collide on case-insensitive macOS filesystems with the `ffmpeg-src/`
  # source clone, the final `ffmpeg` binary, or each other.
  local stage_dir="ffmpeg-build-${arch}"
  local out_bin="${WORK_DIR}/slice-${arch}"

  if [ -f "${out_bin}" ]; then
    echo "==> FFmpeg ${arch}: already built, skipping"
    return
  fi

  echo "==> Building FFmpeg for ${arch}"

  # Mirror sources for each arch so we don't have to make-clean between configures.
  rm -rf "${stage_dir}"
  cp -R ffmpeg-src "${stage_dir}"
  cd "${stage_dir}"

  # Resolve macOS SDK path explicitly. With `--enable-cross-compile`, FFmpeg's
  # configure does not auto-detect the SDK the way a native build would, and
  # the link step then fails with `ld: library 'System' not found`. Native
  # Mac builds outside Xcode (i.e. command-line clang) require -isysroot.
  local sdk_path
  sdk_path="$(xcrun --sdk macosx --show-sdk-path)"
  if [ -z "${sdk_path}" ] || [ ! -d "${sdk_path}" ]; then
    echo "ERROR: could not resolve macOS SDK path. Is Xcode installed and selected?"
    exit 1
  fi

  # No pkg-config dependency: FFmpeg's `--enable-libsnappy` uses `require`
  # (manual link probe via check_lib), not `require_pkg_config`. As long as
  # the Snappy include and lib directories are reachable via --extra-cflags /
  # --extra-ldflags, the link probe succeeds without pkg-config installed.
  # This sidesteps the need for `brew install pkg-config` on Macs where
  # Homebrew is broken or unavailable (issue surfaced on the mac-mini-builder
  # which had Homebrew installed under a different user and couldn't update).
  ./configure \
    --prefix="${prefix}/ffmpeg-install" \
    --target-os=darwin \
    --arch="${arch}" \
    --enable-cross-compile \
    --sysroot="${sdk_path}" \
    --cc="$(xcrun --find clang)" \
    --cxx="$(xcrun --find clang++)" \
    --ar="$(xcrun --find ar)" \
    --ranlib="$(xcrun --find ranlib)" \
    --strip="$(xcrun --find strip)" \
    --extra-cflags="-arch ${arch} -mmacosx-version-min=${MIN_MACOS} -isysroot ${sdk_path} -O2 -I${prefix}/include" \
    --extra-ldflags="-arch ${arch} -mmacosx-version-min=${MIN_MACOS} -isysroot ${sdk_path} -L${prefix}/lib" \
    --disable-everything \
    --enable-encoder=hap --enable-decoder=hap \
    --enable-encoder=prores_ks --enable-decoder=prores \
    --enable-encoder=qtrle --enable-decoder=qtrle \
    --enable-encoder=rawvideo --enable-decoder=rawvideo \
    --enable-muxer=mov --enable-demuxer=mov \
    --enable-muxer=rawvideo --enable-demuxer=rawvideo \
    --enable-protocol=pipe --enable-protocol=file \
    --enable-filter=vflip \
    --enable-filter=format \
    --enable-filter=scale \
    --enable-filter=crop \
    --enable-libsnappy \
    --enable-small \
    --disable-doc --disable-network --disable-htmlpages --disable-manpages \
    --disable-podpages --disable-txtpages \
    --disable-shared --enable-static \
    > "${WORK_DIR}/ffmpeg-configure-${arch}.log" 2>&1

  make -j "$(sysctl -n hw.ncpu)" ffmpeg \
    > "${WORK_DIR}/ffmpeg-build-${arch}.log" 2>&1

  cp ffmpeg "${out_bin}"
  cd "${WORK_DIR}"
}

build_ffmpeg arm64  "${PREFIX_ARM64}"
build_ffmpeg x86_64 "${PREFIX_X86}"

# --- Combine into universal binary ---
echo "==> Creating universal Mach-O via lipo"
lipo -create "${WORK_DIR}/slice-arm64" "${WORK_DIR}/slice-x86_64" -output "${WORK_DIR}/ffmpeg"

echo "==> Stripping (preserves symbol-table for crash-report mapping)"
strip "${WORK_DIR}/ffmpeg"

echo "==> Verifying"
file "${WORK_DIR}/ffmpeg"
lipo -info "${WORK_DIR}/ffmpeg"
ls -lh "${WORK_DIR}/ffmpeg"

echo "==> Smoke test (encoder presence)"
"${WORK_DIR}/ffmpeg" -hide_banner -encoders 2>&1 | grep -iE "^ V.*hap|^ V.*prores_ks|^ V.*qtrle|^ V.*rawvideo" || {
  echo "ERROR: HAP/prores_ks/qtrle/rawvideo encoders not in the binary."
  exit 1
}

echo "==> Smoke test (real encode roundtrip — HAP Alpha)"
# Generate a 30-frame 320x240 RGBA gradient and run it through the actual
# production codepath: -f rawvideo from stdin -> -c:v hap with vflip filter
# -> .mov. This catches "rawvideo demuxer missing" or "scale filter missing"
# (whole classes of bugs that the listing test above silently misses).
python3 -c "
import sys
w, h, n = 320, 240, 30
for f in range(n):
    for y in range(h):
        for x in range(w):
            sys.stdout.buffer.write(bytes([(x*255)//w, (y*255)//h, (f*255)//n, 255]))
" > "${WORK_DIR}/test-frames.rgba"

if ! "${WORK_DIR}/ffmpeg" -hide_banner -loglevel warning -y \
    -f rawvideo -pix_fmt rgba -s 320x240 -r 30 -i "${WORK_DIR}/test-frames.rgba" \
    -vf vflip -c:v hap -format hap_alpha \
    "${WORK_DIR}/test-hap-alpha.mov"; then
  echo "ERROR: end-to-end HAP encode failed — see configure / build logs in ${WORK_DIR}."
  exit 1
fi
echo "    test-hap-alpha.mov: $(ls -l "${WORK_DIR}/test-hap-alpha.mov" | awk '{print $5}') bytes"

echo "==> Smoke test (real encode roundtrip — ProRes 4444)"
# Run the same frames through prores_ks 4444 + yuva444p10le (alpha-bearing
# pixel format). This is the codepath TTVideoRecorderFFmpeg.StartRecording
# will use when codec=ProRes4444 with recordAlpha=true on Windows.
if ! "${WORK_DIR}/ffmpeg" -hide_banner -loglevel warning -y \
    -f rawvideo -pix_fmt rgba -s 320x240 -r 30 -i "${WORK_DIR}/test-frames.rgba" \
    -vf vflip -c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le \
    "${WORK_DIR}/test-prores4444.mov"; then
  echo "ERROR: end-to-end ProRes 4444 encode failed — see configure / build logs in ${WORK_DIR}."
  exit 1
fi
echo "    test-prores4444.mov: $(ls -l "${WORK_DIR}/test-prores4444.mov" | awk '{print $5}') bytes"

echo "==> Smoke test (real encode roundtrip — ProRes 422 HQ)"
# ProRes 422 HQ uses yuv422p10le (no alpha). Profile 3 = 422 HQ per
# `ffmpeg -h encoder=prores_ks` (Proxy=0, LT=1, 422=2, 422 HQ=3, 4444=4, 4444 XQ=5).
if ! "${WORK_DIR}/ffmpeg" -hide_banner -loglevel warning -y \
    -f rawvideo -pix_fmt rgba -s 320x240 -r 30 -i "${WORK_DIR}/test-frames.rgba" \
    -vf vflip -c:v prores_ks -profile:v 3 -pix_fmt yuv422p10le \
    "${WORK_DIR}/test-prores422hq.mov"; then
  echo "ERROR: end-to-end ProRes 422 HQ encode failed — see configure / build logs in ${WORK_DIR}."
  exit 1
fi
echo "    test-prores422hq.mov: $(ls -l "${WORK_DIR}/test-prores422hq.mov" | awk '{print $5}') bytes"

echo "==> Smoke test (real encode roundtrip — Animation w/ alpha)"
# QuickTime Animation codec (qtrle), truly-lossless RLE. rgba is the alpha-
# bearing pixel format TTVideoRecorderFFmpeg.StartRecording uses whenever
# codec=Animation with recordAlpha=true. No quality knob — RLE is bit-exact
# lossless.
if ! "${WORK_DIR}/ffmpeg" -hide_banner -loglevel warning -y \
    -f rawvideo -pix_fmt rgba -s 320x240 -r 30 -i "${WORK_DIR}/test-frames.rgba" \
    -vf vflip,format=rgba -c:v qtrle \
    "${WORK_DIR}/test-animation-alpha.mov"; then
  echo "ERROR: end-to-end Animation encode (with alpha) failed — see configure / build logs in ${WORK_DIR}."
  exit 1
fi
echo "    test-animation-alpha.mov: $(ls -l "${WORK_DIR}/test-animation-alpha.mov" | awk '{print $5}') bytes"

rm -f "${WORK_DIR}/test-frames.rgba"

cat <<EOM

==> SUCCESS

Universal binary at: ${WORK_DIR}/ffmpeg
Size: $(stat -f%z "${WORK_DIR}/ffmpeg" | awk '{ printf "%.1f MB\n", $1/1048576 }')

NEXT STEPS

If you built this on the same machine that holds your Tagtool repo, copy it
directly into the repo:

    cp "${WORK_DIR}/ffmpeg" /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/ffmpeg
    rm /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/PLACE_FFMPEG_HERE.txt
    rm /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/PLACE_FFMPEG_HERE.txt.meta

If you built this on a remote Mac (e.g. the mac-mini-builder), SCP it from
your dev machine:

    scp <mac-host>:${WORK_DIR}/ffmpeg /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/ffmpeg
    rm /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/PLACE_FFMPEG_HERE.txt
    rm /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/PLACE_FFMPEG_HERE.txt.meta

NOTES

- BundledFFmpegBuildHooks (Editor PostProcessBuild) chmods +x at .app build time.
- .github/workflows/main.yml already signs the binary alongside other nested
  binaries during codesign.
- For local Editor testing, you may need to clear the quarantine attribute
  once on first use:
      xattr -d com.apple.quarantine /path/to/Tagtool/src/Assets/StreamingAssets/ffmpeg/osx/ffmpeg

EOM
