photoprism/internal/meta
Keith Martin b6687b6c29
Tests: Improve isolation and add fixes #5263
* Tests: fix random selection of deleted record causing test failure
* Tests: fix test folders being left after test
* Tests: ensure that required records are available
* Tests: make each package execute against it's own testdata folder to ensure no interpackage test failures
* Tests: add unit tests of CleanupTestFolder
* Tests:  Allow defer in TestMain
* Tests: rename testMain to runTestMain
2026-07-12 05:47:21 +02:00
..
iptc Metadata: Add IPTC 2021.1 JSON/YAML Schemas incl. test files #1143 #2252 2022-04-14 12:18:05 +02:00
testdata Metadata: Fix XMP body serial and add tiff: field fallbacks #2260 2026-07-07 11:44:33 +00:00
data.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
data_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
docs.go Meta: Add const, func, and struct comments for easier troubleshooting 2025-09-30 22:00:30 +02:00
duration.go Search: Parse lat,lng string into float64 coordinates #4930 #4812 2025-04-19 16:04:44 +02:00
duration_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
exif.go CI: Apply Go linter recommendations to "internal/meta" package #5330 2025-11-22 12:38:24 +01:00
exif_parser.go CI: Apply Go linter recommendations to "internal/meta" package #5330 2025-11-22 12:38:24 +01:00
exif_test.go CI: Apply Go linter recommendations to "internal/meta" package #5330 2025-11-22 12:38:24 +01:00
gps.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
gps_test.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
json.go CI: Apply Go linter recommendations to "internal/meta" package #5330 2025-11-22 12:38:24 +01:00
json_exiftool.go Lightbox: Add support for 360° photos and videos #352 #4718 #5623 2026-06-17 03:26:25 +02:00
json_gphotos.go Metadata: Clamp invalid Google JSON GPS coordinates to geo bounds #5373 2026-02-11 13:42:58 +01:00
json_motion_test.go CI: Apply Go linter recommendations to "internal/meta" package #5330 2025-11-22 12:38:24 +01:00
json_test.go Lightbox: Add support for 360° photos and videos #352 #4718 #5623 2026-06-17 03:26:25 +02:00
keywords.go Meta: Add const, func, and struct comments for easier troubleshooting 2025-09-30 22:00:30 +02:00
keywords_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
meta.go Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
meta_test.go Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
README.md Metadata: Map XMP dc:subject to Subject field, not Keywords #2075 #2260 2026-06-30 10:12:45 +00:00
report.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
resolver.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
resolver_test.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
sanitize.go CI: Apply Go linter recommendations to "internal/meta" package #5330 2025-11-22 12:38:24 +01:00
sanitize_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
video.go Meta: Add const, func, and struct comments for easier troubleshooting 2025-09-30 22:00:30 +02:00
video_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
xmp.go Metadata: Map XMP dc:subject to Subject field, not Keywords #2075 #2260 2026-06-30 10:12:45 +00:00
xmp_bench_test.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
xmp_document.go Metadata: Fix XMP body serial and add tiff: field fallbacks #2260 2026-07-07 11:44:33 +00:00
xmp_document_test.go Metadata: Fix XMP body serial and add tiff: field fallbacks #2260 2026-07-07 11:44:33 +00:00
xmp_migration_test.go Metadata: Map XMP dc:subject to Subject field, not Keywords #2075 #2260 2026-06-30 10:12:45 +00:00
xmp_security_test.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
xmp_test.go Metadata: Map XMP dc:subject to Subject field, not Keywords #2075 #2260 2026-06-30 10:12:45 +00:00

PhotoPrism — Metadata Pipeline

Last Updated: June 30, 2026

Overview

The internal/meta package extracts, normalizes, and reports metadata from images, videos, and sidecars (Exif, XMP, JSON). It produces a meta.Data struct that downstream components (indexer, UI, API) consume for dates, GPS, camera/lens info, keywords, and motion-photo flags. The package aims to be loss-tolerant (accepts imperfect files), deterministic (stable parsing order), and explicit about fallbacks.

Guidelines

  • Keep nanosecond precision in meta.Data; adjust consumers/tests instead of truncating here.
  • When comparing or persisting times, be aware of second-only storage in entity and DB layers.
  • For stacking or dedupe features, use second-based keys unless the DB schema is upgraded.
  • When adding new parsers, ensure they fail softly and add test fixtures mirroring real-world oddities.

Time & Precision

  • Parsers preserve sub-second timestamps found in Exif/XMP/JSON (TakenAt, TakenAtLocal, TakenNs). Tests expect nanosecond precision where available.
  • Downstream persistence truncates to whole seconds:
    • MediaFile.TakenAt() truncates meta.Data timestamps to seconds before caching.
    • Entity columns photos.taken_at, photos.taken_at_local, and files.photo_taken_at are DATETIME (no fractional seconds).
    • YAML metadata backups serialize the entity values, so they also lose sub-second precision.
  • Stack/search logic remains second-based (MapKey uses takenAt.Unix()), so nanoseconds do not affect grouping or comparisons.
  • If future work needs sub-second storage, columns must switch to DATETIME(6) (or similar) and the truncation in MediaFile.TakenAt() removed.

Parsing Order & Fallbacks

  • Exif → XMP → JSON (ExifTool/GPhotos/motion) → filename → filesystem mtime. Each stage logs source and errors but continues when safe.
  • Brute-force Exif search is used when native parsers fail; errors are logged with context.
  • GPS parsing supports decimal, DMS (51 deg 15' 17.47" N), and the 2-component Adobe XMP form (52,30.4567N); regexes are kept simple and precompiled.

XMP Sidecar Reader

The .xmp sidecar reader (xmp.go + xmp_document.go) is XPath-based on antchfx/xmlquery and namespace-aware via xpath.CompileWithNS. Each accessor declares a chainXPath priority list; the engine evaluates links left-to-right and returns the first non-empty match. Composition (Lat sign from GPSLatitudeRef, sub-second join from SubSecTimeOriginal, etc.) lives in the relevant accessor — never in the chain engine.

  • Loader security guards. Load rejects sidecars larger than 1 MiB (ErrXmpFileTooLarge) and documents nesting deeper than 64 elements (ErrXmpTooDeep). XXE and DTD attacks are mitigated by encoding/xml's default behavior (no external entity resolution); xmp_security_test.go is the regression guard.
  • Element-or-attribute helper. RDF/XML allows scalar properties to be expressed as either child elements or attributes on rdf:Description. The elemOrAttr(qname) helper builds a union XPath that matches both — required because digiKam emits xmpMM:*/exif:*/tiff:* as attributes while Adobe writes them as child elements.
  • Adding an accessor. Declare a chainXPath at package init using mustCompile (or elemOrAttr for scalar fields), document the priority chain in a one-line comment, then add the accessor that calls firstNonEmpty (for scalars) or queryAll (for rdf:Bag/rdf:Seq). Wire the new field into xmp.go with the existing "set only when non-empty" pattern.
  • Source priority. Sidecar values are tagged SrcXmp (priority 32), which outranks SrcMeta (priority 16) at the entity layer. Re-indexing a photo after the sidecar has been added overwrites previously-SrcMeta values without a database wipe.
  • Keywords vs. Subject. dc:subject (Adobe's "Keywords" panel) maps to the descriptive Details.Subject field — never the Details.Keywords field — matching the embedded/ExifTool path where data.Subject comes from the dc:subject-backed Subject tag and data.Keywords from IPTC Keywords. The XMP path adds only the derived flash/panorama/hdr keywords. Hierarchical-label and face-region parsing (Labels, people Subjects) is a planned extension tracked under epic #2260.
  • Coverage. The fixture corpus under testdata/xmp/{adobe,darktable,digikam,synthetic}/ documents the full set of supported tags and their per-fixture provenance.

Motion Photos & Embedded Media

  • Motion-photo JSON readers set HasThumbEmbedded / HasVideoEmbedded, Codec, Duration, and capture accurate timestamps (including ns) when present.
  • Time zones from motion metadata are respected; missing zones fall back to UTC.

Sanitization

  • SanitizeString, SanitizeUnicode, and related helpers strip binary markers, quotes, and invalid Unicode; filenames and keywords use lower-case, dash/underscore-safe regexes.
  • Lower-case regex and quote removal now use ReplaceAll and raw strings to avoid double escaping.
  • Google Photos JSON coordinates are clamped to hard latitude/longitude bounds with geo.ClampCoordinateBounds before assigning meta.Data.Lat and meta.Data.Lng.

Docs & References

  • External tag references are listed in docs.go.
  • Tests under internal/meta/testdata cover Exif, XMP, motion photos, and edge cases (missing headers, panoramas, time offsets).