Sign inSign up

garrardkitchen/gif-creator-api

By garrardkitchen

Updated 6 months ago

GIF Creator API — video-to-GIF conversion backend with AI-assisted blur detection.

Image
0

1.4K

garrardkitchen/gif-creator-api repository overview

GIF Creator

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.

Contents


Prerequisites

ToolMinimum version
.NET SDK10.0
Node.js20 LTS
npm10+
FFmpeg6+ (system install or bundled)
Docker + Compose24+ (optional, for containerised run)

Running Locally (Development)

1. Backend API
cd src/GifCreator.Api
dotnet run
# API available at http://localhost:5232
# Swagger UI at http://localhost:5232/swagger
2. Frontend
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.


Docker Hub Images

Pre-built multi-platform images (linux/amd64, linux/arm64) are published to Docker Hub on every tagged release.

ImagePull command
API backenddocker pull garrardkitchen/gif-creator-api
GIF Encoderdocker pull garrardkitchen/gif-creator-encoder
Web frontenddocker 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 change
  • encoder is rebuilt only when encoder/shared backend paths change
  • web is rebuilt only when frontend/nginx paths change
  • docs/workflow-only tags still create the GitHub Release, but skip Docker Hub work entirely

That 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.


Running with Docker Compose

Option A — Docker Hub images (no source required)

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.


Option B — Build from source (local development)
# 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.


Running Tests

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

Configuring the GitHub Token (AI Blur Detection)

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.

Choosing a vision-capable model (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 IDNotes
gpt-4oRecommended default — fast, accurate, widely available
gpt-4.1Latest GPT-4.1
gpt-4.1-miniLighter / lower cost
gpt-4.1-nanoLightest 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__DefaultModelId to 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.


Architecture

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&lt;Guid&gt; · 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
ColourRepresents
🔵 BlueBrowser / Vue 3 SPA
🩵 Tealnginx reverse proxy
🟣 PurpleASP.NET Core API + Infrastructure layer
🩷 PinkAPI key middleware (auth gate)
🟢 GreenEF Core / SQLite data store
🟠 OrangeEncoder microservice + FFmpeg
🔷 IndigoShared storage volume
💚 EmeraldExternal — GitHub Models API
Project layout
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
Key design decisions
ConcernApproach
API key auth256-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 responsesResult<T> envelope — { success, data, error, traceId }
Real-time progressASP.NET Core SignalR (frame extraction) + SSE from encoder (GIF encoding)
Encode isolationDedicated encoder container — independently scalable, never blocks the API
Blur storageRegions stored as JSON metadata only — raw frames are never modified on disk
EncryptionAES-256-GCM + Argon2id KDF for password-protected projects
Storage abstractionIStorageProvider — swap LocalStorageProvider for AzureBlobStorageProvider without code changes

Tag summary

Content type

Image

Digest

sha256:a47f7041a

Size

375.4 MB

Last updated

6 months ago

docker pull garrardkitchen/gif-creator-api