AI Generation Output Triage for ComfyUI
A fast, keyboard-driven photo triage tool. Open a folder, browse images in a scrollable strip, and sort them into selected or dust sub-folders — all without leaving the keyboard. Pairs naturally with ComfyUI and LM Studio for AI-assisted workflows.
- Keyboard-first navigation across a horizontal thumbnail strip
- Move photos to
__selected(keep) or__dust(reject) with a single keystroke - Multi-select with Space / Ctrl+A, then bulk-move the whole selection
- Undo the last move with Ctrl+Z (in-memory stack)
- Filter strip by filename in real time
- Favorites — mark photos with a heart; toggle the strip to show only favorites
- Full-resolution preview panel with mouse-wheel zoom and click-drag pan
- Three view modes: Fit, 100%, 360° (panoramic scroll)
- Drag-resizable dividers between the strip, info panel, and preview
- Lazy-loaded thumbnails (300 × 300 JPEG cache in
__thumbnails/, mtime-invalidated) - Pagination with configurable page size; jump ±10 pages with Shift+PageDown/Up
- File info: name, modified/created date, size, dimensions, format
- EXIF data extraction (date, GPS, ICC profile, PNG text chunks, ComfyUI workflow)
- ComfyUI workflow details: model, LoRAs, sampler settings, positive prompt, batch size
- Inline EXIF/metadata editor — edit title, artist, description, document name, copyright, user comment (requires ExifTool)
- Batch metadata editing across multiple selected files
- Write AI description to PNG text chunk or JPEG/WebP EXIF
UserComment
- LM Studio — send a free-form text prompt and get a response; copy or forward to ComfyUI
- Describe image — pass the current photo to a vision model (e.g. LLaVA); save description to file metadata
- Prompt Composer — build narrative prompts from randomized preset arrays (ambience, character, action, style)
- One-click "Run LM Studio" / "Run ComfyUI" launcher buttons when the service isn't reachable
- Send the current image's embedded workflow directly to ComfyUI with Generate
- Edit any workflow node values before submitting
- Cartesian-product batch generation: pick multiple LoRA / checkpoint variants, generates all combinations
- Live ComfyUI queue monitor widget (Running / Waiting / Done)
- Automatic GPU stats widget: CPU %, RAM %, GPU %, temperature, VRAM %
ComfyUI and LM Studio can't both hold VRAM, so every non-interactive operation (Send, Send to front, Improve then send, Guided generation, Outpaint, Upscale) is queued as an internal job and executed one at a time on the backend.
- Line up as many operations as you like — including ones that mix an LLM step with rendering — and they run in order
- Before each job: starts ComfyUI / LM Studio if needed, waits for ComfyUI's own queue to drain (or force-clears it), and unloads the other engine to free VRAM
- Jobs are UI-independent: close the dialog or the whole tab and the work continues
- Queue widget + manager dialog: live status, drag-to-reorder, cancel one / cancel all, pause
- Guided generation shows its iterations live and can be re-attached from the queue
- Background file-system watcher — strip auto-refreshes when files are added or removed
- Periodic index validation to catch external changes
- Automatic thumbnail cache cleanup (configurable retention days)
- Folder switcher: source,
__selected,__dust, and the ComfyUI output folder
- Define shell commands in
config.tomlthat run against the current file (%filename%placeholder) - Tools appear as buttons in the info panel
| Component | Version |
|---|---|
| Python | 3.11+ (3.10 with tomli) |
| Flask | ≥ 3.0 |
| Pillow | ≥ 10.0 |
| requests | ≥ 2.33 |
| psutil | ≥ 5.9 |
| nvidia-ml-py | ≥ 12.0 |
| websocket-client | ≥ 1.6 |
| watchdog | ≥ 4.0 |
| ExifTool | optional, for metadata editing |
git clone https://github.com/yourname/photo-parser.git
cd photo-parserpip install -r requirements.txtDownload ExifTool and set its path in config.toml under exiftool_path. Without it, metadata editing is disabled.
The static/ directory is pre-built and committed. Rebuild only if you modify the Angular source:
cd frontend
npx ng buildpython app.py <source_folder>The browser opens automatically at http://localhost:1976.
If comfy_output is set in config.toml, calling python app.py with no argument opens that folder directly.
run.bat <source_folder>Run the Flask backend and Angular dev server in two terminals:
# Terminal 1
python app.py <source_folder>
# Terminal 2
cd frontend
npx ng serve # proxies /api → localhost:1976App is then at http://localhost:4200.
Create config.toml next to app.py. All sections and keys are optional.
[permissions]
# Allow changing source folder from the UI
allow_dir_change = true
[defaults]
port = 1976
selected_dir_name = "__selected"
dust_dir_name = "__dust"
# ComfyUI
comfy_url = "http://127.0.0.1:8188"
comfy_output = "C:/path/to/ComfyUI/output"
run_comfy_command = "C:/path/to/run_nvidia_gpu.bat"
# LM Studio
lmstudio_url = "http://localhost:1234/v1"
run_lmstudio_command = "C:/path/to/LM Studio.exe"
# ExifTool
exiftool_path = "exiftool" # or full path
# Thumbnail cache: delete thumbnails older than N days
thumb_cache_days = 3
# Seconds between background index re-validation (null = disabled)
index_validation_interval = 600
[parameters]
extract_exif = true # show EXIF fields in info panel
extract_gps = false # show GPS coordinates
extract_icc = false # show ICC colour profile info
extract_png = true # show PNG text chunks
[widgets]
gpu_monitor = true # top-right GPU/CPU stats widget
comfy_queue = true # top-right ComfyUI queue widget
# Custom tools — %filename% is replaced with the full file path
[tools]
"Split to Four" = "python C:/tools/split_tiles.py %filename%"
"Upscale" = "python C:/tools/upscale.py %filename%"| Key | Default | Description |
|---|---|---|
defaults.port |
1976 |
HTTP port |
defaults.selected_dir_name |
__selected |
Sub-folder for kept photos |
defaults.dust_dir_name |
__dust |
Sub-folder for rejected photos |
defaults.comfy_url |
http://127.0.0.1:8188 |
ComfyUI API base URL |
defaults.comfy_output |
(empty) | ComfyUI output folder; also used as default source if no CLI arg |
defaults.run_comfy_command |
(empty) | Shell command to start ComfyUI |
defaults.lmstudio_url |
http://localhost:1234/v1 |
LM Studio API base URL |
defaults.run_lmstudio_command |
(empty) | Shell command to start LM Studio |
defaults.exiftool_path |
exiftool |
Path to ExifTool binary |
defaults.thumb_cache_days |
3 |
Days before stale thumbnails are purged |
defaults.index_validation_interval |
(disabled) | Seconds between full index re-checks |
parameters.extract_exif |
true |
Include EXIF fields in the info panel |
parameters.extract_gps |
false |
Include GPS coordinates |
parameters.extract_icc |
false |
Include ICC colour profile |
parameters.extract_png |
true |
Include PNG text chunks |
permissions.allow_dir_change |
true |
Show folder-switch button in UI |
widgets.gpu_monitor |
false |
GPU/CPU stats overlay |
widgets.comfy_queue |
false |
ComfyUI queue overlay |
widgets.lmstudio |
false |
LM Studio activity overlay (loaded model, load/prompt progress) |
widgets.jobs |
true |
Internal job-queue overlay |
queue.auto_start_services |
true |
Launch ComfyUI / LM Studio on demand when a job needs them |
queue.force_clear_comfy |
false |
Clear ComfyUI's own queue instead of waiting for it to drain |
| Key | Action |
|---|---|
→ |
Next photo |
← |
Previous photo |
↑ |
Move up one strip row |
↓ |
Move down one strip row |
Home |
First photo |
End |
Last photo |
Page Down |
Next page |
Page Up |
Previous page |
Shift + Page Down |
Skip forward 10 pages |
Shift + Page Up |
Skip backward 10 pages |
| Key | Action |
|---|---|
+ |
Move current photo to __selected |
Delete |
Move current photo to __dust |
Space |
Toggle selection on current photo |
Ctrl + A |
Select all visible photos |
Ctrl + Z |
Undo last move |
| Key | Action |
|---|---|
= |
Switch source folder (opens folder picker) |
Ctrl + S |
Download current photo |
Keyboard shortcuts are suppressed when a dialog or text input is focused.
.png · .jpg · .jpeg · .webp
photo-parser/
├── app.py # Entry point: CLI parsing, config loading, server start
├── config.toml # Local configuration (not committed)
├── requirements.txt
├── server/ # Flask application package
│ ├── factory.py # create_app() and all route definitions
│ ├── state.py # AppState dataclass
│ ├── utils.py # Image utilities, EXIF extraction, thumbnail cache
│ ├── exiftool.py # ExifTool integration
│ ├── background.py # GPU monitor, ComfyUI WebSocket, queue polling
│ ├── events.py # Server-Sent Events broadcast
│ └── watcher.py # File-system watcher and index builder
├── frontend/ # Angular 20 source
│ └── src/app/
│ ├── app.ts # Root component: layout, keyboard, SSE routing
│ ├── components/ # ImageStrip, InfoPanel, PreviewPanel, all dialogs
│ ├── services/ # PhotoService, KeyboardService, ConnectionStateService, …
│ └── models/ # TypeScript interfaces
├── static/ # Built Angular output (served by Flask)
├── release/ # Windows batch launchers
├── describe.py # CLI: describe an image with LM Studio
├── gen.py # CLI: batch ComfyUI generation from PNG workflows
├── run.py # CLI: re-submit PNG workflows with random seeds
└── prompt.py # CLI: print a single randomised prompt
| Script | Usage | Description |
|---|---|---|
describe.py |
python describe.py <image> [prompt] [model] |
Describe an image via LM Studio vision API |
gen.py |
python gen.py <folder> |
Batch ComfyUI generation — reads PNG workflows, randomises prompts and seeds |
run.py |
python run.py <folder> |
Re-submits PNG workflows with fresh random seeds |
prompt.py |
python prompt.py |
Prints a single random prompt from preset arrays |
MIT
