Two bugs in the encode path, one reported (#1510), one found next to it.
DISCORD. gifski gives every frame its own LOCAL colour table — that is where
its quality comes from. gifsicle then stacks cross-frame transparency on top,
and the result is a GIF that gifsicle itself refuses to reopen: "too complex to
unoptimize — local colour tables or complex transparency". PIL, ffmpeg and
macOS Preview all render it perfectly. Discord's decoder does not: later scenes
come out with stale rows, ghosted captions and holes punched in solid shapes.
The reported file had 199 of 207 frames carrying their own palette.
`--colors 256` collapses them to one global palette. Confirmed on the reported
file: rebuilt, it plays correctly in Discord where the original flashes. It
costs ~6/255 mean colour error — one palette now spans every scene, so
gradients band slightly — and it came out SMALLER, 1210 KB -> 918 KB.
Worth stating plainly because it nearly sent me the wrong way: this is NOT
gifsicle's fault. gifski's raw output has the same 207/207 local tables and
draws the same warning with gifsicle nowhere in the pipeline. "Drop the
gifsicle pass" would have fixed nothing.
FRAME DROP. The ffmpeg fallback set fps= in the filter chain but never passed
-framerate on the INPUT, so ffmpeg read the PNG sequence at its default 25fps
and resampled down — silently discarding 91 of 207 frames (44%) and playing the
rest too fast. _mp4() always passed -framerate; this path never did. With the
input rate correct the fps= filter is redundant, and dropping it is what takes
the output from 205 frames back to all 207. This path runs whenever gifski is
missing, which the mort image allows to happen silently (its install is wrapped
in `|| echo "gifski unavailable"`).
Verified by driving the patched _gif() against the 207 real frames of the
reported render: gifski path 207 frames / 0 local palettes / 918 KB; ffmpeg
path 0 local palettes with total duration preserved (14.78s vs 14.79s source —
gifsicle merges two identical frames and extends their delays).