Skip to content

Self-hosted photo and video management with AI-powered search, grouping, auto-tagging, person detection, single container, high-performance, low resource use.

License

Notifications You must be signed in to change notification settings

srad/GalleryNet

Repository files navigation

GalleryNet Logo

GalleryNet

Self-hosted photo and video gallery with AI-powered search, grouping, and tagging

Build Status Tests Docker Size Rust React License

Upload photos and videos, organize them into folders and use integrated AI tools for search and sorting — all running on your own hardware. No cloud, no API keys, fully private. High performance, low resource usage, and only a single container without any other dependencies.


FireShot Capture 004 - GalleryNet - localhost FireShot Capture 008 - GalleryNet - localhost

Features

  • Visual Search — Find similar photos and videos by uploading a reference image, with adjustable similarity threshold and one-click grouping

  • Face Recognition & Grouping — Automatically detect faces in your library and group them into people clusters with adjustable confidence

  • External Image Search — Search for any image in your library using Yandex Images via a secure server-side proxy (no public URL leaks)

  • Auto Tagging — Tag a few items in the library and let the AI automatically label matching items across your library

  • Duplicate Detection — Duplicates are detected during upload and silently skipped

  • Virtual Folders — Organize media into folders without moving files; one item can live in multiple folders with drag-and-drop support

  • Favorites — Mark items as favorites for quick access in a dedicated view

  • Multi-Select & Batch Operations — Marquee selection, shift-click, batch download (auto-split zip), batch delete, and batch add-to-folder

  • Video Support — Upload any common video format with automatic frame extraction for thumbnails and AI features

  • EXIF Metadata — View camera details, date, GPS, exposure, and more

  • Deep Linking — Bookmarkable URLs for folders, favorites, search states, and individual items

  • Password Protection — Optional single-password auth with rate limiting and secure sessions. No complex user management, only a simple password.

  • Responsive UI — Optimized for mobile with native touch gestures (long-press selection, swipe-to-close), ergonomic bottom-weighted navigation, and a persistent thumbnail resizer (S/M/L) to customize your viewing experience.

  • Drag-and-Drop Upload — Drag files anywhere into the browser window to upload. Context-aware: dropping into a virtual folder automatically adds the files to that folder.

  • Real-time Sync — WebSocket-powered instant updates across all browser clients; all users can see new uploads, favorite toggles, and folder changes immediately as they happen

  • Self-Healing — Automatically detects and repairs missing thumbnails or metadata in the background

  • 100% Self-Hosted — No cloud, no telemetry. Your data stays yours.

How It Works

Feature Technology
Visual Search MobileNetV3-Large extracts 1280-dim feature vectors; cosine similarity via sqlite-vec
Similarity Grouping Agglomerative clustering over the same embedding space with a user-adjustable distance threshold
Face Detection SCRFD-500M detects faces and 5 facial landmarks for precise alignment
Face Clustering ArcFace (MobileFaceNet) extracts 512-dim embeddings from landmark-aligned 112×112 crops; initial grouping via SNN (Shared Nearest Neighbor) clustering with Jaccard Similarity and Chinese Whispers. SNN has proven to be the most accurate method. Min face size 20×20 px.
Face Classification Two-pass pipeline. KNN (primary): L2-normalised centroid per person computed from confirmed exemplars with outlier trimming (cosine similarity threshold 0.38, margin 0.10 between top-2). SVM fallback: Platt-calibrated linear SVM per person trained via linfa-svm (requires ≥ 3 confirmed exemplars; probability threshold 0.55, margin 0.10). Manually removed faces are permanently excluded from re-assignment via the face_exclusions table.
Auto-Tagging Linear SVM with Platt-calibrated probabilities trained on user-provided examples via linfa-svm
Duplicate Detection Perceptual hashing (image_hasher) compared at upload time
Video Processing ffmpeg thumbnail filter selects visually distinct frames for thumbnails, hashing, and embeddings
AI Inference ort (ONNX Runtime) for fast CPU-based model execution
Batch Downloads Real-time ZIP streaming via async_zip with automatic partitioning into ~2 GB parts
Authentication Argon2-hashed password, rate-limited login, secure HTTP-only cookies

Quick Start with Docker

The easiest way to run GalleryNet is with Docker:

docker run -d \
  --name gallerynet \
  -p 3000:3000 \
  -v gallerynet-data:/app/data \
  -e DATABASE_PATH=/app/data/gallery.db \
  -e UPLOAD_DIR=/app/data/uploads \
  -e THUMBNAIL_DIR=/app/data/thumbnails \
  -e GALLERY_PASSWORD=your-secret-password \
  sedrad/gallerynet

Docker Compose

services:
  gallerynet:
    image: sedrad/gallerynet
    container_name: gallerynet
    ports:
      - "3000:3000"
    environment:
      - DATABASE_PATH=/app/data/gallery.db
      - UPLOAD_DIR=/app/data/uploads
      - THUMBNAIL_DIR=/app/data/thumbnails
      - GALLERY_PASSWORD=your-secret-password
    volumes:
      - ./data:/app/data
    restart: unless-stopped
docker compose up -d

Environment Variables

Variable Default Description
DATABASE_PATH gallery.db Path to the SQLite database file
UPLOAD_DIR uploads Directory for original uploaded files
THUMBNAIL_DIR thumbnails Directory for generated thumbnails
MODEL_PATH assets/models/mobilenetv3.onnx Path to the ONNX model file
GALLERY_PASSWORD (empty) Set to enable password authentication. Leave empty for no auth
CORS_ORIGIN (empty) Set to allow cross-origin requests from a specific origin (e.g. https://example.com). Unset = same-origin only

Build from Source

Prerequisites

  • Rust — Latest stable toolchain
  • Node.js — v18+
  • ffmpeg — Must be on PATH for video support

Build & Run

# Clone the repository
git clone https://github.com/srad/GalleryNet.git
cd GalleryNet

# Build the frontend
cd frontend && npm install && npm run build && cd ..

# Run the server
cargo run --release

The server starts on http://localhost:3000.

Development

# Backend (auto-reload with cargo-watch)
cargo watch -x run

# Frontend (Vite dev server with HMR, proxies /api to :3000)
cd frontend && npm run dev

Architecture

GalleryNet follows Hexagonal Architecture with clean separation of concerns:

src/
├── domain/          # Models & trait ports (zero dependencies)
├── application/     # Use cases (upload, search, list, delete) and background tasks
├── infrastructure/  # SQLite, ONNX Runtime, perceptual hashing
├── presentation/    # Axum HTTP handlers & auth middleware
└── main.rs          # Wiring & server startup

Tech Stack

Layer Technology
Backend Rust, Axum, Tokio, WebSockets
Database SQLite + sqlite-vec
AI/ML ort (ONNX Runtime), MobileNetV3-Large, linfa-svm (tag learning)
Frontend React 19, TypeScript, Tailwind CSS v4, Vite
Video ffmpeg (frame extraction)
Hashing image_hasher (perceptual hashing)

API Endpoints

Method Endpoint Description
POST /api/upload Upload media (multipart). Returns MediaItem. 409 for duplicates
POST /api/search Visual similarity search. Multipart with file + similarity
POST /api/media/group Group all media by visual similarity. Body: {"similarity": 80}
POST /api/media/faces/group Group all media by detected faces. Body: {"similarity": 60}
POST /api/media/faces/search Search for similar faces by face ID. Body: {"face_id": "...", "similarity": 60}
GET /api/people List all identified people with representative faces
GET /api/people/stats Face and person statistics (total faces, unassigned, etc.)
GET /api/people/{id} Get person details
PUT /api/people/{id} Update person (name, hidden, representative face)
DELETE /api/people/{id} Delete person profile
POST /api/people/{id}/merge Merge person into another. Body: {"target_id": "..."}
POST /api/people/{id}/faces/{face_id}/confirm Promote an auto-clustered face to manually confirmed. Returns 204.
POST /api/people/{id}/faces/confirm-all Bulk-promote all auto-clustered exemplars for a person to confirmed. Returns { confirmed: N }.
GET /api/people/suggested-merges Top-30 person pairs by centroid similarity above threshold. Query: ?threshold=60.
GET /api/people/{id}/media List all media containing this person
GET /api/media Paginated media list. Params: page, limit, media_type, sort
GET /api/media/{id} Get single media item with EXIF data
POST /api/media/{id}/search-external Trigger server-side external image search (Yandex)
POST /api/media/{id}/favorite Toggle favorite status. Body: {"favorite": true/false}
DELETE /api/media/{id} Delete single media item
POST /api/media/batch-delete Batch delete. Body: ["uuid1", ...]
POST /api/media/fix-thumbnails Trigger background repair of missing thumbnails/metadata
POST /api/media/download/plan Create download plan (partitions large sets into <2GB parts). Body: ["uuid1", ...]
GET /api/media/download/stream/{id} Stream a specific download part incrementally
POST /api/media/download Simple batch download (if under 2GB). Body: ["uuid1", ...]
GET /api/tags List all unique tags
GET /api/tags/count Count auto-tags in current view
POST /api/tags/learn Train model from manual tags. Body: {"tag_name": "..."}
POST /api/tags/auto-tag Apply all trained models to current scope
GET /api/folders List all folders with item counts
POST /api/folders Create folder. Body: {"name": "..."}
DELETE /api/folders/{id} Delete folder (keeps media files)
GET /api/folders/{id}/media Paginated media in folder
POST /api/folders/{id}/media Add media to folder. Body: ["uuid1", ...]
POST /api/folders/{id}/media/remove Remove media from folder
GET /api/folders/{id}/download Get download plan for folder (auto-splits for large folders)
GET /api/library/download Get download plan for the entire library (auto-splits)
POST /api/library/purge Delete all files and data from the library
GET /api/stats Server statistics (counts, storage, disk space)
POST /api/login Authenticate. Body: {"password": "..."}
POST /api/logout Clear session
GET /api/ws WebSocket for real-time library synchronization
GET /api/auth-check Check authentication status

Contributing

Contributions are welcome! Feel free to open an issue or submit a pull request.

  1. Fork the repository
  2. Ideally create a ticket first to outline the matter.
  3. Create your feature branch (git checkout -b feature/123-my-feature) or bug fix branch (git checkout -b bug/123-my-feature) (replace 123 by the ticket number)
  4. Commit your changes (git commit -m 'Add my feature')
  5. Add your tests, place them correctly (server code in src/, frontend code under frontend/)
  6. Push to the branch (git push origin feature/123-my-feature)
  7. Open a Pull Request

Agent use

You can use an agent but you have to honor the AGENTS.md file. You have to be careful and aware though that in a rather mathematical source code that the agents have the tedency to randomly modify constants or mathematical expressions.

License

PolyForm Noncommercial License 1.0.0 means it is free for non-commecial purposes.

About

Self-hosted photo and video management with AI-powered search, grouping, auto-tagging, person detection, single container, high-performance, low resource use.

Resources

License

Stars

Watchers

Forks

Contributors