docs: multi-modality rebrand, 2.0 architecture accuracy, and full OpenAPI coverage (#254)

* docs: rebrand from image-only to multi-modality across docs and metadata

SnapOtter expanded from image-only to 157 tools across 5 modalities
(image, video, audio, document/PDF, data). Update all product-level
copy, metadata, and i18n that still framed it as an image-only tool.

- README, package.json, root llms.txt: multi-modality framing, 157 tools
- OpenAPI info + tags, generated /llms.txt tagline (docs.ts)
- VitePress docs site: hero, getting-started, architecture, security,
  deployment, configuration, developer, supported-formats
- i18n: 10 product keys across all 21 locales (hero, app description,
  privacy notes, AI features, progress messages, getting-started)
- web/demo/landing meta + privacy copy, COMMUNITY_GUIDE, .env.example

Stale tool counts (53/50+/52/70+/35) corrected to 157 throughout.
Database/container deployment claims left unchanged (out of scope).

* docs: fix stale post-rebrand test assertions and README language list

- tests/e2e-docs/homepage.spec.ts: assert the current docs homepage (file toolkit, 157 tools, 5 modalities) instead of the old image-only strings
- tests/unit/api/docs-route.test.ts: sync the reproduced llms.txt tagline with docs.ts
- README.md: 21 languages with the correct list (add Swedish and Chinese Traditional, drop Czech which is not supported)

* docs: correct 2.0 architecture references (Postgres 17 + Redis 8, 3-container stack)

The docs and metadata still described the 1.x stack (SQLite, single container, p-queue). Update them to the current 2.0 reality.

- README: replace the broken single-container `docker run` quick-start with the real Docker Compose stack (app + Postgres 17 + Redis 8); fix the "no Redis, no Postgres" feature bullet
- package.json: description no longer claims a single container
- apps/docs: rewrite database.md for Postgres; configuration.md DB_PATH -> DATABASE_URL + REDIS_URL; architecture.md SQLite/p-queue/better-sqlite3 -> Postgres/BullMQ/pg and add media-engine + doc-engine; developer/security/deployment/docker-tags/getting-started/contributing compose examples now include postgres + redis; index.md + api/ai.md AI count 16 -> 19
- SECURITY.md: Drizzle (SQLite) -> (PostgreSQL)
- landing: enterprise/FeatureHighlights single-container wording; TrustSignals/ToolGrid 150+ -> 157 (dynamic); Pricing/FAQ 15 -> 19 AI tools

* docs(api): document all video, audio, document, and data tool endpoints in OpenAPI

The spec covered only image tools; the Scalar UI and the generated /llms.txt and /llms-full.txt inherited that gap. Add the 104 missing tool endpoints so the API docs match the code.

- Video: 29 endpoints (most long/async; auto-subtitles is AI)
- Audio: 17 (transcribe-audio is AI)
- Document/PDF: 36 (ocr-pdf is AI; conversions are long/async)
- Data: 10
- Image: 12 newer tools (background-replace, blur-background AI; histogram/lqip-placeholder/sprite-sheet custom responses; barcode-generate uses a JSON body)

Each schema is derived from the tool's Zod validator and executionHint (fast -> 200, long -> 202+SSE, AI adds 501 FeatureNotInstalledError, multi-file inputs as arrays), referencing the existing shared schemas. Tool path entries: 64 -> 168. Spec parses as valid YAML with no duplicate paths and only known $refs.
This commit is contained in:
SnapOtter
2026-06-16 18:04:52 +08:00
committed by GitHub
parent 8eee17aeea
commit 17726ae59d
59 changed files with 6135 additions and 468 deletions
+4 -4
View File
@@ -5,7 +5,7 @@ import pkg from "../../../package.json";
export default defineConfig({
title: "SnapOtter",
description:
"Documentation for SnapOtter - A Self Hosted Image Manipulator. 53 tools, local AI, pipelines, REST API.",
"Documentation for SnapOtter - A Self-Hosted File Manipulation Suite. 157 tools for image, video, audio, PDF, and data processing. Local AI, pipelines, REST API.",
base: "/",
appearance: { initialValue: "light" },
srcDir: ".",
@@ -25,7 +25,7 @@ export default defineConfig({
["meta", { property: "og:image", content: "https://docs.snapotter.com/og-image.png" }],
["meta", { property: "og:image:width", content: "1280" }],
["meta", { property: "og:image:height", content: "640" }],
["meta", { property: "og:image:alt", content: "SnapOtter - Self-Hosted Image Processing" }],
["meta", { property: "og:image:alt", content: "SnapOtter - Self-Hosted File Processing" }],
["meta", { property: "og:locale", content: "en_US" }],
["meta", { name: "twitter:card", content: "summary_large_image" }],
["meta", { name: "twitter:site", content: "@SnapOtterHQ" }],
@@ -76,9 +76,9 @@ export default defineConfig({
`,
customTemplateVariables: {
description:
"SnapOtter is a self-hosted, open-source image processing platform with 53 tools including AI/ML. Runs in a single Docker container with GPU auto-detection.",
"SnapOtter is a self-hosted, open-source file processing platform with 157 tools across image, video, audio, PDF, and data. Includes AI/ML tools. Runs via Docker Compose with GPU auto-detection.",
details:
"Resize, compress, convert, remove backgrounds, upscale, run OCR, and more - without sending images to external services.",
"Process images (resize, compress, convert, remove backgrounds, upscale, OCR), videos (trim, merge, subtitles), audio (normalize, transcribe, convert), PDFs (merge, split, watermark, redact), and data files (CSV, JSON, XML conversion) - without sending files to external services.",
},
}),
],
+1 -1
View File
@@ -6,7 +6,7 @@ description: AI engine reference with all local ML tools. Background removal, up
The `@snapotter/ai` package bridges Node.js to a **persistent Python sidecar** for all ML operations. The dispatcher process stays alive between requests for fast warm-start performance. GPU is auto-detected at startup and used when available.
16 AI tool routes. All models run locally - no internet required after initial model download.
19 AI tool routes. All models run locally - no internet required after initial model download.
## Architecture
+15 -13
View File
@@ -4,7 +4,7 @@ description: Monorepo structure, app and package architecture, request lifecycle
# Architecture
SnapOtter is a monorepo managed with pnpm workspaces and Turborepo. Everything ships as a single Docker container.
SnapOtter is a monorepo managed with pnpm workspaces and Turborepo. It deploys as a 3-container Docker Compose stack: the SnapOtter app image, PostgreSQL 17, and Redis 8.
## Project structure
@@ -16,6 +16,8 @@ snapotter/
│ └── docs/ # This VitePress site
├── packages/
│ ├── image-engine/ # Sharp-based image operations
│ ├── media-engine/ # FFmpeg spawn + progress parsing
│ ├── doc-engine/ # qpdf, LibreOffice, ghostscript wrappers
│ ├── ai/ # Python AI model bridge
│ └── shared/ # Types, constants, i18n
└── docker/ # Dockerfile and Compose config
@@ -47,12 +49,12 @@ Shared TypeScript types, constants (like `APP_VERSION` and tool definitions), an
### API (`apps/api`)
A Fastify v5 server exposing 53 tool routes (37 standard image operations + 16 AI-powered) that handles:
A Fastify v5 server exposing 157 tool routes across five modalities (image, video, audio, document, data) that handles:
- File uploads, temporary workspace management, and persistent file storage
- User file library with version chains (`user_files` table) -- each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page
- Tool execution (routes each tool request to the image engine or AI bridge)
- Pipeline orchestration (chaining multiple tools sequentially)
- Batch processing with concurrency control via p-queue
- Batch processing with concurrency control via BullMQ job queues (pools: image, media, ai, docs, system)
- User authentication, RBAC (admin/user roles with a full permission set), API key management, and rate limiting
- Teams management -- admin-only CRUD; users are assigned to a team via the `team` field on their profile
- Runtime settings -- a key-value store in the `settings` table that controls `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, and other operational knobs without redeploying
@@ -60,9 +62,9 @@ A Fastify v5 server exposing 53 tool routes (37 standard image operations + 16 A
- Swagger/OpenAPI documentation at `/api/docs`
- Serving the built frontend as a SPA in production
Key dependencies: Fastify, Drizzle ORM, better-sqlite3, Sharp, Piscina (worker thread pool), Zod for validation.
Key dependencies: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod for validation.
The server handles graceful shutdown on SIGTERM/SIGINT: it drains HTTP connections, stops the worker pool, shuts down the Python dispatcher, and closes the database.
The server handles graceful shutdown on SIGTERM/SIGINT: it drains HTTP connections, stops BullMQ workers, shuts down the Python dispatcher, and closes the database connection.
### Web (`apps/web`)
@@ -78,17 +80,17 @@ This VitePress site. Deployed to Cloudflare Pages automatically on push to `main
## How a request flows
1. The user picks a tool in the web UI and uploads an image.
1. The user picks a tool in the web UI and uploads a file.
2. The frontend sends a multipart POST to `/api/v1/tools/:toolId` with the file and settings.
3. The API route validates the input with Zod, then dispatches processing.
4. For standard tools, the request is offloaded to a Piscina worker thread pool so Sharp operations don't block the main event loop. The worker auto-orients the image based on EXIF metadata, runs the tool's process function, and returns the result. If the worker pool is unavailable, processing falls back to the main thread.
4. For standard tools, the job is enqueued to the appropriate BullMQ pool (image, media, or docs based on modality). The in-process BullMQ worker auto-orients the image based on EXIF metadata, runs the tool's process function, and returns the result.
5. For AI tools, the TypeScript bridge sends a request to the persistent Python dispatcher (or spawns a fresh subprocess as fallback), waits for it to finish, and reads the output file.
6. Job progress is persisted to the `jobs` SQLite table so state survives container restarts. Real-time updates are delivered via SSE at `/api/v1/jobs/:jobId/progress`.
7. The API returns a `jobId` and `downloadUrl`. The user downloads the processed image from `/api/v1/download/:jobId/:filename`.
6. Job progress is persisted to the `jobs` table in PostgreSQL so state survives container restarts. Real-time updates are delivered via SSE at `/api/v1/jobs/:jobId/progress`.
7. The API returns a `jobId` and `downloadUrl`. The user downloads the processed file from `/api/v1/download/:jobId/:filename`.
For pipelines, the API feeds the output of each step as input to the next, running them sequentially.
For batch processing, the API uses p-queue with a configurable concurrency limit (`CONCURRENT_JOBS`) and returns a ZIP file with all processed images.
For batch processing, the API uses BullMQ flows with per-step child jobs and returns a ZIP file with all processed files.
## Resource footprint
@@ -96,14 +98,14 @@ SnapOtter is designed for low idle memory use. Nothing is preloaded or kept warm
### At idle
Only the Node.js/Fastify process is running. Typical idle RAM is **~100-150 MB** (Node.js process + SQLite connection). No Python process, no worker threads, no model weights in memory.
The Node.js/Fastify process, PostgreSQL, and Redis are running. Typical idle RAM is **~200-300 MB** across all three containers (Node.js process, Postgres, and Redis). No Python process, no model weights in memory.
### What starts, and when
| Component | Starts when | Memory while active |
|-----------|-------------|---------------------|
| Fastify server | Container start | ~100-150 MB |
| Piscina worker threads | First standard tool request | Spawned on demand, terminated after **30 s idle** |
| Fastify server + Postgres + Redis | Container start | ~200-300 MB total |
| BullMQ workers | Container start (in-process) | One worker per pool (image, media, ai, docs, system) |
| Python dispatcher | First AI tool request | Python interpreter + pre-imported libraries (PIL, NumPy, MediaPipe, rembg) - no model weights |
| AI model weights | During the specific tool's request | Loaded from disk, freed when the request finishes |
+47 -4
View File
@@ -34,7 +34,8 @@ All configuration is done through environment variables. Every variable has a se
| Variable | Default | Description |
|---|---|---|
| `STORAGE_MODE` | `local` | `local` or `s3`. Only local storage is currently implemented. |
| `DB_PATH` | `./data/snapotter.db` | Path to the SQLite database file. |
| `DATABASE_URL` | `postgres://snapotter:snapotter@postgres:5432/snapotter` | PostgreSQL connection string. |
| `REDIS_URL` | `redis://redis:6379` | Redis connection string (used for BullMQ job queues). |
| `WORKSPACE_PATH` | `./tmp/workspace` | Directory for temporary files during processing. Cleaned up automatically. |
| `FILES_STORAGE_PATH` | `./data/files` | Directory for persistent user files (uploaded images, saved results). |
@@ -91,15 +92,57 @@ services:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=changeme
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
- MAX_UPLOAD_SIZE_MB=200
- CONCURRENT_JOBS=4
- FILE_MAX_AGE_HOURS=12
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
## Volumes
The Docker container uses two volumes:
The Docker Compose stack uses four volumes:
- `/data` -- Persistent storage for the SQLite database and user files. Mount this to keep users, API keys, saved pipelines, and uploaded images across container restarts.
- `/tmp/workspace` -- Temporary storage for images being processed. This can be ephemeral, but mounting it avoids filling up the container's writable layer.
- `/data` (app) -- AI models, Python venv, and user files. Mount this to keep uploaded files and installed AI bundles across restarts.
- `/tmp/workspace` (app) -- Temporary storage for files being processed. This can be ephemeral, but mounting it avoids filling up the container's writable layer.
- `SnapOtter-pgdata` (postgres) -- PostgreSQL data directory. This holds all relational data (users, settings, pipelines, jobs, audit log). Back up via `pg_dump` or volume snapshot.
- `SnapOtter-redisdata` (redis) -- Redis append-only file for durable job queues.
+3
View File
@@ -60,6 +60,9 @@ If you are contributing on behalf of your employer and your employer retains IP
git clone https://github.com/<your-username>/snapotter.git
cd snapotter
# Start Postgres + Redis for local dev
docker compose -f docker-compose.dev.yml up -d
# Install dependencies
pnpm install
+105 -52
View File
@@ -1,12 +1,12 @@
---
description: SQLite database schema, tables, migrations, and backup procedures for SnapOtter.
description: PostgreSQL database schema, tables, migrations, and backup procedures for SnapOtter.
---
# Database
SnapOtter uses SQLite with [Drizzle ORM](https://orm.drizzle.team/) for data persistence. The schema is defined in `apps/api/src/db/schema.ts`.
SnapOtter uses PostgreSQL 17 with [Drizzle ORM](https://orm.drizzle.team/) (pg-core / node-postgres) for data persistence. The schema is defined in `apps/api/src/db/schema.ts`.
The database file lives at the path set by `DB_PATH` (defaults to `./data/snapotter.db`). In Docker, mount the `/data` volume to persist it across container restarts.
The connection is configured via the `DATABASE_URL` environment variable (default `postgres://snapotter:snapotter@postgres:5432/snapotter`). In Docker Compose, the Postgres container stores its data in the `SnapOtter-pgdata` named volume.
## Tables
@@ -16,13 +16,13 @@ Stores user accounts. Created automatically on first run from `DEFAULT_USERNAME`
| Column | Type | Notes |
|---|---|---|
| `id` | integer | Primary key, auto-increment |
| `username` | text | Unique, required |
| `passwordHash` | text | scrypt hash |
| `role` | text | `admin` or `user` |
| `mustChangePassword` | integer | Boolean flag for forced password reset |
| `createdAt` | text | ISO timestamp |
| `updatedAt` | text | ISO timestamp |
| `id` | uuid | Primary key |
| `username` | varchar | Unique, required |
| `passwordHash` | varchar | scrypt hash |
| `role` | varchar | `admin`, `editor`, or `user` |
| `mustChangePassword` | boolean | Forced password reset flag |
| `createdAt` | timestamp | Creation time |
| `updatedAt` | timestamp | Last update time |
### sessions
@@ -30,10 +30,10 @@ Active login sessions. Each row ties a session token to a user.
| Column | Type | Notes |
|---|---|---|
| `id` | text | Primary key (session token) |
| `userId` | integer | Foreign key to `users.id` |
| `expiresAt` | text | ISO timestamp |
| `createdAt` | text | ISO timestamp |
| `id` | varchar | Primary key (session token) |
| `userId` | uuid | Foreign key to `users.id` |
| `expiresAt` | timestamp | Expiry time |
| `createdAt` | timestamp | Creation time |
### teams
@@ -41,9 +41,9 @@ Groups for organizing users. Admins can assign users to teams.
| Column | Type | Description |
|--------|------|-------------|
| `id` | text UUID | Primary key |
| `name` | text (unique, max 50 chars) | Team name |
| `createdAt` | integer | Unix timestamp |
| `id` | uuid | Primary key |
| `name` | varchar (unique, max 50 chars) | Team name |
| `createdAt` | timestamp | Creation time |
### api_keys
@@ -51,12 +51,12 @@ API keys for programmatic access. The raw key is shown once on creation; only th
| Column | Type | Notes |
|---|---|---|
| `id` | integer | Primary key, auto-increment |
| `userId` | integer | Foreign key to `users.id` |
| `keyHash` | text | scrypt hash of the key |
| `name` | text | User-provided label |
| `createdAt` | text | ISO timestamp |
| `lastUsedAt` | text | Updated on each authenticated request |
| `id` | uuid | Primary key |
| `userId` | uuid | Foreign key to `users.id` |
| `keyHash` | varchar | scrypt hash of the key |
| `name` | varchar | User-provided label |
| `createdAt` | timestamp | Creation time |
| `lastUsedAt` | timestamp | Updated on each authenticated request |
Keys are prefixed with `si_` followed by 96 hex characters (48 random bytes).
@@ -66,11 +66,11 @@ Saved tool chains that users create in the UI.
| Column | Type | Notes |
|---|---|---|
| `id` | integer | Primary key, auto-increment |
| `name` | text | Pipeline name |
| `description` | text | Optional description |
| `steps` | text | JSON array of `{ toolId, settings }` objects |
| `createdAt` | text | ISO timestamp |
| `id` | uuid | Primary key |
| `name` | varchar | Pipeline name |
| `description` | varchar | Optional description |
| `steps` | jsonb | Array of `{ toolId, settings }` objects |
| `createdAt` | timestamp | Creation time |
### user_files
@@ -78,18 +78,18 @@ Persistent file library with version chain tracking. Each processing step that s
| Column | Type | Description |
|--------|------|-------------|
| `id` | text UUID | Primary key |
| `userId` | text UUID | FK users (CASCADE DELETE) |
| `originalName` | text | Original upload filename |
| `storedName` | text | Filename on disk |
| `mimeType` | text | MIME type |
| `id` | uuid | Primary key |
| `userId` | uuid | FK to users (CASCADE DELETE) |
| `originalName` | varchar | Original upload filename |
| `storedName` | varchar | Filename on disk |
| `mimeType` | varchar | MIME type |
| `size` | integer | File size in bytes |
| `width` | integer | Image width in px |
| `height` | integer | Image height in px |
| `version` | integer | Version number (1 = original) |
| `parentId` | text UUID \| null | FK user_files (parent version) |
| `toolChain` | text (JSON array) | Tool IDs applied in order to produce this version |
| `createdAt` | integer | Unix timestamp |
| `parentId` | uuid or null | FK to user_files (parent version) |
| `toolChain` | jsonb | Tool IDs applied in order to produce this version |
| `createdAt` | timestamp | Creation time |
### jobs
@@ -97,16 +97,16 @@ Tracks processing jobs for progress reporting and cleanup.
| Column | Type | Notes |
|---|---|---|
| `id` | text | Primary key (UUID) |
| `type` | text | Tool or pipeline identifier |
| `status` | text | `queued`, `processing`, `completed`, or `failed` |
| `progress` | real | 0.01.0 fraction |
| `inputFiles` | text | JSON array of input file paths |
| `outputPath` | text | Path to the result file |
| `settings` | text | JSON of the tool settings used |
| `error` | text | Error message if failed |
| `createdAt` | text | ISO timestamp |
| `completedAt` | text | ISO timestamp |
| `id` | uuid | Primary key |
| `type` | varchar | Tool or pipeline identifier |
| `status` | varchar | `queued`, `processing`, `completed`, or `failed` |
| `progress` | real | 0.0-1.0 fraction |
| `inputFiles` | jsonb | Array of input file paths |
| `outputPath` | varchar | Path to the result file |
| `settings` | jsonb | Tool settings used |
| `error` | varchar | Error message if failed |
| `createdAt` | timestamp | Creation time |
| `completedAt` | timestamp | Completion time |
### settings
@@ -114,16 +114,69 @@ Key-value store for server-wide settings that admins can change from the UI.
| Column | Type | Notes |
|---|---|---|
| `key` | text | Primary key |
| `value` | text | Setting value |
| `updatedAt` | text | ISO timestamp |
| `key` | varchar | Primary key |
| `value` | varchar | Setting value |
| `updatedAt` | timestamp | Last update time |
### roles
Custom roles with granular permissions.
| Column | Type | Notes |
|---|---|---|
| `id` | uuid | Primary key |
| `name` | varchar | Unique role name |
| `description` | varchar | Optional description |
| `permissions` | jsonb | Array of permission strings |
| `createdAt` | timestamp | Creation time |
### audit_log
Security-relevant action log.
| Column | Type | Notes |
|---|---|---|
| `id` | uuid | Primary key |
| `userId` | uuid | FK to users |
| `action` | varchar | Action type |
| `details` | jsonb | Action-specific data |
| `createdAt` | timestamp | Action time |
## Migrations
Drizzle handles schema migrations. The config is in `apps/api/drizzle.config.ts`. During development, run:
Drizzle handles schema migrations. Migration files live in `apps/api/drizzle/`. During development:
```bash
pnpm --filter @snapotter/api drizzle-kit push
cd apps/api
npx drizzle-kit generate # generate a migration from schema changes
npx drizzle-kit migrate # apply pending migrations
```
In production, the schema is applied automatically on startup.
In production, pending migrations are applied automatically on startup.
## Backup and restore
The relational database lives in the Postgres container's `SnapOtter-pgdata` volume, not the app's `/data` volume.
**Option 1: pg_dump (recommended)**
```bash
# Dump the database while the stack is running
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
# Restore into a fresh database
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
```
**Option 2: Volume snapshot**
```bash
# Stop the stack, then snapshot the pgdata volume
docker compose down
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
```
### Migrating from 1.x (SQLite)
If you are upgrading from SnapOtter 1.x, set `SQLITE_MIGRATE_PATH` to the path of your old `snapotter.db` file on first boot. The migration runs once and imports users, settings, pipelines, and files into Postgres. Remove the variable after migration succeeds.
+94 -10
View File
@@ -4,14 +4,14 @@ description: Deploy SnapOtter to production with Docker. Hardware requirements,
# Deployment
SnapOtter ships as a single Docker container. The image supports **linux/amd64** (with NVIDIA CUDA) and **linux/arm64** (CPU), so it runs natively on Intel/AMD servers, Apple Silicon Macs, and ARM devices like the Raspberry Pi 4/5.
SnapOtter deploys as a 3-container Docker Compose stack: the SnapOtter app image, PostgreSQL 17, and Redis 8. The app image supports **linux/amd64** (with NVIDIA CUDA) and **linux/arm64** (CPU), so it runs natively on Intel/AMD servers, Apple Silicon Macs, and ARM devices like the Raspberry Pi 4/5.
See [Docker Image](./docker-tags) for GPU setup, Docker Compose examples, and version pinning.
## Quick Start (CPU)
```yaml
# docker-compose.yml Copy this file and run: docker compose up -d
# docker-compose.yml -- Copy this file and run: docker compose up -d
services:
SnapOtter:
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
@@ -19,7 +19,7 @@ services:
ports:
- "1349:1349" # Web UI + API
volumes:
- SnapOtter-data:/data # Database, AI models, user files (PERSISTENT)
- SnapOtter-data:/data # AI models, user files (PERSISTENT)
- SnapOtter-workspace:/tmp/workspace # Temp processing files (can be tmpfs)
environment:
# --- Authentication ---
@@ -27,6 +27,10 @@ services:
- DEFAULT_USERNAME=admin # First-run admin username
- DEFAULT_PASSWORD=admin # First-run admin password (you'll be forced to change it)
# --- Database + Queue ---
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
# --- Limits (0 = unlimited) ---
# - MAX_UPLOAD_SIZE_MB=0 # Per-file upload limit in MB
# - MAX_BATCH_SIZE=0 # Max files per batch request
@@ -39,6 +43,11 @@ services:
# --- Bind mount permissions ---
# - PUID=1000 # Match your host user's UID (run: id -u)
# - PGID=1000 # Match your host user's GID (run: id -g)
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
@@ -53,9 +62,42 @@ services:
max-size: "10m"
max-file: "3"
postgres:
image: postgres:17-alpine
container_name: SnapOtter-postgres
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter # Change this for non-local deployments
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
redis:
image: redis:8-alpine
container_name: SnapOtter-redis
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
volumes:
SnapOtter-data: # Named volume Docker manages permissions automatically
SnapOtter-data: # Named volume -- Docker manages permissions automatically
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
```bash
@@ -71,7 +113,7 @@ The app is then available at `http://localhost:1349`.
For NVIDIA GPU acceleration on AI tools (background removal, upscaling, face enhancement, OCR):
```yaml
# docker-compose-gpu.yml Requires: NVIDIA GPU + nvidia-container-toolkit
# docker-compose-gpu.yml -- Requires: NVIDIA GPU + nvidia-container-toolkit
# Install toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
services:
SnapOtter:
@@ -86,6 +128,13 @@ services:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=admin
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
@@ -107,9 +156,42 @@ services:
max-size: "10m"
max-file: "3"
postgres:
image: postgres:17-alpine
container_name: SnapOtter-postgres
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
redis:
image: redis:8-alpine
container_name: SnapOtter-redis
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
```bash
@@ -135,7 +217,7 @@ These numbers come from benchmarks run across four systems (Apple M2 Max, AMD Ry
| Recommended | All tools + AI on CPU | 4 cores | 4 GB | None | 20 GB |
| Full | All tools + AI on GPU | 4+ cores | 8 GB | NVIDIA 8 GB+ | 30 GB |
### Minimum (core image tools)
### Minimum (core tools, no AI)
| Resource | Requirement |
|---|---|
@@ -144,7 +226,7 @@ These numbers come from benchmarks run across four systems (Apple M2 Max, AMD Ry
| Disk | 3 GB (image) + 1 GB (data volume) |
| GPU | Not required |
All 35 non-AI tools (resize, crop, rotate, convert, compress, watermark, collage, etc.) run on any hardware. Most operations complete in under 1 second even on a single core. The exception is AVIF encoding, which takes ~27s on 1 core but drops to ~5s on 4 cores.
All 138 non-AI tools (image resize/crop/convert, video trim/merge, audio normalize/convert, PDF merge/split/compress, data format conversion, and more) run on any hardware. Most operations complete in under 1 second even on a single core. The exception is AVIF encoding, which takes ~27s on 1 core but drops to ~5s on 4 cores.
```yaml
deploy:
@@ -259,10 +341,12 @@ See the [complete format list](/guide/supported-formats) for details on every su
## Volumes
| Mount | Purpose | Required? |
| Mount / Volume | Purpose | Required? |
|---|---|---|
| `/data` | SQLite database, AI models, Python venv, user files | **Yes** — data loss without it |
| `/tmp/workspace` | Temporary processing files (auto-cleaned) | Recommended |
| `/data` (app) | AI models, Python venv, user files | **Yes** - file loss without it |
| `/tmp/workspace` (app) | Temporary processing files (auto-cleaned) | Recommended |
| `SnapOtter-pgdata` (postgres) | PostgreSQL data directory (users, settings, pipelines, jobs) | **Yes** - data loss without it |
| `SnapOtter-redisdata` (redis) | Redis append-only file for durable job queues | Recommended |
### Bind mounts vs. named volumes
+13 -4
View File
@@ -1,5 +1,5 @@
---
description: Local development setup, commands, code conventions, and how to add a new image processing tool to SnapOtter.
description: Local development setup, commands, code conventions, and how to add a new tool to SnapOtter.
---
# Developer guide
@@ -10,7 +10,7 @@ How to set up a local development environment and contribute code to SnapOtter.
- [Node.js](https://nodejs.org/) 22+
- [pnpm](https://pnpm.io/) 9+ (`corepack enable && corepack prepare pnpm@latest --activate`)
- [Docker](https://www.docker.com/) (for container builds and AI features)
- [Docker](https://www.docker.com/) (required for local Postgres + Redis, container builds, and AI features)
- Git
Python 3.10+ is only needed if you are working on the AI/ML sidecar (background removal, upscaling, OCR).
@@ -20,6 +20,7 @@ Python 3.10+ is only needed if you are working on the AI/ML sidecar (background
```bash
git clone https://github.com/snapotter-hq/snapotter.git
cd snapotter
docker compose -f docker-compose.dev.yml up -d # start Postgres + Redis
pnpm install
pnpm dev
```
@@ -43,6 +44,8 @@ apps/
packages/
shared/ Constants, types, i18n strings
image-engine/ Sharp-based image operations
media-engine/ FFmpeg spawn + progress parsing
doc-engine/ qpdf, LibreOffice, ghostscript wrappers
ai/ Python sidecar bridge for ML models
tests/
unit/ Vitest unit tests
@@ -76,7 +79,13 @@ pnpm test:coverage # tests with coverage report
## Database
SQLite via Drizzle ORM. The database file lives at `./data/snapotter.db` by default.
PostgreSQL 17 via Drizzle ORM (pg-core). Local dev requires Postgres and Redis running -- start them with:
```bash
docker compose -f docker-compose.dev.yml up -d
```
This gives you Postgres on port 5432 and Redis on port 6379. Then generate and apply migrations:
```bash
cd apps/api
@@ -84,7 +93,7 @@ npx drizzle-kit generate # generate a migration from schema changes
npx drizzle-kit migrate # apply pending migrations
```
Schema is defined in `apps/api/src/db/schema.ts`. Tables: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles.
Schema is defined in `apps/api/src/db/schema.ts`. Tables: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles, roles, auditLog.
## Adding a new tool
+41 -15
View File
@@ -4,7 +4,7 @@ description: SnapOtter Docker image tags, GPU benchmarks, version pinning, and m
# Docker Image
SnapOtter ships as a single Docker image that works on all platforms.
SnapOtter ships as a Docker image that runs alongside PostgreSQL 17 and Redis 8 in a Compose stack. The app image works on all platforms.
## Quick start
@@ -56,6 +56,8 @@ GET /api/v1/admin/health
## Docker Compose
The full Compose stack includes the app, PostgreSQL 17, and Redis 8. See [Deployment](/guide/deployment) for the complete `docker-compose.yml`. A minimal example:
```yaml
services:
SnapOtter:
@@ -65,6 +67,14 @@ services:
volumes:
- SnapOtter-data:/data
- SnapOtter-workspace:/tmp/workspace
environment:
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
logging:
driver: json-file
@@ -72,22 +82,43 @@ services:
max-size: "10m"
max-file: "3"
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
For GPU acceleration via Docker Compose, add the deploy section:
For GPU acceleration via Docker Compose, add the deploy section to the SnapOtter service:
```yaml
services:
SnapOtter:
image: snapotter/snapotter:latest
ports:
- "1349:1349"
volumes:
- SnapOtter-data:/data
- SnapOtter-workspace:/tmp/workspace
deploy:
resources:
reservations:
@@ -95,11 +126,6 @@ services:
- driver: nvidia
count: 1
capabilities: [gpu]
restart: unless-stopped
volumes:
SnapOtter-data:
SnapOtter-workspace:
```
## Version pinning
+46 -12
View File
@@ -48,21 +48,58 @@ services:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=admin
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
volumes:
SnapOtter-data:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
See [Configuration](/guide/configuration) for all environment variables.
## Build from Source
**Prerequisites:** Node.js 22+, pnpm 9+, Python 3.10+ (for AI features), Git.
**Prerequisites:** Node.js 22+, pnpm 9+, Docker (for Postgres + Redis), Python 3.10+ (for AI features), Git.
```bash
git clone https://github.com/snapotter-hq/SnapOtter.git
cd SnapOtter
docker compose -f docker-compose.dev.yml up -d # start Postgres + Redis
pnpm install
pnpm dev
```
@@ -72,18 +109,15 @@ pnpm dev
## What You Can Do
### Image Processing (53 Tools)
### File Processing (157 Tools)
| Category | Tools |
|----------|-------|
| **Essentials** | Resize, Crop, Rotate & Flip, Convert, Compress |
| **Optimization** | Optimize for Web, Strip Metadata, Edit Metadata, Bulk Rename, Image to PDF, Favicon Generator |
| **Adjustments** | Adjust Colors, Sharpening, Replace Color, Color Blindness Simulation |
| **AI Tools** | Remove Background, Upscale, Erase Object, OCR, Blur Faces, Smart Crop, Image Enhancement, Enhance Faces, Colorize, Noise Removal, Red Eye Removal, Restore Photo, Passport Photo, Content-Aware Resize, AI Canvas Expand, PNG Transparency Fixer |
| **Watermark & Overlay** | Text Watermark, Image Watermark, Text Overlay, Image Composition, Meme Generator |
| **Utilities** | Image Info, Compare, Find Duplicates, Color Palette, QR Code Generator, Barcode Reader, Image to Base64, HTML to Image |
| **Layout** | Collage, Stitch, Split, Border & Frame, Beautify |
| **Format** | SVG to Raster, Vectorize, GIF Tools, PDF to Image |
| Modality | Count | Example Tools |
|----------|-------|---------------|
| **Image** | 64 | Resize, Crop, Compress, Convert, Remove Background, Upscale, OCR, Watermark, Collage, Colorize, GIF Tools |
| **Video** | 29 | Trim, Crop, Compress, Convert, Merge, Extract Audio, Auto Subtitles, Video to GIF, Resize, Stabilize |
| **Audio** | 17 | Trim, Merge, Convert, Normalize, Noise Reduction, Transcribe, Pitch Shift, Fade, Ringtone Maker |
| **PDF / Document** | 37 | Merge, Split, Compress, OCR, Watermark, Redact, Word to PDF, Excel to PDF, Rotate, Protect, Repair |
| **Data** | 10 | CSV to JSON, JSON to XML, Merge CSVs, Split CSV, Create ZIP, Extract ZIP, Chart Maker, YAML/JSON |
### Pipelines
+74 -14
View File
@@ -4,7 +4,7 @@ description: Security hardening guide for SnapOtter. Container security, network
# Security & Hardening
SnapOtter processes images entirely on your infrastructure. No telemetry, no external API calls, no phone-home behavior. Images never leave the container.
SnapOtter processes files entirely on your infrastructure. No telemetry, no external API calls, no phone-home behavior. Files never leave the container.
The container runs as a dedicated non-root user (`snapotter`) with all Linux capabilities dropped except the minimum required set. For the full vulnerability disclosure policy and security architecture, see [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) on GitHub.
@@ -26,6 +26,13 @@ services:
- AUTH_ENABLED=true
- DEFAULT_PASSWORD=change-me-immediately
- RATE_LIMIT_PER_MIN=100
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# --- Resource limits ---
mem_limit: 6g # Prevents runaway memory from crashing the host
@@ -61,9 +68,40 @@ services:
shm_size: "2gb" # Required for Python ML shared memory
restart: unless-stopped
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
volumes:
- SnapOtter-redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
volumes:
SnapOtter-data:
SnapOtter-workspace:
SnapOtter-pgdata:
SnapOtter-redisdata:
```
### Why `no-new-privileges` Is Not Set
@@ -78,7 +116,7 @@ If you use Kubernetes or Docker's `--user` flag to run as non-root directly (byp
## Network Isolation
During normal operation, the container makes **zero outbound network connections**. All image processing happens locally using bundled libraries.
During normal operation, the container makes **zero outbound network connections**. All file processing happens locally using bundled libraries.
```
Browser --> Reverse Proxy (TLS) --> SnapOtter container --> (nothing)
@@ -214,29 +252,51 @@ For resource sizing, see [Hardware Requirements](/guide/deployment#hardware-requ
## Backup and Recovery
All persistent state lives in the `/data` volume:
Persistent state is split across two volumes:
| Volume | Contents | Critical? |
|---|---|---|
| `SnapOtter-pgdata` | PostgreSQL database (users, settings, pipelines, jobs, audit log) | Yes |
| `/data` (app volume) | User-uploaded files, AI models, Python venv | Partially (see below) |
Within the `/data` volume:
| Path | Contents | Critical? |
|---|---|---|
| `/data/snapotter.db` | SQLite database (users, settings, pipelines, audit log) | Yes |
| `/data/uploads/` | User-uploaded files (if file storage is enabled) | Yes |
| `/data/uploads/`, `/data/outputs/` | User files and processing results | Yes |
| `/data/ai/` | Downloaded AI model files | No (re-downloadable) |
| `/data/venv/` | Python virtual environment | No (rebuilt on start) |
SQLite uses WAL (Write-Ahead Logging) mode, which means the database is safe to snapshot while the container is running. A minimal backup strategy:
### Database backup
Use `pg_dump` to back up the database while the stack is running:
```bash
# Copy the database while the container is running (WAL-safe)
docker cp SnapOtter:/data/snapotter.db ./backup/snapotter.db
docker cp SnapOtter:/data/snapotter.db-wal ./backup/snapotter.db-wal 2>/dev/null
docker cp SnapOtter:/data/snapotter.db-shm ./backup/snapotter.db-shm 2>/dev/null
# Dump the database
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
# Or snapshot the entire volume
docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-data.tar.gz -C /data .
# Restore into a fresh database
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
```
AI models total up to 14 GB across all bundles. Since they are re-downloadable, exclude `/data/ai/` and `/data/venv/` from backups to save space. Only the database and user uploads are critical.
Alternatively, stop the stack and snapshot the `SnapOtter-pgdata` volume:
```bash
docker compose down
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
```
### User files backup
```bash
# Snapshot the app data volume (excluding re-downloadable AI models)
docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-files.tar.gz \
--exclude='ai' --exclude='venv' -C /data .
```
AI models total up to 14 GB across all bundles. Since they are re-downloadable, exclude `/data/ai/` and `/data/venv/` from backups to save space. Only the database and user files are critical.
## Compliance Artifacts
+41 -2
View File
@@ -1,8 +1,12 @@
---
description: 55+ supported input formats including 23 camera RAW formats, and 14 output formats with full compatibility tables.
description: Supported file formats across all modalities - 55+ image input formats, video, audio, document, and data formats.
---
# Supported Image Formats
# Supported Formats
SnapOtter processes files across five modalities: image, video, audio, document, and data. This page lists all supported formats.
## Image Formats
SnapOtter supports 55+ image formats for input and 14 formats for output.
@@ -114,3 +118,38 @@ SnapOtter supports 55+ image formats for input and 14 formats for output.
| ICO | ImageMagick CLI | Lossless | Convert tool |
| JP2 | opj_compress CLI | Compression ratio | Convert tool |
| QOI | Inline codec | Lossless | Convert tool |
## Video Formats
Video processing is handled by FFmpeg (static build). All common containers and codecs are supported.
| Type | Formats |
|------|---------|
| **Input** | MP4, MOV, WebM, MKV, AVI, M4V, MTS, M2TS, 3GP, FLV, WMV, MPG, MPEG, TS, OGV |
| **Output** | MP4 (H.264/H.265), WebM (VP9), MKV, AVI, MOV, GIF, WebP |
Subtitle formats supported for embedding and burning: SRT, VTT, ASS.
## Audio Formats
Audio processing is also handled by FFmpeg.
| Type | Formats |
|------|---------|
| **Input** | MP3, WAV, FLAC, AAC, M4A, OGG, OPUS, WMA, AIFF, AMR, AC3 |
| **Output** | MP3, WAV, FLAC, AAC, M4A, OGG, OPUS |
## Document Formats
Document processing uses qpdf, LibreOffice, Ghostscript, Pandoc, and WeasyPrint.
| Type | Formats |
|------|---------|
| **Input** | PDF, DOCX, DOC, XLSX, XLS, PPTX, PPT, ODT, ODS, ODP, RTF, TXT, Markdown, HTML, EPUB |
| **Output** | PDF, PDF/A, DOCX, HTML, EPUB, images (via PDF to Image) |
## Data Formats
| Type | Formats |
|------|---------|
| **Input/Output** | CSV, JSON, XML, YAML, ZIP, Excel (XLSX) |
+6 -6
View File
@@ -3,8 +3,8 @@ layout: home
hero:
name: "SnapOtter"
text: "Self-Hosted Image Toolkit"
tagline: 50+ tools. Resize, compress, convert, remove backgrounds, upscale, OCR, and more. One Docker container, fully offline.
text: "Self-Hosted File Toolkit"
tagline: 157 tools for image, video, audio, PDF, and data processing. Resize, compress, convert, remove backgrounds, merge PDFs, trim videos, transcribe audio, and more. Fully offline.
actions:
- theme: brand
text: Get started
@@ -14,12 +14,12 @@ hero:
link: /api/rest
features:
- title: 53 Image Tools
details: Resize, crop, compress, convert, watermark, color adjust, vectorize, create GIFs, build collages, generate passport photos, find duplicates, and more.
- title: 157 Tools, 5 Modalities
details: "Image: resize, crop, compress, convert, watermark, collage, and more. Video: trim, crop, merge, compress, add subtitles. Audio: trim, normalize, convert, transcribe. PDF: merge, split, compress, watermark, OCR, redact. Data: CSV/JSON/XML conversion, ZIP archives, chart maker."
- title: Local AI
details: 16 AI-powered tools - remove backgrounds, upscale, enhance images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR), fix fake transparency, expand canvas with AI fill. All on your hardware, no internet required.
details: 19 AI-powered tools - remove backgrounds, upscale, enhance images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR), fix fake transparency, expand canvas with AI fill. All on your hardware, no internet required.
- title: Pipelines
details: Chain tools into reusable workflows with unlimited steps. Batch process unlimited images at once with a single request.
details: Chain tools into reusable workflows with unlimited steps. Batch process unlimited files at once with a single request.
- title: REST API
details: Every tool available via API with API key auth. Interactive docs at /api/docs, plus /llms.txt and /llms-full.txt for AI agents.
- title: File Library