A fast image viewer for photographers and power users — built with Python and Qt6.
RabbitViewer doesn’t just display images. It orchestrates them.
Rendering, metadata extraction, hashing, and file watching run in-process on background worker threads — no IPC overhead. A companion daemon continues indexing when the GUI is closed. The interface stays fluid — even when you point it at a massive RAW archive.
RabbitViewer keeps heavy work off the UI thread:
- Worker threads handle thumbnail generation, EXIF extraction, database writes, hashing, and file watching — all in-process.
- The GUI is dedicated purely to interaction and presentation.
- A background daemon (
rabbit --daemon) auto-launches when the GUI exits to keep indexing. When the GUI starts, the daemon detects it viaflockand pauses, yielding resources.
The result is predictable latency, smooth scrolling, and immediate feedback — even during large recursive scans or RAW-heavy workloads.
You can scroll aggressively through thousands of files and the interface never stalls.
Decoding, hashing, and metadata extraction never block the UI thread. The viewer remains interactive under load — always.
Images appear as soon as they’re decoded.
A heatmap radiates from the mouse cursor. Thumbnails closest to your pointer load first, decreasing outward across a 10-ring Manhattan diamond. Nearby images receive speculative full-resolution pre-caching within a 4-ring zone, with cooperative cancellation when you move.
The viewer anticipates you.
Ratings are:
- Written to XMP sidecar files (non-destructive — originals are never modified)
- Stored locally in SQLite
- Filterable instantly
No proprietary lock-in. Your metadata stays with your files in an open format.
Shutter speed, aperture, ISO, focal length, lens, camera body — immediately visible without leaving the viewer.
Add, move, or delete files — the library updates automatically.
No manual refresh. No rescans required.
Scan entire directory trees, or stay flat. Your workflow, your choice.
- Range selection
- Select all
- Invert selection
- Undo / redo
Designed for high-volume culling sessions.
Integrated mpv playback supports modern video formats. Scrub the timeline directly from the inspector using mouse position.
Switch seamlessly between stills and motion.
A resizable left-side panel for navigating the filesystem without leaving the viewer (Tab to toggle).
- Lazy-loads subdirectories on expand — no full tree scan on open
- Tracks the current directory automatically as you navigate
- Type any character to open an inline search bar; matches are highlighted and the best result is auto-selected
Enteror double-click to navigate into a folderEscclears the search and returns focus to the tree
Assign free-form tags to any selection via the tag editor (T). Tags are written to XMP sidecars alongside ratings. Filter the grid by tag to narrow large sets instantly.
Filter the grid by any combination of metadata — name, rating, date, tags, duplicates, or visual similarity. All active filters compose: criteria intersect, so narrowing is always additive.
Open the filter menu with F:
- Name (
N) — filename pattern matching with multi-term support (sunset oceanmatchesBeautiful_Sunset_Over_Ocean.jpg) - Rating (
R) — show any combination of star levels, including unrated - Date (
A) — drag a range slider across the capture-date span of your library - Tags (
T) — filter to images carrying any of the specified tags - Duplicates (
D) — surface near-duplicate images by perceptual hash - Similarity (
S) — show images visually similar to a selected source, by CLIP embedding or perceptual hash - Clear all (
C) — reset all active filters in one keystroke
Filters run asynchronously. The grid updates while you drag sliders or type — never blocking the UI.
Search by natural language description using a local CLIP model — no cloud, no API keys.
Press / (or F → V) to open the search bar. Type a description:
sunset over ocean
red vintage car
portrait with soft bokeh
RabbitViewer encodes the query and scores every indexed image by cosine similarity. Adjust the threshold slider to tighten or loosen the match — slider changes filter cached results instantly with no re-inference.
The CLIP encoder runs entirely in-process via ONNX Runtime. Embeddings are indexed once and stored in the local SQLite database. Subsequent searches are fast, even across large libraries.
Bundled script scripts/sort_by_clip.py reorders the grid by visual similarity using a greedy nearest-neighbor chain — useful for grouping thematically related images without any text query.
Image preprocessing follows the OpenAI CLIP spec (ViT-B/32).
A floating info panel (I) shows structured metadata for the hovered or pinned image — EXIF, ratings, tags, and file details — in collapsible sections with configurable opacity.
Generate AI-edited variants of selected images using a local ComfyUI server (G). RabbitViewer dynamically builds form controls from any ComfyUI API workflow JSON. Ships with a built-in Flux Kontext workflow. Multi-image batches run in a background thread with cooperative cancellation.
Point RabbitViewer at your monitor's ICC profile and all images — thumbnails, full-resolution views, comparisons, inspector — are converted from sRGB to your calibrated color space at display time.
color_management:
icc_profile_path: /Library/ColorSync/Profiles/YourMonitor.iccNo cache rebuild required. Leave the path empty to disable.
Requires pip install ".[ocio]" (OpenColorIO + numpy).
Assign an OpenColorIO config and input color space to any selection of files or folders. Folder assignments inherit to all subfolders; a file-level assignment overrides its folder's setting.
Open the Add menu with C → OCIO color space. Pick a .ocio config file and select the input color space from the dropdown (populated from the config). Optionally override the display and view transforms.
Set a global default config path to pre-fill the dialog:
color_management:
ocio_config_path: /path/to/aces_1.3/config.ocioOCIO transforms are applied at display time — no thumbnails are rebuilt when you change an assignment.
- Smooth zoom
- Fluid panning
- Fast image switching
- Pixel-level inspector overlay (images and videos)
Zero friction between browsing and inspection.
Drop plain Python files into scripts/ and bind them to actions.
RabbitViewer exposes a clean API surface for automation. You can batch-edit ratings, reorganize selections, or implement custom workflows in minutes.
Extend format support by adding a file to plugins/.
Subclass BasePlugin and implement:
get_supported_formats()
process_thumbnail()
process_view_image()
generate_thumbnail()
generate_view_image()
Plugins are auto-discovered at startup.
RabbitViewer is designed to be extended — not forked.
- JPEG, PNG, BMP, GIF, TIFF, WebP
- PSD (Photoshop)
- JPEG 2000 (JP2)
- Canon CR2, CR3
- Nikon NEF / NRW
- Sony ARW / SR2 / SRF
- Fujifilm RAF
- Olympus ORF
- Panasonic RW2
- Pentax PEF
- Leica RWL
- Hasselblad 3FR / FFF
- Mamiya MEF / MOS
- Phase One IIQ / CAP / EIP
- Samsung SRW
- Adobe DNG
- OpenEXR (.exr) — via
openexr(pip install ".[exr]") - Radiance HDR (.hdr, .pic) — via numpy (no extra install needed)
Both formats use Reinhard tone-mapping for display.
- MP4, MOV, MKV, AVI, WebM, M4V
- WMV, FLV, MPG, MPEG, 3GP, TS
New formats can be added through plugins.
Scripts are plain Python files placed inside scripts/.
Each script must expose:
def run_script(api, selected_images):Available API methods:
get_selected_images()get_all_images()get_hovered_image()set_selected_images()add_images()remove_images()set_rating_for_images(paths, rating)
Bundled scripts include:
- set_rating_0–4
- select_all
- invert_selection
- delete_selected
- sort_by_name
- sort_by_clip (reorder grid by visual similarity using cached CLIP embeddings)
Automation is a first-class feature — not an afterthought.
- Python 3.10–3.13 (PySide6 does not yet support 3.14+)
- ExifTool (required for RAW support and writing ratings)
- ffmpeg / ffprobe (required for video thumbnails and metadata)
- mpv + libmpv (required for video playback)
macOS
brew install exiftool ffmpeg mpv
Debian / Ubuntu
sudo apt install libimage-exiftool-perl ffmpeg libmpv-dev
git clone https://github.com/Randalix/rabbitviewer.git
cd RabbitViewer
./install.sh
The install script:
- Creates a virtualenv using a compatible Python (3.10–3.13)
- Verifies dependencies
- Installs in editable mode
- Writes a
rabbitCLI wrapper into~/.local/bin/ - Sets up shell completion
- Installs a launcher entry
To update:
./install.sh
Clean reinstall:
./install.sh --clean
Optional extras:
venv/bin/pip install ".[exr]" # OpenEXR support
venv/bin/pip install ".[cr3]" # Canon CR3 support
venv/bin/pip install ".[video]" # video thumbnails
venv/bin/pip install ".[ocio]" # OCIO color space assignments
From any directory:
rabbit /path/to/photos
Thumbnails appear progressively as files are processed.
Options:
rabbit /path/to/photos --no-recursive
Logs:
~/.rabbitviewer/rabbitviewer.log
The rabbit command also exposes standalone utilities:
rabbit --help
rabbit move-selected /dst
rabbit send-stop-signal
New subcommands are added by dropping .py files into cli/.
They are auto-discovered at startup.
The GUI runs all heavy work in-process via worker threads and a ThumbnailService facade — no sockets or serialization overhead.
A background daemon (main.py --daemon) is auto-launched when the GUI exits to continue indexing. When the GUI starts again, it acquires an flock; the daemon detects this and pauses, yielding resources until the GUI exits. The daemon can also be run standalone to pre-populate the cache.
The RenderManager uses a heatmap-driven priority queue:
- 10-ring Manhattan diamond around the cursor
- Priorities from 90 (under cursor) to 40 (outer ring)
- 4-ring speculative full-resolution pre-cache zone
- Cooperative cancellation
- Delta-only viewport updates
- Generation counters drop stale updates during fast scroll
- SourceJob discovers file paths
- Task factory converts paths into render tasks
- RenderTask supports cooperative cancellation via
threading.Event
SQLite (WAL mode) stores:
- Thumbnails
- EXIF metadata
- Ratings
- Content hashes
The thumbnail and view-image cache (~/.rabbitviewer/) is capped by max_cache_size_mb (default 10 GB, 0 = unlimited). When the limit is reached, background scans pause and the least-recently-accessed entries are evicted. Direct GUI requests still work — they trigger eviction reactively.
Based on watchdog. Startup delay avoids race conditions during large initial scans.
- PySide6 (Qt6)
- SQLite
- watchdog
- Pillow
- ExifTool
- ffmpeg / mpv
- ComfyUI (optional, for AI image generation)
- ONNX Runtime + numpy (optional, for CLIP semantic search)
pytest tests/
RabbitViewer is built for speed, determinism, and extensibility.
No blocking UI. No opaque automation. No hidden state.
Just a fast, inspectable system that scales from a small shoot to a multi-terabyte archive.
