Yaffo - Photo Organizer
A Flask-based photo organization tool that uses EXIF metadata, face recognition, and duplicate detection to automatically organize and index photos.
Features
- Photo Organization: Automatically organize and index photos by date using EXIF metadata
- Face Detection & Recognition: Detect, group, and tag faces by person using InsightFace (SCRFD detection + ArcFace embeddings, on ONNX Runtime)
- Duplicate Detection: Find duplicate photos using perceptual hashing
- EXIF Metadata: Extract and display photo metadata
- Location Support: Geocoding, reverse-geocoding, and time-correlation geotagging from neighboring photos
- Auto-Labeling: Offline zero-shot classification (CLIP) tags photos against a user vocabulary
- Automations: Scheduled and event-driven background behaviors (system-built and AI-generated)
- AI Page Builder: Build custom pages from AI-generated, sandboxed widgets over your own photo data
Prerequisites
- Python 3.13+
- Node.js 18+ (for UI tests)
- ExifTool (see EXIFTOOL_SETUP.md)
Quick Start
1. Clone and Setup
git clone <repository-url>
cd yaffo
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -e .
2. Install Git Hooks
./hooks/install.sh
This installs pre-commit hooks that prevent accidental commit of API keys and secrets.
3. Configure the Data Directory
YAFFO_DATA_DIR is the root for the app's own state — the SQLite databases
(yaffo.db, yaffo-queue.db), generated thumbnails, logs, and temp/trash. It
defaults to the OS user-data dir (e.g. ~/Library/Application Support/yaffo on
macOS); set it to override:
export YAFFO_DATA_DIR=/path/to/data
Your actual photo (media) directories are configured separately, in the app's Settings page — they aren't a fixed subfolder. The file sync / watcher scans the configured media dirs and indexes what it finds.
4. Run the Application
# Start the Flask web app
flask run
# In a separate terminal, start the background task host (spawn worker pool)
python -m yaffo.taskq.host
# Or launch the whole local stack (Flask + task host + watcher) at once
inv app-local
The app will be available at http://127.0.0.1:5000
Installed App Launcher
For an installed PyPI/pipx copy, use the app launcher instead of the Flask development server:
pipx install yaffo
yaffo setup
yaffo setup can install a per-user desktop/app shortcut, runs database
migrations, downloads runtime assets up front, and asks whether to launch the app.
Runtime startup still checks migrations and assets, so yaffo remains safe to run
directly.
yaffo install-shortcut
The shortcut launches the installed Python entry point directly, so it does not
depend on your shell PATH. A yaffo-gui entry point is also installed for
platforms that distinguish console and GUI launchers.
Before uninstalling the package, clean up app-managed files:
yaffo uninstall
pipx uninstall yaffo
The uninstall command removes the shortcut, downloaded assets, and logs, then asks before deleting Yaffo user data such as the SQLite databases and config.
Project Structure
yaffo/
├── yaffo/ # Main application
│ ├── app.py # Flask app factory
│ ├── common.py # Configuration and paths
│ ├── db/ # Database models and repositories
│ ├── routes/ # API endpoints
│ ├── templates/ # Jinja2 templates
│ ├── static/ # CSS, JavaScript
│ ├── utils/ # Utility functions
│ ├── background_tasks/ # Background task definitions
│ ├── taskq/ # SQLite-backed task queue + spawn worker host
│ ├── site_agents/ # AI page builder: agent, model clients, tools, schemas
│ └── scripts/ # CLI tools + db/ (init_db, dev migrations)
├── tests/ # Python unit tests
├── yaffo_ui_tests/ # Playwright UI tests
├── hooks/ # Git hooks
├── docs/ # Design references (taskq, automations, page builder, video)
└── resources/ # UI resources bundled with the app
Development
Running Tests
# Python unit tests
pytest
# UI tests (requires app running)
cd yaffo_ui_tests
npm install
npm test
# UI tests in isolated environment
npm run test:isolated
Database
The app uses SQLite. The database file is stored at {YAFFO_DATA_DIR}/yaffo.db.
To reset the database:
rm /path/to/data/yaffo.db
flask run # Will recreate on startup
Background Tasks
Background tasks (photo indexing, face detection, etc.) run on a small
purpose-built queue (yaffo/taskq): a SQLite-durable queue plus a host process
that supervises a pool of spawn-started worker children, so CPU-bound native ML
inference (InsightFace/ONNX Runtime) runs in parallel, isolated, and
crash-contained. See docs/development/task-queue.md.
# Start the task host with 4 workers, recycling each after 200 tasks
python -m yaffo.taskq.host --workers 4 --recycle 200
# Or via invoke
inv start-tasks --workers=4
Tests drive tasks synchronously by setting task_queue.immediate = True.
UI Testing
The project includes an AI-augmented UI testing framework. See yaffo_ui_tests/README.md for details.
cd yaffo_ui_tests
# Install dependencies
npm install
npx playwright install
# Run tests against running app
npm test
# Run tests in isolated environment (recommended)
npm run test:isolated
Environment Variables
| Variable | Description | Default |
|---|---|---|
YAFFO_DATA_DIR |
Root for app state (DBs, thumbnails, logs, temp) | OS user-data dir (e.g. ~/Library/Application Support/yaffo); inv sets ~/Pictures for dev |
FLASK_APP |
Flask application module | yaffo.app:create_app |
FLASK_ENV |
Flask environment | development |
ANTHROPIC_API_KEY |
Anthropic API key for the AI Page Builder and AI-generated automations. Optional override — the key is normally stored in the OS keychain (set via Settings); the env var wins when present (headless/CI). | (unset; keychain used) |
Git Hooks
The project includes pre-commit hooks to prevent accidental commit of secrets:
# Install hooks (run once after cloning)
./hooks/install.sh
The pre-commit hook scans for:
- Anthropic API keys (
sk-ant-...) - OpenAI API keys (
sk-...) - Generic API key patterns
Licenses
Metadata
Release files for yaffo 0.0.27
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| yaffo-0.0.27.tar.gz | 3.1 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| yaffo-0.0.27-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 6.4 MB
Release files / yaffo-0.0.27.tar.gz
| Download URL | yaffo-0.0.27.tar.gz |
|---|---|
| Size | 3.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a36cc1846df212bc834c899b1c86e785d6ea7cb62b29f3bb3d095ba7d5bb13ff
|
|
BLAKE2b-256 checksum How to use checksums |
19eb9ae3c49387d9f076c7e5c6dc53a945619341648e93d78094f77ebbe474a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / yaffo-0.0.27-py3-none-any.whl
| Download URL | yaffo-0.0.27-py3-none-any.whl |
|---|---|
| Size | 3.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e0f6c5b1d9b36ac87b741f069ea045fb37607772726fd07dc5f8cd22fe1d155a
|
|
BLAKE2b-256 checksum How to use checksums |
23422a6ffcbad8ce01ef76a2a6c2833b941218c5aead5be45092548f80ae4fc5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency log