# Bundled FFmpeg (HAP + Windows ProRes + Animation recording)

This directory ships a minimal FFmpeg CLI binary that Tagtool's bundled
video-codec path invokes as a subprocess. See `TTVideoRecorderFFmpeg` and
the architecture doc `render-output-system.md` § "Video Recording" for
runtime details, and issue #943 for the original HAP motivation.

The same binary now drives **three** Tagtool codec paths:

- HAP / HAP Alpha — on Windows + macOS Standalone.
- ProRes 422 HQ + ProRes 4444 — on **Windows only**. On macOS the ProRes
  buttons route to Apple's hardware encoder via AVPro, not this binary.
- Animation (QuickTime RLE / `qtrle`) — on Windows + macOS Standalone.
  Alpha-capable lossless slot: truly bit-exact lossless RLE compression,
  ~25% of ProRes 4444 file size on Tagtool's flat-cartoon content,
  native NLE import since 1991 (QuickTime 1.0).

## Layout

```text
ffmpeg/
├── README.md                    (this file)
├── LICENSES.txt                 (LGPL 2.1 notice + source link)
├── win-x64/
│   ├── build-windows-ffmpeg.sh  (WSL cross-compile build script)
│   └── ffmpeg.exe               (Windows x64 — committed, ~2 MB)
└── osx/
    ├── build-macos-ffmpeg.sh    (build script for the Mac mini runner)
    └── ffmpeg                   (macOS universal — committed, ~3.5 MB)
```

Both binaries ship in the repo. They're not regenerated by CI on every
build — when the build script changes (e.g. a new codec is added to the
configure flags), a dev rebuilds locally via the scripts in this
directory and commits the updated binaries alongside the script change.
See [`docs/workflow/bundled-ffmpeg-build.md`](../../../../docs/workflow/bundled-ffmpeg-build.md)
for the full runbook.

If a binary is missing for some reason, the matching codec button still
appears in the codec menu but recording fails at runtime with
`[TTVideoRecorderFFmpeg] FFmpeg binary not found …`.

## How to obtain the binaries

The FFmpeg build must include the `hap` encoder + decoder, the `prores_ks`
encoder + `prores` decoder, the `qtrle` encoder + decoder, the `rawvideo`
encoder + decoder + muxer + demuxer, the `mov` muxer + demuxer, the `pipe`
and `file` protocols, and link against `libsnappy`. All other codecs,
muxers, protocols, and programs should be disabled to keep the binary small.

**macOS shortcut**: the script [`osx/build-macos-ffmpeg.sh`](osx/build-macos-ffmpeg.sh)
automates the full build (clone Snappy + FFmpeg, build both arch slices,
lipo into a universal binary, strip, smoke-test). Run on an Apple Silicon
Mac with Xcode command-line tools installed; takes ~5–10 minutes.

**Windows**: build from a Linux box with `mingw-w64` cross-compiler. The
canonical build for the bundled `win-x64/ffmpeg.exe` was produced in WSL
Ubuntu 24.04 via the configure flags below. The repo ships the resulting
binary directly (~1.9 MB) — only re-run this if you're bumping the FFmpeg
version or rebuilding from updated upstream.

### Recommended source — build from upstream

```sh
# FFmpeg 7.x (any release after Hap encoder gained alpha support is fine)
git clone --depth 1 --branch n7.1 https://github.com/FFmpeg/FFmpeg.git
cd FFmpeg

./configure \
  --disable-everything \
  --enable-programs \
  --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-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

make -j8
```

The four filter enables are required because `--disable-everything`
disables `FILTER_LIST` too. `vflip` corrects the AsyncGPUReadback
bottom-left → FFmpeg top-left origin flip on every codec path; `format`
performs the rgba → yuv422p10le / yuva444p10le / yuv444p10le / rgb24
pixel-format conversion ProRes and Animation require; `crop` truncates
HAP input to /4-aligned dimensions (HAP's DXT-based 4×4 block compression
rejects non-aligned input — the recorder appends `crop=W:H:0:0` after
`vflip` whenever the canvas RT isn't already /4-aligned); `scale` is
enabled as a defensive fallback for edge cases where `format` isn't
sufficient
(lighter than going the other way around).

Resulting `ffmpeg` (or `ffmpeg.exe`) is the binary to drop into the
appropriate sub-folder above.

### Pre-built alternative

If a verified static build matches the above config, pre-built binaries can
be sourced from:

- Windows: [BtbN AutoBuild](https://github.com/BtbN/FFmpeg-Builds) or gyan.dev
- macOS: build locally; reliable third-party universal-binary pre-builds with
  the exact minimal config above are not commonly published.

Either way, verify the binary supports HAP, ProRes, and Animation encoding:

```sh
./ffmpeg -hide_banner -encoders 2>&1 | grep -iE "hap|prores|qtrle"
# Should output:  V..... hap                  Vidvox Hap
#                 V....D prores_ks            Apple ProRes (iCodec Pro)
#                 V....D qtrle                QuickTime Animation (RLE) video
```

## Building a universal-binary macOS ffmpeg

For Tagtool's macOS distribution, the `osx/ffmpeg` binary must be a universal
Mach-O containing both arm64 and x86_64 slices. Build each arch separately
then combine with `lipo`:

```sh
# arm64 build
./configure --enable-cross-compile --arch=arm64 --target-os=darwin \
            --extra-cflags="-arch arm64" --extra-ldflags="-arch arm64" \
            [...configure flags from above...]
make -j8
mv ffmpeg ffmpeg-arm64

# x86_64 build (on Apple Silicon, needs Rosetta toolchain; on Intel, native)
make clean
./configure --enable-cross-compile --arch=x86_64 --target-os=darwin \
            --extra-cflags="-arch x86_64" --extra-ldflags="-arch x86_64" \
            [...configure flags from above...]
make -j8
mv ffmpeg ffmpeg-x86_64

# Combine
lipo -create ffmpeg-arm64 ffmpeg-x86_64 -output ffmpeg
lipo -info ffmpeg
# Should output:  Architectures in the fat file: ffmpeg are: x86_64 arm64
```

Then drop `ffmpeg` into `osx/`. The `chmod +x` is applied at build time by
`Assets/tagtool/scripts/Editor/BundledFFmpegBuildHooks.cs`.

## File-size budget

Expected sizes for a minimal-config build with HAP + ProRes + Animation:

- `win-x64/ffmpeg.exe`     ~2 MB (single arch, static)
- `osx/ffmpeg`             ~3.5 MB (universal: arm64 + x86_64)

Adding ProRes (`prores_ks` encoder + `prores` decoder) and Animation
(`qtrle` encoder + decoder) each increase the binary by roughly 30–80 KB
over the HAP-only build — within noise.

If the binary is significantly larger, the build likely enabled features that
weren't disabled — re-check that `--disable-everything` is the first flag in
the configure invocation.

## Code-signing (macOS only)

The GitHub Actions Mac workflow signs nested binaries via a `find` invocation
that matches `*.dylib`, `*.so`, `*.bundle`. The bundled `ffmpeg` Mach-O
matches none of those patterns, so the existing codesign step must be
extended to include it explicitly. See `.github/workflows/main.yml` and
`build-automation.md` § "macOS Code Signing".

## LGPL 2.1 compliance

Bundling FFmpeg in a closed-source application is permitted under LGPL 2.1
provided the corresponding source is made available and the license notice
is included. See `LICENSES.txt` in this directory for the notice, and host
the exact source tarball + configure flags used to build the binaries at a
public URL referenced from `LICENSES.txt` whenever the bundled binary is
rebuilt.

## Platforms not covered

- **iOS** — bundled subprocesses are forbidden by App Store policy 2.5.2 and
  the iOS sandbox doesn't allow `posix_spawn`. The runtime gate in
  `TTVideoRecorderFFmpeg.ResolveFFmpegPath` returns `null` on iOS and the
  HAP codec button is hidden in the codec menu. The bundled binaries here
  are dead weight on iOS builds — small enough (~15 MB total) to ignore for
  now. If/when build size becomes a concern, add an `IPreprocessBuildWithReport`
  Editor script that moves this directory aside for iOS / Android targets.
- **Android** — same as iOS in this codebase; no current HAP support.
- **Linux** — works in principle (FFmpeg builds cleanly on Linux, the runtime
  gate could be extended to `UNITY_STANDALONE_LINUX`), but Tagtool doesn't
  ship a Linux build target today.
