The archive this app was built on is 314,391 files: about 223,000 photos and 91,000 videos, sitting on a 6 TB external drive, 1.7 TB of it photos and videos, spanning 1998 to 2026. It's one family's entire visual record, and like many families, they went through two very different eras of keeping it.
From 2004 to 2016, every event got its own folder, named by hand, the day after it happened: 2012-04 — the spring trip, that kind of thing. Roughly 600 of these. Then, sometime around 2016, the folders stopped. What replaced them was the modern default: giant, flat phone-backup dumps, thousands of files named IMG_4471.HEIC, grouped by nothing except the date range a backup happened to run. (If your recent years are still trapped on the phone itself, the iPhone offload guide covers getting them onto the drive.)
The obvious plan for a tool like this is to auto-detect events and auto-generate names for the whole archive, named era and dumped era alike. What I didn't expect was that the named era wasn't just nicer to look at. It was a labeled dataset, twelve years deep, sitting there for free.
Ground truth, not a guess
Six hundred human-drawn event boundaries are the kind of thing you'd normally pay for. This family had already decided, event by event, where one moment ends and the next begins. So instead of picking an event-clustering approach off a shelf and hoping it felt right, I treated the named folders as ground truth for how this particular family splits time into events, and tuned against them directly.
The clustering approach itself is deliberately simple: a time-gap pass, grouping photos into the same event whenever the gap between consecutive timestamps stays under a threshold. The only real knob is where you set that gap. I measured it with pairwise co-membership agreement. For every pair of photos in the named era, check whether the human folder split agrees with the algorithm's split (both together, or both apart), and score the fraction that agree. Sweep the threshold, plot the curve, take the peak.
The peak landed at a 24-hour gap, with an F1 of 0.93 against the named folders. That is not a benchmark from a paper. It is a number tuned against one family's twelve years of judgment calls about where their events begin and end. The right clustering threshold isn't universal; it's a property of how a specific family lives. This family apparently doesn't split a two-day trip into two events just because they slept somewhere in between. A 24-hour gap captures that. A different family's named folders might peak somewhere else entirely, and the app is built to re-measure per archive rather than assume.
Once events cluster the way the family already clusters them, naming is the next problem, and the same trick applies again: the 600 named folders are also a style guide. 2012-04 — the spring trip shows the family's convention: date prefix, em dash, lowercase, short and specific rather than generic ("the spring trip," not "Family Outing"). Auto-naming for the un-named decade learns that voice instead of imposing a generic one.
The wrinkles that would have quietly corrupted everything
Three things came up while building this that are worth writing down. None is visible until you look, and each would have silently degraded the timeline if I'd trusted the obvious source of truth.
Camera clocks lie, and folder names don't. In the named era, camera clocks were sometimes a year off, wrong in the way a camera clock gets wrong when nobody resets it after a battery change. The fix was to stop trusting EXIF as the default truth for dating and instead let the human-typed folder date prefix win whenever the two disagree. The person who typed 2012-04 into a folder name the week it happened is a better witness than a camera's internal clock that hasn't been checked in years.
Digitized media carries the wrong date twice. Old camcorder tapes, digitized later, are worse: the file's timestamp reflects when the digitization happened, not when the footage was shot. For this class of file, EXIF is wrong in a structural way. The fix is the same shape as the camera-clock fix, one level up: for scanned and digitized media, the year folder the family filed it under overrides the file's own timestamp, because the folder is describing the memory and the timestamp is describing the scanning session.
Trips need a definition of "home" that isn't fooled by enthusiasm. To detect a trip, meaning a departure from normal life, the app needs to know where "normal life" was, and that shifts over 28 years as a family moves. Counting photos per location fails: a five-day vacation generates far more photos than five ordinary days at home, so a location a family visited once for a week can out-vote the house they actually lived in for six months. The fix is a home model that votes per half-year using distinct days present at a location, not photo counts. A place only counts toward "home" by being lived in on many different days, not photographed a lot on a few of them. Counting days instead of photos is what lets a trip register as a departure from home instead of quietly rewriting where home was.
The threshold doesn't generalize. The method does: if part of an archive was hand-labeled by the people who lived it, that part is ground truth, and it's worth measuring against it instead of assuming a default is close enough.