Skip to content

feat: allow animated gifs in commits and replies - #896

Closed
wotta wants to merge 1 commit into
dev-null-sh:productionfrom
wotta:feat/gif-images
Closed

wotta wants to merge 1 commit into
dev-null-sh:productionfrom
wotta:feat/gif-images

Conversation

@wotta

@wotta wotta commented Oct 20, 2026 •

Copy link
Copy Markdown

Is this wanted at all? I built this to post a screen recording, but I'm not sure animated GIFs fit /dev/null. Before going further I'd like a yes or no from you, especially on the moderation trade-off below. It's fine to close this if it doesn't fit.

What it does

Animated GIFs can be dropped, pasted or picked into the commit and reply composers, and play on the timeline and the commit page. Messages, terminal commits and scheduled commits still refuse them.

The problem with checking GIFs

Every image is checked by OpenAI's moderation API, and by the minors check, before a commit is approved for others to see. Neither takes animated images: OpenAI accepts only non-animated GIFs. An animated GIF is either refused, which leaves the commit pending forever, or judged by a single frame. A GIF can show something harmless in its first frame and something else later, so checking one frame isn't enough.

What this PR does instead: UpdateCommitAiDecision takes 10 evenly spaced frames, first and last included, and draws each one as a viewer would see it. Most GIF frames only redraw a small patch, so frame offsets and disposal are handled. Each of those frames is checked as a still PNG. On a 280-frame recording that takes about 3 s and 41 MB in the job.

The gap that remains: frames between the samples aren't checked. A bad frame shown only briefly between two samples could still get the commit auto-approved. Options I see:

  1. Sample frames (this PR). Narrow the gap by raising GIF_FRAMES. Each extra frame costs one more moderation call and one more image in the minors check.
  2. Check every frame. Closes the gap, but a 280-frame GIF means 280 moderation calls.
  3. Never auto-approve commits with a GIF. The AI still checks the sampled frames, but a moderator always approves.
  4. Don't support GIFs.

Why GIFs aren't converted to WebP

I benchmarked the conversion on a real 280-frame, 1112×550 screen recording (1.4 MB):

Approach Result
GD (current pipeline) out of memory at 1 GB
Imagick → animated WebP 25 s, ~4.9 GB RSS, 4.9 MB output
ffmpeg → animated WebP 8 s, 72 MB, but needs the binary on Laravel Cloud
Store as GIF (this PR) 0.08 s, 35 MB

So GIFs are kept as GIFs. Each one is parsed into its blocks with intervention/gif, already a dependency, and written back out. This drops anything appended outside the GIF blocks, and a GIF that is cut short is refused. GIFs are not scaled down. CommitImage::mimeType() picks the content type from the file extension for the image route, the feed and the og:image metadata.

The composer picker is shared, so a GIF dropped into chat passes the browser check and then gets the server's validation error.

Questions

  1. Are animated GIFs wanted at all?
  2. If so, which moderation option above: sampling, every frame, or always a human?
  3. Is storing the original GIF, without re-encoding or scaling, acceptable?

GIFs are stored as GIFs rather than converted to WebP: re-encoding every
frame took 25s and ~5GB with Imagick for a 280-frame screen recording, and
GD runs out of memory. The GIF is parsed into its blocks and written back,
which drops bytes outside them, in ~0.1s.

The AI cannot moderate animated images, so ten evenly spaced frames,
first and last included, are composited as shown and checked instead.

Messages, terminal and scheduled commits still refuse GIFs.
@wotta
wotta marked this pull request as draft October 20, 2026 21:13
@wotta

wotta commented Oct 20, 2026

Copy link
Copy Markdown
Author

Gif used in question:

rec-o3

/**
* How many frames of a GIF are checked, spread evenly from its first to its last.
*/
private const int GIF_FRAMES = 10;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing a bit, 10 seemed fine but I can imagine for longer gifs more could be wanted.
If this feature is wanted at all

*
* @return list<string>
*/
private function gifFrames(string $bytes): array

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely something that I am unsure about. this would mean for a gif at least ten frames are sent to OpenAI

@nunomaduro

Copy link
Copy Markdown
Member

Closing as a duplicate of #1184. Thanks for the work on this!

@nunomaduro nunomaduro closed this Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants