unarr/scripts/download-ffmpeg-static.sh
Deivid Soto e68b127acc feat(release): bundle ffmpeg + ffprobe in tarballs and Docker image
Operators no longer have to install ffmpeg manually. Both the release
tarballs (5 platforms × 2 binaries) and the Docker image now ship a
working ffmpeg + ffprobe pair adjacent to the unarr binary;
ResolveFFmpeg / ResolveFFprobe pick them up via the "adjacent to
executable" branch with zero configuration.

Tarball bundle (scripts/download-ffmpeg-static.sh + .goreleaser.yml):
- ffbinaries.com (johnvansickle / Zeranoe-style static GPL builds) for
  linux-amd64, linux-arm64, darwin-amd64, windows-amd64
- evermeet.cx universal Mach-O for darwin-arm64 (ffbinaries lacks it)
- BtbN/FFmpeg-Builds for windows-arm64 (ffbinaries lacks it)
- Idempotent fetch with curl --retry 5 so transient github.com SSL
  errors don't fail the goreleaser before-hook
- New `before.hooks` runs the script automatically per release; archive
  files glob `dist-ffbinaries/{{ .Os }}-{{ .Arch }}/*` + strip_parent
- Migrated to non-deprecated `formats: [tar.gz]` / `formats: [zip]`
- Verified via `goreleaser release --snapshot --clean --skip=publish` —
  6 archives all carry ffmpeg + ffprobe (~60-130MB each)

Docker image (Dockerfile):
- Replaced the failing BtbN static glibc binaries with Alpine's native
  musl `apk add ffmpeg`. The static GPL builds need glibc + libmvec /
  libgcc_s; gcompat alone is not enough (vector-math symbols unresolved).
  Alpine ships ffmpeg 6.1.2 which is fine for the WebRTC transcoder.
- Image size 174MB, built + ffmpeg/ffprobe/unarr smoke OK.

Targets the v0.8 unarr release (per user direction — new feature, not
a patch). dist-ffbinaries/ added to .gitignore.
2026-05-06 11:26:01 +02:00

117 lines
3.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# scripts/download-ffmpeg-static.sh — fetch static ffmpeg + ffprobe binaries
# for every platform we ship. Run by goreleaser's `before.hooks` so each
# tarball can bundle the binaries adjacent to `unarr`.
#
# Source: https://ffbinaries.com (same index the runtime fallback uses).
# Output:
# dist-ffbinaries/<goos>-<goarch>/{ffmpeg, ffprobe}[.exe]
# Idempotent: skips downloads when the target file already exists.
set -euo pipefail
# Map ffbinaries platform key → goreleaser {Os}-{Arch}. ffbinaries.com only
# ships an x86_64 macOS build; for darwin-arm64 we fall back to evermeet.cx
# universal binaries (handled separately below).
PLATFORMS=(
"linux-64:linux-amd64"
"linux-arm64:linux-arm64"
"osx-64:darwin-amd64"
"windows-64:windows-amd64"
)
DEST_ROOT="${FFBINARIES_DEST:-dist-ffbinaries}"
INDEX_URL="https://ffbinaries.com/api/v1/version/latest"
for cmd in curl jq unzip; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "[ffbin] missing required tool: $cmd" >&2
exit 2
}
done
mkdir -p "$DEST_ROOT"
echo "[ffbin] fetching index from $INDEX_URL"
INDEX_JSON="$(curl -fsSL "$INDEX_URL")"
VERSION="$(echo "$INDEX_JSON" | jq -r .version)"
echo "[ffbin] ffbinaries version: $VERSION"
for entry in "${PLATFORMS[@]}"; do
ffbkey="${entry%%:*}"
goplat="${entry##*:}"
outdir="$DEST_ROOT/$goplat"
mkdir -p "$outdir"
for tool in ffmpeg ffprobe; do
binname="$tool"
[[ "$goplat" == windows-* ]] && binname="${tool}.exe"
if [ -f "$outdir/$binname" ]; then
echo "[ffbin] skip $goplat/$binname (already present)"
continue
fi
url="$(echo "$INDEX_JSON" | jq -r ".bin[\"$ffbkey\"][\"$tool\"] // empty")"
if [ -z "$url" ]; then
echo "[ffbin] WARN $goplat/$tool: no download URL in index" >&2
continue
fi
tmpzip="$(mktemp --suffix=.zip)"
echo "[ffbin] fetch $goplat/$tool from $url"
curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors "$url" -o "$tmpzip"
unzip -p "$tmpzip" "$binname" > "$outdir/$binname"
chmod +x "$outdir/$binname"
rm -f "$tmpzip"
done
done
# --- darwin-arm64 via evermeet.cx (universal binary; ffbinaries lacks it) ---
darwin_arm_dir="$DEST_ROOT/darwin-arm64"
mkdir -p "$darwin_arm_dir"
for tool in ffmpeg ffprobe; do
out="$darwin_arm_dir/$tool"
if [ -f "$out" ]; then
echo "[ffbin] skip darwin-arm64/$tool (already present)"
continue
fi
url="https://evermeet.cx/ffmpeg/getrelease/$tool/zip"
tmpzip="$(mktemp --suffix=.zip)"
echo "[ffbin] fetch darwin-arm64/$tool from $url"
curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors "$url" -o "$tmpzip"
unzip -p "$tmpzip" "$tool" > "$out"
chmod +x "$out"
rm -f "$tmpzip"
done
# --- windows-arm64 via BtbN/FFmpeg-Builds (ffbinaries lacks it) ---
# BtbN ships a single zip per platform with ffmpeg.exe + ffprobe.exe under
# ffmpeg-master-latest-winarm64-gpl/bin/. Extract both in one fetch.
win_arm_dir="$DEST_ROOT/windows-arm64"
mkdir -p "$win_arm_dir"
needs_win_arm=0
for tool in ffmpeg.exe ffprobe.exe; do
[ -f "$win_arm_dir/$tool" ] || needs_win_arm=1
done
if [ "$needs_win_arm" = "1" ]; then
url="https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-winarm64-gpl.zip"
tmpzip="$(mktemp --suffix=.zip)"
echo "[ffbin] fetch windows-arm64/{ffmpeg,ffprobe}.exe from $url"
curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors "$url" -o "$tmpzip"
for tool in ffmpeg.exe ffprobe.exe; do
out="$win_arm_dir/$tool"
member="$(unzip -Z1 "$tmpzip" "*/bin/$tool" 2>/dev/null | head -1)"
if [ -z "$member" ]; then
echo "[ffbin] WARN windows-arm64/$tool: not found in BtbN zip" >&2
continue
fi
unzip -p "$tmpzip" "$member" > "$out"
chmod +x "$out"
done
rm -f "$tmpzip"
else
echo "[ffbin] skip windows-arm64 (already present)"
fi
echo "[ffbin] done. layout:"
find "$DEST_ROOT" -type f -printf " %p (%s bytes)\n"