Turn a photo, GIF, or video into a Signal-compliant sticker — with a browser GUI for framing, trimming, captions, and a seamless boomerang loop, or a one-shot CLI.
Output always satisfies Signal's hard limits:
- Animated → APNG (256×256) · static → PNG (512×512)
- ≤ 300 KB per sticker · ≤ 3 s · transparent background supported · loops forever
Why 256² for animation? Signal stores animated stickers as‑is (it only re‑renders static images up to 512). A smaller canvas keeps ~4× the byte budget per pixel, so far more colour/frames fit under 300 KB; Signal upscales it to the sticker slot on display.
- Smart quality search — predicts size from a sample, then lets you pick a look (Sharp / Soft / Balanced) when a clip can't fit at full quality; asks you to lower the fps only when nothing fits.
- One shared palette + inter‑frame diffing (ffmpeg APNG) — no per‑frame colour shimmer, static backgrounds cost almost nothing.
- Ordered (Bayer) dithering — smooth skin/gradients with no frame‑to‑frame flicker.
- Framing — drag/zoom/pan onto the 512 canvas, dominant‑edge or transparent padding.
- Trim clips longer than 3 s.
- Boomerang loop — append a speed‑matched reverse for a seamless cycle.
- Text overlay — multi‑line caption with font, size, outline, alignment, and colours, baked into the frames (so it never shimmers and compresses essentially free).
Runs standalone — no tunnel required, listening on all interfaces:
docker compose up -d --build # -> http://<host-ip>:8765 (0.0.0.0 by default)The app has no auth, so the default port is open to anyone who can reach the host. Restrict or relocate it as needed:
STICKER_BIND=127.0.0.1 docker compose up -d # localhost only
STICKER_PORT=9000 docker compose up -d # different host portOr pull the published multi-arch image directly:
docker run -d --name sticker -p 8765:8000 -v sticker_work:/work \
ghcr.io/catsec/signal-animated-sticker-maker:latest # -> http://<host-ip>:8765Behind a tunnel / reverse proxy is optional. To use one (e.g. Cloudflare Access),
put the container on the proxy's Docker network and point the proxy at
http://sticker:8000 — no published port needed. See the optional networks blocks in
docker-compose.yml.
Download the build for your platform from the Releases page
(macOS arm64, Linux x64, Linux arm64), unzip, and run
signal-sticker-studio — it starts a local server and opens your browser.
ffmpeg is bundled in the download, so it runs as‑is. pngquant is optional (only
improves static PNG quality); install it (brew install pngquant /
apt install pngquant) or drop it next to the executable if you want it — the app adds
its own folder to PATH automatically.
Windows isn't shipped as a binary — use Docker or run from source (below). macOS binaries are unsigned, so first launch needs right‑click → Open (or
xattr -dr com.apple.quarantine <folder>).
pip install -r requirements.txt # + ffmpeg & (optional) pngquant on PATH
python signal_sticker_gui.py # GUI: binds 127.0.0.1:<random>, opens browserpython signal_sticker_gui.py (or the desktop binary). Drop a file, frame it, optionally
trim / loop / add a caption, click Convert, download the sticker. Use New
conversion to start a new file or Restart to reset the current one.
python signal_sticker.py input.gif # -> input.sticker.png
python signal_sticker.py clip.mp4 -o out.png --fps 24 --colors 128| Var | Default | Meaning |
|---|---|---|
WORK_DIR |
tempdir (/work in compose) |
Session storage (must be writable). |
MAX_UPLOAD_MB |
100 | Upload cap. |
MAX_PIXELS / MAX_EDGE |
40 MP / 8192 | Source resolution caps. |
MAX_CONCURRENT |
2 | Simultaneous conversions. |
QUEUE_TIMEOUT_S |
20 | Wait for a slot before 429. |
SESSION_TTL_MIN |
20 | Session reaper age. |
STEP_TIMEOUT_S |
60 | Per‑subprocess timeout. |
MIN_FREE_MB |
512 | Reject uploads below this free disk. |
The server has no app‑level auth — run it behind an authenticating reverse proxy (e.g. Cloudflare Access) if exposed. All input is validated server‑side; uploaded text is rendered with Pillow and never reaches the ffmpeg filtergraph.
GitHub Actions:
docker.yml— builds a multi‑arch (linux/amd64+linux/arm64) image and pushes to GHCR on pushes tomainand version tags.release.yml— on av*tag, builds standalone desktop binaries for macOS (arm64) and Linux (x64, arm64), with a static ffmpeg bundled in, and attaches them to the GitHub Release. (Windows is intentionally omitted — use Docker.)
Cut a release:
git tag v1.0.0 && git push origin v1.0.0No license is set yet — add a LICENSE file before publishing if you intend others to use
it. Note that ffmpeg (a runtime dependency) is GPL/LGPL; bundling it in binaries carries
its own license obligations.