- Expose birefnet-hr-matting in UI (People/Ultra) and fix model defaults
(People/Max now uses birefnet-matting for true alpha matting)
- Add output format selector (PNG/WebP/AVIF) with lossless alpha support
- Add edge smoothing post-processing (Off/Light/Medium/Strong) via
morphological mask refinement to reduce gray halo artifacts
- Add color decontamination to remove background color spill from
semi-transparent edge pixels
- Thread new settings through full stack: frontend -> API schema ->
Python sidecar -> Sharp effects pipeline
- Add i18n keys for all 21 locales
- Add unit tests for new option serialization (3 tests)
- Add integration tests for new settings validation (4 tests)
- Filter known client-error noise (rate limit, empty body, unsupported
media type, content-length mismatch, premature close) from Sentry
via beforeSend to stop 644 events of non-actionable noise
- Sanitize x-output-filename header to prevent TypeError on non-ASCII
filenames in optimize-for-web preview (23 events)
- Handle EPIPE on Python dispatcher stdin write with graceful fallback
to per-request spawning instead of crashing (NODE-W)
- Map EACCES on storage directory/file write to proper 503 status
instead of generic 500 (NODE-P, 3 events)
The GPU detection in gpu.py had two issues preventing GPU usage in
containers (especially rootless podman with CDI):
1. When torch was installed but torch.cuda.is_available() returned
False, the function returned immediately without trying the
ONNX Runtime + nvidia-smi fallback. This meant a CPU-only torch
build (installed before GPU was available) would block all GPU
detection, even for ONNX-based tools.
2. The failure logged a generic "torch loaded but CUDA not available"
with no diagnostic information, making it impossible to debug
whether the issue was a CPU-only build, missing libraries, or
device permissions.
The fix restructures gpu_available() into three detection tiers
(torch -> ONNX Runtime -> nvidia-smi) that always fall through on
failure. When torch CUDA fails, it now checks torch.version.cuda to
distinguish CPU-only builds from CUDA builds that can't access the
GPU, and logs LD_LIBRARY_PATH, torch.cuda.init() errors, and
nvidia-smi results.
Also fixes two env var passthrough bugs in buildMinimalEnv():
- SNAPOTTER_GPU was never passed to the Python subprocess, so the
user-facing GPU override env var had no effect
- MODELS_DIR was a dead entry (never set as env var); replaced with
MODELS_PATH which the Dockerfile sets and Python scripts read
Closes#134
Auth: login rate limit 30/min (was 500), global rate limit 1000/min (was
unlimited), password/username max lengths on all Zod schemas, session
invalidation on role change, API key legacy scan bounded to 100 keys.
SVG: hardened regex sanitizer with CDATA stripping, XML entity decoding,
set/animate/iframe/embed blocking, comprehensive data: URI blocking,
use element external href blocking. 11 attack payload fixtures added.
SSRF: fixed DNS rebinding TOCTOU by pinning resolved IPs via custom
HTTP/HTTPS agents. Added 6to4 and NAT64 to blocked IPv6 ranges.
Docker: capability dropping (cap_drop ALL + minimal cap_add), resource
limits (4g/8g mem, 512/1024 pids), healthcheck timeout, password
removed from startup banner, default password warning comments.
Network: CSP and HSTS applied in all environments (not just production),
stack traces removed from all error responses, internal paths stripped
from error details, per-route rate limits on uploads (60/min) and URL
fetches (200/hour).
Files: exclusive temp file creation (O_EXCL), disk space circuit
breaker, per-user storage quotas, settings payload 64KB size guard.
Python sidecar: script name allowlist in dispatcher, minimal environment
for subprocess spawns.
Dependencies: fixed 6 production CVEs (drizzle-orm, fastify, fast-uri,
@fastify/static, next, archiver/lodash). Pinned all GitHub Actions to
SHA hashes.
114 security tests added. Full OWASP Top 10 penetration test matrix
verified against production Docker container (30/30 pass after
hardening).
AVIF (and other Sharp-native formats) were written as raw bytes to a
.png temp file, causing PIL to fail with "cannot identify image file".
Every other AI module wrapper already converts via sharp().png().toBuffer()
before writing; face-landmarks was the only one that skipped this step.
- Refactor use-tool-processor and use-pipeline-processor hooks
- Enhance dropzone component with improved UX
- Improve seam carving with better error handling and tests
- Add JXL format encoding support to format-encoders
- Update tool routes for consistent format handling
- Add dropzone unit tests
The upscale function called runPythonWithProgress without a timeout parameter,
defaulting to the bridge's 10-minute hard limit. On CPU-only systems like
Synology NAS devices, Real-ESRGAN 4x upscaling easily exceeds this for modest
images. Additionally, when the timeout fired on the dispatcher path, the Python
process was left running and blocked all subsequent AI operations.
This fix adds an adaptive timeout based on input megapixels, scale factor, and
GPU availability (180s/effective-MP on CPU, 30s/effective-MP on GPU, floor of
10 minutes). It also kills the dispatcher on timeout so subsequent requests can
proceed via a fresh restart.
Closes#119
The dispatcher was lazy-initialized on first AI request, but a race
condition meant the first call always missed it (dispatcherReady still
false) and fell through to cold per-request Python. initDispatcher()
starts the dispatcher eagerly and returns a Promise that resolves with
GPU status once ready (or after a timeout).
The close handler called recordCrash() unconditionally, even for exit
code 0 (normal MAX_REQUESTS restart). After 5 normal cycles within 60s
the dispatcher was permanently disabled. Now only non-zero exits count.
- Add 55 unit tests for feature-status.ts (installed.json CRUD, cache
behavior, install lock, model verification, crash recovery, composite
state) using real temp directories
- Add 36 integration tests for full install/uninstall lifecycle against
Docker containers (face-detection bundle, SSE progress, tool gates,
shared model protection, concurrent install prevention, auth guards,
container restart recovery)
- Fix noise-removal CPU timeout by adding megapixel-based timeout
calculation (120s/MP, min 5 minutes)
- Fix Playwright auth storage state race condition (mkdirSync before
saving analytics-user.json)
- Fix 2 skipped tests in fixes-verification.spec.ts by replacing
external ~/Downloads/sample dependency with existing test fixtures
- Enable skipped analytics-consent settings toggle test
- Restructure features.spec.ts to manage bundle state (uninstall/
reinstall OCR) so 501 guard tests run instead of skipping
- Update noise-removal test mock to include sharp metadata() method
Skip alpha matting on CPU (pymatting's sparse matrices are the main
memory hog), auto-downscale images above 2048px before sending to
rembg, and retry with the lighter u2net model when OOM is detected.
Closes#17, #18, #19, #31, #32, #33, #34
Format preservation (#17, #18, #19):
- Add resolveOutputFormat to rotate, resize, text-overlay, watermark-text,
border, replace-color, blur-faces, upscale, erase-object, restore-photo
- Alpha-aware fallback: border with corner radius/shadow and replace-color
with makeTransparent fall back to PNG for non-alpha formats (JPEG)
- Python sidecar tools (blur-faces, upscale, erase-object) now convert
PNG output back to input format, matching restore-photo/colorize pattern
- Upscale and erase-object default to "auto" format detection instead of PNG
Dispatcher stability (#31, #32):
- Add gc.collect() and torch.cuda.empty_cache() after each dispatcher request
- Add configurable max_requests (default 50) for periodic dispatcher restart
- Add exponential backoff to dispatcher crash recovery in bridge.ts
- Circuit breaker: 5 crashes within 60s permanently disables dispatcher
- Reset crash counter on successful dispatcher startup
Health & security (#33, #34):
- Export getDispatcherStatus() from @snapotter/ai with running/ready/failed/
gpu/pid/consecutiveCrashes fields
- Admin health endpoint now includes full dispatcher status
- Add pip-audit job to CI workflow for Python dependency scanning
Code fixes:
- Sidebar state bleed: reset file store on HomePage mount
- restore-photo: raise error instead of silently skipping colorize
when DDColor model missing
- PaddleOCR OOM: cap input images to 2048px before OCR inference
- Torch CPU optimization: use --index-url .../whl/cpu on CPU nodes
Test fixes:
- upscale: add exact:true to scale factor button locators
- smart-crop: add exact:true to "Pad to square" locator
- colorize: use regex for model button names (Best/Balanced/Fast)
- enhance-faces: use .first() for ambiguous percentage display
- passport-photo: fix DPI locator, .or() compound, generate fallback
- people: update maxUsers assertions for unlimited (0) default
- automate: "Save Pipeline" → "Save" matching actual button text
- tools.test: add resize to Sharp mock chain for OCR tests
- Add enable_mkldnn=False to PaddleOCR constructor to bypass PaddlePaddle
3.3+ OneDNN/PIR crash on CPU-only systems
- Add 25MP and 75% max-reduction guard to seam carving with clear error
messages instead of silent timeout/crash
- Replace barcode/QR AVIF test fixtures with actual scannable codes
(old fixtures did not contain real barcodes)
- Convert all AI bridge inputs to PNG before writing to disk so PIL can
read AVIF/WebP/TIFF (7 bridge files; face-detection and OCR already
had this pattern)
- Add title/author aliases to edit-metadata schema so common field names
actually write EXIF tags instead of being silently stripped by Zod
- Port extend/pad crop logic from passport-photo single endpoint to the
batch pipeline so crop regions extending beyond the image get filled
with background color instead of producing all-white output
- Clamp quantized color channels to 255 in color-palette to prevent
Math.round(255/16)*16=256 from producing invalid hex like #100100100
- Compare OCR fallback warning against expected engine name per tier
instead of comparing engine name against tier name (always mismatch)
- Added model mismatch warnings in colorize, enhance-faces, and upscale routes.
- Improved error handling in colorize, enhance_faces, remove_bg, restore, and upscale scripts with detailed logging.
- Updated Dockerfile to align NCCL versions for compatibility.
- Introduced a new full tool audit script to test all tools for functionality and GPU usage.
- Created Playwright E2E tests for GPU-dependent tools to ensure proper functionality and performance.
- Replace [object Object] errors with readable messages across all 20+ API
routes by normalizing Zod validation errors to strings (formatZodErrors)
- Add parseApiError() on frontend to defensively handle any details type
- Add global Fastify error handler with full stack traces in logs
- Fix image-to-pdf auth: Object.entries(headers) → headers.forEach()
- Fix passport-photo: safeParse + formatZodErrors, safe error extraction
- Fix OCR silent fallbacks: log exception type/message when falling back,
include actual engine used in API response and Docker logs
- Fix split tool: process all uploaded images, combine into ZIP with
subfolders per image
- Fix batch support for blur-faces, strip-metadata, edit-metadata,
vectorize: add processAllFiles branch for multi-file uploads
- Docker: LOG_LEVEL=debug, PYTHONWARNINGS=default for visibility
- Add Playwright e2e tests verifying all fixes against Docker container
Set U2NET_HOME=/opt/models/rembg so rembg models pre-downloaded at
build time as root are found at runtime by the non-root ashim user.
Without this every fresh container re-downloaded the 973 MB BiRefNet
models on first background-removal request.
Apply the same fix to PaddleOCR: download to /opt/models/paddlex and
symlink into both /root/.paddlex and /app/.paddlex so PaddleX finds
models regardless of which HOME gosu resolves at runtime.
Fall back to per-request spawning in bridge.ts when the persistent
dispatcher crashes mid-request (e.g. OOM loading a large ONNX model),
so the operation succeeds instead of surfacing "Python dispatcher
exited unexpectedly" to the user.
Improve entrypoint.sh permission warning to mention Windows bind mounts
as the likely cause.
* feat(passport-photo): add passport specs database and tool constants
* feat(passport-photo): add MediaPipe FaceMesh landmark detection script
* feat(passport-photo): add TypeScript bridge for face landmark detection
* feat(passport-photo): add API routes with analyze and generate endpoints
* fix(passport-photo): accept landmarks from request body and fix pixel coordinate conversion
- Generate endpoint now accepts landmarks + imageWidth/imageHeight in request body
instead of re-running AI face detection (makes generate phase instant)
- Fixed bug where normalized landmark coordinates (0-1) were used directly
as pixel values in crop computation - now properly multiplied by imgW/imgH
- Fixed same bug in pipeline process function
* feat(passport-photo): add UI component with live preview and compliance overlay
---------
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
Add comprehensive photo restoration tool that chains multiple AI models:
- Scratch/tear/spot detection via morphological analysis (top-hat/black-hat transforms)
- Damage inpainting via LaMa ONNX model (reuses existing infrastructure)
- Face enhancement via CodeFormer ONNX (~377MB, from facefusion/models-3.0.0)
- Noise reduction via OpenCV NLMeans in LAB color space
- Optional B&W auto-colorization via DDColor (reuses existing model)
Settings: 3 restoration modes (Light/Auto/Heavy), individual feature toggles
for scratch removal, face enhancement (with fidelity slider), denoising
(with strength slider), and auto-colorize. Before/after comparison view.
Handles HEIC, HEIF, and all standard formats. Batch processing supported.
No new Python dependencies - reuses onnxruntime, cv2, mediapipe, PIL.
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
* feat(shared): add enhance-faces tool definition and i18n strings
* feat(ai): add face enhancement script with GFPGAN and CodeFormer support
Detects faces via MediaPipe dual-model approach, then enhances using
GFPGAN (proven) or CodeFormer (via codeformer-pip) with auto fallback.
Supports strength-based alpha blending with original image.
* feat(ai): add TypeScript bridge for face enhancement
* feat(api): add enhance-faces route with GFPGAN/CodeFormer support
* feat(web): add enhance-faces settings component and register in tool registry
* feat(docker): add CodeFormer dependency and model download
- Add codeformer-pip to both CPU and GPU requirements
- Download CodeFormer model (~375MB) at Docker build time
- Add CodeFormer to smoke test verification
* fix(enhance-faces): address code review findings
- Skip alpha blend for CodeFormer (strength already applied via fidelity weight)
- Hide "only enhance main face" checkbox when Best (CodeFormer) is selected
- Fix sensitivity slider labels (swap More/Fewer faces to match actual behavior)
- Register EnhanceFacesControls in pipeline step settings
- Remove model names from user-facing descriptions
* fix(enhance-faces): fix CodeFormer integration and Docker setup
- Add codeformer-pip install to Dockerfile with --no-deps to avoid numpy 2.x conflict
- Re-pin numpy==1.26.4 after codeformer-pip install
- Pin codeformer-pip==0.0.4 in requirements files
- Broaden auto-mode fallback to catch any Exception from CodeFormer
---------
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
* feat(noise-removal): register tool in shared constants and i18n
* feat(noise-removal): add SCUNet and NAFNet model architectures
* feat(noise-removal): add Python denoising engine with 4 quality tiers
* feat(noise-removal): add TypeScript bridge for Python sidecar
* feat(noise-removal): add frontend settings with 4-tier selector
* feat(noise-removal): register in tool registry and pipeline
* feat(noise-removal): add Fastify API route with Zod validation
* feat(noise-removal): add SCUNet and NAFNet model downloads to Docker build
* test(noise-removal): add to e2e tool page rendering tests
* test(noise-removal): add integration tests for API endpoint
* style: fix biome formatting and import ordering
* fix(noise-removal): use correct model download URLs
NAFNet model is hosted on HuggingFace, not GitHub releases.
Also align SCUNet URL to use the KAIR releases (same as Docker build).
* fix(noise-removal): remove emojis from tier selector, simplify labels
Drop emoji icons from Quick/Balanced/Quality/Maximum buttons. Replace
technical algorithm names with plain descriptions users can understand.
---------
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
Add AI-powered photo colorization that converts B&W/grayscale images to
full color using DDColor (ICCV 2023 dual-decoder architecture) via ONNX
Runtime. Includes model selection (Auto/DDColor/Classic), adjustable color
intensity, batch processing, before/after preview, and full HEIC/HEIF support.
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
Replace the confusing 2-mode smart crop with a clear 3-mode system:
- Subject Focus: Sharp attention/entropy saliency crop with social media presets
- Face Focus: MediaPipe face detection with headshot framing presets
- Auto Trim: Border removal with optional pad-to-square
Adds detectFaces() to AI package, face preset constants, backward
compatibility for old mode names, and comprehensive integration tests.
- Pin PaddlePaddle to 3.0.0 on ARM64 to fix segfault in PIR inference
engine (3.1+ crashes on aarch64 Debian Bookworm)
- Fix text extraction for PaddleOCR 3.4.x result format (rec_texts)
- Add Node.js-level fallback chain (best -> balanced -> fast) when
Python subprocess crashes
- Add multi-image OCR: processes all uploaded files sequentially with
per-file progress and filename headers in combined output
- Convert input images to PNG via Sharp before OCR so HEIC, AVIF, WebP,
TIFF all work transparently
- Implement real auto-detect language using Tesseract multi-lang script
detection (analyzes Unicode ranges for Hangul, CJK, Kana, Latin)
- Default enhance to off (hurts clean digital images)
- Fix multi-image: process selected file, not always first
- Fix progress bar: asymptotic fill prevents visual stalling
- Fix slider: write results to captured index, not current selection
- Add model selection (Auto/AI/Fast), face enhancement, denoise
- Add output format (PNG/JPEG/WebP) with quality control
- Add Upscale All for sequential batch processing with queue
- More granular Python progress stages for smoother UX
- Add bidirectional HEIF support: decode (input) and encode (output) via system heif-convert/heif-enc
- Add server-side WebP preview generation for non-browser-previewable formats (HEIC, TIFF)
- Fix content-aware resize failing on HEIF input (decode before passing to caire)
- Fix content-aware resize timeout on large images by downscaling to max 1200px and using JPEG intermediate
- Add HEIF as target format in convert tool
- Add loading spinner for HEIF preview decode in file store
- Fix file picker not accepting HEIF files (explicit .heic,.heif,.hif extensions)
- Extend frontend timeout for medium tools to 180s with 45s progress animation
- Redesign rotate controls with preset buttons and compact flip section
- Remove misleading savings percentage from convert tool
Replace the Python seam-carving library with caire (esimov/caire v1.5.0),
a Go-based content-aware resize engine that is faster and supports both
shrinking and enlarging via seam insertion.
- Add Go builder stage in Dockerfile to compile caire from source
- Rewrite seam-carving.ts to call caire via execFile (no Python sidecar)
- Remove content-aware-resize from PYTHON_SIDECAR_TOOLS (60s timeout)
- Add new options: blur radius, edge sensitivity, square mode, face detection
- Move content-aware toggle below standard resize in UI (subtler placement)
- Rename "Don't enlarge" to "Limit to original size" with hover tooltip
- Add smooth progress bar for medium-duration tools
- Delete seam_carve.py and remove seam-carving pip dependency
- Update integration tests and visual regression screenshots
Add a :cuda Docker image tag that auto-detects NVIDIA GPU at runtime
and falls back gracefully to CPU. Same pattern as Immich.
- New gpu.py shared utility for cached CUDA detection
- Background removal (rembg): pass CUDAExecutionProvider to ONNX Runtime
- Upscaling (Real-ESRGAN): use CUDA device + FP16 when GPU available
- OCR (PaddleOCR): enable use_gpu when CUDA detected
- Dispatcher reports GPU status at startup via readiness signal
- Admin health endpoint exposes GPU availability
- Dockerfile uses ARG GPU=false with conditional NVIDIA CUDA base image
- docker-compose.gpu.yml override for GPU users
- CI/CD workflows build and publish :cuda tag (amd64 only)
Three tags: :latest (CPU), :lite (no AI), :cuda (GPU with CPU fallback)
- Set up semantic-release with zero-touch CI pipeline on push to main
- Add version sync script to keep all package.json files and APP_VERSION
constant in sync automatically
- Consolidate Docker publishing into single tag-triggered workflow that
pushes to both Docker Hub and ghcr.io with semver tags
- Add help dialog with keyboard shortcuts, getting started guide, and
resource links
- Sync all versions to 0.2.1 to match Docker Hub latest