#5799·photoprism

Stacks: Verify embedded content identifiers before stacking same-name photo and video files

Author: magnusfrodellCreated Aug 27, 2026Updated Aug 27, 2026

User Story

As a user importing photos exported from an iPhone, I would like PhotoPrism to verify embedded content identifiers before stacking a photo and a video that share the same base name, so that unrelated files are not merged into a single photo card with mismatched thumbnails, labels, and Live Photo previews.

What problem does this solve, and why would it be valuable to many users?

Same-name stacking (files with an identical base name in the same folder are always grouped) is essential for RAW+JPG pairs, converted sidecars, and Live Photos. However, the rule is unconditional and purely name-based, which makes it produce false positives in a common real-world scenario:

  1. iPhone file numbering (IMG_0001IMG_9999) rolls over and restarts, so a camera roll spanning several years contains multiple different assets with the same base name. On the device they never collide because Apple separates them into DCIM/100APPLE, 101APPLE, etc.
  2. Many export paths (iCloud downloads, drag-and-drop copies) flatten this structure into a single folder. Notably, export tools resolve image-vs-image collisions by appending (1) to the file name — but let image-vs-video collisions coexist silently, since the extensions differ.
  3. On import, an old standalone video (IMG_0182.MOV, e.g. a 40-second clip from 2019) is then stacked with an unrelated new photo (IMG_0182.JPG from 2026). The adopted file is renamed with a hash infix (e.g. 20260310_102511_21A4CDEE.21a4cdee.mov), cementing the false pair in the originals tree.

The result is a photo card whose grid thumbnail is rendered from the unrelated video while title, metadata, and labels come from the primary image — the symptom users have reported in e.g. discussion #5136 ("two completely unrelated pictures were displayed in one thumbnail") and discussion #5016 (Live Photo previews showing the wrong content).

A single flat iPhone export analyzed for this report contained 3,743 same-basename image+video pairs. Ground-truth verification using the Apple ContentIdentifier embedded in both members of genuine Live Photo pairs (read with exiftool) showed that 728 of the 3,743 pairs (19%) were false — unrelated videos stacked onto photos — while 2,929 were verified genuine (86 remained unresolved due to missing metadata). Notably, an initial date-based heuristic misclassified roughly 1,000 genuine pairs as false before identifier verification was introduced, underscoring that file names and timestamps are not reliable signals for this decision: the embedded identifier is the only authoritative one. Every false pair produced a corrupted photo card in the index. There is currently no configuration option that prevents this, since exact-name stacking is not covered by the StackSequences/StackUUID/StackMeta settings.

What solution do you propose?

Apple binds the members of a Live Photo pair with a shared content identifier that is embedded in both the still image and the video. This identifier is already extracted by PhotoPrism's metadata pipeline (MetaData().DocumentID, used today by the StackUUID stacking rule). It can therefore serve as a cheap, authoritative veto:

  • If a same-name photo and video both carry a non-empty unique identifier and the identifiers differ, they are provably not a pair and must not be stacked. The video should instead be indexed as a standalone media file.
  • Files without identifiers (RAW+JPG, converted sidecars, older material) are unaffected and keep the current behavior.

Which alternatives or workarounds have you considered?

No response

Have you verified that no similar issues exist?

  • This is not a support request, and I verified that no similar issues exist

Additional Context

  • Base-name grouping happens in MediaFile.RelatedFiles() (internal/photoprism/mediafile_related.go); stacking decisions and merge logic are applied in internal/photoprism/index_mediafile.go (see the existing StackUUID / HasDocumentID() checks and photo.Merge(...)).
  • The veto only requires comparing already-extracted metadata of the group members before the video is attached to the image's photo entity — no new metadata extraction pass is needed.
  • Affected libraries could be repaired with the existing unstack API (POST /api/v1/photos/:uid/files/:file_uid/unstack); a follow-up photoprism stacks verify maintenance command is out of scope here but would be a natural companion.

Testing Notes

  • Fixture: two unrelated files sharing a base name with different embedded identifiers → must index as two photos.
  • Fixture: genuine Live Photo pair with matching identifiers → must stack exactly as today.
  • Fixture: RAW+JPG pair without identifiers → must stack exactly as today.
  • Regression: photo.Merge paths with StackUUID/StackMeta enabled.

Acceptance Criteria

Importing a flat folder that contains both a genuine Live Photo pair and a name-colliding unrelated video results in: one correctly stacked Live Photo, one standalone video, and a log line documenting the vetoed stack.

Environment

PhotoPrism CE (Docker, photoprism/photoprism), observed on build 260728 with MariaDB 11; reproduced across two independent instances.

Acceptance Criteria

  • MUST NOT stack a photo and a video file that share a base name when both carry non-empty embedded unique identifiers that do not match.
  • MUST keep the current stacking behavior whenever one or both files lack an identifier, so RAW+JPG pairs, sidecars, and legacy material are unaffected.
  • MUST log a clear notice when a same-name stack is vetoed, so users can locate affected files.
  • SHOULD apply a secondary plausibility guard when identifiers are unavailable: a video longer than the Live Photo maximum (~4 seconds) is never the motion component of a photo and SHOULD NOT be stacked as one (it may still be grouped as an ordinary related file where appropriate).
  • MAY expose the verification as a library setting (default: enabled) for operators who prefer the legacy behavior.