feat(dates): container video dates, filesystem branch, manual edit (#115)
The exiftool branch of the date waterfall now pulls raw strings with group prefixes (one `exiftool -j -q -G1` subprocess per batch, no `-d %s`) and classifies each candidate: - zoned strings (own offset / Z) are true instants; - naive strings from container groups (QuickTime/Track*/Apple) are UTC-rendered creation_time fields -> true instants (utc_direct). Running them through the camera wall-clock cascade was the "videos off by 4-5 hours" bug: it reinterpreted a UTC instant in the server zone; - naive strings from photo-EXIF groups are camera wall clocks and run the offset cascade (OffsetTime* pairing + GPS evidence pulled in the same exiftool pass). The legacy date_taken column stays byte-identical to the old server-local interpretation (reproduced in Rust, DST-aware); the new-contract date_utc / camera_offset_minutes / date_taken_tz_source columns carry the true instant. Manual date edit (POST /image/exif/date) reinterprets the naive-as-UTC epoch the client sent as a wall clock in the user's display zone (camera_tz::manual_date_resolution, tz_source = 'manual'), snapshots the pre-override new-contract triple next to the existing legacy snapshot pair (3 new columns, migration 2026-09-25-000000), and revert restores both pairs. ADR-0001 records the container-date finding; CLAUDE.md date-pipeline section updated.
This commit is contained in:
1 parent
6f020f4201
commit
d2263be6ac
13 files changed
+866
-178
No files matched your search
@@ -380,9 +380,21 @@ land on this date?". Order:
|
||||
QuickTime/MP4 (`MediaCreateDate`, `TrackCreateDate`, `CreateDate`),
|
||||
Apple's `ContentCreateDate`, MakerNote sub-IFDs. Required for
|
||||
videos to land a real date. Single-file at ingest; the per-tick
|
||||
drain feeds the whole batch through one `exiftool -@ -` subprocess.
|
||||
Degrades silently when `exiftool` isn't on PATH (resolver caches the
|
||||
"available" check via `OnceLock`).
|
||||
drain feeds the whole batch through one `exiftool -j -q -G1 -@ -`
|
||||
subprocess (raw strings + group prefixes, parsed in Rust — no
|
||||
`-d %s`). Degrades silently when `exiftool` isn't on PATH (resolver
|
||||
caches the "available" check via `OnceLock`).
|
||||
**Group classification (ADR-0001, ticket #115):** zoned strings
|
||||
(own offset / `Z`) are true instants; naive strings from container
|
||||
groups (QuickTime/Track*/Apple) are UTC-rendered `creation_time`
|
||||
fields → true instants (`utc_direct`, no cascade — running them
|
||||
through the wall-clock cascade was the "videos off by 4–5 hours"
|
||||
bug); naive strings from photo-EXIF groups are camera wall clocks →
|
||||
offset cascade (`OffsetTime*` + GPS evidence pulled in the same
|
||||
pass). The legacy `date_taken` column stays byte-identical to the
|
||||
old server-local `-d %s` interpretation; the new-contract
|
||||
`date_utc` / `camera_offset_minutes` / `date_taken_tz_source`
|
||||
columns carry the true instant.
|
||||
3. **`filename`** — `extract_date_from_filename` in `memories.rs`
|
||||
matches screenshot, chat-export, and timestamp-named patterns.
|
||||
4. **`fs_time`** — `earliest_fs_time(metadata)` (earlier of created /
|
||||
@@ -401,8 +413,9 @@ watcher tick alongside `backfill_unhashed_backlog` (also `src/backfill.rs`). It
|
||||
`date_taken IS NULL` (backed by the `idx_image_exif_date_backfill`
|
||||
partial index), runs the waterfall batch via `resolve_dates_batch`,
|
||||
and writes results via the `backfill_date_taken` DAO method (touches
|
||||
only `date_taken` + `date_taken_source` so EXIF / hash / perceptual
|
||||
columns are preserved). Resolved rows — including the ones the
|
||||
`date_taken` + `date_taken_source` plus the new-contract
|
||||
`date_utc` / `camera_offset_minutes` / `date_taken_tz_source` triple,
|
||||
so EXIF / hash / perceptual columns are preserved). Resolved rows — including the ones the
|
||||
waterfall could only resolve via `fs_time` — are not re-eligible:
|
||||
the resolver is deterministic on file bytes + filename + fs metadata,
|
||||
so re-running on the same inputs lands on the same source every time.
|
||||
|
||||
Reference in new issue
Block a user