chore(docker): pin FFmpeg 8.0.1 in the Docker images and host CI (#5576)

* chore(docker): pin FFmpeg 8.0.1 in the Docker images and host CI

Install one pinned static BtbN build (n8.0.1-66, month-end release) in
Dockerfile.test, the CLI render image, the Cloud Run image and the k8s
example, and bump the host CI install-ffmpeg-linux action from 7.1.3 to
the same build. Regression baselines and the shipped images now use the
same FFmpeg version instead of Debian's apt ffmpeg 5.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(producer): regenerate six regression baselines on FFmpeg 8.0.1

The Docker test image now renders with FFmpeg 8.0.1 instead of Debian's
5.1. Six fixtures fail against the 5.1 baselines; everything else passes
unchanged, including vfr-screen-recording.

- style-2-prod, style-10-prod: FFmpeg 8 honors the MP3 gapless header,
  so the probed SFX duration is 1.0s instead of 1.044898s. Regenerate
  output.mp4 and compiled.html.
- vignelli-stacking: one checkpoint (8.06s) now shows source frame 201
  of the 25fps a-roll, which is the right frame for that time; the old
  baseline showed frame 202.
- missing-host-comp-id, variables-prod, speed-ramp: AAC encoder output
  differs enough to drop the RMS-envelope correlation on flat signals
  below 0.9. Sample correlation against the source stays at 0.9999.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): read the ffmpeg version from n-prefixed release builds

Release-branch ffmpeg builds, including the BtbN build now pinned in CI,
print "ffmpeg version n8.0.1-66-...". extractMajorVersion skipped the
"n8" token and returned 0 (and 1 for n7.1.3), so the CLI telemetry
smoke check failed. parseToolVersion fell back to the whole banner line.
Accept an optional "n" before the version number in both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): keep the preflight comment share flat

Fold the n-prefix note into the existing doc line instead of adding new
comment lines, which the comment ratchet rejects.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(docker): pick the FFmpeg build per arch and fail on a bad binary

Dockerfile.test, the k8s example and the Cloud Run image always extracted the
amd64 archive on multiarch bases, and the piped version check passed even when
the binary could not run. All four images now pick linux64 or linuxarm64 by
TARGETARCH, fail on any other arch, and grep both ffmpeg and ffprobe for the
pinned version.

style-2-prod and style-10-prod compiled.html go back to the previous golden
with only the SFX data-duration/data-end changed, instead of a full
recompile that also picked up unrelated compiler output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
James Russo
2026-10-14 05:37:15 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 74e17fa9cf
commit 692b89b6e5
15 changed files with 127 additions and 2504 deletions
@@ -7,13 +7,13 @@ inputs:
release-url:
description: >-
BtbN/FFmpeg-Builds linux64 tarball. Bump the tag, filename and sha256
together.
together, and keep in sync with the Dockerfiles' FFMPEG pins.
required: false
default: https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-04-30-13-44/ffmpeg-n7.1.3-46-g957b06a788-linux64-gpl-7.1.tar.xz
default: https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-02-28-12-59/ffmpeg-n8.0.1-66-g27b8d1a017-linux64-gpl-8.0.tar.xz
sha256:
description: sha256 of the tarball, from the release's checksums.sha256.
required: false
default: 15a62cc3facdacfa5ce1fbe80f898fc77f159bdd5f3ed6b60dd2c970d52b3f19
default: 26d2ecd0cefba99f9d1ea92252a3a4f743a58df4c1c3cf9f8bbaaa40b1fca5b0
runs:
using: composite
+23 -1
View File
@@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
unzip \
ffmpeg \
xz-utils \
chromium \
libgbm1 \
libnss3 \
@@ -45,6 +45,28 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& apt-get clean \
&& fc-cache -fv
# ── FFmpeg (pinned) ──────────────────────────────────────────────────────────
# One pinned static build (BtbN n8.0.1) instead of Debian's apt ffmpeg, so
# regression baselines and the shipped images use the same FFmpeg version.
# Bump the URL and sha256 together, and keep in sync with the other Dockerfiles
# and .github/actions/install-ffmpeg-linux/action.yml.
ARG TARGETARCH
ARG FFMPEG_RELEASE=https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-02-28-12-59
ARG FFMPEG_BUILD=ffmpeg-n8.0.1-66-g27b8d1a017
ARG FFMPEG_SHA256_AMD64=26d2ecd0cefba99f9d1ea92252a3a4f743a58df4c1c3cf9f8bbaaa40b1fca5b0
ARG FFMPEG_SHA256_ARM64=d72cc324cb65dd92a9ffe699cc95ccbacfef4def8d3ad9fe2b5164a6a71b9595
RUN case "$TARGETARCH" in \
amd64) FF_ARCH=linux64; FF_SHA256="$FFMPEG_SHA256_AMD64" ;; \
arm64) FF_ARCH=linuxarm64; FF_SHA256="$FFMPEG_SHA256_ARM64" ;; \
*) echo "unsupported TARGETARCH '$TARGETARCH'" >&2; exit 1 ;; \
esac \
&& curl -fsSL -o /tmp/ffmpeg.tar.xz "$FFMPEG_RELEASE/$FFMPEG_BUILD-$FF_ARCH-gpl-8.0.tar.xz" \
&& echo "$FF_SHA256 /tmp/ffmpeg.tar.xz" | sha256sum -c - \
&& tar -xJf /tmp/ffmpeg.tar.xz -C /usr/local/bin --strip-components=2 --wildcards '*/bin/ffmpeg' '*/bin/ffprobe' \
&& rm /tmp/ffmpeg.tar.xz \
&& ffmpeg -version | grep -F "ffmpeg version ${FFMPEG_BUILD#ffmpeg-}" \
&& ffprobe -version | grep -F "ffprobe version ${FFMPEG_BUILD#ffmpeg-}"
# Use system Chromium (same as production)
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
+23 -2
View File
@@ -37,7 +37,6 @@
FROM node:22-bookworm-slim AS base
# ── System deps ──────────────────────────────────────────────────────────────
# - ffmpeg: the producer's encode + audio mix
# - libfontconfig / libfreetype / fonts-*: Chrome text shaping, the font set
# packages/gcp-cloud-run/Dockerfile uses (CJK and emoji included)
# - chromium-style ABI deps (the minimum set chrome-headless-shell needs):
@@ -47,7 +46,6 @@ FROM node:22-bookworm-slim AS base
# - tini: clean PID 1 for container teardown signals (Cloud Run / Fargate
# send SIGTERM at the 10-min grace boundary).
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \
ca-certificates \
fonts-dejavu-core \
fonts-freefont-ttf \
@@ -85,6 +83,29 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
xz-utils \
&& rm -rf /var/lib/apt/lists/*
# ── FFmpeg (pinned) ──────────────────────────────────────────────────────────
# The producer's encode + audio mix. One pinned static build (BtbN n8.0.1)
# instead of Debian's apt ffmpeg, so regression baselines and the shipped
# images use the same FFmpeg version.
# Bump the URL and sha256 together, and keep in sync with the other Dockerfiles
# and .github/actions/install-ffmpeg-linux/action.yml.
ARG TARGETARCH
ARG FFMPEG_RELEASE=https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-02-28-12-59
ARG FFMPEG_BUILD=ffmpeg-n8.0.1-66-g27b8d1a017
ARG FFMPEG_SHA256_AMD64=26d2ecd0cefba99f9d1ea92252a3a4f743a58df4c1c3cf9f8bbaaa40b1fca5b0
ARG FFMPEG_SHA256_ARM64=d72cc324cb65dd92a9ffe699cc95ccbacfef4def8d3ad9fe2b5164a6a71b9595
RUN case "$TARGETARCH" in \
amd64) FF_ARCH=linux64; FF_SHA256="$FFMPEG_SHA256_AMD64" ;; \
arm64) FF_ARCH=linuxarm64; FF_SHA256="$FFMPEG_SHA256_ARM64" ;; \
*) echo "unsupported TARGETARCH '$TARGETARCH'" >&2; exit 1 ;; \
esac \
&& wget -q -O /tmp/ffmpeg.tar.xz "$FFMPEG_RELEASE/$FFMPEG_BUILD-$FF_ARCH-gpl-8.0.tar.xz" \
&& echo "$FF_SHA256 /tmp/ffmpeg.tar.xz" | sha256sum -c - \
&& tar -xJf /tmp/ffmpeg.tar.xz -C /usr/local/bin --strip-components=2 --wildcards '*/bin/ffmpeg' '*/bin/ffprobe' \
&& rm /tmp/ffmpeg.tar.xz \
&& ffmpeg -version | grep -F "ffmpeg version ${FFMPEG_BUILD#ffmpeg-}" \
&& ffprobe -version | grep -F "ffprobe version ${FFMPEG_BUILD#ffmpeg-}"
# ── Chrome ───────────────────────────────────────────────────────────────────
# Use `@puppeteer/browsers` to fetch the chrome-headless-shell version
# packages/gcp-cloud-run/Dockerfile pins.
@@ -378,6 +378,12 @@ describe("parseToolVersion", () => {
"ffprobe 7.1.1-essentials_build-www.gyan.dev",
);
});
it("keeps the n prefix of release-branch builds", () => {
expect(parseToolVersion("ffmpeg version n8.0.1-66-g27b8d1a017-20260228 Copyright")).toBe(
"ffmpeg n8.0.1-66-g27b8d1a017-20260228",
);
});
});
describe("extractMajorVersion", () => {
@@ -385,6 +391,13 @@ describe("extractMajorVersion", () => {
expect(extractMajorVersion("ffmpeg version 7.1.1-essentials_build")).toBe(7);
});
it("reads the major from a release-branch build with an n prefix", () => {
expect(
extractMajorVersion("ffmpeg version n8.0.1-66-g27b8d1a017-20260228 Copyright (c) 2000-2025"),
).toBe(8);
expect(extractMajorVersion("ffmpeg version n7.1.3-46-g957b06a788-20260430")).toBe(7);
});
it("reads the major from a Chrome/HeadlessShell banner", () => {
expect(extractMajorVersion("Google Chrome 119.0.6045.105")).toBe(119);
expect(extractMajorVersion("HeadlessShell 119.0.6045.199")).toBe(119);
+3 -3
View File
@@ -49,13 +49,13 @@ export interface EnvironmentCheckOptions {
}
export function parseToolVersion(raw: string): string {
const m = raw.match(/(ffmpeg|ffprobe)\s+version\s+([\d][\d.\-\w]*)/i);
const m = raw.match(/(ffmpeg|ffprobe)\s+version\s+(n?\d[\d.\-\w]*)/i);
return m ? `${m[1]} ${m[2]}` : raw.trim();
}
/** First `X.Y`-shaped number in a version banner (ffmpeg/ffprobe/Chrome all share this shape). */
/** First `X.Y`-shaped number in a version banner, allowing the "n" of release-branch ffmpeg builds (n8.0.1). */
export function extractMajorVersion(raw: string): number | undefined {
const m = raw.match(/\b(\d+)\.\d+/);
const m = raw.match(/\bn?(\d+)\.\d+/);
return m?.[1] ? Number(m[1]) : undefined;
}
+21 -1
View File
@@ -14,7 +14,7 @@ ARG TARGETARCH=amd64
ARG PLAYWRIGHT_VERSION=1.61.1
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl unzip ffmpeg chromium \
ca-certificates curl unzip xz-utils chromium \
libgbm1 libnss3 libatk-bridge2.0-0 libdrm2 libxcomposite1 \
libxdamage1 libxrandr2 libcups2 libasound2 libpangocairo-1.0-0 \
libxshmfence1 libgtk-3-0 \
@@ -23,6 +23,26 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
fontconfig \
&& rm -rf /var/lib/apt/lists/* && apt-get clean && fc-cache -fv
# One pinned static FFmpeg build (BtbN n8.0.1, per arch) instead of Debian's apt
# ffmpeg, so regression baselines and the shipped images use the same version.
# Bump the URLs and sha256s together, and keep in sync with the other Dockerfiles
# and .github/actions/install-ffmpeg-linux/action.yml.
ARG FFMPEG_RELEASE=https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-02-28-12-59
ARG FFMPEG_BUILD=ffmpeg-n8.0.1-66-g27b8d1a017
ARG FFMPEG_SHA256_AMD64=26d2ecd0cefba99f9d1ea92252a3a4f743a58df4c1c3cf9f8bbaaa40b1fca5b0
ARG FFMPEG_SHA256_ARM64=d72cc324cb65dd92a9ffe699cc95ccbacfef4def8d3ad9fe2b5164a6a71b9595
RUN case "$TARGETARCH" in \
amd64) FF_ARCH=linux64; FF_SHA256="$FFMPEG_SHA256_AMD64" ;; \
arm64) FF_ARCH=linuxarm64; FF_SHA256="$FFMPEG_SHA256_ARM64" ;; \
*) echo "unsupported TARGETARCH '$TARGETARCH'" >&2; exit 1 ;; \
esac \
&& curl -fsSL -o /tmp/ffmpeg.tar.xz "$FFMPEG_RELEASE/$FFMPEG_BUILD-$FF_ARCH-gpl-8.0.tar.xz" \
&& echo "$FF_SHA256 /tmp/ffmpeg.tar.xz" | sha256sum -c - \
&& tar -xJf /tmp/ffmpeg.tar.xz -C /usr/local/bin --strip-components=2 --wildcards '*/bin/ffmpeg' '*/bin/ffprobe' \
&& rm /tmp/ffmpeg.tar.xz \
&& ffmpeg -version | grep -F "ffmpeg version ${FFMPEG_BUILD#ffmpeg-}" \
&& ffprobe -version | grep -F "ffprobe version ${FFMPEG_BUILD#ffmpeg-}"
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV CONTAINER=true
+23 -1
View File
@@ -28,7 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
unzip \
ffmpeg \
xz-utils \
libgbm1 \
libnss3 \
libatk-bridge2.0-0 \
@@ -54,6 +54,28 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& apt-get clean \
&& fc-cache -fv
# ── FFmpeg (pinned) ──────────────────────────────────────────────────────────
# One pinned static build (BtbN n8.0.1) instead of Debian's apt ffmpeg, so
# regression baselines and the shipped images use the same FFmpeg version.
# Bump the URL and sha256 together, and keep in sync with the other Dockerfiles
# and .github/actions/install-ffmpeg-linux/action.yml.
ARG TARGETARCH
ARG FFMPEG_RELEASE=https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-02-28-12-59
ARG FFMPEG_BUILD=ffmpeg-n8.0.1-66-g27b8d1a017
ARG FFMPEG_SHA256_AMD64=26d2ecd0cefba99f9d1ea92252a3a4f743a58df4c1c3cf9f8bbaaa40b1fca5b0
ARG FFMPEG_SHA256_ARM64=d72cc324cb65dd92a9ffe699cc95ccbacfef4def8d3ad9fe2b5164a6a71b9595
RUN case "$TARGETARCH" in \
amd64) FF_ARCH=linux64; FF_SHA256="$FFMPEG_SHA256_AMD64" ;; \
arm64) FF_ARCH=linuxarm64; FF_SHA256="$FFMPEG_SHA256_ARM64" ;; \
*) echo "unsupported TARGETARCH '$TARGETARCH'" >&2; exit 1 ;; \
esac \
&& curl -fsSL -o /tmp/ffmpeg.tar.xz "$FFMPEG_RELEASE/$FFMPEG_BUILD-$FF_ARCH-gpl-8.0.tar.xz" \
&& echo "$FF_SHA256 /tmp/ffmpeg.tar.xz" | sha256sum -c - \
&& tar -xJf /tmp/ffmpeg.tar.xz -C /usr/local/bin --strip-components=2 --wildcards '*/bin/ffmpeg' '*/bin/ffprobe' \
&& rm /tmp/ffmpeg.tar.xz \
&& ffmpeg -version | grep -F "ffmpeg version ${FFMPEG_BUILD#ffmpeg-}" \
&& ffprobe -version | grep -F "ffprobe version ${FFMPEG_BUILD#ffmpeg-}"
# ── chrome-headless-shell (deterministic BeginFrame capture) ─────────────────
# Pinned deliberately. The build-stage contract probe below launches this
# exact executable and requires a renderer-ready warm-up plus a PNG-returning
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61cc9a67d53cfe17fd701f716347567be875f53a71fd3a6df9bef9c306937ace
size 130189
oid sha256:af362a2663b53d50093bea4ea3a3fd8673c5cdbd8d2bb2be5e92e078b483420f
size 118044
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:45a3e8e979aed5ca14e6896366231bc7767a8d4526ffaeed0282c31c31a10dd8
size 277990
oid sha256:345e129cfef4cea951ac3bbdddfaae3c8933f12e4a2c243a93ad70bfbca8db1d
size 259520
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4dcecc39634a51efe1709f75b016b1a90a8eeea72f31b3e15d5cae43dfc8021e
size 11356525
oid sha256:3ecb9cd1cd268927c31d65b3264d59880bdd05cd57b5de2317fd1d82e4891191
size 10266131
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b2344fe8cbd76870b11b5ec8fa3affc3d7ec0f52d82f5c9d4d01620f1caad0a3
size 10991291
oid sha256:c5813e9a55d47c7215d086683e3b23792e8674683cf7ff759f75d9bc530adedf
size 10871515
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8661c4f42b2ec4315180d51a498b199f90e8234983e27b4c572f93e12ca44992
size 116892
oid sha256:d13aa856b2eb5ca2d04cf7db8c9cf981bdea6bdd1d50485c07478d9e427bc255
size 109724
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:520015e780742d74ea28bc88251bbf3adc03bfe42e16a3b28ef6b22bffaea4c0
size 4786337
oid sha256:1087e541522fb100e92c86b25cced009ff7cef8e449bfcfccf2f8af233deed94
size 7269359