|
|
||
|---|---|---|
| assets | ||
| .gitignore | ||
| api.php | ||
| encoder.py | ||
| FIGMA-INTEGRATION.md | ||
| index.php | ||
| process.php | ||
| README.md | ||
GIF Encoder
Batch PNG-to-animated-GIF converter with a PHP web frontend and Python (Pillow) backend. Upload sets of PNGs, group them into GIF animations with per-frame timing control, and download the results. Includes a JSON API for integration with Figma plugins and external scripts.
Features
- Drag-and-drop PNG uploads
- Group frames into multiple GIF sets (batch ~10 GIFs at once)
- Per-frame delay control (ms) — different timing for each frame
- Per-group loop toggle (infinite loop or play once)
- Adjustable quality (color count 8-256)
- Same PNG can be used across multiple groups
- JSON API endpoint for Figma plugin / script integration
- API returns base64-encoded GIFs + download URLs
- CORS enabled for cross-origin requests
Project Structure
GIF-ENCODER/
├── index.php Frontend UI
├── process.php PHP backend (form handling + API logic)
├── api.php API endpoint (proxies to process.php)
├── encoder.py Python GIF encoder (Pillow)
├── venv/ Python virtual environment
├── assets/
│ └── style.css Styles (Montserrat, black/#FFC407)
├── uploads/ Temporary uploaded PNGs (auto-created)
└── output/ Generated GIFs (auto-created)
Requirements
- PHP 7.4+ with
shell_execenabled - Python 3.8+
- Apache (MAMP, XAMPP, or standalone) or PHP built-in server
Installation
1. Clone / copy the project
cd /path/to/your/webroot
git clone <repo-url> GIF-ENCODER
cd GIF-ENCODER
Or just copy the folder into your web server's document root.
2. Set up the Python virtual environment
python3 -m venv venv
source venv/bin/activate
pip install Pillow
deactivate
3. Create working directories
mkdir -p uploads output
These are auto-created by the app, but pre-creating them ensures permissions are right.
4. Set permissions
chmod 775 uploads output
On a production server, make sure the Apache/PHP user (e.g. www-data) can write to these directories:
chown -R www-data:www-data uploads output
5. PHP configuration
If uploading many or large PNGs, update your php.ini:
upload_max_filesize = 64M
post_max_size = 64M
max_file_uploads = 100
MAMP: Edit /Applications/MAMP/bin/php/phpX.X.X/conf/php.ini
Apache: Edit /etc/php/X.X/apache2/php.ini then restart Apache.
Verify shell_exec is NOT in the disable_functions list in php.ini.
6. Configure your web server
MAMP (local development):
- Set the document root to the
GIF-ENCODERfolder, or place it inside your existing MAMP htdocs. - Access at
http://localhost:8888/GIF-ENCODER/
Apache (production): Add a virtual host or alias pointing to the project directory:
<VirtualHost *:80>
ServerName gifencoder.yourdomain.com
DocumentRoot /var/www/GIF-ENCODER
<Directory /var/www/GIF-ENCODER>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
PHP built-in server (quick test):
cd GIF-ENCODER
php -S localhost:8000
Then open http://localhost:8000.
Usage — Web UI
- Open the app in your browser.
- Drag PNG files onto the upload zone (or click to browse).
- Uploaded PNGs appear in the file pool. Drag them into GIF group cards.
- Click + Add Group to create additional GIF sets (up to 10+).
- Adjust settings per group:
- Default delay (ms): Base timing for frames in that group.
- Per-frame delay: Each frame has its own delay input below the thumbnail.
- Loop checkbox: Toggle infinite loop on/off.
- Set the Quality slider (global — controls GIF color palette size).
- Click Generate GIFs.
- Download individual GIFs from the results, or right-click to save.
Tips:
- The same PNG can be dragged into multiple groups.
- Double-click a frame inside a group to remove it.
Usage — Python CLI
The encoder can be used standalone from the command line:
# Activate the virtual environment
source venv/bin/activate
# Basic: uniform 500ms delay, looping
python encoder.py --input frame1.png frame2.png frame3.png --output out.gif
# Custom uniform delay
python encoder.py --input frame1.png frame2.png frame3.png --output out.gif --delay 200
# Per-frame delays
python encoder.py --input frame1.png frame2.png frame3.png --output out.gif --delays 500,200,800
# Play once (no loop)
python encoder.py --input frame1.png frame2.png frame3.png --output out.gif --delay 300 --no-loop
# Lower quality (fewer colors, smaller file)
python encoder.py --input frame1.png frame2.png frame3.png --output out.gif --quality 64
CLI Arguments
| Argument | Default | Description |
|---|---|---|
--input |
required | Space-separated list of PNG file paths |
--output |
required | Output GIF file path |
--delay |
500 | Uniform delay for all frames (ms) |
--delays |
— | Comma-separated per-frame delays (e.g. 500,200,800) |
--quality |
256 | Color count for palette quantization (2-256) |
--no-loop |
false | Play once instead of looping infinitely |
Usage — API
Endpoint
POST /api.php
CORS is enabled. Responses are JSON.
Option A: Multipart upload (recommended for Figma)
Send PNG files as multipart uploads with a json field containing the configuration.
curl -X POST http://localhost:8000/api.php \
-F "json={\"groups\":{\"banner\":{\"files\":[\"frame1.png\",\"frame2.png\",\"frame3.png\"],\"delays\":[500,200,800],\"loop\":true}},\"quality\":256}" \
-F "files[]=@frame1.png" \
-F "files[]=@frame2.png" \
-F "files[]=@frame3.png"
Option B: JSON body with base64 files
Send everything as a single JSON payload with base64-encoded file data.
curl -X POST http://localhost:8000/api.php \
-H "Content-Type: application/json" \
-d '{
"files": {
"frame1.png": "<base64 encoded PNG data>",
"frame2.png": "<base64 encoded PNG data>"
},
"groups": {
"my_animation": {
"files": ["frame1.png", "frame2.png"],
"delays": [500, 300],
"loop": true
}
},
"quality": 256
}'
Request Schema
{
// Base64-encoded PNG files (Option B only)
"files": {
"filename.png": "<base64 string>"
},
// GIF groups to generate
"groups": {
"group_name": {
"files": ["file1.png", "file2.png"], // filenames matching uploads
"delays": [500, 200], // per-frame delays in ms (optional)
"delay": 500, // uniform delay fallback (optional, default 500)
"loop": true // true = infinite, false = play once (optional, default true)
}
},
// Global quality setting (optional, default 256)
"quality": 256
}
Response Schema
{
"success": true,
"batchId": "api_67be1a2f3c4d5",
"results": [
{
"group": "group_name",
"success": true,
"frames": 3,
"url": "output/api_67be1a2f3c4d5/group_name.gif",
"download_url": "http://yourserver.com/GIF-ENCODER/output/api_67be1a2f3c4d5/group_name.gif",
"base64": "<base64 encoded GIF>"
}
]
}
Figma Plugin Example (JavaScript)
async function generateGif(frames, name, delays) {
const formData = new FormData();
// Add PNG files
frames.forEach((blob, i) => {
formData.append('files[]', blob, `${name}_frame${i}.png`);
});
// Add config
const config = {
groups: {
[name]: {
files: frames.map((_, i) => `${name}_frame${i}.png`),
delays: delays,
loop: true
}
},
quality: 256
};
formData.append('json', JSON.stringify(config));
const response = await fetch('https://yourserver.com/GIF-ENCODER/api.php', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success && data.results[0].success) {
// Use base64 data directly
const gifBase64 = data.results[0].base64;
// Or download via URL
const gifUrl = data.results[0].download_url;
}
}
Troubleshooting
GIFs not generating / empty results
- Check that
shell_execis enabled inphp.ini. - Verify the venv exists:
ls venv/bin/python3 - Test the encoder directly:
venv/bin/python3 encoder.py --input test.png --output test.gif
Upload fails or files missing
- Check
upload_max_filesizeandpost_max_sizeinphp.ini. - Verify
uploads/andoutput/are writable by the PHP process.
CORS errors from Figma plugin
- The API sets
Access-Control-Allow-Origin: *by default. If you need to restrict this, edit the headers inprocess.php.
RGBA PNGs look wrong
- The encoder composites RGBA onto a white background since GIF doesn't support full alpha transparency. This is expected.
Large file sizes
- Lower the quality slider (fewer colors = smaller GIFs).
- Use shorter delays — fewer unique frames = better compression.