Skip to content

Migrate to full-feature FFmpeg build (forge) + ffmpeg-bins2 + manifest.json catalog, preserving legacy installs #20

Description

@zackees

Summary

Move static-ffmpeg onto a freshly built, full-feature FFmpeg (all features enabled, including GPL/nonfree/licensed codecs), built reproducibly via zackees/forge, published to a new zackees/ffmpeg-bins2 repo (content stored via Git LFS), and cataloged with a zackees/manifest.json-format manifest so the Python client resolves the correct artifact per platform.

Hard constraint: existing static-ffmpeg installs that download from the legacy zackees/ffmpeg_bins repo must keep working forever, and we must be able to ship future FFmpeg upgrades without breaking already-deployed installs.


Goals

  1. Build FFmpeg + FFprobe with all features enabled, including options that require GPL and --enable-nonfree (see Licensing risk below).
  2. Build the full target matrix (see below), including Linux glibc 2.17 and musl variants.
  3. Create zackees/ffmpeg-bins2, store binaries via Git LFS.
  4. Publish a manifest.json catalog (per the zackees/manifest.json schema) describing every artifact with sha256, size_bytes, and download urls[].
  5. Update the static-ffmpeg client to resolve + verify artifacts through the manifest.
  6. Backward compatibility: never mutate ffmpeg_bins or its raw URLs; old installs keep resolving their pinned binaries.
  7. Forward compatibility: future FFmpeg upgrades are a manifest/channel-pointer change, not a client release, and don't disturb existing installs.

Target build matrix

Key (platform tuple) os arch libc Notes
win_x64 windows x86_64
win_arm64 windows arm64 forge windows_arm64
darwin_x64 macos x86_64
darwin_arm64 macos arm64
linux_x64_glibc217 linux x86_64 glibc 2.17 manylinux2014 baseline
linux_arm64_glibc217 linux arm64 glibc 2.17 manylinux2014 baseline
linux_x64_musl linux x86_64 musl forge linux_x64_musl
linux_arm64_musl linux arm64 musl forge linux_arm64_musl

8 targets. Each artifact packages ffmpeg + ffprobe (+ LICENSE/build-info).


Build: zackees/forge

  • Author/point to a Conan recipe for FFmpeg configured with the full option set (GPL + nonfree + all external codec libs the recipe supports).
  • Drive builds via the reusable workflow zackees/forge/.github/workflows/forge-conan.yml@main, toggling the platform inputs (windows_arm64, linux_x64_musl, linux_arm64_musl, etc.).
  • forge uploads a max-compression archive per platform → collect these and republish into ffmpeg-bins2.

⚠️ glibc 2.17 risk: forge builds on native GitHub runners (Ubuntu with glibc 2.35+). A native build will not run on glibc 2.17 systems. The glibc-2.17 targets need a manylinux2014 / CentOS 7 container (or an equivalent sysroot toolchain) so the resulting binaries carry a 2.17 floor. Decide whether this is a forge job variant or a separate container build feeding the same manifest.


New repo: zackees/ffmpeg-bins2

  • Create the repo (owner note: the goal text says "fmpeg-bins2" — assuming ffmpeg-bins2).
  • .gitattributes: * binary binaries tracked by LFS (*.zip/*.tar.zst filter=lfs diff=lfs merge=lfs -text).
  • Layout: versioned dirs, e.g. ffmpeg/<version>/<platform-key>.<ext> plus a top-level manifest.json.

⚠️ LFS + download-URL pitfall (learned the hard way in this repo): do not assume github.com/.../raw/... will serve LFS content the way it serves plain blobs. The manifest must carry the canonical download URLs plus sha256, and the client must verify the hash — so integrity never depends on raw-URL smudging behavior.

Transport (decided): serve the LFS files behind a www site fronted by a CDN. The binaries are stored in-repo via LFS, but the manifest's Asset.urls[] point at CDN-fronted https://<www-site>/... URLs (not GitHub raw/LFS-media or Release-asset endpoints). This puts delivery on a CDN with no bandwidth/quota limits (avoiding both GitHub LFS bandwidth caps and Release-asset rate limits), gives stable cacheable URLs, and keeps integrity guaranteed by the manifest sha256. LFS remains the in-repo source of truth; the www/CDN layer is the distribution front-end.


Catalog: manifest.json (per zackees/manifest.json)

Produce a Catalog document for tool: "ffmpeg":

  • kind: "Catalog", schema_version: 1, tool: "ffmpeg"
  • channels: { "latest-stable": "<version>" }
  • releases[]: newest-first, each with version, published_at, and platforms[] mapping platform tuples → Asset { filename, sha256, size_bytes, media_type, urls[] }.
  • Platform tuple fields used: {os, arch, libc} (libc = glibc/musl; optionally os_version for the 2.17 floor).

Client resolution contract: resolve("ffmpeg", <detected-platform>, "latest-stable") → Asset.


Client changes (static_ffmpeg/run.py)

  1. Platform detection must gain libc awareness on Linux (distinguish glibc vs musl — e.g. platform.libc_ver() / probe for ld-musl-* / ctypes), producing the platform tuple {os, arch, libc}.
  2. Manifest resolver: fetch ffmpeg-bins2/manifest.json, resolve the tuple + latest-stable channel to an Asset, download from Asset.urls, and verify sha256 before extracting.
  3. Version pinning: record the resolved version + sha256 in installed.crumb. Existing installs stay on their pinned artifact and don't re-download; only fresh installs (or an explicit upgrade) pull the newest channel version.
  4. Fallback: if the manifest fetch/resolve fails, fall back to the current hardcoded legacy behavior so we never regress availability.

Backward compatibility & upgrade strategy (must-have)

  • Freeze zackees/ffmpeg_bins. Never migrate it to LFS, never move/rename v8.0/*.zip. Old clients hardcode ffmpeg_bins/raw/main/v8.0/<platform>.zip; those exact URLs must keep serving the exact bytes forever. (This is why the recent local LFS migration of ffmpeg_bins was reverted rather than pushed.)
  • New installs use ffmpeg-bins2 + manifest.
  • Upgrading FFmpeg later = publish a new release into ffmpeg-bins2 and repoint the latest-stable channel in manifest.json. No client release required. Already-installed users keep their pinned version (crumb) and are unaffected; new installs get the new version. This decouples FFmpeg version cadence from static-ffmpeg releases.

⚠️ Licensing risk (decision needed, not a blocker)

Enabling --enable-gpl --enable-nonfree produces binaries whose redistribution is legally restrictednonfree output is, per FFmpeg's own docs, not redistributable. Publishing such binaries in a public repo/release may violate those terms. Options to decide:

  • Keep nonfree artifacts in a private distribution channel, or
  • Ship a public GPL (no-nonfree) default plus an opt-in nonfree channel, or
  • Accept the risk explicitly (owner's call).

Flagging per repo policy; proceeding as the owner directs.


Acceptance criteria / task checklist

  • FFmpeg Conan recipe with full feature set (GPL + nonfree) builds green in forge for all 8 targets.
  • Linux glibc-2.17 binaries verified to run on a glibc 2.17 host; musl binaries verified on Alpine.
  • zackees/ffmpeg-bins2 created; binaries stored (LFS in-repo and/or Release assets).
  • manifest.json (Catalog) validates against the zackees/manifest.json schema and resolves all 8 tuples.
  • static-ffmpeg client resolves via manifest, verifies sha256, and falls back to legacy on failure.
  • Client distinguishes glibc vs musl on Linux.
  • Regression test: an "old" client config still downloads from ffmpeg_bins/raw/main/v8.0/*.zip successfully.
  • Documented upgrade runbook: how to publish a new FFmpeg version by repointing the channel without breaking existing installs.
  • Licensing decision recorded.

Open questions

  1. glibc 2.17: forge container job vs. separate manylinux2014 build?
  2. Distribution transport Decided: LFS files served over a www site fronted by a CDN (no bandwidth limits); manifest urls[] point at the CDN. Remaining sub-question: which host/CDN for the www site?
  3. Nonfree licensing channel: public, private, or split?
  4. FFmpeg version to target for the first ffmpeg-bins2 release?

Filed via Claude Code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions