Repository navigation
GIF to video conversion: upload hangs forever when a GIF's frames x height exceeds the JPEG dimension limit #80259
Description
Description
While testing the animated GIF → video conversion flow (#78410, made opt-in via block transforms in #80072), @bph discovered that some GIFs hang during the upload forever: the block spinner keeps spinning and the upload never completes and never errors. Other GIFs convert fine.
The failing sample is a 1517x855, 769-frame, 79-second screen recording GIF (2.2 MB). The working sample is a 600x385, 121-frame, 5-second GIF. Sample files to reproduce are attached in the first comment below.
Preliminary investigation
The mediabunny/WebCodecs video conversion itself is not the problem. Reproducing each pipeline stage in isolation in the browser (Chromium, mediabunny 1.49.0, wasm-vips 0.0.18):
| Stage | Working GIF (600x385, 121 frames) | Failing GIF (1517x855, 769 frames) |
|---|---|---|
convertGifToVideo() (mediabunny, all frames decoded + AVC encoded) |
OK, 0.3s | OK, 2.0s, valid 2.2 MB MP4 |
vips poster conversion (convertImageFormat, GIF → JPEG) |
OK, 0.3s | throws WebAssembly.Exception |
vips poster conversion through the real @wordpress/worker-threads RPC (wrap/expose) |
OK, 0.3s | promise never settles - hang reproduced |
vips animated thumbnail resize (thumbnailBuffer 1024px, GIF out) |
fast | OK but takes 44 seconds for a single sub-size |
Root cause chain
- After a successful video conversion,
transcodeGifItemqueues a poster sideload that runsTranscodeImage(GIF → JPEG) through vips (private-actions.ts). convertImageFormatin@wordpress/vipsloads any animation-capable input with[n=-1], i.e. all frames, regardless of the output format (packages/vips/src/index.ts). libvips represents this as one vertical strip: for the failing GIF that is a 1517 x 657,495 px image (855 x 769 frames).writeToBuffer('.jpeg')on that strip throws, because libjpeg's maximum image dimension is 65,500 px. wasm-vips surfaces this as aWebAssembly.Exception(not a JSError).- That exception never crosses the worker RPC boundary: the rejection is silently lost (a
WebAssembly.Exceptionis not structured-cloneable and is not normalized to anErrorbefore the reply is posted), andwrap()disables comctx'sheartbeatCheckand registers no worker error handling (main-thread.ts, see also #79955, still open). transcodeImageItem'sawait vipsConvertImageFormat(...)therefore stays pending forever, the sideload never finishes, the parent item's finalize gate never closes, and the UI spins indefinitely.
Why one GIF works and the other doesn't: the JPEG strip height is frames x frame height. The working GIF is 121 x 385 = 46,585 px - just under the 65,500 px libjpeg limit. The failing GIF is 769 x 855 = 657,495 px. Any animated GIF whose frame count x height exceeds 65,500 px currently hangs the upload - which is easy to hit with screen recordings (this sample is only 2.2 MB).
Additional bugs found along the way
- The poster is wrong even when it "works". The comment in
transcodeGifItemsays "vips decodes just the first GIF frame", butconvertImageFormatloads all frames (n=-1), so theanimated_video_posterJPEG produced for the working sample is the entire 600 x 46,585 px film strip (1.2 MB), not the first frame. - Animated thumbnail generation is extremely slow for long GIFs: ~44s per no-crop sub-size for the failing sample in wasm (single-threaded worker, sizes processed sequentially). Even without the hang, a user would stare at a spinner for minutes. Possibly worth a frame-count/pixel budget cutoff.
Proposed resolution
- Poster generation must load only the first frame. In
convertImageFormat, only applyn=-1when the output format also supports animation (GIF → JPEG poster then decodes one frame). This fixes the hang trigger, the film-strip poster bug, and makes poster generation fast. - Never hang - fall back to a regular upload. When a GIF cannot be converted (or any companion step fails), the item should gracefully fall back to a plain server upload of the original GIF with no video transcoding, and the spinner must clear. Two layers:
- Normalize non-
Errorthrowables (e.g.WebAssembly.Exception) at the worker boundary so every failure reaches the main thread as a rejection. - Land #79955 (reject pending RPC calls on worker failure/termination) so a crashed worker can't strand the queue.
- Audit
transcodeGifItem/transcodeImageItem/sideload error paths so a failed companion never blocks the parent's finalize gate.
- Normalize non-
- E2E tests. A synthetic fixture GIF over the threshold (e.g. 100x100 px with ~700 frames keeps the file tiny while
frames x height> 65,500) should cover:- Uploading + converting a "long" GIF completes without hanging and falls back to a regular GIF upload (or completes with a correct single-frame poster once fixed).
- The generated poster is a single frame with the GIF's dimensions.
- Regression coverage for the working case (small GIF converts, poster correct).
Step-by-step reproduction instructions
- On trunk, start the editor and insert an Image block.
- Upload the failing sample GIF (first comment) and opt into "Convert to video" (block transform / on-drop prompt).
- The spinner never stops; no error is shown. DevTools shows no console error either (the failure is swallowed inside the media worker).
Repeat with the working sample GIF: conversion completes, but inspect the animated_video_poster attachment - it is a film-strip JPEG rather than the first frame.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fields✅ Done
Activity
adamsilverstein commentedon Jul 14, 2026
Sample GIF images
Working image
original in a zip: Tabs-block.gif.zip
Failing image
original in a zip: svgator-bird-alligator-cta.gif.zip
adamsilverstein commentedon Jul 14, 2026
Working on this in #80260
adamsilverstein commentedon Jul 15, 2026
cc: @andrewserong & @swissspidy