Media Metadata Explorer is a local-first tool designed to bring structure to large, unorganized collections of photos and videos. It scans directories, extracts metadata (EXIF, GPS, device info, timestamps), and stores it in a queryable database.
The goal is simple: make your media collection searchable, explorable, and actually usable.
Local-first ingestion and metadata extraction for large personal media collections.
- recursive scan of a root folder
- supported media type detection (images + common videos)
- filesystem metadata collection
- embedded metadata extraction via ExifTool (images) and ffprobe (videos)
- incremental content hashing (
sha256) for duplicate/backup comparison foundations - basic filename datetime parsing
- normalization into a stable flat schema
- persistence in DuckDB
- export to CSV or Parquet
- coverage and failure summary reporting
- Filterable table view
- Photo gallery ("Show Photos")
- Timeline visualization
- Map view (GPS-based)
- Scene classification (e.g. indoor, landscape, people)
- Similarity search ("find photos like this")
- Duplicate detection
- Face clustering and labeling (opt-in)
- Event grouping
- Video frame sampling and tagging
Most people have thousands of photos and videos scattered across folders, devices, and naming conventions. Existing tools are often cloud-based, opaque, or limited.
This project is:
- Local-first (your data stays on your machine)
- Transparent (you control the pipeline)
- Extensible (add your own analysis later)
- Python 3.11+
- ExifTool available on
PATH - ffprobe (from FFmpeg) available on
PATHfor video metadata extraction - ffmpeg available on
PATHfor video thumbnail generation - Pillow (installed via project dependencies) for thumbnail generation
Install ExifTool:
- macOS:
brew install exiftool - Ubuntu/Debian:
sudo apt-get install libimage-exiftool-perl - Windows: install ExifTool and ensure
exiftoolis available in terminal
Install ffprobe (FFmpeg):
- macOS:
brew install ffmpeg - Ubuntu/Debian:
sudo apt-get install ffmpeg - Windows: install FFmpeg and ensure
ffprobeis available in terminal
pip install -e ".[dev]"python -m photo_archive.cli scan /path/to/root \
--db-path data/db/photo_archive.duckdb \
--export-path data/exports/photo_metadata.parquetBy default, scans are incremental: only new and changed files are sent to metadata extractors (ExifTool for images, ffprobe for videos). Unchanged files reuse previously stored metadata.
The same new/changed subset is used for content hashing so reruns stay fast.
The CLI now prints structured progress lines with timestamp, topic, purpose, expectation, and stage duration.
Final output also includes a Comparison line with mode, duration, extraction-attempt ratio, and new+changed count to compare incremental vs full-rescan runs.
python -m photo_archive.cli scan /path/to/root \
--db-path data/db/photo_archive.duckdb \
--full-rescanTo suppress progress lines:
python -m photo_archive.cli scan /path/to/root --quiet-progresspython -m photo_archive.cli scan /path/to/root --dry-runpython -m photo_archive.cli report --db-path data/db/photo_archive.duckdbThe report command prints:
- latest scan summary
- changed/new/missing counts
- image/video extraction stats
- failed files list
- thumbnail status counts and failed thumbnail list
- video frame status counts and failed video frame list
- non-null coverage by column
Optional flags:
python -m photo_archive.cli report \
--db-path data/db/photo_archive.duckdb \
--scan-id scan_20260330T123717_65c7fea4 \
--failed-limit 100 \
--coverage-sort ascpython -m photo_archive.cli backup-audit \
--db-path data/db/photo_archive.duckdb \
--primary-root /path/to/primary \
--backup-root /path/to/backup \
--limit 200This compares files from the latest scan of each root and reports files present in primary but missing in backup.
python -m photo_archive.cli scan /path/to/root \
--extension .jpg --extension .jpeg --extension .pngpython -m photo_archive.cli thumbs \
--db-path data/db/photo_archive.duckdb \
--out-dir data/thumbnails \
--max-size 512Thumbnail generation is incremental:
- generates thumbnails for supported
imageandvideomedia rows - image thumbnails use Pillow; video thumbnails use
ffmpegframe extraction - always generates for files marked
neworchanged - regenerates if a thumbnail row/file is missing or previous status was failed
- skips unchanged files with an existing successful thumbnail
- cleans stale thumbnail rows/files for media now marked missing
python -m photo_archive.cli frames \
--db-path data/db/photo_archive.duckdb \
--out-dir data/frames \
--interval-sec 10Frame sampling is incremental:
- targets indexed videos only
- samples one frame every
--interval-secseconds (starting at 0s) - always regenerates for
new/changedvideos - regenerates missing/failed frame rows for unchanged videos
- cleans stale frame rows/files for videos marked missing
Install UI extras:
pip install -e ".[ui]"Run:
streamlit run src/photo_archive/streamlit_explorer.pyWhat it shows:
- available fields in your selected table
- non-null count and non-null percentage per field
- least-populated fields
- sample rows for quick inspection
- thumbnail status counts and a live gallery preview (when
thumbnailsexists) - filterable media table (state/status/extension/text) joined with thumbnails
- date/folder/device/GPS filters for focused local exploration
- pagination controls and sortable filtered results for large collections
- filter-synced timeline chart (day/month/year buckets with line/bar toggle)
- filter-synced map preview with GPS point cap and location summary
- draw-a-box map selection to apply lon/lat bounding filters
- one-click filter reset and filtered export to CSV/Parquet
- filtered gallery preview from current query results
file_metadatakeeps the latest state per file (new,changed,unchanged,missing)scansrecords one row per completed scan run with delta counts
A single wide table (file_metadata) is used in Phase 1. It includes:
- file identity and filesystem fields
- extracted and normalized metadata fields
- raw ExifTool JSON payload
- extraction status and error fields
- filename parsing fields
Raw metadata is preserved as JSON to allow future enrichment without reprocessing files.
- Python
- DuckDB (local analytical database)
- ExifTool (metadata extraction)
- ffprobe (video metadata extraction)
- ffmpeg (video thumbnails + frame sampling)
- Pandas / Polars (data processing)
- Pillow (thumbnail generation)
media-metadata-explorer/
├── data/ # database + artifacts
├── src/
│ ├── scanner/ # folder scanning
│ ├── extractors/ # metadata extraction
│ ├── parsers/ # filename parsing
│ ├── db/ # database logic
│ ├── thumbnails/ # thumbnail generation
│ └── utils/
├── scripts/
│ └── run_indexing.py
├── notebooks/ # experiments
└── README.md
- Phase 1: metadata ingestion pipeline
- Robust metadata extraction across formats
- Incremental re-indexing
- UI (Streamlit)
- Map + timeline sync
- Image embeddings
- Face clustering (opt-in)
- Metadata-first: structure before AI
- Local-first: no cloud dependency
- Modular: each step is replaceable
- Progressive complexity: simple → powerful
This is currently a personal project, but ideas and suggestions are welcome.
TBD