- compose.yml: generic /path/to/winnow/... placeholder paths - docs/setup.md: replace TrueNAS section with generic GPU passthrough guide - docs/faq.md: remove platform-specific path example - README.md: soften crop quality disclaimer, remove TrueNAS compose note - CHANGELOG.md: scrub TrueNAS mentions from release notes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.8 KiB
7.8 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.2.1] - 2026-06-12
Fixed
- Container startup reinstalling packages:
entrypoint.shuseduv run, which performs a sync check on every startup and re-downloadedruffand rebuilt the package each time. Replaced with direct.venv/bin/pythoncalls to skip the sync entirely. - InsightFace double
models/path:INSIGHTFACE_HOME=/modelscaused InsightFace to download Buffalo_L to/models/models/buffalo_l(InsightFace always appendsmodels/to the root). Updated default incompose.ymland.env.exampleto/models/.insightface. - Lint errors in CI: unused imports in
tests/test_config.pyandtests/test_upload_tracker.py, unsorted imports inscheduler.py— all would have failed the ruff CI check.
[0.2.0] - 2026-06-12
First release of winnow. Forked from if-curator by Sebastian and rewritten for headless Docker deployment.
Added
Headless operation
AUTO_MODEenv var — runs without any interactive prompts; required for Docker/cron useDRY_RUNenv var — previews selection without downloading, cropping, or uploading anythingRETRY_REJECTEDenv var — re-attempts assets previously rejected by Frigate's face APIRESET_PERSONenv var — clears upload and rejection history for one named person
Docker and scheduling
Dockerfile— multi-stage build (CUDA 12.9 on amd64, plain Ubuntu on arm64); runtime stage excludes build tools (g++, python3.12-dev, curl, gnupg)compose.yml— fully annotated with inline comments grouped by concernentrypoint.sh— runs the tool once on startup, then hands off to the scheduler ifCRON_SCHEDULEis setscheduler.py— in-process cron scheduler that keeps the container (and loaded models) alive between runsCRON_SCHEDULEenv var — standard cron expression for recurring runs; unset exits after first run.dockerignore— keeps.venv,__pycache__, test files, and logs out of the image context- Multi-arch image:
linux/amd64andlinux/arm64built and merged into a single manifest on GHCR tinias PID 1 init process for correct signal handling- Non-root container user (
appuser, uid 568) HEALTHCHECKin Dockerfile
Object mode
TRAINING_MODE=object— runs YOLOv9c detection on full images, crops each detected instance of a target class, and saves crops to the output volume (Frigate has no training API for objects — crops are placed manually)OBJECT_CLASSenv var — target YOLO class label (e.g.dog,cat,car); defaults todog
People filtering
ONLY_PEOPLEenv var — comma-separated whitelist; only these people are processedSKIP_PEOPLEenv var — comma-separated list; these people are skippedMIN_FACE_COUNTenv var — skip people with fewer than N tagged assets in ImmichYEARS_FILTERenv var — ignore assets older than N years (default: 10)
Image quality controls (previously hardcoded)
BLUR_THRESHOLDenv var — Laplacian variance threshold for blur rejectionMIN_CONFIDENCEenv var — minimum Immich face detection confidenceMAX_AUTO_IMAGESenv var — hard cap on auto-diversity selectionFACE_MARGINenv var — padding around bounding box crops as a fraction of face sizeUSE_FULL_RESOLUTIONenv var — download full-res originals vs preview thumbnailsENABLE_FACE_ALIGNMENTenv var — align to ArcFace 112×112 format via InsightFace landmarks
GPU and model configuration
FORCE_CPUenv var — disable GPU; fall back to CPU for embedding computationINSIGHTFACE_HOMEenv var — controls model persistence for Buffalo_LHF_HOMEenv var — HuggingFace model cache path for SigLIPLD_LIBRARY_PATHset in the image to expose CUDA and cuDNN pip libraries soonnxruntime-gpucan find them at runtime
Caching and upload tracking
ENABLE_CACHE/CACHE_DIRenv vars — opt-in embedding cache to skip recomputation on reruns- Per-person upload tracker persisted as JSON; prevents the same asset from being uploaded twice across runs weeks apart, even if the container is recreated
- Startup summary showing uploaded and rejected counts per person
LIMITenv var — exact image count overridingSTRATEGYpreset
CI/CD
docker-publish.yml— builds multi-arch image and pushes to GHCR on push tomain(:latest) ordev(:dev)release.yml— triggered byv*tags orworkflow_dispatch; creates a GitHub Release, extracts changelog notes, builds and pushes versioned image to GHCRlint.yml— runs Ruff on push/PR tomainanddevtest.yml— runs pytest on push/PR tomainanddevupdate-lockfile.yml— regeneratesuv.lockand commits it whenpyproject.tomlchanges- Dependabot: weekly grouped PRs for Python dependencies (uv ecosystem) and GitHub Actions versions
Testing
- 24 unit tests across four modules:
test_config,test_immich_api,test_jobs,test_upload_tracker
Documentation
docs/setup.md— step-by-step install and GPU passthrough guidedocs/troubleshooting.md— common failure modes with fixesdocs/faq.md— answers to questions new users will ask.env.example— copy-paste starting point with every env var and inline comments- README rewritten: pipeline diagram, env var reference tables, scheduling behaviour, requirements
Changed
cli.pysplit into three focused modules —cli.py(entry point),jobs.py(configuration and strategy resolution),executor.py(download, crop, upload)- Dependency management replaced with
uv;uv.lockpins the full transitive graph for reproducible builds compose.ymlfully annotated; all env vars documented with inline commentsLD_LIBRARY_PATHextended to include both cuDNN and CUDA runtime libraries
Fixed
- EXIF orientation: PIL opens JPEGs without applying rotation metadata; Immich computes face bounding boxes on orientation-corrected images, so portrait photos produced misaligned crops.
ImageOps.exif_transpose()now normalizes orientation before any coordinate math. - Model persistence:
FaceAnalysiswas initialized withroot="~/.insightface"(hardcoded), ignoringINSIGHTFACE_HOME. Buffalo_L was re-downloaded into the container on every run instead of persisting to the mounted volume. - Upload deduplication:
upload_to_frigate()scanned the output directory withos.listdir(), picking up leftover files from previous runs and re-uploading them. Now only files created in the current run are uploaded. - RGBA images: Images in RGBA mode raised an error when encoding to JPEG. All images are now converted to RGB before saving.
- Object mode uploads: Object mode incorrectly called the Frigate face registration API. Frigate has no API for object training data — object mode now only saves crops to disk.
- Stale output files: The output directory was not cleaned between runs, causing crops to accumulate. Now wiped at the start of each face-mode run.
- JSON decode errors:
get_people()andfetch_all_assets()only caughtRequestException, leavingJSONDecodeErrorunhandled on non-JSON 200 responses from Immich. - Spaces in names: People names with spaces caused downstream errors.
- Inconsistent headers: Some API calls used a raw header dict instead of
get_headers(). - Docker layer caching:
uv syncwas placed afterCOPY if_curator/, so any source change invalidated the 800 MB dependency cache. Dependencies are now installed before source is copied.
Security
- CUDA base image bumped from
nvidia/cuda:12.6.3tonvidia/cuda:12.9.2-cudnn-runtime-ubuntu22.04, picking up Ubuntu security patches flagged by Dependabot.