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:
2026-06-12 01:07:46 +00:00
co-authored by Claude Sonnet 4.6
parent ca7b90f8ef
commit eea7baa19f
28 changed files with 126 additions and 118 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ on:
env:
REGISTRY: ghcr.io
IMAGE_NAME: sudolulo/if-curator-headless
IMAGE_NAME: sudolulo/winnow
jobs:
verify-lockfile:
+2 -2
View File
@@ -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
View File
@@ -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
+10 -10
View File
@@ -1,8 +1,8 @@
[![Publish Docker Image](https://github.com/sudolulo/if_curator_headless/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/sudolulo/if_curator_headless/actions/workflows/docker-publish.yml) [![Release](https://github.com/sudolulo/if_curator_headless/actions/workflows/release.yml/badge.svg)](https://github.com/sudolulo/if_curator_headless/actions/workflows/release.yml) [![Lint](https://github.com/sudolulo/if_curator_headless/actions/workflows/lint.yml/badge.svg)](https://github.com/sudolulo/if_curator_headless/actions/workflows/lint.yml) [![Test](https://github.com/sudolulo/if_curator_headless/actions/workflows/test.yml/badge.svg)](https://github.com/sudolulo/if_curator_headless/actions/workflows/test.yml)
[![Publish Docker Image](https://github.com/sudolulo/winnow/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/sudolulo/winnow/actions/workflows/docker-publish.yml) [![Release](https://github.com/sudolulo/winnow/actions/workflows/release.yml/badge.svg)](https://github.com/sudolulo/winnow/actions/workflows/release.yml) [![Lint](https://github.com/sudolulo/winnow/actions/workflows/lint.yml/badge.svg)](https://github.com/sudolulo/winnow/actions/workflows/lint.yml) [![Test](https://github.com/sudolulo/winnow/actions/workflows/test.yml/badge.svg)](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.
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+3 -3
View File
@@ -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()
+6 -6
View File
@@ -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
View File
@@ -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"
+8 -8
View File
@@ -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"]
Generated
+55 -55
View File
@@ -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()
+3 -3
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
"""Configuration management for if-curator."""
"""Configuration management for winnow."""
import json
import logging
+1 -1
View File
@@ -1,4 +1,4 @@
"""Logging configuration for if-curator."""
"""Logging configuration for winnow."""
import logging
import os