From c1285964707d775e6691eac5a52dcc856f95cb68 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Thu, 7 May 2026 12:40:50 -0400 Subject: [PATCH] date_resolver: drop -fast2 so MP4 moov-at-end files resolve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For QuickTime/MP4 files whose `moov` atom sits at the end of the file (non-faststart — common for Snapchat exports and any MP4 muxed without `-movflags +faststart`), `-fast2` causes exiftool to skip the trailer and return no `CreateDate` / `MediaCreateDate`, dropping the resolver to the `fs_time` fallback for files that actually have a real capture date. Reported cases: Snapchat-477624257.mp4 fs_time: 2026-05-04 (today, file was just modified) real: QuickTime CreateDate 2018-09-02 action_compound_cc92e65b709d1deb895b4c2a9484fc6a.mp4 fs_time: 2026-05-04 real: MediaCreateDate 2018-03-01 The waterfall pre-filters to files kamadak-exif couldn't read, so the JPEG fast-path is already covered without `-fast2`. Paying full-scan cost on the residual is the right trade. The per-tick drain re-resolves `source = 'fs_time'` rows, so existing rows recover automatically on the next watcher tick after deploy — no SQL migration needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/date_resolver.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/date_resolver.rs b/src/date_resolver.rs index 8498cb8..f8b156c 100644 --- a/src/date_resolver.rs +++ b/src/date_resolver.rs @@ -230,12 +230,21 @@ fn exiftool_available() -> bool { /// One-file exiftool invocation. Used by the upload + GPS-write paths, /// which deal with one file at a time. The batch path uses /// `exiftool_dates_batch` so we don't pay subprocess startup per row. +/// +/// Notably absent: `-fast` / `-fast2`. For QuickTime/MP4 files whose +/// `moov` atom sits at the end (non-faststart, common for Snapchat +/// exports and any MP4 muxed without `-movflags +faststart`), `-fast2` +/// causes exiftool to skip the trailer and return no `CreateDate` / +/// `MediaCreateDate`, dropping us to the `fs_time` fallback for files +/// that actually have a real capture date. We pre-filter to files that +/// kamadak-exif couldn't read, so the JPEG fast-path is already covered +/// — paying full-scan cost on the residual is the right trade. fn exiftool_date_single(path: &Path) -> Option { if !exiftool_available() { return None; } let mut cmd = Command::new("exiftool"); - cmd.arg("-j").arg("-q").arg("-d").arg("%s").arg("-fast2"); + cmd.arg("-j").arg("-q").arg("-d").arg("%s"); for tag in EXIFTOOL_DATE_TAGS { cmd.arg(format!("-{}", tag)); } @@ -261,7 +270,10 @@ fn exiftool_dates_batch(paths: &[&Path]) -> HashMap { } let mut cmd = Command::new("exiftool"); - cmd.arg("-j").arg("-q").arg("-d").arg("%s").arg("-fast2"); + // No `-fast2` — see exiftool_date_single for the rationale (QuickTime + // moov-at-end files miss CreateDate / MediaCreateDate when the trailer + // is skipped). + cmd.arg("-j").arg("-q").arg("-d").arg("%s"); for tag in EXIFTOOL_DATE_TAGS { cmd.arg(format!("-{}", tag)); } -- 2.49.1