Crear GIFs Animados con Python y Pillow
Pillow permite crear, editar y extraer frames de GIFs animados directamente desde Python. También puedes crear GIFs desde imágenes estáticas, screenshots o vídeos con imageio.
Instalación
pip install pillow imageio imageio-ffmpeg
Crear un GIF desde Imágenes
from PIL import Image
from pathlib import Path
def imagenes_a_gif(rutas_imagenes, salida_gif, duracion_ms=200, loop=0):
frames = []
for ruta in rutas_imagenes:
img = Image.open(ruta).convert("RGBA")
frames.append(img)
if not frames:
print("Sin imágenes")
return
frames[0].save(
salida_gif,
format="GIF",
save_all=True,
append_images=frames[1:],
duration=duracion_ms, # ms por frame
loop=loop, # 0 = infinito
optimize=True
)
size = Path(salida_gif).stat().st_size // 1024
print(f"GIF creado: {salida_gif} | {len(frames)} frames | {size} KB")
# Uso
imagenes_a_gif(
["frame_01.png", "frame_02.png", "frame_03.png", "frame_04.png"],
"animacion.gif",
duracion_ms=150
)
Crear GIF desde una Carpeta de Imágenes
from PIL import Image
from pathlib import Path
def carpeta_a_gif(carpeta, salida_gif, duracion_ms=200, ancho=None):
rutas = sorted(Path(carpeta).glob("*.png")) + sorted(Path(carpeta).glob("*.jpg"))
if not rutas:
print("No se encontraron imágenes")
return
frames = []
for ruta in rutas:
img = Image.open(ruta).convert("RGBA")
if ancho:
ratio = ancho / img.width
img = img.resize((ancho, int(img.height * ratio)), Image.LANCZOS)
frames.append(img)
frames[0].save(
salida_gif,
save_all=True,
append_images=frames[1:],
duration=duracion_ms,
loop=0,
optimize=True
)
print(f"GIF: {salida_gif} | {len(frames)} frames | {Path(salida_gif).stat().st_size//1024} KB")
carpeta_a_gif("capturas/", "demo.gif", duracion_ms=100, ancho=800)
Extraer Frames de un GIF
from PIL import Image
from pathlib import Path
def extraer_frames_gif(ruta_gif, carpeta_salida="frames"):
Path(carpeta_salida).mkdir(exist_ok=True)
gif = Image.open(ruta_gif)
n = 0
try:
while True:
frame = gif.copy().convert("RGBA")
frame.save(f"{carpeta_salida}/frame_{n:04d}.png")
print(f" Frame {n}: {frame.size}")
n += 1
gif.seek(gif.tell() + 1)
except EOFError:
pass
print(f"Extraídos {n} frames en {carpeta_salida}/")
return n
extraer_frames_gif("animacion.gif")
Crear GIF con Texto Animado
from PIL import Image, ImageDraw, ImageFont
def gif_contador(hasta, salida, ancho=300, alto=150, duracion=500):
frames = []
try:
fuente = ImageFont.truetype("arial.ttf", 60)
except:
fuente = ImageFont.load_default()
for i in range(hasta + 1):
img = Image.new("RGBA", (ancho, alto), (30, 30, 30, 255))
draw = ImageDraw.Draw(img)
texto = str(i)
bbox = draw.textbbox((0,0), texto, font=fuente)
x = (ancho - (bbox[2]-bbox[0])) // 2
y = (alto - (bbox[3]-bbox[1])) // 2
draw.text((x, y), texto, font=fuente, fill=(100, 200, 255, 255))
frames.append(img)
frames[0].save(
salida,
save_all=True,
append_images=frames[1:],
duration=duracion,
loop=0
)
print(f"GIF contador: {salida}")
gif_contador(10, "contador.gif", duracion=400)
Vídeo a GIF con imageio
import imageio
from PIL import Image
def video_a_gif(video_path, salida_gif, fps_salida=10, ancho=480, segundo_inicio=0, duracion_seg=5):
reader = imageio.get_reader(video_path)
fps_video = reader.get_meta_data().get("fps", 30)
paso = max(1, int(fps_video / fps_salida))
frames = []
total = int(fps_video * duracion_seg)
inicio = int(fps_video * segundo_inicio)
for i, frame in enumerate(reader):
if i < inicio: continue
if i >= inicio + total: break
if (i - inicio) % paso == 0:
img = Image.fromarray(frame).convert("RGBA")
if ancho:
ratio = ancho / img.width
img = img.resize((ancho, int(img.height * ratio)), Image.LANCZOS)
frames.append(img)
reader.close()
frames[0].save(
salida_gif,
save_all=True,
append_images=frames[1:],
duration=int(1000 / fps_salida),
loop=0,
optimize=True
)
import os
print(f"GIF: {salida_gif} | {len(frames)} frames | {os.path.getsize(salida_gif)//1024} KB")
video_a_gif("clip.mp4", "clip.gif", fps_salida=12, ancho=480, duracion_seg=4)
Optimizar Tamaño de GIF
from PIL import Image
def optimizar_gif(entrada, salida, colores=128, ancho_max=600):
gif = Image.open(entrada)
frames = []
duraciones = []
try:
while True:
frame = gif.copy().convert("P", palette=Image.ADAPTIVE, colors=colores)
if ancho_max and gif.width > ancho_max:
ratio = ancho_max / gif.width
frame = frame.resize((ancho_max, int(gif.height * ratio)), Image.LANCZOS)
frames.append(frame)
duraciones.append(gif.info.get("duration", 100))
gif.seek(gif.tell() + 1)
except EOFError:
pass
import os
frames[0].save(salida, save_all=True, append_images=frames[1:],
duration=duraciones, loop=0, optimize=True)
orig = os.path.getsize(entrada)
dest = os.path.getsize(salida)
print(f"Optimizado: {orig//1024}KB -> {dest//1024}KB (ahorro {100-dest*100//orig:.0f}%)")
optimizar_gif("grande.gif", "comprimido.gif", colores=64)
Recurso Adicional
Para convertir GIFs a MP4, WebM o PNG sin instalar nada, usa KaijuConverter — gratis y sin registro.
Conversiones relacionadas
Lo más habitual al trabajar con imágenes son estas direcciones de conversión: