GIF Creator API — video-to-GIF conversion backend with AI-assisted blur detection.
1.4K
A cross-platform desktop web application that converts video recordings into GIFs with a primary differentiator: the ability to blur sensitive information before encoding. Blurring is delivered through two complementary options — AI-assisted detection and manual rectangle placement — and both can be combined.
| Tool | Minimum version |
|---|---|
| .NET SDK | 10.0 |
| Node.js | 20 LTS |
| npm | 10+ |
| FFmpeg | 6+ (system install or bundled) |
| Docker + Compose | 24+ (optional, for containerised run) |
cd src/GifCreator.Api
dotnet run
# API available at http://localhost:5232
# Swagger UI at http://localhost:5232/swagger
cd src/GifCreator.Web
npm install
npm run dev
# Frontend available at http://localhost:5173
The Vite dev server proxies /api and /hubs to the API automatically.
Pre-built multi-platform images (linux/amd64, linux/arm64) are published to Docker Hub on every tagged release.
| Image | Pull command |
|---|---|
| API backend | docker pull garrardkitchen/gif-creator-api |
| GIF Encoder | docker pull garrardkitchen/gif-creator-encoder |
| Web frontend | docker pull garrardkitchen/gif-creator-web |
Pin to a specific version with a tag, e.g. :1.0.0.
Release tags now publish images selectively:
api is rebuilt only when API/shared backend paths changeencoder is rebuilt only when encoder/shared backend paths changeweb is rebuilt only when frontend/nginx paths changeThat also means a release tag does not automatically create :<version> tags for every image. If a service did not change in that release, its previous published image remains the latest published version for that service.
Google font downloads for the api and encoder images are best-effort for local builds so docker compose up --build keeps working on restricted networks. In CI/tag releases they are strict and the build fails if any expected Google font file is missing or empty.
Create a docker-compose.yml and a .env file, then run docker compose up.
docker-compose.yml
services:
api:
image: garrardkitchen/gif-creator-api:latest
container_name: gif-creator-api
restart: unless-stopped
volumes:
- gif_data:/data
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Data Source=/data/gifcreator.db
- Storage__BasePath=/data/storage
- Ai__GitHubToken=${GH_TOKEN}
- Ai__DefaultModelId=gpt-4o
- Ai__CropLeftPercent=37
- Ai__CropTopPercent=28
- Cors__AllowedOrigins=http://localhost
- Encoder__BaseUrl=http://encoder:8002
- Api__InternalBaseUrl=http://api:8002
ports:
- "5232:8002"
depends_on:
- encoder
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
encoder:
image: garrardkitchen/gif-creator-encoder:latest
container_name: gif-creator-encoder
restart: unless-stopped
volumes:
- gif_data:/data
environment:
- ASPNETCORE_ENVIRONMENT=Production
- Storage__BasePath=/data/storage
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
web:
image: garrardkitchen/gif-creator-web:latest
container_name: gif-creator-web
restart: unless-stopped
depends_on:
- api
ports:
- "8001:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
volumes:
gif_data:
driver: local
.env
GH_TOKEN=ghp_your_token_here
docker compose up -d
# Frontend: http://localhost:8001
# API: http://localhost:5232
# Swagger: http://localhost:5232/swagger
Pin to a specific version by replacing :latest with e.g. :1.0.0.
# Clone and start — images are built locally from source
git clone https://github.com/garrardkitchen/gif-creator.git
cd gif-creator
# Set your GitHub token (required for AI blur detection)
export GH_TOKEN=ghp_your_token_here
docker compose up --build
# Frontend: http://localhost:8001
# API: http://localhost:5232
# Swagger: http://localhost:5232/swagger
# Stop
docker compose down
# Stop and remove volumes (data)
docker compose down -v
The root docker-compose.yml builds all three images from the local source tree.
dotnet test tests/GifCreator.Tests/GifCreator.Tests.csproj
With code coverage:
dotnet test tests/GifCreator.Tests/GifCreator.Tests.csproj \
--collect:"XPlat Code Coverage"
# Generate HTML report (requires reportgenerator tool)
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator \
-reports:**/coverage.cobertura.xml \
-targetdir:coverage-report
AI-powered blur detection uses the GitHub Models API. You need a GitHub Personal Access Token with read:models scope.
Development (user secrets):
cd src/GifCreator.Api
dotnet user-secrets set "Ai:GitHubToken" "ghp_your_token_here"
Docker Compose: Uncomment and set the environment variable in docker-compose.yml:
- Ai__GitHubToken=ghp_your_token_here
Security note: Never commit your GitHub token. It is stored in user secrets or environment variables only.
Ai__DefaultModelId)AI blur detection sends video frames as images to the model for analysis. The model must support vision (multimodal input) — text-only models will cause the AI blur detection feature to fail.
The following models are confirmed vision-capable on the GitHub Models API:
| Model ID | Notes |
|---|---|
gpt-4o | Recommended default — fast, accurate, widely available |
gpt-4.1 | Latest GPT-4.1 |
gpt-4.1-mini | Lighter / lower cost |
gpt-4.1-nano | Lightest option |
The app dynamically discovers additional vision models from the GitHub Models catalogue (any model tagged multimodal). You can see the full live list in the editor's model selector, or by calling GET /api/ai/models.
If you set
Ai__DefaultModelIdto a text-only model, AI blur detection will not work. The feature will either return an error or produce no blur regions. Use one of the models above to ensure it functions correctly.
graph TB
subgraph Browser["🌐 Browser"]
UI["Vue 3 SPA<br/>(Vite + Tailwind CSS)<br/>fetches x-api-key on mount"]
end
subgraph Docker["🐳 Docker Compose"]
subgraph Web["web · :8001"]
Nginx["nginx<br/>reverse proxy / SPA host"]
end
subgraph API["api · :5232 (host) / :8002 (internal)"]
AspNet["ASP.NET Core 10<br/>REST API + SignalR hub"]
ApiKey["🔑 ApiKeyMiddleware<br/>x-api-key header<br/>256-bit · per session"]
EF["EF Core · SQLite<br/>(projects · frames · blur regions<br/>AI token usage)"]
Infra["Infrastructure<br/>VideoProcessor · BlurRender · AI Client"]
end
subgraph Enc["encoder · :8003 (internal only — not port-exposed)"]
Queue["Encode Queue Service<br/>Channel<Guid> · BackgroundService"]
EncKey["🔑 ApiKeyMiddleware<br/>key seeded from first job"]
FFmpeg["FFMpegCore<br/>Two-pass palette GIF encoding"]
end
Vol[("📦 gif_data volume<br/>/data/storage<br/>frames · GIFs · DB")]
end
subgraph External["☁️ External"]
GHModels["GitHub Models API<br/>models.inference.ai.azure.com<br/>─────────────────────<br/>GET /models → model catalogue<br/>POST /chat/completions → vision analysis"]
end
UI -- "GET /api/config → { apiKey }<br/>(public bootstrap, exempt)" --> Nginx
UI -- "HTTP/WS :8001<br/>x-api-key on all requests" --> Nginx
Nginx -- "/api/* → :8002" --> ApiKey
Nginx -- "/hubs/* → WS :8002<br/>?access_token=" --> ApiKey
Nginx -- "GET /api/encode/* → :8002<br/>(SSE · read-only)" --> ApiKey
ApiKey --> AspNet
AspNet -- "POST /jobs + x-api-key<br/>+ SharedApiKey in payload" --> EncKey
EncKey --> Queue
Queue -- "callback POST /api/internal/...<br/>x-api-key" --> ApiKey
AspNet <--> EF
AspNet --> Infra
Infra -- "GET /models (cached 1 hr)<br/>POST /chat/completions (vision)" --> GHModels
EF -- "reads/writes" --> Vol
Infra -- "frame files" --> Vol
FFmpeg -- "reads frames / writes GIF" --> Vol
Queue --> FFmpeg
classDef browser fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
classDef proxy fill:#0f3b3b,stroke:#14b8a6,color:#5eead4
classDef api fill:#2d1b69,stroke:#8b5cf6,color:#c4b5fd
classDef auth fill:#4a1942,stroke:#ec4899,color:#f9a8d4
classDef db fill:#1a2e1a,stroke:#22c55e,color:#86efac
classDef encoder fill:#3b1f00,stroke:#f97316,color:#fdba74
classDef volume fill:#1f1f2e,stroke:#6366f1,color:#a5b4fc
classDef external fill:#0f2b1a,stroke:#10b981,color:#6ee7b7
class UI browser
class Nginx proxy
class AspNet,Infra api
class ApiKey,EncKey auth
class EF db
class Queue,FFmpeg encoder
class Vol volume
class GHModels external
| Colour | Represents |
|---|---|
| 🔵 Blue | Browser / Vue 3 SPA |
| 🩵 Teal | nginx reverse proxy |
| 🟣 Purple | ASP.NET Core API + Infrastructure layer |
| 🩷 Pink | API key middleware (auth gate) |
| 🟢 Green | EF Core / SQLite data store |
| 🟠 Orange | Encoder microservice + FFmpeg |
| 🔷 Indigo | Shared storage volume |
| 💚 Emerald | External — GitHub Models API |
src/
├── GifCreator.Core/ # Domain models + interfaces (no infrastructure deps)
├── GifCreator.Infrastructure/ # EF Core/SQLite · FFMpegCore · BlurRender · AI client · Storage
├── GifCreator.Api/ # ASP.NET Core 10 REST API + SignalR hub
├── GifCreator.Encoder/ # Encode microservice — queue, two-pass FFmpeg, SSE progress
└── GifCreator.Web/ # Vite + Vue 3 + Tailwind CSS v4 SPA
tests/
└── GifCreator.Tests/ # xUnit 3 + Moq · unit + integration · code coverage
| Concern | Approach |
|---|---|
| API key auth | 256-bit random session key (x-api-key header) generated on every startup — printed to terminal. SPA bootstraps via GET /api/config. Encoder seeded via first job dispatch. Timing-safe comparison (CryptographicOperations.FixedTimeEquals). |
| API responses | Result<T> envelope — { success, data, error, traceId } |
| Real-time progress | ASP.NET Core SignalR (frame extraction) + SSE from encoder (GIF encoding) |
| Encode isolation | Dedicated encoder container — independently scalable, never blocks the API |
| Blur storage | Regions stored as JSON metadata only — raw frames are never modified on disk |
| Encryption | AES-256-GCM + Argon2id KDF for password-protected projects |
| Storage abstraction | IStorageProvider — swap LocalStorageProvider for AzureBlobStorageProvider without code changes |
Content type
Image
Digest
sha256:a47f7041a…
Size
375.4 MB
Last updated
6 months ago
docker pull garrardkitchen/gif-creator-api