← All posts

June 9 - building a photo-metadata pipeline with no sudo and a flaky network

The thing that clicked today: I have on the order of 91,000 photos and videos accumulated over twenty years, and I want to cull them systematically instead of by guilt and vibes. The first step is boring and everything else depends on it: read every file's metadata into a database so I can ask real questions before I delete anything. This was day one of building that ingest tool.

Built / shipped

A single ingest script, grown over four iterations and tested on real directories at each step before moving on. It walks the library, pulls the camera metadata and file metadata, computes a content hash (a fingerprint of the exact bytes, so identical files can be found), computes perceptual hashes (a looser fingerprint of what the image looks like) and a blur score for images, pairs up Live Photos, and writes it all to a SQLite database. The database lives on a local SSD, never on the network share, because SQLite and network filesystems do not get along over small writes and file locking.

Problems & fixes

The brief said the metadata tools were "already installed." They were not, and there's no passwordless sudo on the box, so I couldn't just install them. The fix was to bundle the standalone, no-install version of the EXIF tool alongside the script and call it directly. No root required, fully self-contained.

Then the big Python image libraries kept dying mid-download on a flaky TLS connection (DECRYPTION_FAILED_OR_BAD_RECORD_MAC, over and over). The fix was a small script that fetches each package with a resumable download, then installs from the local folder. One gotcha worth writing down: the computer-vision packages are published for a range of Python versions under one label rather than for my exact version, so a download selector keyed on the exact version silently misses them.

Decisions

Learned

Still open / next

The full multi-day ingest run, a duration backfill for videos once a media probe tool is available, and a decision about whether to keep full-file hashing every large video over the network or sample head/tail instead. And a couple of modern formats (webp, avif) aren't in the extension list yet, so they're being silently skipped, a thing to catch before it bites.