
Gif Maker
- 32 installs
- 28 repo stars
- Updated July 16, 2026
- guanyang/super-publisher
Convert a folder of sequence frames or a single sprite sheet into a looping GIF, with FPS control and size-capped compression.
About
A Python CLI that builds GIF animations from ordered image frames or a grid sprite sheet, auto-managing its virtualenv. A developer uses it to produce GIFs, including under WeChat sticker size limits via gifsicle compression.
- Supports frame folders and NxN sprite-sheet slicing with --fps and --loop
- --max-size auto-compresses output to a target file size (e.g. 950KB)
Gif Maker by the numbers
- 32 all-time installs (skills.sh)
- Ranked #960 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/guanyang/super-publisher --skill gif-makerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 32 |
|---|---|
| repo stars | ★ 28 |
| Last updated | July 16, 2026 |
| Repository | guanyang/super-publisher ↗ |
What it does
Convert a folder of sequence frames or a single sprite sheet into a looping GIF, with FPS control and size-capped compression.
Files
GIF 动画生成器 (GIF Maker)
本 Skill 旨在帮助用户快速将一组序列帧图片或单张精灵表(Sprite Sheet)转换为 GIF 动画。
核心功能
- 序列帧转 GIF:支持读取文件夹内的
png,jpg序列,按文件名排序合成 GIF。 - 精灵表转 GIF:支持读取单张 Grid 图片(如 4x4 精灵表),自动切分并合成为 GIF。
- 智能压缩:支持通过
--max-size参数指定目标文件大小(如 950KB),自动调整压缩参数以满足微信表情包等平台的体积限制。 - 参数控制:
-
--fps:指定每秒帧数(默认为 12)。 -
--layout:指定精灵表布局(如4x4),仅在这个模式下需要。 -
--loop:默认永久循环。 -
--max-size: 指定最大体积 (KB)。
使用指南
1. 快速开始
无需手动安装依赖,直接运行脚本即可。工具会自动创建虚拟环境 (.venv) 并安装所需依赖。
# 基本用法 1:从文件夹读取序列帧
./skills/gif-maker/scripts/run.sh /path/to/frame_folder --output my_anim.gif
# 基本用法 2:从单张精灵表生成 (需要指定布局)
./skills/gif-maker/scripts/run.sh /path/to/sheet.png --layout 4x4 --output my_sheet_anim.gif
# 常用选项:
# - 指定 FPS (例如 24)
./skills/gif-maker/scripts/run.sh /path/to/folder --fps 24
# - 开启自动压缩 (限制文件大小在 950KB 以内)
./skills/gif-maker/scripts/run.sh /path/to/folder --max-size 9502. (可选) 手动安装
如果您希望手动管理环境(注意:压缩功能依赖 gifsicle 工具,请确保系统已安装):
- macOS:
brew install gifsicle - Ubuntu:
sudo apt-get install gifsicle
python3 -m venv skills/gif-maker/.venv
source skills/gif-maker/.venv/bin/activate
pip install -r skills/gif-maker/requirements.txt
python3 skills/gif-maker/scripts/make_gif.py ...参数说明
-
source: 输入路径。可以是包含图片的文件夹,也可以是单张图片文件。 -
--output(-o): 输出 GIF 文件名,默认为output.gif。 -
--fps: 帧率,默认为 12。 -
--layout: 仅当source为单文件时使用,格式为行数x列数(如4x4)。 -
--max-size: (可选) 启用 GIF 压缩,指定目标文件最大大小 (KB)。仅当原始文件超过此大小时才会尝试压缩。
示例
假设你有一个文件夹 frames/ 包含 01.png, 02.png ... 10.png:
./skills/gif-maker/scripts/run.sh frames/ -o animation.gif --fps 15假设你有一张 sheet.png 是 4x4 的动作序列,且需要生成符合微信表情包规范(<1MB)的 GIF:
./skills/gif-maker/scripts/run.sh sheet.png --layout 4x4 -o action.gif --max-size 950GIF Maker (GIF 动画生成助手)
这是一个专为 Agent 流程设计的 Skill,旨在快速将序列帧图片或精灵表(Grid Sprite Sheet)转换为 GIF 动画,并支持针对微信表情包等场景的智能压缩。
🧩 核心原理
本 Skill 基于 Python 的 Pillow 和 gifsicle (可选) 构建,核心处理流程如下:
1. 灵活的输入源处理 (Input Handling):
- 序列帧模式: 自动扫描指定目录下的
png/jpg图片,按文件名自然排序作为动画帧。 - 精灵表模式: 支持读取单张大图(如 4x4 网格图),通过
--layout参数将其切分为独立的帧序列。
2. 动画合成 (Animation Composition):
- 根据用户指定的帧率 (
--fps),精确计算每一帧的持续时间。 - 使用 Pillow 库构建动画,设置循环模式(默认为永久循环)和处理方式(Disposal Method 为 Background,避免透明图层重叠残留)。
3. 智能压缩 (Smart Compression):
- (集成
gifsicle) 针对微信表情包限制(通常需 <1MB 或 <500KB),提供--max-size参数。 - 当输出文件超标时,系统通过二分逼近或预设梯度(Lossy 强度、尺寸缩放)自动尝试压缩,直到文件体积达标,无需人工反复试错。
- 注:此功能依赖系统安装 `gifsicle` 工具。
📂 目录结构
Skill 的文件组织结构如下:
skills/gif-maker/
├── README.md # 本说明文档
├── SKILL.md # Agent Skill 定义及规范引用
├── requirements.txt # Python 依赖 (Pillow)
└── scripts/
├── make_gif.py # 核心处理脚本 (包含压缩逻辑)
└── run.sh # 自动环境配置与执行入口💡 最佳实践
配合 WeChat Sticker Maker 使用
通常我们使用 wechat-sticker-maker 切分好的静态图序列来生成 GIF:
1. 切分: 先用 wechat-sticker-maker 将九宫格切分为 sticker_output/main/ 下的序列图。 2. 合成: 再用本 Skill 指向该目录生成 GIF。
./skills/gif-maker/scripts/run.sh sticker_output/main/ -o my_anim.gif --fps 10精灵表直接生成
如果不需要中间的静态图,也可以直接从原图生成:
# 从 4x4 原图直接生成 25fps 的 GIF
./skills/gif-maker/scripts/run.sh origin_grid.png --layout 4x4 --fps 25 --output direct_anim.gif控制体积
为微信表情包制作动画时,务必加上体积限制:
./skills/gif-maker/scripts/run.sh input_frames/ --max-size 950🗣️ 自然语言调用示例
在 Agent 环境中,你可以使用自然语言直接调用此能力:
场景一:序列帧合成
User: "把 frame_folder 文件夹里的图片合成一个 GIF,每秒播放 10 帧。" Agent: (运行脚本 --fps 10)
场景二:制作表情包动画并压缩
User: "用这个 4x4 的图 motion.png 做一个 GIF,要做成微信表情包那种,不要超过 1M。" Agent: (运行脚本 --layout 4x4 --max-size 1000)
---
🛠️ 命令行使用
建议通过 run.sh 脚本运行,它会自动管理 Python 虚拟环境:
# 基础用法
./skills/gif-maker/scripts/run.sh /path/to/frames_dir -o output.gif
# 高级用法 (布局切分 + FPS + 压缩)
./skills/gif-maker/scripts/run.sh /path/to/sheet.png --layout 4x4 --fps 24 --max-size 1024Pillow>=9.0.0
import os
import sys
import glob
import argparse
import subprocess
from PIL import Image
def compress_gif(input_path, output_path, target_size_kb=950):
"""
Compress GIF consistently using gifsicle via subprocess.
"""
if not os.path.exists(input_path):
print(f"[ERROR] {input_path} not found for compression.")
return
# Check initial size
initial_size = os.path.getsize(input_path) / 1024
print(f"[INFO] Initial size: {initial_size:.2f} KB")
if initial_size <= target_size_kb:
print(
f"[INFO] Skipping compression. Size is already within target ({target_size_kb} KB)."
)
return
print(f"[INFO] Compressing to under {target_size_kb} KB...")
# Optimization levels and lossy values to try
attempts = [
# (optimize level, lossy factor, scale)
(3, 30, 1.0),
(3, 80, 1.0),
(3, 120, 1.0),
(3, 200, 1.0),
(3, 80, 0.9),
(3, 120, 0.9),
(3, 200, 0.8),
]
# Temporary file for compression attempts
temp_output = output_path + ".tmp.gif"
for opt_level, lossy, scale in attempts:
cmd = ["gifsicle", "-O" + str(opt_level)]
if lossy > 0:
cmd.extend(["--lossy=" + str(lossy)])
if scale < 1.0:
cmd.extend(["--scale", str(scale)])
cmd.extend([input_path, "-o", temp_output])
try:
subprocess.run(
cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
new_size = os.path.getsize(temp_output) / 1024
print(
f" > Attempt (O{opt_level}, lossy={lossy}, scale={scale}): {new_size:.2f} KB"
)
if new_size <= target_size_kb:
print(f"[SUCCESS] Compressed to {new_size:.2f} KB")
os.replace(temp_output, output_path)
return
except Exception as e:
print(f"[WARN] Error during compression attempt: {e}")
if os.path.exists(temp_output):
os.remove(temp_output)
print(
"[WARN] Could not compress to target size with current settings. Keeping original."
)
if os.path.exists(temp_output):
os.remove(temp_output)
def create_gif(source, output_path, fps=12, layout=None, max_size_kb=0):
"""
Create a GIF from a sequence of images or a sprite sheet.
"""
frames = []
# Check if source is directory or file
if os.path.isdir(source):
print(f"[INFO] Source is a directory: {source}")
# Find images in the directory
image_files = sorted(
glob.glob(os.path.join(source, "*.png"))
+ glob.glob(os.path.join(source, "*.jpg"))
+ glob.glob(os.path.join(source, "*.jpeg"))
)
if not image_files:
print(f"[ERROR] No images found in {source}")
sys.exit(1)
print(f"[INFO] Found {len(image_files)} images.")
for img_path in image_files:
try:
img = Image.open(img_path).convert("RGBA")
frames.append(img)
except Exception as e:
print(f"[WARN] Skipped {img_path}: {e}")
elif os.path.isfile(source):
print(f"[INFO] Source is a file: {source}")
if not layout:
print(
"[ERROR] When source is a single file (sprite sheet), you MUST provide --layout (e.g., 4x4)."
)
sys.exit(1)
try:
rows, cols = map(int, layout.lower().split("x"))
except ValueError:
print("[ERROR] Layout format must be 'RowsxCols' (e.g., 4x4).")
sys.exit(1)
try:
sheet = Image.open(source).convert("RGBA")
sheet_w, sheet_h = sheet.size
tile_w = sheet_w // cols
tile_h = sheet_h // rows
print(
f"[INFO] Slicing {source} ({sheet_w}x{sheet_h}) into {rows}x{cols} grid (Tile: {tile_w}x{tile_h})."
)
for row in range(rows):
for col in range(cols):
left = col * tile_w
top = row * tile_h
right = left + tile_w
bottom = top + tile_h
frame = sheet.crop((left, top, right, bottom))
frames.append(frame)
except Exception as e:
print(f"[ERROR] Failed to process sprite sheet: {e}")
sys.exit(1)
else:
print(f"[ERROR] Source not found: {source}")
sys.exit(1)
if not frames:
print("[ERROR] No frames to save.")
sys.exit(1)
# Calculate duration per frame in milliseconds
duration_ms = int(1000 / fps)
print(
f"[INFO] Saving GIF to {output_path} (FPS: {fps}, Duration per frame: {duration_ms}ms, Loop: Forever)"
)
# Save GIF
frames[0].save(
output_path,
save_all=True,
append_images=frames[1:],
optimize=False,
duration=duration_ms,
loop=0,
disposal=2, # Restore to background color
)
print(f"[SUCCESS] Created {output_path}")
# Compression logic
if max_size_kb > 0:
compress_gif(output_path, output_path, target_size_kb=max_size_kb)
def main():
parser = argparse.ArgumentParser(
description="Generate a GIF from a sprite sheet or a sequence of images."
)
parser.add_argument(
"source", help="Path to a directory of images OR a single sprite sheet file."
)
parser.add_argument(
"--output",
"-o",
default="output.gif",
help="Output GIF filename (default: output.gif).",
)
parser.add_argument(
"--fps", type=int, default=12, help="Frames per second (default: 12)."
)
parser.add_argument(
"--layout",
help="Grid layout for sprite sheet (e.g., '4x4'). Required if source is a file.",
)
parser.add_argument(
"--max-size",
type=int,
default=0,
help="Maximum size in KB to compress the GIF (default: 0, disabled).",
)
args = parser.parse_args()
create_gif(args.source, args.output, args.fps, args.layout, args.max_size)
if __name__ == "__main__":
main()
#!/bin/bash
set -e
# Get the directory where the script is located
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
VENV_DIR="$SKILL_DIR/.venv"
# Check if python3 is available
if ! command -v python3 &> /dev/null; then
echo "Error: python3 is not installed."
exit 1
fi
# Create virtual environment if it doesn't exist
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
# Install dependencies
source "$VENV_DIR/bin/activate"
pip install --upgrade pip
if [ -f "$SKILL_DIR/requirements.txt" ]; then
pip install -r "$SKILL_DIR/requirements.txt"
fi
else
source "$VENV_DIR/bin/activate"
fi
# Run the python script with all passed arguments
python3 "$SCRIPT_DIR/make_gif.py" "$@"