- immich_api: `score or confidence` treated 0.0 score as falsy; use explicit None check - diversity: same falsy-zero fix in _get_face_confidence - diversity: _crop_face_from_thumbnail scale loop now filters by person_id (was using first person's imageWidth/imageHeight regardless of target in group photos) - jobs: partially-trained auto mode kept limit="auto" for adaptive stopping, then caps result to remaining capacity (was converting to int, silently disabling FPS adaptive threshold and early-stop) - embeddings: _suppress_output finally block wraps first dup2 in try/finally so stderr is always restored even if stdout restore raises OSError - Dockerfile: ldconfig find uses python3.* glob instead of hardcoded python3.13 - scheduler: sleep until next_run instead of fixed 60s; eliminates late-fire jitter and unnecessary wakeups on long schedules Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
53 lines
1.6 KiB
Python
53 lines
1.6 KiB
Python
#!/usr/bin/env python3
|
|
import logging
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from croniter import croniter
|
|
except ImportError:
|
|
print("croniter not installed. Run: uv add croniter")
|
|
sys.exit(1)
|
|
|
|
# Imported at module level so models loaded during the first run stay
|
|
# resident in memory across all subsequent scheduled runs.
|
|
from winnow.cli import main
|
|
|
|
SCHEDULE = os.environ["CRON_SCHEDULE"]
|
|
MODELS_DIR = os.environ.get("HF_HOME", "/models/huggingface")
|
|
INSIGHTFACE_HOME = os.environ.get("INSIGHTFACE_HOME", "/models/.insightface")
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def check_models() -> None:
|
|
buffalo = Path(INSIGHTFACE_HOME) / "models" / "buffalo_l"
|
|
hf_hub = Path(MODELS_DIR) / "hub"
|
|
if not buffalo.exists():
|
|
print(" InsightFace Buffalo_L not found — will download on first run", flush=True)
|
|
if not (hf_hub.exists() and any(hf_hub.iterdir())):
|
|
print(" HuggingFace models not found — will download on first run", flush=True)
|
|
|
|
|
|
NOW = time.time()
|
|
cron = croniter(SCHEDULE, NOW)
|
|
next_run = cron.get_next(float)
|
|
|
|
while True:
|
|
now = time.time()
|
|
if now >= next_run:
|
|
print(f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}] Starting winnow run...", flush=True)
|
|
check_models()
|
|
try:
|
|
main()
|
|
print("winnow run complete", flush=True)
|
|
except KeyboardInterrupt:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"winnow run failed: {e}", exc_info=True)
|
|
print(f"winnow run failed: {e}", flush=True)
|
|
next_run = cron.get_next(float)
|
|
time.sleep(max(1, next_run - time.time()))
|