feat(ffmpeg)!: FFmpeg 8.1.2 (MT) + slim/full release variants - #2
Open
asoulliereHT wants to merge 30 commits into
Open
asoulliereHT wants to merge 30 commits into
asoulliereHT wants to merge 30 commits into
Conversation
Reproduce the current (pre-upgrade) FFmpeg n5.1.4 WASM build as a rollback reference before the 7.1 upgrade effort. - build/ffmpeg.sh: cap FFmpeg `make` parallelism (FFMPEG_JOBS, default 4) so individual clang processes aren't OOM-killed when the amd64-only emsdk 3.1.40 toolchain runs under x86 emulation on Apple Silicon - .gitignore: ignore local buildx cache dirs (build-cache-st/-mt) - BASELINE-n5.1.4.md: record toolchain, external-lib pins, FFmpeg configure flags, artifact sizes, test results (ST 23 pass, MT wrapper 11 pass), and pre-existing harness gaps (broken serve script, MT-core main-thread test, Node test path) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the toolchain off the amd64-only emsdk 3.1.40, which forced slow, OOM-prone x86 emulation on Apple Silicon. Only emscripten/emsdk:latest is multi-arch, so pin it by manifest-list digest (= Emscripten 6.0.2): reproducible builds that run native on both arm64 (local) and amd64 (CI). Drop x265: its x86-centric CMake passes `-march=`, which emsdk 6.0.2's newer clang rejects for the wasm target. x265 was already slated for removal (HEVC *encode* only; HEVC decode remains via FFmpeg's native decoder), so this both unblocks the toolchain bump and advances the trim. All other vendored libs build cleanly under 6.0.2. FFmpeg itself is not built here yet; the current n5.1.4 is superseded by the 7.1 upgrade (the pre-upgrade baseline is captured on chore/phase-0-baseline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Toolchain + build now produce a statically-linked MT ffmpeg-core.wasm (~27.5 MB) reporting "version 7.1.5" (libavcodec 61). NOT yet functional at runtime — MT scheduler hangs during the test suite; re-entrancy, progress, and timeout bridges are not reimplemented for 7.x yet. Build/toolchain: - Dockerfile: FFMPEG_VERSION n5.1.4 -> n7.1.5 - build/ffmpeg.sh: scope -fPIC to FFmpeg configure (emsdk 6.0.2 probe fix) and strip *.so from the dep prefix so -l<lib> links statically (zlib's CMake emits libz.so despite BUILD_SHARED_LIBS=OFF, which emsdk 6.0.2 turned into a runtime dlopen) - build/ffmpeg-wasm.sh: add 7.x fftools (dec/demux/enc/mux_init/sched + objpool/sync_queue/thread_queue); add -I./compat/stdbit (C23 header) - build/fribidi.sh: serial install (c2man man-page race under -j) fftools (pristine 7.1.5 + minimal wasm patches): - vendored all 7.1.5 fftools/*.c/*.h (scheduler-based frontend) - ffmpeg.c: main() -> ffmpeg(); ffprobe.c: main() -> ffprobe() - ffprobe.c: rename program_name / program_birth_year / show_help_default (+_ffprobe) to avoid link collisions with ffmpeg.c - bind.js: capture _ffmpeg()/_ffprobe() return value into Module.ret (7.x removed exit_program(); main() returns the code directly) Still TODO: MT runtime hang; re-entrancy (init_globals equivalent); progress bridge; timeout; ST-core threading decision; lib version bumps + theora/vorbis trim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MT core now loads, remuxes, and transcodes (single-instance, incl.
auto codec-threading and re-entrant execs when logging paces them).
- ffmpeg-wasm.sh: bake pthread-worker-URL fix — emsdk 6.0.2 spawns
pthread workers from _scriptName (the wrapper worker's URL, not the
core's), hanging load(); post-build sed restores the
mainScriptUrlOrBlob override so pthreads load the core. Fixes the
silent load() hang.
- ffmpeg-wasm.sh: PTHREAD_POOL_SIZE 32 -> 64 — FFmpeg 7.x scheduler
(per-component threads) + codec frame-threading overflowed a 32-pool
and deadlocked mid-transcode (overflow pthread_create can't spawn
while the worker main thread is blocked).
- ffmpeg.c: add init_globals() reset at the top of ffmpeg() — 7.x
ffmpeg_cleanup() frees the input/output/filtergraph/decoder arrays but
leaves nb_* counts and transcode_init_done/nb_output_dumped set, so a
2nd exec() ran against stale state and hung.
Known remaining (runtime): (1) rapid successive transcodes have a
timing-sensitive scheduler race (masked by log pacing); (2) the test
suite creates a fresh 64-worker instance per describe block and exhausts
browser workers ("Maximum call stack size"); pool sizing vs multi-
instance needs tuning (likely cap default threads + smaller pool).
Also still TODO: progress bridge, timeout, ST-core decision, lib trim.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single-instance now transcodes repeatedly with no deadlock: -h -> N
sequential transcodes all succeed, without needing log pacing.
- PTHREAD_POOL_SIZE 64 -> 32 (matches the historical footprint; keeps
per-instance worker count sane).
- Post-build: cap navigator.hardwareConcurrency to 8 so FFmpeg's
av_cpu_count (sysconf -> hardwareConcurrency) requests at most ~8
threads/codec. Uncapped, the 7.x scheduler + frame-threading requested
~2*cores threads, overflowed the pool, and deadlocked mid-transcode
(the "race" that log pacing masked). Explicit -threads still overrides.
- Fix -o output extraction in the post-build patch step (printf '%s'
dropped arg spaces so neither patch applied).
Known remaining: terminate() after a transcode does not reap the MT
core's pthread workers, so creating a fresh instance afterwards fails
("Maximum call stack size"). Single long-lived instance (the common
case) is unaffected. Still TODO: progress bridge, timeout, ST decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopt the single-long-lived-instance pattern (create/load once, reuse for every operation, terminate once) instead of one instance per describe block. This mirrors real-world usage and sidesteps the emsdk 6.0.2 pthread-pool leak that surfaces when many instances are created and terminated in one page (see Phase 3 handoff). The MT suite now runs cleanly; remaining failures are the not-yet-ported progress and timeout bridges, not instance lifecycle. Dropped the brittle "should list root dir" exact-count assertion (a shared instance pre-writes video.mp4, changing the count); listDir stays covered by the directory-API tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-implement the two wasm JS bridges against FFmpeg 7.x's scheduler frontend (bind.js already wired them): - send_progress (EM_JS -> Module.receiveProgress): computed in print_report() from pts / longest input duration, before the -stats early return so it always fires; emits 1.0 on the final report. - is_timeout / get_timeout (EM_JS -> Module.timeout): checked in the transcode() scheduler loop. 7.x has no exit_program(), so a timeout breaks the loop and forces ret=1 (bind.js captures ffmpeg()'s return). The loop's poll period is tightened to the timeout when one is set (it otherwise wakes only every stats_period ~0.5s, too coarse for short timeouts); print_report self-rate-limits so stats are unaffected. The MT wrapper suite (tests/ffmpeg.test.js) now passes 10/10: FS/dir APIs, help, transcode+progress, timeout, abort. The MT-core *direct* suite still fails — it runs the MT core on the page main thread where Atomics.wait is illegal; pre-existing, needs a worker-based harness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the theora, vorbis, and ogg builder stages, their COPYs into ffmpeg-base, the --enable-libtheora/--enable-libvorbis configure flags, and the -logg/-ltheora/-lvorbis* link flags. These are dead formats for a web clip/stitch/H.264 workload; x264 (re-encode), vpx, opus, mp3, aac (native), and webp remain. MT core: 27.5 MB -> 24.9 MB; wrapper suite still 10/10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The clip/stitch workload burns in no subtitles or text overlays, so the
entire subtitle/text-shaping stack is removed: libass, freetype, fribidi,
harfbuzz builder stages, their COPY, the --enable-lib{freetype,fribidi,ass}
configure flags, and the -l{freetype,fribidi,harfbuzz,ass} link flags.
This removes three CVE-carrying libraries outright (freetype 2.10.4,
fribidi 1.0.9, harfbuzz 5.2.0) rather than bumping them, and shrinks the
MT core 24.9 MB -> 23.6 MB. Also disables the drawtext filter (shares
freetype) — re-add freetype if in-browser text overlays are needed later.
Wrapper suite still 10/10. (build/{freetype2,fribidi,harfbuzz,libass,
ogg,theora,vorbis}.sh are now unused; left in place, harmless.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `serve` script piped COOP/COEP/CORP headers to `http-server --headers`, which is a silent no-op (http-server has no such flag) — so pages were never cross-origin isolated and the MT (SharedArrayBuffer) tests could not run against it. Add scripts/serve.js, a dependency-free Node static server that actually sends the isolation headers and correct MIME types (esp. application/wasm), and point `serve` at it. MT wrapper suite now runs 10/10 against `npm run serve`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add correctness tests for the browser-side clipping workload: lossless cut (-ss/-t -c copy) and lossless concat (-f concat -c copy) — both verified working on the MT 7.1.5 core. Suite now 12/12. Documents (in a comment) that multi-input filtergraphs (xfade transitions, concat filter, overlay) deadlock on the MT 7.x core in wasm and must run server-side; only stream-copy stitching is browser-side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supply-chain hardening: - x264 (was branch `4-cores`) and lame (was branch `master`) are now pinned to their branch-HEAD commit SHAs — those floating branches made the build non-reproducible. - zlib 1.2.11 -> 1.3.1 from upstream madler/zlib (pinned by the v1.3.1 commit SHA; the ffmpegwasm fork has no 1.3.1 tag). Fixes CVE-2018-25032 and CVE-2022-37434. Confirmed: buildkit resolves the commit-SHA git refs, zlib 1.3.1 is embedded in the core, wrapper suite still 12/12. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite .github/workflows/CI.yml for the 7.1.5/emsdk-6.0.2 toolchain: - checkout@v4, setup-node@v4 (Node 20), buildx v3; add least-privilege permissions block (was none; deprecated checkout@v2/setup-node@v2 before). - Build the MT core via `make prd-mt` with GHA buildx cache (emsdk 6.0.2 is multi-arch -> native amd64 on the runner). MT-only: no ST core exists on 7.x. Chromium comes from the puppeteer bundled by mocha-headless-chrome (npm ci), so no separate setup-chrome action. - Run the MT suite via start-server-and-test against the fixed serve script. - release job (on v* tags): build + tar core-mt/dist and ffmpeg/dist and attach to the GitHub Release, so a Go app can vendor from a targeted release. Also: - test:all -> MT-only (npm test now passes 12/12; ST/core-direct targets can't run on 7.x). - scripts/serve.js returns 200 at "/" so start-server-and-test/wait-on's health check passes. Note: CI itself is verified only once pushed to GitHub Actions; the flow it runs is validated locally (npm test = 12/12). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Team-facing doc: what changed vs upstream (7.1.5, lean codec set, MT-only), the browser capability map (cut/transcode/concat work; multi-input filtergraphs run server-side), the required COOP/COEP headers, single-instance usage, build steps, and how to vendor from a tagged release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The committed package-lock.json is out of sync with the apps/* example workspaces, so strict `npm ci` fails. Those apps aren't needed to build or test the core; use `npm install` (lenient). Bump Node 20 -> 22 to clear the EBADENGINE warning from an apps/* devDep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-base the vendored fftools frontend onto FFmpeg n8.1.2 and rebuild the MT core. Builds clean; 12/12 MT browser suite passes; core reports libavformat 62.12.102 (genuine 8.1.2). The 7.1→8.1 delta is small — same thread-based scheduler architecture — so the 7.1 port carried over with minor reworking. - Dockerfile: FFMPEG_VERSION n7.1.5 → n8.1.2 - Re-vendor src/fftools from 8.1.2, re-applying the wasm patches: ffmpeg.c (EM_JS progress/timeout bridges, init_globals, main→ffmpeg, timeout poll-period) and ffprobe.c (symbol renames, main→ffprobe) - Drop objpool.c/.h — 8.x thread_queue uses libavutil/container_fifo - Add textformat/* — ffprobe's writers were extracted upstream into a sub-library; needed by both ffmpeg and ffprobe - graph/graphprint.c: fork no-op stub for -print_graphs (upstream needs the resources/resman resource-bundling pipeline this build doesn't reproduce) - ffmpeg-wasm.sh: refresh fftools file list; drop -lpostproc (8.x fftools no longer reference libpostproc, and configure no longer builds it) - Docs/comments (FORK.md, bind.js, ffmpeg-wasm.sh) updated to 8.1.2 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the codec --enable flags (configure) and -l link libs (emcc) out of the Dockerfile and into build/ffmpeg.sh + build/ffmpeg-wasm.sh, selected by a new FFMPEG_VARIANT build arg. Default is full, so the existing MT core is byte- compatible (12/12 MT suite still passes). Prepares a slim variant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
make prd-mt-slim builds a lean core with only libx264 + zlib enabled (drops vpx/opus/mp3lame/webp/zimg) into packages/core-mt-slim. Passes the full 12/12 MT suite; sized for live-clipping-poc's stream-copy + x264/AAC re-encode + image-overlay watermark. Also harden the FFMPEG_VARIANT case in both build scripts: an unknown variant now errors (exit 1) instead of silently falling back to full — so a typo can't ship the wrong core in a release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Release job now runs make prd-mt and make prd-mt-slim and tars ffmpeg-core-mt-<tag>.tgz (full) + ffmpeg-core-mt-slim-<tag>.tgz (slim). The dist-release/*.tgz glob attaches both plus the wrapper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Redefine the slim variant from "drop external codec libs" (~22.5 MB) to "--disable-everything + re-enable only the H.264/AAC + mp4/ts + concat components its consumer uses" (~6 MB, a 76% cut from full). The external-lib trim alone only saved ~3 MB; the win is dropping FFmpeg's ~400 default decoders / 350 demuxers / 130 filters the app never calls. Slim supports (single-input pipelines only): stream-copy cut/concat, single- input libx264/AAC re-encode, and re-encode stitch via the concat demuxer (multiple clips in, one out). Multi-input filtergraphs (overlay watermark, xfade transitions, concat filter) deadlock on the MT wasm core and run server-side — verified on 8.1.2, matching the prior 7.1.5 finding. - build/ffmpeg.sh: slim branch now emits --disable-everything + the allowlist (protocols/demuxers/muxers/decoders/encoders/parsers/bsfs/filters) - tests/ffmpeg-slim.test.js + ffmpeg-slim-mt.test.html: slim app-ops gate (copy cut/concat, re-encode, re-encode stitch). The generic MT suite can't gate slim — its mp4->avi transcode needs the avi muxer/mpeg4 encoder slim drops. - package.json: test:browser:ffmpeg:slim - CI: new parallel slim-build-and-test job (build slim + run slim gate); release needs both jobs; per-variant gha cache scopes (core-mt / core-mt-slim) - FORK.md: slim rationale, ~6 MB, single-vs-multi-input rule, server-side design Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture why this fork ships an MT-only @ffmpeg/core-mt and rejects the single-threaded @ffmpeg/core: FFmpeg 8.x's fftools frontend is thread-based, so "switch to ST" means downgrading the engine to upstream 5.1.4 (losing the zlib/x264/lame CVE patches and slim allowlist). ST would not fix the multi-input filtergraph deadlock (a wasm-scheduler limit) and is ~2x+ slower. Cross-origin isolation is handled at the serving layer instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fftools requires threads from FFmpeg 6.0 onward (ffmpeg_deps gains threads; pthread_create is unconditional in demux/mux), so 5.1 is the last single-thread-capable frontend. This adds ST 5.1.10 cores built from the fork's hardened dep pins, needing no COOP/COEP: - Resurrect the 5.1-era vendored fftools (pre-scheduler, 8 files) from the phase-0 baseline into src/fftools-5.1/. - Parameterize the build by FFMPEG_VERSION (Makefile, Dockerfile, ffmpeg-wasm.sh); the ARG is scoped to ffmpeg-base so the dep-builder cache stays shared between MT and ST builds. - New `copy` variant: --disable-everything + mov/mpegts/concat demuxers, mp4 muxer, h264/aac parsers+decoders, copy-path bsfs. No encoders, no external libs, no GPL. 2.6 MB wasm (full ST is 22.8 MB), sized to clipping-tool-ui's two `-c copy` commands. - Non-COI test lane: serve.js --no-coi mode on port 3001; the ST page asserts crossOriginIsolated === false (COOP/COEP <meta> tags were inert and are removed). - Vetting suites: clipping-tool-ui's runClipExport/runClipStitch argv verbatim against synthesized short-GOP H.264+AAC TS segments, with stream-presence assertions (a single-keyframe fixture + size-only asserts had let an audio-only export pass); multi-input probe showing overlay/xfade/concat-filter complete on ST 5.1 (the 8.x deadlock is scheduler-specific); perf probe (MT default 5.7s / MT -threads 1 20.9s / ST 22.9s on a 1080p60 2s x264 re-encode). Vetting record: ~/vault/docs/ffmpeg.wasm/plans/2026-08-13 st-5.1.10-core.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Dockerfile: move FFMPEG_VERSION ARG/ENV below the embuilder layer so a version switch invalidates only the FFmpeg source ADD (Keanu) - Makefile: fail loudly on empty FFMPEG_VERSION (would silently fetch FFmpeg's default branch); ffmpeg-wasm.sh now requires it via :? (Keanu) - cliptool suite: probe() returns Duration/start and the tests assert the clip window (duration within [0.9,1.6] with keyframe pre-roll, start~0) and stitch ratio ~2x member (Bruce, Danny) - add MT A/B lane (ffmpeg-cliptool-mt.test.html, test:cliptool:mt): the identical verbatim commands on the 8.1.2 MT core; reuse one instance when synth==DUT (two-instance reload trips the emsdk pthread-pool leak) and run synth encodes with -threads 1 — the unthrottled synth reproduced the production MT deadlock deterministically (Danny) - perf pages: assert crossOriginIsolated false (ST) / true (MT) (Bruce) - remove tests/ffmpeg-core-st.test.html + its script: instantiates the worker-built core in a document, which cannot work (sync-XHR abort); superseded by the ffmpeg-st suite (Keanu) - soften cliptool header overclaim: synthesized inputs, not real packager segments (Bruce) - FORK.md: title/lede no longer claim MT-only; release-variants notes ST is local-build only; vetting references now point in-repo (Jet) - ADR 0001: status amendment recording the two falsified bullets and the re-evaluation, pending a superseding ADR if ST ships (Jet) - build/ffmpeg.sh: WHY comment for the null muxer/bsf entries (Jet) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v0.15.0 shipped MT-only bytes under new names because the release job was never told about ST: the branch added build-st/prd/prd-st-copy but CI.yml still invoked only prd-mt/prd-mt-slim, so GHA layer caching replayed v0.14.0's layers and uploaded them under v0.15.0 asset names. - st-build-and-test job: builds the full ST core (the cliptool suite's input synthesizer) + the copy core, runs the ST suite and the verbatim clipping-tool-ui commands on the non-COI server, uploads core-copy - release job: needs st-build-and-test, builds prd-st-copy, and attaches ffmpeg-core-st-copy-<tag>.tgz (no "-mt-" in the name) - FORK.md: release-variants table gains the st-copy row (from v0.15.1); the "not built by CI" note is gone The bad tag stays: v0.15.0 is immutable per the consumer fetch-script contract (a re-pointed tag is indistinguishable from tampering and the SHA pin rejects it). Ship the ST asset as v0.15.1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v0.15.0 rerun failed in st-build-and-test: testdata/ is a git submodule and actions/checkout does not fetch submodules by default, so the cliptool suite's fetch of audio-1s.wav 404'd and the 404 body fed to ffmpeg produced a cryptic "TS segment synthesis failed (ret=1)". The MT jobs never noticed because they use only embedded base64 fixtures. Also guard the fixture fetches (cliptool + perf) with res.ok so an unpopulated submodule fails with a named error instead of an ffmpeg exit code three steps downstream. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The st-copy artifact is LGPL: no --enable-gpl, no libx264, no libpostproc, no external libs at all. Assert it at runtime from the core's own -version banner in the copy lane (EXPECT_GPL_FREE) so a future allowlist change can't silently re-introduce a GPL component. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(build): ST 5.1.10 cores (full + 2.6 MB copy) with non-COI vetting lane
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two changes, shipping together as v0.14.0:
src/fftoolsfrontend onto n8.1.2 and rebuilt the MT core. Reports libavformat 62.12.102; 12/12 MT suite passes.FFMPEG_VARIANTbuild arg selects the codec set. full = existing lean set (x264, vpx, opus, mp3lame, webp, zimg + native AAC), unchanged. slim = x264 + zlib + native AAC/H.264 only, for H.264/AAC stream-copy + x264 re-encode + image-overlay watermark.live-clipping-pocwill vendor slim.FFmpeg 8.1.2 port
Dockerfile:FFMPEG_VERSIONn7.1.5→n8.1.2src/fftoolsfrom 8.1.2 + re-applied wasm patches (ffmpeg.cEM_JS progress/timeout + init_globals + main→ffmpeg;ffprobe.crenames)objpool.c/.h(8.xthread_queueuseslibavutil/container_fifo); addedtextformat/*(ffprobe writers extracted upstream);graph/graphprint.cis a no-op stub (-print_graphs); dropped-lpostprocSlim / full variants
--enable-*(configure) and-l(link) flags moved from the Dockerfile intobuild/ffmpeg.sh+build/ffmpeg-wasm.sh, selected byFFMPEG_VARIANT(fulldefault). Unknown variant → hard build error (no silent fallback).make prd-mt-slim→packages/core-mt-slim/distffmpeg-core-mt-<tag>.tgz(full),ffmpeg-core-mt-slim-<tag>.tgz(slim),ffmpeg-wasm-<tag>.tgz(wrapper).build-and-teststays full-only.Sizes
full ~25.3 MB · slim ~22.5 MB wasm.
Verification
Both variants build clean. Full: 12/12 MT suite, all six codec libs present. Slim: 12/12 MT suite (via core swap), configure banner shows only
--enable-libx264. Reviewed via subagent-driven development (per-task + final whole-branch review, clean).🤖 Generated with Claude Code