Converting MP4 Video to Optimized Looping GIFs with Python
Prreequisites: Installing the Required Packages
Set up the Python environment with these packages before starting:
pip install moviepy -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install imageio -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pillow -i https://pypi.tuna.tsinghua.edu.cn/simple
Phase 1: Generating an Initial GIF with MoviePy
MoviePy handles the video-to-GIF conversion. The snippet below clips a segment, rescales it, and exports at a reduced framerate to keep file size manageabel.
from moviepy.editor import VideoFileClip
source_path = "E:\\videos\\input\\sample.mp4"
clip = VideoFileClip(source_path)
# Extract a 5-second segment and scale down to 50%
trimmed = clip.subclip((0, 0), (0, 5)).resize(0.5)
# Export with targeted frame skipping
trimmed.write_gif("E:\\videos\\output\\sample_raw.gif", fps=12)
print('Raw GIF created.')
Phase 2: Compressing, Smoothing, and Enabling Looping
Raw GIFs often suffer from jerky playback, large file size, and missing loop behavior. The following approach rebuilds frames to fix these problems.
Using Imageio with Pillow for Controlled Compression
This method reads the existing GIF, resizes each frame, normalizes timing, and ensures infinite looping.
import imageio
from PIL import Image, ImageSequence
MAX_DIM = 480
frame_collection = []
original = Image.open("E:\\videos\\output\\sample_raw.gif")
for frame in ImageSequence.Iterator(original):
frame = frame.convert('RGBA')
width, height = frame.size
if max(width, height) > MAX_DIM:
frame.thumbnail((MAX_DIM, MAX_DIM))
frame_collection.append(frame)
# Set a uniform frame delay equivalent to ~24 fps (in milliseconds)
frame_delay = 1000 / 24
imageio.mimsave(
"E:\\videos\\compressed\\sample_looping.gif",
frame_collection,
duration=frame_delay,
loop=0
)
print('Compressed looping GIF saved.')
Pillow-Only Compression with Loop Fix
When you don't need fine-tuned timing adjustments, Pillow alone can resize frames and enforce looping.
from PIL import Image, ImageSequence
TARGET_SIZE = 400
frames = []
src_image = Image.open("E:\\videos\\output\\sample_raw.gif")
for page in ImageSequence.Iterator(src_image):
page = page.convert('RGBA')
w, h = page.size
if max(w, h) > TARGET_SIZE:
page.thumbnail((TARGET_SIZE, TARGET_SIZE))
frames.append(page)
frames[0].save(
"E:\\videos\\compressed\\sample_pillow.gif",
save_all=True,
append_images=frames[1:],
loop=0
)
print('Pillow-based GIF ready.')
Key Adjustments Explained
- Choppy playback usually stems from missing or inconsistent frame timing metadata. Assigning a fixed delay (
1000/24ms) produces smoother motion. - Excessive file size is controlled by three levers: lowering output resolution, applying scaling factors (like
0.5), and skipping frames via thefpsparameter during export. - Color mode selection affects compression:
RGBAoften yields smaller files thanRGBin practice. - Single-loop problem is resolved by explicitly setting
loop=0in the saving step—even when the API suggests it should be the default.