refactor: rename project to winnow
- Rename Python package directory if_curator/ → winnow/ - Update all imports, entry points, and CLI references - Update pyproject.toml: name, scripts, package list, repository URL - Update Dockerfile, compose.yml, entrypoint.sh, scheduler.py - Update GitHub Actions workflow image names - Update README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ on:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: sudolulo/if-curator-headless
|
||||
IMAGE_NAME: sudolulo/winnow
|
||||
|
||||
jobs:
|
||||
verify-lockfile:
|
||||
|
||||
@@ -136,6 +136,6 @@ jobs:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: |
|
||||
ghcr.io/sudolulo/if-curator-headless:latest
|
||||
ghcr.io/sudolulo/if-curator-headless:${{ steps.tag.outputs.TAG }}
|
||||
ghcr.io/sudolulo/winnow:latest
|
||||
ghcr.io/sudolulo/winnow:${{ steps.tag.outputs.TAG }}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ COPY pyproject.toml uv.lock ./
|
||||
RUN uv sync --frozen --no-dev \
|
||||
&& uv cache clean
|
||||
|
||||
COPY if_curator/ if_curator/
|
||||
COPY winnow/ winnow/
|
||||
COPY entrypoint.sh scheduler.py ./
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[](https://github.com/sudolulo/if_curator_headless/actions/workflows/docker-publish.yml) [](https://github.com/sudolulo/if_curator_headless/actions/workflows/release.yml) [](https://github.com/sudolulo/if_curator_headless/actions/workflows/lint.yml) [](https://github.com/sudolulo/if_curator_headless/actions/workflows/test.yml)
|
||||
[](https://github.com/sudolulo/winnow/actions/workflows/docker-publish.yml) [](https://github.com/sudolulo/winnow/actions/workflows/release.yml) [](https://github.com/sudolulo/winnow/actions/workflows/lint.yml) [](https://github.com/sudolulo/winnow/actions/workflows/test.yml)
|
||||
|
||||
# if-curator-headless
|
||||
# winnow
|
||||
|
||||
`if-curator-headless` pulls photos of people and objects from your [Immich](https://immich.app) library, selects the most diverse and highest-quality subset using AI embeddings, and delivers them as training data for [Frigate](https://frigate.video)'s face recognition and object classification models.
|
||||
`winnow` pulls photos of people and objects from your [Immich](https://immich.app) library, selects the most diverse and highest-quality subset using AI embeddings, and delivers them as training data for [Frigate](https://frigate.video)'s face recognition and object classification models.
|
||||
|
||||
It runs fully headless in Docker, is configured entirely through environment variables, and can run on a schedule — no interactive prompts, no manual steps.
|
||||
|
||||
@@ -14,7 +14,7 @@ Frigate's face recognition model (ArcFace) and object classifier are only as goo
|
||||
|
||||
If you upload 100 photos from the same week, the model learns the lighting in your living room and the jacket you wore that month. It struggles the moment anything changes. What you actually want is a spread: different years, different lighting conditions, different angles, different contexts.
|
||||
|
||||
Finding that spread manually across a library of thousands of photos is not practical. `if-curator-headless` does it automatically.
|
||||
Finding that spread manually across a library of thousands of photos is not practical. `winnow` does it automatically.
|
||||
|
||||
---
|
||||
|
||||
@@ -103,8 +103,8 @@ If the embedding model is unavailable, the tool falls back to **time spread**: e
|
||||
|
||||
```yaml
|
||||
services:
|
||||
if-curator:
|
||||
image: ghcr.io/sudolulo/if-curator-headless:latest
|
||||
winnow:
|
||||
image: ghcr.io/sudolulo/winnow:latest
|
||||
environment:
|
||||
- IMMICH_URL=http://192.168.1.10:2283
|
||||
- API_KEY=your-immich-api-key
|
||||
@@ -204,10 +204,10 @@ The first run after a fresh install downloads the embedding models (~1-2 GB). Su
|
||||
## Local Install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sudolulo/if_curator_headless.git
|
||||
cd if_curator_headless
|
||||
git clone https://github.com/sudolulo/winnow.git
|
||||
cd winnow
|
||||
uv sync
|
||||
uv run if-curator
|
||||
uv run winnow
|
||||
```
|
||||
|
||||
Requires Python 3.12+ and [uv](https://astral.sh/uv/). An NVIDIA GPU is strongly recommended — CPU mode works but embedding computation is significantly slower.
|
||||
@@ -225,4 +225,4 @@ Requires Python 3.12+ and [uv](https://astral.sh/uv/). An NVIDIA GPU is strongly
|
||||
|
||||
## Attribution
|
||||
|
||||
Based on [if-curator](https://github.com/ds-sebastian/if_curator) by Sebastian, licensed MIT.
|
||||
Based on [winnow](https://github.com/ds-sebastian/if_curator) by Sebastian, licensed MIT.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
if-curator:
|
||||
build: .
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /code/if-curator/models:/models
|
||||
- /code/if-curator/embeddings:/app/.if_cache
|
||||
- /code/if-curator/output:/app/output
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
if-curator:
|
||||
image: ghcr.io/sudolulo/if-curator-headless:latest
|
||||
container_name: if-curator
|
||||
winnow:
|
||||
image: ghcr.io/sudolulo/winnow:latest
|
||||
container_name: winnow
|
||||
environment:
|
||||
# ── Required ──────────────────────────────────────────────────────────
|
||||
- IMMICH_URL=${IMMICH_URL}
|
||||
@@ -52,10 +52,10 @@ services:
|
||||
# - CRON_SCHEDULE=0 3 1 * *
|
||||
# - CRON_SCHEDULE=*/30 * * * *
|
||||
volumes:
|
||||
# Replace <pool> with your TrueNAS pool name, e.g. /mnt/tank/if-curator/...
|
||||
- /mnt/<pool>/if-curator/models:/models
|
||||
- /mnt/<pool>/if-curator/embeddings:/app/.if_cache
|
||||
- /mnt/<pool>/if-curator/output:/app/frigate_train
|
||||
# Replace <pool> with your TrueNAS pool name, e.g. /mnt/tank/winnow/...
|
||||
- /mnt/<pool>/winnow/models:/models
|
||||
- /mnt/<pool>/winnow/embeddings:/app/.if_cache
|
||||
- /mnt/<pool>/winnow/output:/app/frigate_train
|
||||
stdin_open: true
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ export PYTHONUNBUFFERED=1
|
||||
|
||||
# 1. Run the job immediately on startup
|
||||
echo "▶ Running on startup..."
|
||||
uv run python -m if_curator.cli
|
||||
uv run python -m winnow.cli
|
||||
|
||||
# 2. If a schedule exists, start the scheduler
|
||||
if [ -n "${CRON_SCHEDULE:-}" ]; then
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
[project]
|
||||
name = "if-curator"
|
||||
name = "winnow"
|
||||
version = "0.1.0"
|
||||
description = "Immich to Frigate training sets"
|
||||
license = "MIT"
|
||||
@@ -30,10 +30,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
if-curator = "if_curator.cli:main"
|
||||
winnow = "winnow.cli:main"
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/sudolulo/if_curator_headless"
|
||||
Repository = "https://github.com/sudolulo/winnow"
|
||||
|
||||
[tool.uv]
|
||||
override-dependencies = ["onnxruntime-gpu>=1.23.2"]
|
||||
@@ -62,7 +62,7 @@ dev = [
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["if_curator"]
|
||||
packages = ["winnow"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
|
||||
+6
-6
@@ -35,7 +35,7 @@ def check_models():
|
||||
print(" ✅ HuggingFace models: present", flush=True)
|
||||
else:
|
||||
print(" ⬇️ HuggingFace models: not found — will download", flush=True)
|
||||
print("🚀 Starting if-curator...", flush=True)
|
||||
print("🚀 Starting winnow...", flush=True)
|
||||
|
||||
|
||||
NOW = time.time()
|
||||
@@ -45,14 +45,14 @@ 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 if-curator...", flush=True)
|
||||
print(f"\n▶ [{time.strftime('%Y-%m-%d %H:%M:%S')}] Starting winnow...", flush=True)
|
||||
check_models()
|
||||
result = subprocess.run(["uv", "run", "if-curator"], env=RUN_ENV)
|
||||
result = subprocess.run(["uv", "run", "winnow"], env=RUN_ENV)
|
||||
if result.returncode != 0:
|
||||
logger.error(f"if-curator exited with code {result.returncode}")
|
||||
print(f"❌ if-curator failed with exit code {result.returncode}", flush=True)
|
||||
logger.error(f"winnow exited with code {result.returncode}")
|
||||
print(f"❌ winnow failed with exit code {result.returncode}", flush=True)
|
||||
else:
|
||||
print("✅ if-curator completed successfully", flush=True)
|
||||
print("✅ winnow completed successfully", flush=True)
|
||||
next_run = cron.get_next(float)
|
||||
time.sleep(60)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ def test_config_loads_defaults(monkeypatch):
|
||||
monkeypatch.setenv("IMMICH_URL", "http://test:2283")
|
||||
monkeypatch.setenv("API_KEY", "test-key")
|
||||
|
||||
from if_curator.config import _Config
|
||||
from winnow.config import _Config
|
||||
|
||||
_Config.reset()
|
||||
cfg = _Config()
|
||||
@@ -46,7 +46,7 @@ def test_config_env_overrides(monkeypatch):
|
||||
monkeypatch.setenv("ENABLE_FACE_ALIGNMENT", "false")
|
||||
monkeypatch.setenv("ENABLE_CACHE", "true")
|
||||
|
||||
from if_curator.config import _Config
|
||||
from winnow.config import _Config
|
||||
|
||||
_Config.reset()
|
||||
cfg = _Config()
|
||||
@@ -69,7 +69,7 @@ def test_get_headers_returns_api_key(monkeypatch):
|
||||
monkeypatch.setenv("IMMICH_URL", "http://test:2283")
|
||||
monkeypatch.setenv("API_KEY", "my-secret-key")
|
||||
|
||||
from if_curator.config import _Config, get_headers
|
||||
from winnow.config import _Config, get_headers
|
||||
|
||||
_Config.reset()
|
||||
headers = get_headers()
|
||||
|
||||
@@ -9,14 +9,14 @@ def _asset(days_ago: int) -> dict:
|
||||
|
||||
|
||||
def test_filter_recent_keeps_new_assets(monkeypatch):
|
||||
from if_curator.immich_api import filter_recent_assets
|
||||
from winnow.immich_api import filter_recent_assets
|
||||
assets = [_asset(1), _asset(30), _asset(365 * 5)]
|
||||
result = filter_recent_assets(assets, years=10)
|
||||
assert len(result) == 3
|
||||
|
||||
|
||||
def test_filter_recent_removes_old_assets(monkeypatch):
|
||||
from if_curator.immich_api import filter_recent_assets
|
||||
from winnow.immich_api import filter_recent_assets
|
||||
old = _asset(365 * 15)
|
||||
recent = _asset(10)
|
||||
result = filter_recent_assets([old, recent], years=10)
|
||||
@@ -25,7 +25,7 @@ def test_filter_recent_removes_old_assets(monkeypatch):
|
||||
|
||||
|
||||
def test_filter_recent_boundary(monkeypatch):
|
||||
from if_curator.immich_api import filter_recent_assets
|
||||
from winnow.immich_api import filter_recent_assets
|
||||
just_inside = _asset(365 * 10 - 1)
|
||||
just_outside = _asset(365 * 10 + 1)
|
||||
result = filter_recent_assets([just_inside, just_outside], years=10)
|
||||
@@ -34,7 +34,7 @@ def test_filter_recent_boundary(monkeypatch):
|
||||
|
||||
|
||||
def test_filter_recent_skips_missing_date():
|
||||
from if_curator.immich_api import filter_recent_assets
|
||||
from winnow.immich_api import filter_recent_assets
|
||||
assets = [{"id": "no-date"}, _asset(5)]
|
||||
result = filter_recent_assets(assets, years=10)
|
||||
assert len(result) == 1
|
||||
@@ -42,12 +42,12 @@ def test_filter_recent_skips_missing_date():
|
||||
|
||||
|
||||
def test_filter_recent_skips_bad_date():
|
||||
from if_curator.immich_api import filter_recent_assets
|
||||
from winnow.immich_api import filter_recent_assets
|
||||
assets = [{"id": "bad", "fileCreatedAt": "not-a-date"}, _asset(5)]
|
||||
result = filter_recent_assets(assets, years=10)
|
||||
assert len(result) == 1
|
||||
|
||||
|
||||
def test_filter_recent_empty_list():
|
||||
from if_curator.immich_api import filter_recent_assets
|
||||
from winnow.immich_api import filter_recent_assets
|
||||
assert filter_recent_assets([], years=10) == []
|
||||
|
||||
+8
-8
@@ -11,21 +11,21 @@ def clean_env(monkeypatch):
|
||||
|
||||
|
||||
def test_resolve_strategy_default_auto():
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("auto", has_embedding=True)
|
||||
assert limit == "auto"
|
||||
assert mode == "smart"
|
||||
|
||||
|
||||
def test_resolve_strategy_standard():
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("standard", has_embedding=True)
|
||||
assert limit == 30
|
||||
assert mode == "smart"
|
||||
|
||||
|
||||
def test_resolve_strategy_broad():
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("broad", has_embedding=True)
|
||||
assert limit == 100
|
||||
assert mode == "smart"
|
||||
@@ -33,7 +33,7 @@ def test_resolve_strategy_broad():
|
||||
|
||||
def test_resolve_strategy_custom_limit_env(monkeypatch):
|
||||
monkeypatch.setenv("LIMIT", "50")
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("auto", has_embedding=True)
|
||||
assert limit == 50
|
||||
assert mode == "smart"
|
||||
@@ -41,13 +41,13 @@ def test_resolve_strategy_custom_limit_env(monkeypatch):
|
||||
|
||||
def test_resolve_strategy_limit_overrides_strategy(monkeypatch):
|
||||
monkeypatch.setenv("LIMIT", "25")
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("broad", has_embedding=True)
|
||||
assert limit == 25
|
||||
|
||||
|
||||
def test_resolve_strategy_no_embedding_falls_back_to_time():
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("auto", has_embedding=False)
|
||||
assert mode == "time"
|
||||
assert isinstance(limit, int)
|
||||
@@ -55,14 +55,14 @@ def test_resolve_strategy_no_embedding_falls_back_to_time():
|
||||
|
||||
def test_resolve_strategy_no_embedding_respects_limit(monkeypatch):
|
||||
monkeypatch.setenv("LIMIT", "60")
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("auto", has_embedding=False)
|
||||
assert limit == 60
|
||||
assert mode == "time"
|
||||
|
||||
|
||||
def test_resolve_strategy_unknown_falls_back_to_auto():
|
||||
from if_curator.jobs import _resolve_strategy
|
||||
from winnow.jobs import _resolve_strategy
|
||||
limit, mode = _resolve_strategy("unknown-strategy", has_embedding=True)
|
||||
assert limit == "auto"
|
||||
assert mode == "smart"
|
||||
|
||||
@@ -11,41 +11,41 @@ import pytest
|
||||
def isolated_cache(monkeypatch, tmp_path):
|
||||
"""Point tracker at a temp directory so tests don't touch real cache files."""
|
||||
monkeypatch.setenv("CACHE_DIR", str(tmp_path))
|
||||
from if_curator.config import _Config
|
||||
from winnow.config import _Config
|
||||
_Config.reset()
|
||||
yield tmp_path
|
||||
_Config.reset()
|
||||
|
||||
|
||||
def test_filter_returns_all_when_empty():
|
||||
from if_curator.upload_tracker import filter_already_uploaded
|
||||
from winnow.upload_tracker import filter_already_uploaded
|
||||
ids = ["a1", "b2", "c3"]
|
||||
assert filter_already_uploaded(ids) == ids
|
||||
|
||||
|
||||
def test_mark_uploaded_excludes_from_filter():
|
||||
from if_curator.upload_tracker import filter_already_uploaded, mark_uploaded
|
||||
from winnow.upload_tracker import filter_already_uploaded, mark_uploaded
|
||||
mark_uploaded("a1", person_name="Alice")
|
||||
result = filter_already_uploaded(["a1", "b2"])
|
||||
assert result == ["b2"]
|
||||
|
||||
|
||||
def test_mark_rejected_excludes_from_filter():
|
||||
from if_curator.upload_tracker import filter_already_uploaded, mark_rejected
|
||||
from winnow.upload_tracker import filter_already_uploaded, mark_rejected
|
||||
mark_rejected("x9", person_name="Bob")
|
||||
result = filter_already_uploaded(["x9", "y8"])
|
||||
assert result == ["y8"]
|
||||
|
||||
|
||||
def test_retry_rejected_includes_rejected():
|
||||
from if_curator.upload_tracker import filter_already_uploaded, mark_rejected
|
||||
from winnow.upload_tracker import filter_already_uploaded, mark_rejected
|
||||
mark_rejected("x9", person_name="Bob")
|
||||
result = filter_already_uploaded(["x9", "y8"], retry_rejected=True)
|
||||
assert "x9" in result
|
||||
|
||||
|
||||
def test_reset_person_clears_records():
|
||||
from if_curator.upload_tracker import filter_already_uploaded, mark_uploaded, reset_person
|
||||
from winnow.upload_tracker import filter_already_uploaded, mark_uploaded, reset_person
|
||||
mark_uploaded("a1", person_name="Alice")
|
||||
mark_uploaded("a2", person_name="Alice")
|
||||
mark_uploaded("b1", person_name="Bob")
|
||||
@@ -55,7 +55,7 @@ def test_reset_person_clears_records():
|
||||
|
||||
|
||||
def test_get_person_summary():
|
||||
from if_curator.upload_tracker import get_person_summary, mark_rejected, mark_uploaded
|
||||
from winnow.upload_tracker import get_person_summary, mark_rejected, mark_uploaded
|
||||
mark_uploaded("a1", person_name="Alice")
|
||||
mark_uploaded("a2", person_name="Alice")
|
||||
mark_rejected("a3", person_name="Alice")
|
||||
@@ -68,7 +68,7 @@ def test_get_person_summary():
|
||||
|
||||
|
||||
def test_duplicate_marks_are_idempotent():
|
||||
from if_curator.upload_tracker import filter_already_uploaded, mark_uploaded
|
||||
from winnow.upload_tracker import filter_already_uploaded, mark_uploaded
|
||||
mark_uploaded("dup", person_name="Alice")
|
||||
mark_uploaded("dup", person_name="Alice")
|
||||
assert filter_already_uploaded(["dup", "new"]) == ["new"]
|
||||
|
||||
@@ -549,61 +549,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if-curator"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "croniter" },
|
||||
{ name = "insightface" },
|
||||
{ name = "numpy" },
|
||||
{ name = "nvidia-cudnn-cu12" },
|
||||
{ name = "onnxruntime-gpu" },
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "pillow" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "requests" },
|
||||
{ name = "rich" },
|
||||
{ name = "torch", version = "2.12.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine != 's390x' and sys_platform == 'darwin'" },
|
||||
{ name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 's390x' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "torch", version = "2.12.0+cu126", source = { registry = "https://download.pytorch.org/whl/cu126" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "transformers" },
|
||||
{ name = "ultralytics" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "croniter", specifier = ">=5.0.2" },
|
||||
{ name = "insightface", specifier = ">=0.7.3" },
|
||||
{ name = "numpy", specifier = ">=2.2.6" },
|
||||
{ name = "nvidia-cudnn-cu12", specifier = ">=9.0.0" },
|
||||
{ name = "onnxruntime-gpu", specifier = ">=1.23.2" },
|
||||
{ name = "opencv-python-headless", specifier = ">=4.12.0.88" },
|
||||
{ name = "pillow", specifier = ">=12.1.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
{ name = "rich", specifier = ">=14.2.0" },
|
||||
{ name = "torch", marker = "sys_platform != 'linux'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cpu" },
|
||||
{ name = "torch", marker = "platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux'", specifier = ">=2.6.0" },
|
||||
{ name = "torch", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cpu" },
|
||||
{ name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cu126" },
|
||||
{ name = "transformers", specifier = ">=4.57.6" },
|
||||
{ name = "ultralytics", specifier = ">=8.3.252" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "pytest", specifier = ">=8.0" },
|
||||
{ name = "ruff", specifier = ">=0.9.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imageio"
|
||||
version = "2.37.3"
|
||||
@@ -2341,3 +2286,58 @@ sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "croniter" },
|
||||
{ name = "insightface" },
|
||||
{ name = "numpy" },
|
||||
{ name = "nvidia-cudnn-cu12" },
|
||||
{ name = "onnxruntime-gpu" },
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "pillow" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "requests" },
|
||||
{ name = "rich" },
|
||||
{ name = "torch", version = "2.12.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine != 's390x' and sys_platform == 'darwin'" },
|
||||
{ name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 's390x' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "torch", version = "2.12.0+cu126", source = { registry = "https://download.pytorch.org/whl/cu126" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "transformers" },
|
||||
{ name = "ultralytics" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "croniter", specifier = ">=5.0.2" },
|
||||
{ name = "insightface", specifier = ">=0.7.3" },
|
||||
{ name = "numpy", specifier = ">=2.2.6" },
|
||||
{ name = "nvidia-cudnn-cu12", specifier = ">=9.0.0" },
|
||||
{ name = "onnxruntime-gpu", specifier = ">=1.23.2" },
|
||||
{ name = "opencv-python-headless", specifier = ">=4.12.0.88" },
|
||||
{ name = "pillow", specifier = ">=12.1.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
{ name = "rich", specifier = ">=14.2.0" },
|
||||
{ name = "torch", marker = "sys_platform != 'linux'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cpu" },
|
||||
{ name = "torch", marker = "platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux'", specifier = ">=2.6.0" },
|
||||
{ name = "torch", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cpu" },
|
||||
{ name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cu126" },
|
||||
{ name = "transformers", specifier = ">=4.57.6" },
|
||||
{ name = "ultralytics", specifier = ">=8.3.252" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "pytest", specifier = ">=8.0" },
|
||||
{ name = "ruff", specifier = ">=0.9.2" },
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from if_curator.cli import main
|
||||
from winnow.cli import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Interactive CLI for if-curator."""
|
||||
"""Interactive CLI for winnow."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
@@ -17,12 +17,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Entry point for if-curator CLI."""
|
||||
"""Entry point for winnow CLI."""
|
||||
try:
|
||||
setup_logging(verbose=False)
|
||||
|
||||
console.print(r"""
|
||||
[bold blue]if-curator[/bold blue]
|
||||
[bold blue]winnow[/bold blue]
|
||||
[dim]Immich -> Frigate Training Data Curator[/dim]
|
||||
""")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Configuration management for if-curator."""
|
||||
"""Configuration management for winnow."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Logging configuration for if-curator."""
|
||||
"""Logging configuration for winnow."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
Reference in New Issue
Block a user