Siftr

Siftr

Docker app from Daneng66's Repository

Overview

Siftr is a self-hosted photo and video manager that helps you sift through your library with smart renaming, organizing, and deduplication. Exact and perceptual duplicate detection (for both photos and videos) is powered by czkawka, video thumbnails/metadata use ffmpeg, and EXIF write-back uses exiftool—all bundled in the image, nothing else to install. Point the /data volume at where your media lives and open the web UI.

Siftr

Sift through your photo and video library with smart renaming, browsing, and deduplication.

Siftr is a self-hosted photo (and video) manager that ships as a single all-in-one Docker container — React frontend, Node/Express API, SQLite index, and the czkawka_cli deduplication engine all bundled and served from one port.

Features

  • Library browser — fast, virtualized grid of photos and videos that stays smooth at 1,000–10,000 items. Hover quick-actions (download / select) and corner badges (duplicate indicator, file size, video duration).
  • Deduplication — czkawka_cli finds exact (hash-based) duplicate groups across both photos and videos. Compare copies side-by-side (videos play inline) and pick which to keep; deletions move to a .trash you can restore from or empty at any time. (Perceptual "similar images" and "similar videos" passes are also wired up, behind DEDUP_SIMILAR=true and DEDUP_SIMILAR_VIDEO=true respectively — the video pass needs ffmpeg; both off by default for now.)
  • Bulk rename — pattern tokens ({date:…}, {seq:N}, {original}, {camera}, {custom}) with a live preview and collision detection.
  • Organize — Nextcloud-style folder/filename templates ({date:YYYY}/{date:MM}/{original}) that restructure photos on disk from metadata. Scoped to whatever's selected in the sidebar: pick a folder to reorganize just that subtree, or run it over the whole library with the choice to keep every existing folder intact (reorganizing inside each one) or rebuild the tree from the library root.
  • Metadata editing — view and edit EXIF (date, GPS, camera) individually or across a selection; changes are written back into the files with exiftool.
  • Folders — the sidebar mirrors the actual directory structure of your photos on disk; pick a folder to browse just the photos it contains.
  • UX — card dashboard, left sidebar (folders / filters / stats), top nav, dark mode, and low-friction bulk selection (click, shift-click range, ctrl-click toggle, drag-select marquee).

Quick start (Docker)

docker build -t siftr .
docker run -p 8080:8080 -v ./data:/data siftr

Then open http://localhost:8080.

Put your photos and videos under the mounted volume at ./data/photos (sub-folders are scanned recursively) and click Scan. All persistent data lives under the one volume:

data/
  photos/       # your photos and videos (the watched library — source of truth)
  thumbnails/   # generated WebP thumbnails (a poster frame, for videos)
  db/siftr.sqlite
  .trash/       # files removed via "Move to trash" (restore or empty from the UI)

Install on unraid

Siftr ships as an unraid template (unraid/siftr.xml) backed by the prebuilt GHCR image ghcr.io/daneng66/siftr. Until it lands in Community Apps, add it manually:

  1. Docker → Add Container, then in Template repositories (under Docker settings) add https://github.com/Daneng66/Siftr and Save.

  2. Back on Add Container, select the Siftr template.

  3. Set the port and path mappings, then apply. Each row below maps a value on your unraid host to a fixed location inside the container — only the Host value is yours to choose; leave the Container value as shown.

    Port (Config Type: Port)

    Name Container port Host port Notes
    WebUI Port 8080 8080 TCP. Change the host port only if 8080 is already taken.

    Paths (Config Type: Path)

    Name Container path Host path (example) Access mode Notes
    App Data /data /mnt/user/appdata/siftr Read/Write Database, thumbnails, and .trash. Keep on a fast cache/appdata share.
    Photo Library /data/photos /mnt/user/Photos Read/Write Your existing photo share. Must be writable so Siftr can rename files and write EXIF. The container path is intentionally nested under /data, but its host path can live on a separate (e.g. array) share.
    Trash Folder (advanced, optional) /data/.trash /mnt/user/appdata/siftr/.trash Read/Write Where "Move to trash" relocates removed files. Defaults inside App Data; map to its own host share to keep trashed files off appdata or recover them elsewhere.
  4. Open the WebUI on the host port from above (default 8080) and click Scan.

Advanced options (similar-image/video dedup, scan concurrency, thumbnail size, etc.) are exposed as template variables; see Configuration.

[!NOTE] The image is linux/amd64 only — czkawka has no ARM build — so Siftr targets standard Intel/AMD unraid hardware.

Architecture

A single Node.js process serves the API under /api/* and the built React SPA for everything else.

  • Backend: Express + TypeScript, better-sqlite3 (in-process, no DB daemon), sharp (image thumbnails/dimensions), ffmpeg/ffprobe (video poster frames/dimensions/duration), exifr (EXIF read), exiftool (EXIF write), czkawka_cli (dedup) via child process. An in-process job queue runs scans and dedup with progress exposed at /api/jobs.
  • Frontend: React + Vite + Tailwind, TanStack Query (server state), Zustand (selection/UI), @tanstack/react-virtual (grid virtualization).
server/   Express API, scanner, dedup, exif, db
client/   React app
Dockerfile  multi-stage build (client + server -> slim runtime)

Local development

Requires Node 20 or 22 LTS (native modules better-sqlite3/sharp ship prebuilt binaries for LTS releases). exiftool and czkawka_cli must be on PATH for metadata-write and dedup features (or point EXIFTOOL_BIN / CZKAWKA_BIN at them); ffmpeg/ffprobe must be on PATH for video thumbnails/metadata and the similar-video dedup pass (or point FFMPEG_BIN / FFPROBE_BIN at them). Exact (hash-based) duplicate detection covers videos either way; only poster frames, dimensions/duration, and the perceptual similar-video pass need ffmpeg.

npm install
npm run dev        # Express on :8080, Vite dev server on :5173 (proxies /api)
# or, production-style:
npm run build && DATA_DIR=./data npm start

Run the backend unit tests (rename pattern engine + czkawka output parser):

npm test

Configuration (environment variables)

Var Default Purpose
PORT 8080 HTTP port
DATA_DIR /data Root of the persisted volume
TRASH_DIR $DATA_DIR/.trash Holding area for "Move to trash" deletions; point at a separate path/share to keep trashed files off the data volume
SCAN_ON_STARTUP true Scan the library on boot
SCAN_CONCURRENCY 4 Parallel hash/thumbnail workers
THUMB_SIZE 256 Thumbnail max dimension (px)
DEDUP_SIMILAR false Also run czkawka's perceptual near-duplicate image pass
CZKAWKA_IMAGE_PRESET High Similarity sensitivity (Minimal…VeryHigh) for the perceptual image pass
DEDUP_SIMILAR_VIDEO false Also run czkawka's perceptual near-duplicate video pass (requires ffmpeg)
CZKAWKA_VIDEO_TOLERANCE 10 Frame-difference tolerance (0…20, lower = stricter) for the perceptual video pass
CZKAWKA_BIN czkawka_cli Path to the dedup binary
EXIFTOOL_BIN exiftool Path to exiftool
FFMPEG_BIN ffmpeg Path to ffmpeg (video poster frames)
FFPROBE_BIN ffprobe Path to ffprobe (video dimensions/duration)

Notes

  • czkawka_cli flag/output formats vary across versions; the parser is tolerant of both duplicate and similar-image result files. The Docker image pins a version via the CZKAWKA_VERSION build arg.
  • Metadata edits are written in place with -overwrite_original. Keep backups of irreplaceable originals.

Related apps

Details

Repository
ghcr.io/daneng66/siftr:latest
Last Updated2026-10-08
First Seen2026-06-22

Runtime arguments

Web UI
http://[IP]:[PORT:8080]/
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUI PortPorttcp

Port the Siftr web interface is served on.

Target
8080
Default
8080
Value
8080
App DataPathrw

Working data: the SQLite database, generated thumbnails, and the .trash holding area for removed duplicates. Keep this on a fast share (e.g. cache/appdata).

Target
/data
Default
/mnt/user/appdata/siftr
Value
/mnt/user/appdata/siftr
Photo LibraryPathrw

Your photo library. Siftr reads, renames, organizes and writes EXIF here, so it must be read/write. Mapped inside /data so it can live on a large array share separate from App Data.

Target
/data/photos
Default
/mnt/user/Photos
Value
/mnt/user/Photos
Trash FolderPathrw

Holding area for duplicates removed via 'Move to trash' (reversible deletion). By default it lives inside App Data; map it to a separate share to keep trashed files off your appdata or to recover them elsewhere.

Target
/data/.trash
Default
/mnt/user/appdata/siftr/.trash
Value
/mnt/user/appdata/siftr/.trash
Enable Similar-Image DedupVariable

Also run czkawka's perceptual (near-duplicate) image pass in addition to exact hash matching. Slower. Set to true to enable.

Target
DEDUP_SIMILAR
Default
false
Value
false
Similar-Image PresetVariable

czkawka similarity sensitivity for the perceptual pass: Minimal, VeryLow, Low, Medium, High, or VeryHigh. Only used when similar-image dedup is enabled.

Target
CZKAWKA_IMAGE_PRESET
Default
High
Value
High
Enable Similar-Video DedupVariable

Also run czkawka's perceptual (near-duplicate) video pass, e.g. to catch the same clip re-encoded at a different bitrate/resolution. Slower. Set to true to enable.

Target
DEDUP_SIMILAR_VIDEO
Default
false
Value
false
Similar-Video ToleranceVariable

czkawka frame-difference tolerance for the perceptual video pass, 0 (strict) to 20 (loose). Only used when similar-video dedup is enabled.

Target
CZKAWKA_VIDEO_TOLERANCE
Default
10
Value
10
Scan On StartupVariable

Scan the photo library for new/changed files when the container starts. Set to false to only scan on demand.

Target
SCAN_ON_STARTUP
Default
true
Value
true
Scan ConcurrencyVariable

How many photos to hash and thumbnail in parallel during a scan. Raise on powerful CPUs, lower to reduce load.

Target
SCAN_CONCURRENCY
Default
4
Value
4
Thumbnail SizeVariable

Edge length in pixels of generated grid thumbnails.

Target
THUMB_SIZE
Default
256
Value
256