# Sticker Lab Turn a one-line character idea into **18 animated stickers** for Telegram, WhatsApp and the web. ![Stickers generated by Sticker Lab](docs/demo.gif) An image model draws your character's head with 12 expressions in a single image. A small animation engine cuts the heads out and brings them to life: bouncing, winking, melting, with hearts, tears and confetti. Every sticker loops seamlessly and exports to the format your messenger expects. ## Features - **One prompt, 18 stickers.** Hello, laugh, love, sad, angry, shocked, sleepy, dance, wink, kiss, thinking, dizzy, jelly, yes, no, party, scared and melting. - **Cheap.** One image and one short text request per character. New animations never need a new image. - **Any provider.** OpenAI, OpenRouter, polza.ai or any other OpenAI-compatible API. Image models without transparent backgrounds work too: the background is removed automatically. - **Consistent.** All expressions come from one sheet, so the character looks the same in every sticker. - **Six export formats.** WebM, WebP, GIF, APNG, MP4 and PNG, one by one or the whole pack as a ZIP. WebM fits Telegram's video sticker rules and WebP fits WhatsApp's size limit. - **Local-first.** Characters and stickers are plain files in `./data`. No database, no account. ## How it works 1. A text model turns your idea (in any language) into a short visual description. 2. The image model draws a 4 × 3 sheet of heads on a transparent or plain white background (a white one is removed automatically): Expression sheet with 12 heads 3. The sheet is cut into 12 heads by finding connected regions in the alpha channel. 4. Each animation is a function of time that moves, squashes and warps a head and draws vector effects on top. The same code renders the live previews in the browser and the exported files on the server. ## Requirements - Node.js 20.9 or newer and [pnpm](https://pnpm.io/installation) - [ffmpeg](https://ffmpeg.org/download.html) with `libvpx-vp9` and `libx264` (the standard builds include both) - macOS: `brew install ffmpeg` - Ubuntu/Debian: `sudo apt install ffmpeg` - Windows: `winget install Gyan.FFmpeg` - An API key from any OpenAI-compatible provider that offers an image model, for example [OpenAI](https://platform.openai.com/api-keys), [OpenRouter](https://openrouter.ai/keys) or [polza.ai](https://polza.ai) ## Getting started ```bash git clone https://github.com//sticker-lab.git cd sticker-lab pnpm install cp .env.example .env.local ``` Open `.env.local`, uncomment the preset for your provider and paste your key. For example, with OpenAI: ```bash API_BASE_URL=https://api.openai.com/v1 API_KEY=sk-... TEXT_MODEL=gpt-4o-mini IMAGE_MODEL=gpt-image-1 IMAGE_API=images ``` Start the app: ```bash pnpm dev ``` Open [http://localhost:3000](http://localhost:3000), describe a character and click **Create character**. The first result usually appears in about a minute; slower providers can take a few minutes. ## Configuration All settings are environment variables in `.env.local`. | Variable | Required | Description | |---|---|---| | `API_BASE_URL` | yes | Base URL of an OpenAI-compatible API, for example `https://api.openai.com/v1` | | `API_KEY` | yes | API key for that provider | | `TEXT_MODEL` | yes | Any chat model. It only turns the idea into a short description | | `IMAGE_MODEL` | yes | Image model that draws the expression sheet | | `IMAGE_API` | no | How to call the image model: `images` (default), `chat` or `polza`, see below | | `IMAGE_API_BASE_URL`, `IMAGE_API_KEY` | no | Use a different provider for images than for text | | `IMAGE_BACKGROUND` | no | `transparent` or `white`. Defaults to `white` for `chat` and `transparent` otherwise | | `IMAGE_SIZE` | no | `1536x1024` by default for `images`; `1K` or `2K` (default) for `polza` | | `IMAGE_QUALITY` | no | Passed to the images API as is, for example `low`, `medium` or `high` | | `STICKER_LAB_PASSWORD` | no | Protects the whole app with a password (HTTP Basic Auth, any username) | | `MAX_CONCURRENT_GENERATIONS` | no | How many characters can be generated at the same time. Default `2` | `IMAGE_API` picks the request format: | Value | Request | Works with | |---|---|---| | `images` | `POST /images/generations`, the OpenAI Images API | OpenAI (`gpt-image-1`), polza.ai at `https://polza.ai/api/v2`, many aggregators | | `chat` | image returned by `POST /chat/completions` | OpenRouter with Gemini image models and similar | | `polza` | `POST /media`, the polza.ai media API | polza.ai, including `qwen/image-2.1` | If a model cannot draw a transparent background, set `IMAGE_BACKGROUND=white`: the sheet is drawn on white and the background is cut out by the app. Near-white parts of a character that touch the background without an outline may be cut out with it, so a native transparent background gives the most reliable result. ## Export formats | Format | Best for | Transparency | Typical size | |---|---|---|---| | WebM | Telegram video stickers (VP9, up to 3 s) | yes | ≤ 256 KB | | WebP | WhatsApp stickers, websites | yes | ≤ 500 KB | | GIF | Anywhere | on/off only, no soft edges | 1–3 MB | | APNG | Best quality with transparency | yes | 2–9 MB | | MP4 | Social media, stories | no, white background | 50–200 KB | | PNG | Static sticker or avatar (512 × 512) | yes | ~100 KB | Download stickers one by one or all 18 at once as a ZIP. Files are rendered on the first download and cached next to the character, so later downloads are instant. ## Deployment Sticker Lab runs anywhere Node.js and ffmpeg are available: ```bash pnpm build pnpm start ``` Set the same variables as in `.env.local` on the server. **Every generation spends money from your API key**, so if anyone else can reach the app, set `STICKER_LAB_PASSWORD`. Generated files live in `./data`, so give that folder persistent storage. Serverless platforms with a read-only or temporary file system (such as Vercel) are not supported. ## Development ```bash pnpm lint # ESLint pnpm typecheck # TypeScript pnpm test # unit tests (Vitest) pnpm check:stickers # render every sticker of the saved characters and validate each file ``` Project layout: ``` src/ app/ Next.js pages and API routes components/ UI lib/engine/ animations, effects and the renderer shared by browser and server lib/server/ image generation, sheet cutting, export encoders, file storage lib/prompts.ts expressions and prompts scripts/ maintenance scripts ``` ## Limitations - Sometimes the image model merges two heads or draws fewer than 12. The app reports it, and **Redraw expressions** usually fixes it with one more image. - Generation jobs are tracked in memory. A server restart interrupts a running job; click **Rebuild** to continue from the cached sheet. - Image models differ in how well they follow the 4 × 3 grid. Models that draw heads touching each other fail more often. `gpt-image-1` and `qwen/image-2.1` follow the grid reliably. ## Contributing Issues and pull requests are welcome. Please run `pnpm lint`, `pnpm typecheck` and `pnpm test` before opening a pull request. To add an animation, add an entry to `src/lib/engine/animations.ts`: a pose function and an effect function. It will appear in the UI and in every export format automatically. ## License [MIT](LICENSE)