Extended metadata schema
The PhotoSpeak XMP-ps namespace — generated from the shared schema
PhotoSpeak stores everything it knows about a photo inside the photo file itself, as XMP metadata. The descriptive fields use standard namespaces (Dublin Core, IPTC, MWG regions); everything richer — voice recordings, transcripts, people, place, story, embeddings, provenance — lives in a custom namespace so it travels with the image and is yours to keep.
http://photospeak.app/xmp/1.0/ps (e.g. ps:Weather)Recovering your data without PhotoSpeak
Every enriched image also carries a plain-language manifest in the standard
photoshop:Instructions field (visible in Photoshop, Lightroom, Bridge and Windows file
details), naming this namespace and listing its fields. So even if PhotoSpeak is gone, the image tells
you what it holds and where to read it. The data is standard XMP: any XMP library (for example the
open-source ExifTool) can extract the raw ps:* values, then decode them as described
below. The free Universal Gallery viewer does all of this in your browser, with
no account and no server.
How the heavy fields are packed
- Vectors (CLIP / face / voice embeddings) - little-endian float16 -> zlib deflate -> base64, stored as text. Each entry is tagged with its model id and dimension so it can be decoded without guessing.
- Audio (transcription, context notes, voice threads) - Opus in an Ogg container, base64-encoded. Duration and a short SHA-256 hash travel alongside for integrity.
- Images (photographer portrait, reversible watermark patch) - JPEG (portrait) or lossless PNG (watermark patch), base64-encoded, with a short SHA-256 integrity hash.
- Structured data (regions, voice threads, mode data, ledgers) - UTF-8 JSON stored as text. Face/object regions also use the standard MWG RegionInfo structure so other tools can read them.
Every field in this namespace
This namespace defines 82 fields. Not every image contains every field — each carries only what applies to it.
| Field | Type | Description |
|---|---|---|
| Context note (audio) | ||
| ps:ContextNote | string | Context note record identifier |
| ps:ContextNoteDuration | string | Duration of the context note audio |
| ps:ContextNoteFormat | string | Audio format of the context note source |
| ps:ContextNoteHash | string | Hash of the context note audio file |
| ps:ContextNoteText | string | Full text of the context note |
| Continuation sets | ||
| ps:ContinuationOf | string | pHash of the parent image this is a continuation of |
| ps:LinkedImages | string | JSON array of image UUIDs linked in this continuation set |
| ps:VolumeNumber | integer | Volume number in a continuation set (1 = original, 2+ = overflow) |
| Derivatives | ||
| ps:AnimatedFacePaths | string | Paths to animated face derivatives |
| ps:ColourisedPath | string | Path to the colourised version of this image |
| ps:DepthMapPath | string | Path to the depth map for this image |
| Detection | ||
| ps:DetectReviewedTypes | string | Detection types that have been reviewed |
| ps:DetectSuppressed | string | Detection results suppressed by the user |
| Diarization | ||
| ps:TranscriptionDiarization | string | Speaker diarization L1 block (JSON) for the transcription audio. |
| ps:VoiceEmbeddings | string | Per-segment packed voice embeddings for the transcription audio. |
| Embeddings | ||
| ps:FaceEmbeddings | string | Per-region face embeddings (model-tagged JSON array). See desktop xmp.py. |
| Enrichment control | ||
| ps:EnrichmentOptOut | string | When '1', the geocode/environment/weather pipeline steps skip this image so derived fields can be hand-curated. |
| Environment | ||
| ps:AirQuality | string | Air quality index at capture location |
| ps:DominantColors | string | Dominant colour palette extracted from the image |
| ps:Elevation | string | Elevation at capture location |
| ps:GoldenHour | string | Golden hour window at capture date and location |
| ps:MoonPhase | string | Moon phase at capture date |
| ps:NearbyPOIs | string | Points of interest near capture location |
| ps:Season | string | Season at capture date and location |
| ps:SunPosition | string | Sun altitude and azimuth at capture time |
| ps:Sunrise | string | Sunrise time at capture date and location |
| ps:Sunset | string | Sunset time at capture date and location |
| ps:Terrain | string | Terrain type at capture location |
| ps:Timezone | string | Timezone at capture location |
| ps:UVIndex | string | UV index at capture time and location |
| ps:Weather | string | Weather conditions at time and place of capture |
| Hashes | ||
| ps:ClipEmbedding | string | CLIP model embedding vector |
| ps:ClipEmbeddingHash | string | Hash of the CLIP embedding for integrity check |
| ps:ClipEmbeddingModel | string | CLIP model identifier used for embedding |
| ps:ImageDHash | string | Difference hash of the image |
| ps:ImageHashDimensions | string | Dimensions used when computing image hashes |
| ps:ImagePHash | string | Perceptual hash of the image |
| Identity / author | ||
| ps:AppVersion | string | PhotoSpeak app version that last wrote metadata |
| ps:PhotographerIdentityId | string | Links the author to a row in the identity library (unified people model). |
| ps:PhotographerPortrait | string | Embedded or referenced photographer portrait |
| ps:PhotographerPortraitHash | string | Hash of the photographer portrait data |
| ps:SourceNotes | string | Notes about the source or origin of the image (free-text journal entry) |
| Modes (knowledge substrate) | ||
| ps:Collections | string | RESERVED, DORMANT (do not reuse this tag). Was the planned per-image collection-membership codec (JSON array [{id, name}]). SUPERSEDED for curated Collections by the account-data model (Collections S2-S6, 2026-07-05..06): membership now lives in the DB + profile mirror keyed by ImageUUID, NOT in the image - see docs/adr/0007 (Collections half) + docs/current/Image_Grouping_And_Relationships_WIP.md. The tag name stays reserved as the likely foundation for Linked-images (Concept B) / share-in-file, so it is intentionally NOT deleted. |
| ps:ModeData | string | Knowledge substrate v2 (ADR 0007). JSON ARRAY of self-describing mode entries: [{mode, name, version, fields:[{k, label, type, unit?, value}]}]. Self-describing so any PhotoSpeak client or library reader interprets it WITHOUT the mode definition. Field option-lists/order/icon/lens-hints/capture-prompts live in the definition registry (profile/ mirror), NOT the image. |
| Provenance | ||
| ps:CopiedFrom | string | The parent instance's ImageUUID. Stamped when a same-machine duplicate (same UUID, different file) is renumbered to a fresh instance UUID, recording the lineage link (C2PA ingredient-reference pattern). |
| ps:ImageContentHash | string | Exact sha256 of the image's pixel bytes. Confirms a byte-identical copy (vs corruption) when a same-machine ImageUUID collision is detected. The exact UUID collision is the fork trigger; this is the copy-vs-corruption confirmation. |
| ps:ImageUUID | string | Unique identifier for this image instance |
| ps:OriginInstance | string | Instance identifier of the originating PhotoSpeak install |
| ps:OriginOwner | string | Owner identifier from the originating instance |
| ps:OriginPlatform | string | The platform on which this image's PhotoSpeak instance ORIGINATED (desktop/mobile/web). Set ONCE at mint time alongside ImageUUID + OriginInstance and preserved thereafter - a later edit on another platform does NOT change it (that is AppVersion / FieldHLCs). A fork mints a new instance, so it takes the forking platform; CopiedFrom preserves the lineage. |
| ps:ProvenanceChain | string | The HLC-ordered, hash-linked event ledger (JSON array of ledger entries) - the durable twin of the coordinator's sync_events. See docs/reference/Ledger_Entry_Schema.md. Supersedes the SyncVersion/PublishedVersion slots. |
| ps:PublishedVersion | integer | Version number when last published |
| ps:SyncVersion | integer | Monotonic sync version counter |
| Scene analysis | ||
| ps:EraMismatch | string | Flag indicating EXIF date conflicts with estimated era |
| ps:EstimatedEra | string | Estimated decade or era the photo depicts |
| ps:EstimatedEraScore | string | Confidence score for era estimation |
| ps:IsBlackAndWhite | string | Whether the image is black and white |
| ps:PhotoOfPhoto | string | Whether this image is a photo of another photo |
| ps:PhotoOfPhotoScore | string | Confidence score for photo-of-photo detection |
| Species | ||
| ps:Species | string | Species detected in the image |
| Subject (vs capture) | ||
| ps:SubjectDate | string | What the photo is OF (ISO-8601 partial e.g. 1960 / 1960-05), distinct from EXIF capture date. |
| ps:SubjectGPS | string | Lat/lon of the scene depicted, distinct from capture GPS. |
| Sync / merge | ||
| ps:FieldHLCs | string | {field_name: hlc} - the HLC of the last write to each scalar metadata field, so cross-device edits converge last-writer-wins on HLC instead of arrival order. Travels with the image as part of merge state. |
| Transcription (audio) | ||
| ps:Transcription | string | Audio transcription record identifier |
| ps:TranscriptionDuration | string | Duration of the transcription audio |
| ps:TranscriptionFormat | string | Audio format of the transcription source |
| ps:TranscriptionHash | string | Hash of the transcription audio file |
| ps:TranscriptionText | string | Full text of the audio transcription (human-editable; feeds the LLM extractor) |
| Transcription boundaries | ||
| ps:TranscriptionOffsets | string | Per-recording boundaries within the appended transcription audio. |
| Voice threads | ||
| ps:VoiceThread1 | string | Voice thread slot 1 |
| ps:VoiceThread2 | string | Voice thread slot 2 |
| ps:VoiceThread3 | string | Voice thread slot 3 |
| ps:VoiceThread4 | string | Voice thread slot 4 |
| ps:VoiceThread5 | string | Voice thread slot 5 |
| ps:VoiceThread6 | string | Voice thread slot 6 |
| ps:VoiceThread7 | string | Voice thread slot 7 |
| ps:VoiceThreadCount | integer | Number of voice threads attached to this image |
| ps:VoiceThreads | string | Voice threads container or summary |
| Watermark | ||
| ps:WatermarkPatch | string | Reversible watermark: lossless clean-crop patch that restores the marked region in PhotoSpeak/HTML viewers. |
| ps:WatermarkPatchHash | string | Integrity hash of the reversible-watermark clean-crop patch. |
| ps:WatermarkRect | string | Placement rectangle of the reversible watermark. |
| ps:WatermarkStyle | string | Style descriptor of the reversible watermark. |