An image management power tool. Scan directories of images, build a metadata database, detect duplicates, recognize faces, and organize files into a clean folder structure.
- Scan — recursively scan directories, computing SHA256 + perceptual hashes, extracting EXIF data, and storing everything in SQLite
- Duplicates — find exact copies (same SHA256) and visually similar images (perceptual hash)
- Copy/Move — copy or move images into a structured layout (e.g.,
YYYY/MM/) with configurable format strings, conflict resolution, and dry-run mode - Search — query the database by filename, date, EXIF data, file size, visual similarity, or face matching
- Face Recognition — detect faces, compute embeddings, name people, and search by person
- Purge — remove entries from the database by glob pattern
- Match/filter — repeatable
--matchand--filterglobs across all commands, matching against full paths, with filter taking precedence
Requires Nix with devenv and optionally direnv.
cd phig
direnv allow # auto-activates the dev environment (one-time)
cmake -B build
cmake --build buildThe binary is at ./build/phig. It links against Nix store libraries and is intended for local development use.
phig builds against stock system libraries — OpenCV (core modules only; no
contrib needed), SQLite3, libexif, libcurl, and FFmpeg. Install the dependencies
with your package manager:
# Debian / Ubuntu
sudo apt install build-essential cmake pkg-config libopencv-dev libsqlite3-dev \
libexif-dev libcurl4-openssl-dev libavformat-dev libavcodec-dev \
libavutil-dev libswscale-dev libgtest-dev
# Fedora
sudo dnf install gcc-c++ cmake make pkgconf-pkg-config opencv-devel sqlite-devel \
libexif-devel libcurl-devel ffmpeg-free-devel gtest-devel
# Arch Linux
sudo pacman -S base-devel cmake opencv sqlite libexif curl ffmpeg gtest
# macOS (Homebrew)
brew install cmake pkg-config opencv sqlite libexif ffmpeg googletestThen build and install:
cmake -B build
cmake --build build
sudo cmake --install build # installs to /usr/local/bin/phig
# or
cmake --install build --prefix ~/.local # installs to ~/.local/bin/phigNotes:
- OpenCV ≥ 4.5 for core features; face recognition (the YuNet/SFace models)
needs ≥ 4.7. Only core modules are used — no
opencv_contrib. - FFmpeg on Fedora: the official repos ship
ffmpeg-free-devel, which is sufficient. The fully-featuredffmpeg-devellives in the third-party RPM Fusion repo; you only need it ifffmpeg-free-develis missing a codec you require. - Check your build with
phig --version.
phig follows the XDG base directory convention on all platforms (honoring
XDG_CONFIG_HOME/XDG_DATA_HOME/XDG_CACHE_HOME when set):
| What | Default location |
|---|---|
| Database | ~/.local/share/phig/phig.db |
| Face-recognition models | ~/.cache/phig/models/ |
| Thumbnails | ~/.cache/phig/thumbs/ |
| Config file | ~/.config/phig/config.ini |
The database is designed to be the single source of truth for your image collection — reusable by other tools, a future API, or a GUI.
If ~/.config/phig/config.ini exists it is loaded automatically; point at a
different file with the global --config <path> flag. Values set defaults;
an explicit CLI flag always overrides the config, which overrides the built-in
default. Leading ~ in path values is expanded.
[paths]
db = ~/photos/phig.db
models_dir = ~/.cache/phig/models
thumbs_dir = ~/.cache/phig/thumbs
[general]
parallel = 4
[scan]
recursive = true
on_error = warn # warn | fail
media = all # image | video | all
thumbs = false
faces = false
[organize]
format = %Y/%m/%original
[duplicates]
type = all # exact | near | all
threshold = 5 # 0-64
[faces]
threshold = 0.6Destructive or per-invocation options are deliberately not configurable and
must be given on the command line: --force, --dry-run, --on-conflict,
--ignore-mount-warning, and the selection/query flags (--match, --filter,
etc.). Unknown keys produce a warning; invalid values are a hard error.
phig --help prints an "Active configuration" summary showing the resolved
paths and any values your config overrides.
Scan directories to build the image database:
phig scan ~/Photos --recursive
phig scan ~/Photos --recursive --force # re-hash everything
phig scan ~/Photos --recursive --faces # also detect faces
phig scan ~/Photos --faces --parallel 2 # control parallelismphig duplicates # all duplicates
phig duplicates --type exact # exact copies only
phig duplicates --type near --threshold 3 # very similar images
phig duplicates --match "*/Photos/*" # only in Photos directory
phig duplicates --format json --output dupes.json# By metadata
phig search --ext jpg
phig search --after 2023-01-01 --before 2024-01-01
phig search --camera "iPhone"
phig search --min-size 5MB
phig search --match "*/Vacation/*"
# Visual similarity (provide a reference image)
phig search --similar reference.jpg
phig search --similar reference.jpg --threshold 3
cat photo.jpg | phig search --similar - # from stdin
# Count results
phig search --ext jpg --count
# Machine-readable output
phig search --porcelain # tab-separated
phig search --print0 # null-separated (for xargs -0)
phig search --format json
phig search --format csv --output results.csv
# View results in an image viewer
phig search --match "*/Vacation/*" --print0 | xargs -0 eogDownload face detection models (one-time):
phig models downloadScan with face detection:
phig scan ~/Photos --recursive --facesName people and search:
# Name a person using a clear headshot (single face)
phig face name headshot.jpg "Brett"
# Auto-label all matching faces in the database
phig face identify "Brett"
# Search for images of a person
phig search --person "Brett"
phig search --person "Brett" --print0 | xargs -0 eog
# Search by face (provide any photo of the person)
phig search --face photo.jpg
cat photo.jpg | phig search --face -
# List known people
phig face list
# Re-detect faces (e.g., after model update)
phig face rebuild
phig face rebuild --match "*/2024/*"New scans with --faces automatically label detected faces that match known people.
Copy or move images into a structured folder layout:
# Preview what would happen
phig cp ~/Photos/Organized --dry-run
phig cp ~/Photos/Organized --format "%Y/%m/%original"
phig cp ~/Photos/Organized --format "%Y/%m/%d/%original"
phig cp ~/Photos/Organized --format "%Y/%m/%camera/%original"
# Move with filters
phig mv ~/Photos/Organized --match "*.jpg" --filter "*_copy*"
# Handle conflicts
phig cp ~/Photos/Organized --on-conflict rename # auto-rename duplicates
phig cp ~/Photos/Organized --on-conflict overwriteFormat tokens: %Y (year), %m (month), %d (day), %camera (EXIF model), %make (EXIF make), %original (original filename).
phig purge --match "*/old-folder/*" --dry-run # preview
phig purge --match "*.tmp" # remove by pattern
phig purge --match "*/Trash/*" # remove by directoryAll commands support --match and --filter for glob-based path filtering:
# Match includes files (repeatable)
phig search --match "*/Vacation/*" --match "*/Beach/*"
# Filter excludes files (repeatable, wins over match)
phig search --match "*.jpg" --filter "*_thumb*"
# Tilde expansion works
phig search --match "~/Photos/*"
# Globs match against the full path
phig duplicates --match "*/2024/*"
phig purge --match "*/old-backup/*"cmake --build build && ./build/phig-tests