🍊 AI photo upscaler & deblur for Windows. Two engines — Real‑ESRGAN (any GPU, always works) and SeedVR2 7B/3B (NVIDIA, pro quality). Batch queue, before/after wipe preview, portable build. 繁體中文 / English.
Python
4
1 commits
updated Jul 22, 2026
Standalone Windows desktop app that enlarges and deblurs/sharpens photos with AI upscaling. No ComfyUI, no Python setup, no cloud. Black + orange (citrine) themed PySide6 UI, in Traditional Chinese (default) and English.
| Engine | Tech | Hardware | Status |
|---|---|---|---|
| 快速 (Fast) | Real-ESRGAN ncnn-vulkan (+ optional GFPGAN face restore) | Any Vulkan GPU / CPU | ✅ Working |
| 高品質 (Pro) | SeedVR2 7B / 3B (GGUF) via PyTorch cu128 | NVIDIA 20xx–50xx | ✅ Working (real inference) |
photo_realesrgan.png, photo_realesrgan_face.png, photo_seedvr2.png; a collision appends _1, _2, …python -m pip install -r requirements.txt
python run.py # or double-click run.bat
The app runs immediately. Without the realesrgan-ncnn-vulkan binary present,
the Fast engine falls back to a high-quality Lanczos + unsharp pass so you can
exercise the full flow; drop in the binary (see below) for true Real-ESRGAN
output. The Pro engine is installed on demand from Settings (see below).
Results go to data/output/ (changeable in Settings). Each file is named after
the source with an engine tag, and existing files are never overwritten:
| Run | Output name |
|---|---|
| Fast | photo_realesrgan.png |
| Fast + Face Restore | photo_realesrgan_face.png |
| Pro | photo_seedvr2.png |
| Re-run / collision | photo_realesrgan_1.png, _2, … |
Format (PNG / JPEG q80–100) and EXIF preservation are toggles in the Options
panel. EXIF (orientation, capture date, …) is re-attached to the output and
stamped Software = Citrine Photo.
run.py / run.bat # dev launcher
build_portable.py / .bat # build the portable dist + zip
make_icons.py # generate icon.ico + PNG pack from icon.png
citrine.spec # PyInstaller one-file (small exe, torch excluded)
citrine_portable.spec # PyInstaller one-dir (portable package)
icon.png # master app icon (rounded corners, transparent)
icons/ # generated PNG icon pack (16 … 1024)
citrine/
├── app.py # entry: single-instance, theme, icon, GPU detect, VRAM model auto-select
├── config.py # data/config.json load/save
├── i18n.py # zh-Hant (default) + en locale dicts
├── theme.py # black/orange palette + QSS
├── gpu.py # nvidia-smi compatibility gate
├── download.py # resumable HTTP + SHA256 (installer / first-launch)
├── installer.py # SeedVR2 install/uninstall flow (QThread, 6 steps)
├── paths.py # runtime path + bundled-resource resolution
├── engines/
│ ├── protocol.py # JSON-lines stdin/stdout protocol
│ ├── pipeline.py # MP-scale, 3×3/15% tiling, feathered reassembly, EXIF
│ ├── ncnn_engine.py # Real-ESRGAN subprocess wrapper (+ Lanczos fallback)
│ ├── seedvr2_engine.py # GUI-side launcher for the embedded-Python engine
│ └── worker.py # QThread bridge → progress/done/error signals
├── gui/
│ ├── main_window.py # main window, run orchestration, batch driver, regenerate
│ ├── drop_zone.py # drag-drop files/folders
│ ├── preview.py # before/after split-wipe slider
│ ├── batch_queue.py # per-item status list
│ ├── settings_dialog.py # language, folders, engine mgmt, log viewer
│ ├── install_wizard.py # SeedVR2 install wizard (progress, disk check, log)
│ └── widgets.py # Card, SegmentedControl, PrimaryButton, …
└── resources/
├── manifest.json # pinned download URLs / SHA256 / vendor pin
├── icon.png / icon.ico # runtime + exe icon
└── seedvr2/ # Pro-engine code, extracted into the embedded env
├── engine.py # JSON-protocol entry + tiling pipeline
├── seedvr2_infer.py# Runner + vendor-binding resolver
├── gguf_loader.py # GGUF → torch state_dict
├── color_fix.py # wavelet / AdaIN colour correction
├── fetch_vendor.py # pull the pinned numz inference code
└── vendor/ # vendored SeedVR2 CLI (fetched, gitignored)
Download realesrgan-ncnn-vulkan (URL in citrine/resources/manifest.json) and
place it at data/engines/realesrgan/realesrgan-ncnn-vulkan.exe with its
models/ folder alongside. The Fast engine picks it up automatically. (The
portable build bundles this for you.)
Settings → 引擎管理 → 安裝 SeedVR2 launches the install wizard (install_wizard.py driven by installer.py). It shows a model picker (7B/3B, defaulting to the GPU-recommended size), a required-vs-available disk-space check, live speed/ETA, and a streaming log, then runs 6 steps:
get-pip.py → enable
Lib\site-packages in python3xx._pthpip install torch --index-url .../cu128 + safetensors/einops/gguf/…engine.py + adapters) from bundled resourcesseedvr2.installed is set only after it passesEverything supports cancel; interrupted downloads resume. Uninstall (same button
once installed) deletes engines/seedvr2/ and reports freed space.
Total download is ~8–9 GB; the app warns and shows required vs available space
before starting. Model weights come from Hugging Face
(AInVFX/SeedVR2_comfyUI for the GGUFs, numz/SeedVR2_comfyUI for the VAE).
The Pro engine's model code lives under citrine/resources/seedvr2/ and runs in
the separate embedded Python (never in the GUI exe). Rather than importing
numz's ComfyUI-coupled internals, the binding drives its standalone
inference_cli.py as a subprocess over a directory of tiles (one model load
per image). The image pipeline is a native port of the ComfyUI workflow
Upscale_by_SeedVR2_v4:
target-MP scale → Lanczos pre-scale → 3×3 grid with 15% overlap → per-tile 0.25× downscale → SeedVR2 4× restore → wavelet colour-match → feathered 64px reassembly → EXIF re-attach.
| File | Role |
|---|---|
engine.py | JSON-protocol entry, tiling, SeedVR2Model → Runner |
seedvr2_infer.py | batched Runner + VendorBinding resolver (numz-CLI → bytedance → fallback) |
gguf_loader.py | DiT weights: GGUF → dequantized torch state_dict |
color_fix.py | wavelet / AdaIN colour correction (spec default: wavelet) |
fetch_vendor.py | pull the pinned upstream inference code into vendor/ |
Status: verified working — real 7B and 3B Q4_K_M inference runs the full
VAE→DiT→VAE→post pipeline on an RTX A4000. Until the vendored code is present the
Runner resolves to an honest on-GPU FallbackBinding (bicubic 4× + unsharp,
logs backend=fallback) so the app degrades gracefully instead of failing.
Low-VRAM mode passes --blocks_to_swap/--dit_offload_device for block-swap
offload. The optional last step is the spec §6.3 parity spike (compare a 100%
crop against the original ComfyUI workflow).
icon.png (repo root) is the master. Regenerate the pack any time:
python make_icons.py
This writes citrine/resources/icon.ico (multi-size, 16→256), the 256px runtime
icon.png, and icons/icon_<N>.png (16…1024). The exe embeds icon.ico; the
running app sets it as the window/taskbar icon (with a Windows AppUserModelID so
dev runs show it too). build_portable.py regenerates it automatically before
building.
python -m pip install pyinstaller
pyinstaller citrine.spec
torch is explicitly excluded — the exe stays small (target ≤ 70 MB); the Pro engine lives in its own on-demand embedded Python.
python build_portable.py # -> dist/CitrinePhoto/ (~11 GB)
python build_portable.py --zip # also -> dist/CitrinePhoto-portable.zip
# or: build_portable.bat [--no-zip | --only-zip | --skip-engines | --skip-exe]
Produces a self-contained folder: CitrinePhoto.exe + _internal/ +
data/engines/{realesrgan,seedvr2} with the embedded Python, torch cu128, both
GGUF models (7B + 3B), the VAE, and the vendored inference code. Unzip anywhere
and run — no Python, no CUDA toolkit, no install. The cu128 wheels carry the
CUDA runtime; the target machine needs only an NVIDIA driver (≥ ~570).
Notes:
C:\Program Files. The app uses data/ beside the exe only when that folder
is writable; otherwise it falls back to %LOCALAPPDATA%\CitrinePhoto, where
the bundled engines wouldn't be found and the Pro engine would show as not
installed.%TEMP% on every launch (and torch is deliberately never bundled in the exe).--skip-engines reuses the already-copied 11 GB engines for a fast rebuild;
--only-zip re-zips without re-copying.app._auto_select_model() picks 7B on ≥12 GB VRAM and 3B on
6–10 GB at startup, until the user pins one in the Options panel.config.json with no absolute paths and
seedvr2.installed = true, and prunes torch dev files (test/, include/,
*.lib).PYTHONUTF8/
PYTHONIOENCODING + UTF-8 decode) so emoji in vendored logs and non-ASCII
(e.g. Chinese) filenames never trigger a cp1252 UnicodeEncodeError.deleteLater, so advancing
between images can't garbage-collect a still-running thread.Python
98.6%
Batchfile
1.4%
🍊 AI photo upscaler & deblur for Windows. Two engines — Real‑ESRGAN (any GPU, always works) and SeedVR2 7B/3B (NVIDIA, pro quality). Batch queue, before/after wipe preview, portable build. 繁體中文 / English.
Python
4
1 commits
updated Jul 22, 2026
Standalone Windows desktop app that enlarges and deblurs/sharpens photos with AI upscaling. No ComfyUI, no Python setup, no cloud. Black + orange (citrine) themed PySide6 UI, in Traditional Chinese (default) and English.
| Engine | Tech | Hardware | Status |
|---|---|---|---|
| 快速 (Fast) | Real-ESRGAN ncnn-vulkan (+ optional GFPGAN face restore) | Any Vulkan GPU / CPU | ✅ Working |
| 高品質 (Pro) | SeedVR2 7B / 3B (GGUF) via PyTorch cu128 | NVIDIA 20xx–50xx | ✅ Working (real inference) |
photo_realesrgan.png, photo_realesrgan_face.png, photo_seedvr2.png; a collision appends _1, _2, …python -m pip install -r requirements.txt
python run.py # or double-click run.bat
The app runs immediately. Without the realesrgan-ncnn-vulkan binary present,
the Fast engine falls back to a high-quality Lanczos + unsharp pass so you can
exercise the full flow; drop in the binary (see below) for true Real-ESRGAN
output. The Pro engine is installed on demand from Settings (see below).
Results go to data/output/ (changeable in Settings). Each file is named after
the source with an engine tag, and existing files are never overwritten:
| Run | Output name |
|---|---|
| Fast | photo_realesrgan.png |
| Fast + Face Restore | photo_realesrgan_face.png |
| Pro | photo_seedvr2.png |
| Re-run / collision | photo_realesrgan_1.png, _2, … |
Format (PNG / JPEG q80–100) and EXIF preservation are toggles in the Options
panel. EXIF (orientation, capture date, …) is re-attached to the output and
stamped Software = Citrine Photo.
run.py / run.bat # dev launcher
build_portable.py / .bat # build the portable dist + zip
make_icons.py # generate icon.ico + PNG pack from icon.png
citrine.spec # PyInstaller one-file (small exe, torch excluded)
citrine_portable.spec # PyInstaller one-dir (portable package)
icon.png # master app icon (rounded corners, transparent)
icons/ # generated PNG icon pack (16 … 1024)
citrine/
├── app.py # entry: single-instance, theme, icon, GPU detect, VRAM model auto-select
├── config.py # data/config.json load/save
├── i18n.py # zh-Hant (default) + en locale dicts
├── theme.py # black/orange palette + QSS
├── gpu.py # nvidia-smi compatibility gate
├── download.py # resumable HTTP + SHA256 (installer / first-launch)
├── installer.py # SeedVR2 install/uninstall flow (QThread, 6 steps)
├── paths.py # runtime path + bundled-resource resolution
├── engines/
│ ├── protocol.py # JSON-lines stdin/stdout protocol
│ ├── pipeline.py # MP-scale, 3×3/15% tiling, feathered reassembly, EXIF
│ ├── ncnn_engine.py # Real-ESRGAN subprocess wrapper (+ Lanczos fallback)
│ ├── seedvr2_engine.py # GUI-side launcher for the embedded-Python engine
│ └── worker.py # QThread bridge → progress/done/error signals
├── gui/
│ ├── main_window.py # main window, run orchestration, batch driver, regenerate
│ ├── drop_zone.py # drag-drop files/folders
│ ├── preview.py # before/after split-wipe slider
│ ├── batch_queue.py # per-item status list
│ ├── settings_dialog.py # language, folders, engine mgmt, log viewer
│ ├── install_wizard.py # SeedVR2 install wizard (progress, disk check, log)
│ └── widgets.py # Card, SegmentedControl, PrimaryButton, …
└── resources/
├── manifest.json # pinned download URLs / SHA256 / vendor pin
├── icon.png / icon.ico # runtime + exe icon
└── seedvr2/ # Pro-engine code, extracted into the embedded env
├── engine.py # JSON-protocol entry + tiling pipeline
├── seedvr2_infer.py# Runner + vendor-binding resolver
├── gguf_loader.py # GGUF → torch state_dict
├── color_fix.py # wavelet / AdaIN colour correction
├── fetch_vendor.py # pull the pinned numz inference code
└── vendor/ # vendored SeedVR2 CLI (fetched, gitignored)
Download realesrgan-ncnn-vulkan (URL in citrine/resources/manifest.json) and
place it at data/engines/realesrgan/realesrgan-ncnn-vulkan.exe with its
models/ folder alongside. The Fast engine picks it up automatically. (The
portable build bundles this for you.)
Settings → 引擎管理 → 安裝 SeedVR2 launches the install wizard (install_wizard.py driven by installer.py). It shows a model picker (7B/3B, defaulting to the GPU-recommended size), a required-vs-available disk-space check, live speed/ETA, and a streaming log, then runs 6 steps:
get-pip.py → enable
Lib\site-packages in python3xx._pthpip install torch --index-url .../cu128 + safetensors/einops/gguf/…engine.py + adapters) from bundled resourcesseedvr2.installed is set only after it passesEverything supports cancel; interrupted downloads resume. Uninstall (same button
once installed) deletes engines/seedvr2/ and reports freed space.
Total download is ~8–9 GB; the app warns and shows required vs available space
before starting. Model weights come from Hugging Face
(AInVFX/SeedVR2_comfyUI for the GGUFs, numz/SeedVR2_comfyUI for the VAE).
The Pro engine's model code lives under citrine/resources/seedvr2/ and runs in
the separate embedded Python (never in the GUI exe). Rather than importing
numz's ComfyUI-coupled internals, the binding drives its standalone
inference_cli.py as a subprocess over a directory of tiles (one model load
per image). The image pipeline is a native port of the ComfyUI workflow
Upscale_by_SeedVR2_v4:
target-MP scale → Lanczos pre-scale → 3×3 grid with 15% overlap → per-tile 0.25× downscale → SeedVR2 4× restore → wavelet colour-match → feathered 64px reassembly → EXIF re-attach.
| File | Role |
|---|---|
engine.py | JSON-protocol entry, tiling, SeedVR2Model → Runner |
seedvr2_infer.py | batched Runner + VendorBinding resolver (numz-CLI → bytedance → fallback) |
gguf_loader.py | DiT weights: GGUF → dequantized torch state_dict |
color_fix.py | wavelet / AdaIN colour correction (spec default: wavelet) |
fetch_vendor.py | pull the pinned upstream inference code into vendor/ |
Status: verified working — real 7B and 3B Q4_K_M inference runs the full
VAE→DiT→VAE→post pipeline on an RTX A4000. Until the vendored code is present the
Runner resolves to an honest on-GPU FallbackBinding (bicubic 4× + unsharp,
logs backend=fallback) so the app degrades gracefully instead of failing.
Low-VRAM mode passes --blocks_to_swap/--dit_offload_device for block-swap
offload. The optional last step is the spec §6.3 parity spike (compare a 100%
crop against the original ComfyUI workflow).
icon.png (repo root) is the master. Regenerate the pack any time:
python make_icons.py
This writes citrine/resources/icon.ico (multi-size, 16→256), the 256px runtime
icon.png, and icons/icon_<N>.png (16…1024). The exe embeds icon.ico; the
running app sets it as the window/taskbar icon (with a Windows AppUserModelID so
dev runs show it too). build_portable.py regenerates it automatically before
building.
python -m pip install pyinstaller
pyinstaller citrine.spec
torch is explicitly excluded — the exe stays small (target ≤ 70 MB); the Pro engine lives in its own on-demand embedded Python.
python build_portable.py # -> dist/CitrinePhoto/ (~11 GB)
python build_portable.py --zip # also -> dist/CitrinePhoto-portable.zip
# or: build_portable.bat [--no-zip | --only-zip | --skip-engines | --skip-exe]
Produces a self-contained folder: CitrinePhoto.exe + _internal/ +
data/engines/{realesrgan,seedvr2} with the embedded Python, torch cu128, both
GGUF models (7B + 3B), the VAE, and the vendored inference code. Unzip anywhere
and run — no Python, no CUDA toolkit, no install. The cu128 wheels carry the
CUDA runtime; the target machine needs only an NVIDIA driver (≥ ~570).
Notes:
C:\Program Files. The app uses data/ beside the exe only when that folder
is writable; otherwise it falls back to %LOCALAPPDATA%\CitrinePhoto, where
the bundled engines wouldn't be found and the Pro engine would show as not
installed.%TEMP% on every launch (and torch is deliberately never bundled in the exe).--skip-engines reuses the already-copied 11 GB engines for a fast rebuild;
--only-zip re-zips without re-copying.app._auto_select_model() picks 7B on ≥12 GB VRAM and 3B on
6–10 GB at startup, until the user pins one in the Options panel.config.json with no absolute paths and
seedvr2.installed = true, and prunes torch dev files (test/, include/,
*.lib).PYTHONUTF8/
PYTHONIOENCODING + UTF-8 decode) so emoji in vendored logs and non-ASCII
(e.g. Chinese) filenames never trigger a cp1252 UnicodeEncodeError.deleteLater, so advancing
between images can't garbage-collect a still-running thread.Python
98.6%
Batchfile
1.4%