Photo cataloguer and metadata assistant.
Project description
Madify
Madify is a local CLI that catalogues photo, PSD, and video files in SQLite, lets you set titles/descriptions/tags, and renames files from those titles. On tag, it also writes an XMP sidecar next to the media file (catalog stays source of truth). The core never touches the filesystem, database, or clock directly — those are injected ports so unit tests stay deterministic.
Features
- Recursive scan into SQLite (images, Photoshop
.psd/.psb, common video) - Tag by id or path — tags merge by default;
--replace-tagsreplaces the set - XMP sidecar write-back on tag (
--no-sidecarto skip) - List / search the catalog
- Rename from sanitized titles (
Clip One→Clip_One.jpg), collisions →_2,_3, … - Zero runtime dependencies (stdlib only)
- Injectable Clock / FileSystem / CatalogStore / MetadataWriter ports
Install
Requires Python ≥ 3.12 and uv.
git clone https://github.com/scottmills306/Madify.git
cd Madify
uv sync --group dev
uv run madify
Madify 0.2.0
After PyPI trusted publishing is configured (see below):
uv tool install madify
# or: pip install madify
Quickstart
Keep the catalog outside the media folder so the .sqlite file is not skipped as unsupported media.
# PowerShell demo layout
$demo = Join-Path $env:TEMP "madify-demo"
$dbdir = Join-Path $env:TEMP "madify-db"
New-Item -ItemType Directory -Path $demo, $dbdir -Force | Out-Null
Set-Content "$demo\a.jpg" "x"
Set-Content "$demo\b.psd" "x"
Set-Content "$demo\c.mp4" "x"
Set-Content "$demo\readme.txt" "skip"
$db = Join-Path $dbdir "catalog.sqlite"
uv run madify --db $db scan $demo
uv run madify --db $db tag --id 1 --title "Clip One" --tag demo --tag photo
uv run madify --db $db list
uv run madify --db $db search --query clip
uv run madify --db $db rename --id 1
Commands
| Command | Purpose |
|---|---|
madify scan <dir> |
Upsert supported media under <dir> |
madify tag --id N | --path P |
Set title/description/tags (+ XMP sidecar) |
madify rename [--id N] |
Rename from titles |
madify list |
List all assets |
madify search [--query Q] [--tag T] |
Filter assets |
Global: --db PATH (default madify.sqlite).
Architecture
| Module | Role |
|---|---|
cli.py |
Argparse wiring |
models.py / errors.py |
Domain types and errors |
media_kinds.py |
Extension → image / PSD / video |
tagging.py |
Normalize/validate; tag merge/replace |
naming.py |
Title → filename + collisions |
ports.py |
Clock / FS / Catalog / MetadataWriter protocols |
scan.py / tag_asset.py / rename_assets.py |
Use cases |
query.py |
List / search filters |
xmp_sidecar.py |
XMP sidecar writer |
sqlite_catalog.py / local_fs.py / system_clock.py |
Adapters |
Development
uv sync --group dev
uv run pre-commit install
uv build
uv run ruff check .
uv run ruff format --check .
uv run pytest -q --cov=madify --cov-report=term-missing
uv run pdoc madify -d google -o docs/api
API HTML lands in docs/api/ (gitignored).
See CHANGELOG.md, SECURITY.md, and CODE_OF_CONDUCT.md.
PyPI trusted publishing (one-time, manual)
- Create a PyPI project named
madify(or claim the name). - On PyPI → Publishing → add a trusted publisher:
- Owner:
scottmills306 - Repository:
Madify - Workflow:
publish.yml - Environment:
pypi
- Owner:
- On GitHub → Settings → Environments → create
pypi(optional protection rules). - Publish a GitHub Release — the Publish workflow uploads wheels via OIDC (no API token).
License
MIT — see LICENSE. Copyright (c) 2026 Scott Mills.
Appendix: Supported extensions
Classification is extension-only (case-insensitive). Anything else is skipped on scan.
| Kind | Extensions |
|---|---|
| Image | .jpg, .jpeg, .png, .gif, .webp, .tif, .tiff, .bmp, .heic, .heif, .raw, .cr2, .nef, .arw, .dng, .orf, .rw2 |
| Photoshop | .psd, .psb |
| Video | .mp4, .mov, .mkv, .avi, .webm, .m4v, .wmv, .mpg, .mpeg, .3gp |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file madify-0.2.0.tar.gz.
File metadata
- Download URL: madify-0.2.0.tar.gz
- Upload date:
- Size: 16.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f9c6b58832670c309bc54b2d0e0941614477b1c624722f3072e6542a5d83b37
|
|
| MD5 |
297393d92b9f729d2cf5247db373b9c9
|
|
| BLAKE2b-256 |
561e8f1d1e9357413d895569ae1005f810901b3b0b93e046473a306f682f048d
|
Provenance
The following attestation bundles were made for madify-0.2.0.tar.gz:
Publisher:
publish.yml on sgm-audio/Madify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
madify-0.2.0.tar.gz -
Subject digest:
5f9c6b58832670c309bc54b2d0e0941614477b1c624722f3072e6542a5d83b37 - Sigstore transparency entry: 2213365864
- Sigstore integration time:
-
Permalink:
sgm-audio/Madify@a8c090c01a434e7b22cb5b9efc7831020662f88f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/sgm-audio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a8c090c01a434e7b22cb5b9efc7831020662f88f -
Trigger Event:
release
-
Statement type:
File details
Details for the file madify-0.2.0-py3-none-any.whl.
File metadata
- Download URL: madify-0.2.0-py3-none-any.whl
- Upload date:
- Size: 23.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7805fa5ae44477845c40063286dfc8651d69c25a8fcedb14f7f8df3def4d4e1
|
|
| MD5 |
03f67d9fb8b005ffb4278afcd6f5c859
|
|
| BLAKE2b-256 |
b05170b0737fae5160a4608ddbd5f34d64bb977872be77dd11d7e154e3606314
|
Provenance
The following attestation bundles were made for madify-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on sgm-audio/Madify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
madify-0.2.0-py3-none-any.whl -
Subject digest:
f7805fa5ae44477845c40063286dfc8651d69c25a8fcedb14f7f8df3def4d4e1 - Sigstore transparency entry: 2213365983
- Sigstore integration time:
-
Permalink:
sgm-audio/Madify@a8c090c01a434e7b22cb5b9efc7831020662f88f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/sgm-audio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a8c090c01a434e7b22cb5b9efc7831020662f88f -
Trigger Event:
release
-
Statement type: