feat(metadata,people): richer metadata editing + face-naming flow

Metadata (sidebar):
- New editable fields: Title, Credits section (Artist/Copyright/
  License via Details), GPS Altitude.
- Video facts in the File section: Duration, FPS, Codec — required
  fixing videoFile(): PhotoPrism serializes MediaType as the bare word
  "video", so the old startsWith('video/') check never matched and the
  helper always fell back to the JPEG poster.
- Timezone correctness: buildTakenAtPatch no longer forces
  TakenAtLocal=UTC; it preserves the photo's existing UTC↔local offset
  (per-photo in bulk edits) so PhotoPrism can't clobber manual date
  edits when recomputing from TimeZone, and Year/Month/Day now derive
  from local wall-clock time.

People (was "disabled" — really: zero subjects because naming is what
creates a person, and the UI had no naming flow; prod has 40k face
markers in 790 unnamed clusters):
- Sidecar GET /api/sidecar/subjects — scoped people list via one
  markers→files→photos SQL pass (labels pattern), replacing the
  client-side probe-per-subject N+1 filter.
- Sidecar GET /api/sidecar/faces/unnamed — the caller's unnamed face
  clusters with count, crop thumb, and a representative marker UID.
- "Name new faces" panel on /tags/people: face-crop cards with inline
  name input; naming uses PhotoPrism's own flow (PUT /markers/:uid
  {Name, SubjSrc:manual}, verified against PP source) which creates
  the Subject and propagates across the cluster.
- Scoped proxy: marker PUT / subject-clear DELETE now allowed with
  per-marker ownership checks (was blanket-forbidden, which would have
  blocked naming for scoped users).
- Per-photo People chips in the sidebar from named Files[].Markers,
  linking to the person's page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 18:35:27 +02:00
parent a239cece10
commit 246d159d93
11 changed files with 624 additions and 53 deletions

View File

@@ -82,11 +82,13 @@
() =>
patchTargets(
ids,
buildTakenAtPatch(iso),
// Per-photo patch so each photo keeps its own UTC↔local
// offset when the date is stamped across a selection.
(p) => buildTakenAtPatch(iso, p),
label,
(p) =>
p.TakenAt
? buildTakenAtPatch(p.TakenAt)
? buildTakenAtPatch(p.TakenAt, p)
: ({ TakenSrc: '' } as UpdatePhotoBody)
),
label