{{ message }}
fix: Add proper GIF support with transparency and per-frame timing#595
Open
Dav1d-Fn wants to merge 3 commits into
Open
fix: Add proper GIF support with transparency and per-frame timing#595Dav1d-Fn wants to merge 3 commits into
Dav1d-Fn wants to merge 3 commits into
Conversation
GIFs were previously decoded using OpenCV (cv2), which does not support GIF transparency or animation metadata. This caused two issues: - Transparent GIFs rendered with a white background - All GIF frames played at a fixed FPS regardless of per-frame delays, causing animations to play at the wrong speed or appear cut short Changes: - key_video_cache.py: Detect GIFs by file extension and decode them using PIL instead of cv2. Convert each frame to RGBA to preserve the alpha channel. Cache GIF frames as PNG instead of JPEG to retain transparency. Read per-frame delays from GIF metadata and store them in frame_delays for use during playback. - KeyVideo.py: Add _get_next_gif_frame() which advances playback based on per-frame delay in milliseconds rather than a fixed FPS value. This correctly handles GIFs with variable frame timing (e.g. a long first frame followed by short animation frames).
… deduplication Loading pages with multiple animated GIFs caused multi-second freezes because all frames were decoded synchronously on the calling thread. - Render frame 0 synchronously in __init__ so every button shows a static preview immediately; all remaining work runs in a background daemon thread - Save per-frame delays to delays.json next to the cached frames so the slow PIL seek-loop only runs once; subsequent loads read the JSON instantly - Add VideoFrameCache.get_or_create() registry so the same GIF+size is only loaded once even when multiple buttons reference the same file; use it in InputVideo to avoid redundant work on page switches
Author
…ming accuracy key_video_cache.py: Replace the single global registry lock with a per-key initialization lock. The previous approach ran __init__ (including Image.open and LANCZOS resize) outside the global lock, allowing two threads to create instances for the same GIF simultaneously and write to the same cache files concurrently — corrupting PNGs and causing segfaults in PIL. The per-key lock ensures only one thread initialises a given (path, size) at a time without blocking unrelated keys. KeyVideo.py: Replace tick-based GIF timing with time.perf_counter(). The previous implementation accumulated elapsed time in media player ticks, which caused playback to drift when the media player was under CPU load. Also replace the single if with a while loop so multiple frames are skipped when catch-up is needed, keeping the animation in sync with real time.
Author
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.

Problem
GIFs were decoded using OpenCV (
cv2.VideoCapture), which has two limitations:P) with a transparency index were composited onto a white background, losing the alpha channel.Solution
GIF files are now detected by extension and handled separately using PIL:
key_video_cache.py: GIFs are opened withImage.open()and each frame is converted to RGBA, preserving transparency. Frames are cached as PNG instead of JPEG to retain the alpha channel. Per-frame delays are read from the GIF metadata (img.info["duration"]) and stored inframe_delays.KeyVideo.py: A new_get_next_gif_frame()method advances playback by accumulating elapsed milliseconds and comparing against the current frame's delay, rather than using a global FPS value.All non-GIF video formats (mp4, webm, etc.) are unaffected and continue to use the existing cv2 code path.
Testing
Tested with: