Skip to content

feat: gif->gif memory usage optimizations; add gifski option - #40

Merged
fdintino merged 1 commit into
mainfrom
feat/gif-memory-opt-gifski
Jul 13, 2026
Merged

feat: gif->gif memory usage optimizations; add gifski option#40
fdintino merged 1 commit into
mainfrom
feat/gif-memory-opt-gifski

Conversation

@fdintino

@fdintino fdintino commented Jul 1, 2026

Copy link
Copy Markdown
Member

This fixes excessive python heap usage when the engine processed animated GIFs (a 1920x1080 125-frame GIF would use 6 GiB of memory in-process, likely causing OOM). It also adds an optional, faster and higher-quality gifski encoding pipeline. All new code has test coverage, and not an insignificant amount of existing code has additional test coverage.

The two sources of unnecessary memory usage and how they were fixed:

  1. The probe() function decoded every frame just to read metadata. This is unavoidable when using Pillow because their GifImagePlugin loads every frame into memory. In our case, where we only need the metadata, this can be avoided.

    This was fixed by adding a parse_gif function that follows Pillow's parsing logic as closely as it can while skipping frame loading and the parsing of metadata unnecessary for the gif pipeline (tile, palette, disposal, mode). Pillow's own tests for the metadata parsing have been copied over into this repository to check for correctness, including those testing error handling of malformed GIFs.

  2. When both the input and output formats are GIF, the code would load the decoded, original-size GIF frames into memory, use Pillow to perform the crop and resize operations, then pipe them into gifsicle via stdin. It now uses ffmpeg to perform crops and resizing first, saving the intermediate files on disk in temporary files (so no part of the original geometry operations loads the entire decoded GIF into memory). When gifsicle is enabled the optimization is run file-to-file, so only the final image data is ever fully loaded into memory.

Some additional related changes:

  • Added gifski pipeline (FFMPEG_GIF_PIPELINE="gifski")

    This allows users to optionally use gifski if the binary is available and the conditions allow it. Gifski is much faster and produces higher perceptual quality than gifsicle. At small-to-moderate output sizes, gifski gifs are also generally smaller than gifsicle's. But its quantizer's memory usage scales with output dimensions, and at larger dimensions it underperforms gifsicle on output file size, so use of gifski is gated by GIFSKI_MAX_TARGET_PIXELS to account for both limitations. Gifski does not support animated gifs with variable frame durations, so those get passed through the old pipeline as well.

  • Added MAX_ANIMATED_GIF_PIXELS gate

    As an additional safety check, allows users to return a 400 if an input animated gif's total pixel size across frames exceeds a configurable threshold and the output format is also gif. The default value is 0, which disables any such restrictions. As you can see from the measurement table below, the non-gifski encoding options might be prohibitively slow for large images. Users can make trade-offs between response time, peak memory usage, and output file sizes by toggling GIFSKI_MAX_TARGET_PIXELS and MAX_ANIMATED_GIF_PIXELS.

Measurements

I've done before-and-after tests using a 1920x1080x125 source GIF with FFMPEG_USE_GIFSICLE_ENGINE=True and GIFSICLE_ARGS=['--lossy=80']

target pipeline wall Python peak RSS output
640×360 legacy (before) 26–29s 294 MB 2.4 MB
640×360 legacy (this PR) 7.1s 82 MB 2.3 MB
640×360 gifski 1.6s ~100 MB 1.8 MB
1600×900 legacy (before) 80s 277 MB 14.5 MB
1600×900 legacy (this PR) 44s 98 MB 18.7 MB
1600×900 gifski 7s 103 MB (+~450 MB in the gifski subprocess) 21.1 MB

@codecov-commenter

codecov-commenter commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.32%. Comparing base (286be4f) to head (175ca03).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #40      +/-   ##
==========================================
+ Coverage   93.80%   95.32%   +1.52%     
==========================================
  Files          17       17              
  Lines         855     1134     +279     
  Branches      144      188      +44     
==========================================
+ Hits          802     1081     +279     
  Misses         48       48              
  Partials        5        5              
Files with missing lines Coverage Δ
src/thumbor_video_engine/__init__.py 100.00% <100.00%> (ø)
src/thumbor_video_engine/engines/ffmpeg.py 100.00% <100.00%> (ø)
src/thumbor_video_engine/utils.py 99.09% <100.00%> (+1.56%) ⬆️

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 286be4f...175ca03. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Unit Test Results

    6 files  ±  0      6 suites  ±0   2m 36s ⏱️ +16s
  278 tests +103    274 ✅ + 99   4 💤 + 4  0 ❌ ±0 
1 668 runs  +618  1 630 ✅ +594  38 💤 +24  0 ❌ ±0 

Results for commit 175ca03. ± Comparison against base commit 286be4f.

♻️ This comment has been updated with latest results.

@fdintino
fdintino force-pushed the feat/gif-memory-opt-gifski branch 4 times, most recently from f3d97c3 to c9762dd Compare July 2, 2026 15:02
@fdintino
fdintino marked this pull request as ready for review July 2, 2026 15:04
@fdintino
fdintino requested review from a team July 2, 2026 15:04
@fdintino
fdintino force-pushed the feat/gif-memory-opt-gifski branch from c9762dd to 4b0a6b4 Compare July 2, 2026 15:20
@fdintino
fdintino force-pushed the feat/gif-memory-opt-gifski branch from 4b0a6b4 to 175ca03 Compare July 2, 2026 16:22

@sirspen sirspen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems worth a shot

@fdintino
fdintino merged commit ea0beaf into main Jul 13, 2026
9 checks passed
@fdintino
fdintino deleted the feat/gif-memory-opt-gifski branch July 13, 2026 17:10
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.

3 participants