Data¶
Picturesque keeps three kinds of data:
Data |
Location |
Source of truth for |
|---|---|---|
Photos and XMP sidecars |
Clean library (user-chosen folder) |
The photo files; decisions shared with other tools |
Catalog |
|
Metadata, scores, series, decisions, import history, settings |
Caches |
App cache directory |
Thumbnails and previews (always rebuildable) |
The catalog is the source of truth for decisions inside Picturesque. XMP sidecars are written from it (ADR-0009); on import, existing sidecar values are read into the catalog.
Catalog entities¶
Entities marked planned do not have tables yet; they are added by the task that introduces them. The rest is in the schema today (Schema and migrations).
erDiagram
PHOTO ||--|{ ASSET : "has files"
PHOTO ||--o{ ANALYSIS : has
PHOTO ||--o| SCORES : has
PHOTO ||--o| EMBEDDING : has
PHOTO ||--o| SERIES_MEMBER : "member of"
PHOTO ||--o| SERIES_EDIT : "corrected by"
PHOTO ||--o| DECISION : has
IMPORT_SOURCE ||--o{ IMPORT_JOB : "photos found"
IMPORT_JOB }o--o| PHOTO : "becomes"
photoOne captured image. Capture time (with UTC offset when known), camera, lens, exposure, GPS, resolved place, orientation.
assetOne file belonging to a photo: the RAW, the JPEG, an XMP sidecar. Stores the path inside the Clean library, size and BLAKE3 content hash. RAW+JPEG pairs are two assets of one photo.
analysisOne analyzer’s result for a photo: analyzer name, version and a JSON result (or
{"error": ...}). A photo is analyzed again when an analyzer’s version changes (ADR-0011), or when the user asks (stale: the result stays in use until the analyzer replaces it).scores(planned)Technical scores are stored as the
technicalanalysis result (Quality scores). AI scores (aesthetics, faces, eyes closed) and the combined score will join them; values needed for filtering and sorting may be copied into columns (#38).embedding(planned)CLIP image embedding and model version. Used for semantic search and visual similarity. Until then, similarity uses a perceptual hash stored as the
similarityanalysis result (Visual similarity).series_member/series_editSeries are derived:
series_membermaps each photo in a series to the series (identified by its first photo) and its position, and is rewritten whenever series are recomputed.series_editholds the user’s corrections (split or join before a photo), which are kept and applied on every run (Series). Members also carry their rank, score and whether they are the best photo (ranked or chosen);best_choicekeeps the user’s choices in order.decisionPick, reject or unflagged; star rating; timestamp of the last change and of the last sidecar write.
import_source/import_job/pipeline_logThe continuous import pipeline (ADR-0012): each source folder (watched or not, when last scanned and why the scan failed), one job per source photo (its files with size and modification time as JSON, stage, error, and the catalog photo once imported or found as a duplicate, and where a failed photo stopped) and the pipeline log (newest 20,000 entries).
Schema and migrations¶
The schema lives in numbered SQL migrations in
crates/infra/src/catalog/migrations/, embedded in the binary and
applied in order when the catalog opens. PRAGMA user_version records
how many have run. Each migration runs in its own transaction.
Migrations are append-only: once merged, a migration is never edited; changes go in a new migration.
A catalog whose
user_versionis newer than the running build is refused rather than modified, so downgrading the app cannot damage it.The catalog uses WAL journaling and enforces foreign keys.
Version |
Migration |
Adds |
|---|---|---|
1 |
|
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
|
8 |
|
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
|
|
13 |
|
|
14 |
|
|
15 |
|
|
Conventions:
Capture time is stored as local ISO 8601 text (
captured_local), which sorts chronologically, plus the UTC offset in seconds when known.Asset paths are relative to the Clean library root and always use
/, so a library on an external drive reads the same on every operating system.A photo without a
decisionrow is unflagged and unrated; queries useLEFT JOINwith defaults.Browsing queries page with keyset cursors on
(captured_local, id); over 10,000 photos a filtered page takes well under 50 ms (tested).A unique partial index on
asset.hash(excluding sidecars) enforces that each RAW or image content is catalogued once.
Identity and duplicates¶
A file’s identity is its BLAKE3 hash. Importing a file whose hash already exists in the catalog is skipped as an exact duplicate (ADR-0008). Near-duplicates (the same scene shot twice) are not deduplicated; they are grouped into series instead.
Data flow¶
flowchart LR
scan[Scan sources] --> meta[Read metadata] --> geo[Reverse geocode]
geo --> plan[Render target paths] --> copy[Copy + verify] --> rec[Record in catalog]
rec --> thumbs[Thumbnails] --> analyze[Scores + embeddings]
analyze --> series[Group series] --> rank[Rank best]
rank --> ui[Timeline UI]
ui --> decide[Decisions] --> xmp[XMP sidecars]