No description
Find a file
DJP eb34dbffe0 Merge remote and resolve .gitignore conflict
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 12:05:50 -05:00
assets Initial commit — GIF Encoder tool 2026-02-25 12:05:09 -05:00
.gitignore Merge remote and resolve .gitignore conflict 2026-02-25 12:05:50 -05:00
api.php Initial commit — GIF Encoder tool 2026-02-25 12:05:09 -05:00
encoder.py Initial commit — GIF Encoder tool 2026-02-25 12:05:09 -05:00
FIGMA-INTEGRATION.md Initial commit — GIF Encoder tool 2026-02-25 12:05:09 -05:00
index.php Initial commit — GIF Encoder tool 2026-02-25 12:05:09 -05:00
process.php Initial commit — GIF Encoder tool 2026-02-25 12:05:09 -05:00
README.md Initial commit — GIF Encoder tool 2026-02-25 12:05:09 -05:00

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_exec enabled
  • 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-ENCODER folder, 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

  1. Open the app in your browser.
  2. Drag PNG files onto the upload zone (or click to browse).
  3. Uploaded PNGs appear in the file pool. Drag them into GIF group cards.
  4. Click + Add Group to create additional GIF sets (up to 10+).
  5. 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.
  6. Set the Quality slider (global — controls GIF color palette size).
  7. Click Generate GIFs.
  8. 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.

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_exec is enabled in php.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_filesize and post_max_size in php.ini.
  • Verify uploads/ and output/ 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 in process.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.