Skip to main content

team-photo-sorter

Sort a team photographer's photos by player and share them with team families as a private, password-protected website that costs nothing to host.

Volunteer photographers at youth and high school games often shoot thousands of frames a game. Families then scroll through all of them looking for their own kid. team-photo-sorter finds and groups the faces in every photo on your own computer, then builds a static site where each family picks their player and gets that player's photos, game by game.

Unlock the site, pick a game, name a player, browse their photos

Try the demo site (password lakeview-demo). The demo team's players are illustrations, not real people. It's built by examples/demo/make_demo.py on every push.

  • Runs locally. Face detection and grouping happen on your machine. No photo or face data is sent to a face-recognition service.
  • No server. The output is a folder of static files. Host it free on Firebase Hosting, Netlify, Cloudflare Pages or GitHub Pages.
  • Private by default. Everything except the page shell is encrypted (AES-256-GCM, key from a shared password). Player names, game names and photos are unreadable without the password, and file names reveal nothing.
  • People confirm, the computer suggests. Families put a name on a face group once. The site then offers "Could these also be Sam?" for the same player's other groups, and "Same face as Sam in another game" across games. Nothing is merged automatically. In team sports, two teammates in matching caps can look as similar to the model as one player's sharp and blurry shots do.
  • Scales to a season. Each thumbnail and viewer image is its own encrypted file, fetched only when someone looks at it. Rebuilds are byte-identical for anything unchanged, so hosts only re-upload what's new. It grew out of a tool used for a full season: about 53,000 photos across 17 games.

Read docs/privacy.md before using this with photos of minors. This tool creates face embeddings, which many jurisdictions treat as biometric data. It is meant for a team photographer sharing with that team's families, with the team's knowledge.

Quick start

Requires Python 3.10 to 3.14 (the face model's runtime, ONNX Runtime, has no builds for 3.15 yet) and 4 GB of RAM. The biggest games, around 20,000 detected faces, need most of that. Check your version with python3 --version.

python3 -m venv ~/.venvs/tps && source ~/.venvs/tps/bin/activate
pip install team-photo-sorter            # HEIC photos from iPhones: "team-photo-sorter[heic]"

On a Mac whose python3 is 3.15, or the old 3.9 from Apple's developer tools, install a supported one with Homebrew and use it instead:

brew install python@3.13
python3.13 -m venv ~/.venvs/tps && source ~/.venvs/tps/bin/activate
pip install team-photo-sorter

In a new Terminal window, run source ~/.venvs/tps/bin/activate again before using tps.

Then make a folder for your team, with one subfolder of photos per game:

my-team/
  photos/
    2026-09-12 vs North/      IMG_0001.JPG, IMG_0002.JPG, ...
    2026-09-19 at South/
cd my-team
tps init       # writes team.toml with one game per photos/ subfolder

Then edit team.toml: team name, colours and roster, and check the game titles and dates tps init filled in from your folder names. The comments in the example config explain every setting.

tps all            # scan faces, group them, make thumbnails, build the site

tps all asks for the site password the first time. Use a long, random one (16+ characters), because anyone who gets a copy of the site can try passwords offline. To skip the prompt, set the TPS_PASSWORD environment variable.

The site is in site/public/. Deploy that folder to any static host; see docs/hosting.md. Share the link and the password with families.

The first scan downloads InsightFace's face models (about 280 MB, once) into ~/.insightface. Check their licence; see NOTICE.

Tested with InsightFace 1.0.1. InsightFace 2.x reworked its API and isn't supported yet.

Working on the code

git clone https://github.com/andyknosp1/team-photo-sorter.git
cd team-photo-sorter
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" && pytest

Adding a game

Add an [[event]] to team.toml and run tps all again. Everything is resumable: photos already scanned are skipped, and unchanged files are rebuilt byte for byte, so a redeploy uploads only the new game.

Commands

Command What it does
tps init Write team.toml (one game per photos/ subfolder) and a .gitignore
tps scan [--event ID] [--workers N] Detect and embed faces in new photos (resumable)
tps cluster [--event ID] Group each game's faces into player tiles
tps images [--event ID] Make face crops, contact-sheet thumbnails and viewer copies
tps build Build the encrypted site into site/public/
tps all scan, cluster, images, then build
tps labels import FILE Add labels that families shared from the site (see below)
tps labels list Show the organiser labels in use
tps export --player N --to DIR Copy one player's full-resolution photos into a folder per game

Use -c path/to/team.toml to point at a config somewhere else.

Sharing names across families

Names a family picks are saved only in their browser. To make them visible to everyone:

  1. A family clicks Share my labels at the bottom of the site and sends you the copied text.
  2. You save it to a file and run tps labels import shared.txt, then tps build. Labels are stored by face group, so they survive re-clustering as long as the group's best face stays in it.

You can also pre-name a player in every game from a few reference photos of their face. Add a [[seed]] to the config and see docs/how-it-works.md.

Linking to photos hosted elsewhere

By default the site includes a viewer-size copy of each photo (1280 px on the long side, about 200 KB). If the originals are already in a shared album somewhere, give the event a links CSV with columns filename,view_url,download_url. The site then links each photo there, and you can set full_size = 0 to keep the site small. See examples/links.csv.

Documentation

Limitations

  • One shared password for everyone. It's not user accounts, and it can't be revoked per family; change the password and rebuild to cut off access.
  • Grouping works on faces only, so backs of heads and fully covered faces are missed. Expect each player to be split across a few groups per game, and expect some groups that aren't on the team (opponents, referees, parents); families can hide those.
  • CPU only. Scanning runs at roughly 0.2–0.5 s per photo per core.
  • Very large single events (over ~20,000 faces) need splitting, for example one event per tournament day.

License

Apache License 2.0. Copyright 2026 Andy Knosp. The face models are not part of this project and have their own licence; see NOTICE.

Metadata

Release files for team-photo-sorter 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for team-photo-sorter 0.1.2
File Size Uploaded Provenance Actions
team_photo_sorter-0.1.2.tar.gz 45.2 kB With provenance Details for team_photo_sorter-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for team-photo-sorter 0.1.2
File Interpreter ABI Platform Actions
team_photo_sorter-0.1.2-py3-none-any.whl Python 3 none any Details for team_photo_sorter-0.1.2-py3-none-any.whl

Total release size: 88.8 kB

Release files / team_photo_sorter-0.1.2.tar.gz

Download URL team_photo_sorter-0.1.2.tar.gz
Size 45.2 kB
Tags Source
SHA-256 checksum
How to use checksums
0b3b9057216bab0d8f7531063d9b9f8b6a221b5231d732ed5650a3bcb5314f9b
BLAKE2b-256 checksum
How to use checksums
10a2568c1db997500e8f56a84f6f9171bf6fde7c668eb8cb8a138775dd693ce8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Release files / team_photo_sorter-0.1.2-py3-none-any.whl

Download URL team_photo_sorter-0.1.2-py3-none-any.whl
Size 43.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b2225906c31c993d425c97183a4dfe75d98bd2112cb7162b209aff9e4b544c7c
BLAKE2b-256 checksum
How to use checksums
a3f2e0611b40b10415f61cdb38aa63bc47333da0a7c7d13677668a57950f6ba5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Security for team-photo-sorter 0.1.2

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for team_photo_sorter-0.1.2.tar.gz

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 Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page