Skip to content

Turn Daily Photos into a Searchable Lifelog with n8n and Local AI

A photo library is the densest personal dataset most people own, and the least examined. Every frame carries three distinct disclosures stacked on top of each other, and commercial cloud storage flattens all three into a single terms-of-service checkbox.

Three Privacy Surfaces Hiding in Every Photograph

It helps to classify before building. The pixels expose people, interiors, documents, and places. The EXIF block — timestamps and coordinates, reconstructs routines with a precision no diary matches. The generated caption converts both into searchable statements, which is exactly what makes a lifelog useful and exactly what makes it a liability once it sits on someone else's disk.

That three-way classification points toward one design: originals, derived metadata, model requests, embeddings, and the final Markdown all remain on storage the operator controls. The trade is real. Convenience drops, maintenance rises, and nobody else patches the stack. What is gained is the ability to query memory deeply without renting that capability from a third party whose retention policy can change in a quarter.

Sizing comes before architecture. Inventory something like 100 recent originals and measure the median. As a worked example, 300 files at a measured median of 6 MB consume 1.8 GB before backups, thumbnails, or filesystem overhead enter the calculation. That figure scales badly if it is guessed rather than measured.

Egress Audit Window

After deployment, inspect DNS and firewall logs for 24–72 hours while importing test images. A correctly built pipeline shows no outbound model, telemetry, geocoding, or storage requests during that window. If anything leaves the network, find it before the historical import begins.

Where n8n Sits in the Ingestion Path

n8n functions here as a transaction coordinator. It catches the file, validates it, stages it, and hands it onward; it is a poor permanent image store and should never be treated as one.

Two entry points cover almost every setup. A phone can POST directly to an authenticated webhook on the private network, or a synchronization tool can drop the untouched file into an inbox folder that n8n watches. Both work. The choice usually comes down to whether the phone is reliably on the LAN at capture time.

Watched folder configuration

Poll every 10–30 seconds. Before processing, require two identical file size readings taken 5 seconds apart, which prevents n8n from grabbing a half-synced file mid-write. That single guard eliminates most truncated-image errors downstream.

Webhook configuration

Send multipart/form-data with one binary field named data, and retain fileName, mimeType, and fileSize as metadata alongside it. Set the accepted payload ceiling somewhere between 25 and 100 MB, chosen against the largest phone panorama or RAW-derived export expected in practice. The n8n documentation on webhook triggers covers the authentication options in detail.

Bind the endpoint to the private LAN or a localhost-facing reverse proxy, require an unguessable credential, and allow 120–300 seconds for the downstream local-model call rather than leaving the connection open indefinitely. The first nodes in the workflow should validate MIME type and filename, preserve the upload under the binary property, and copy it to a staging path. Nothing else.

Timestamps, Coordinates, and the SHA-256 Fingerprint

Parse DateTimeOriginal first. When the camera supplies OffsetTimeOriginal, combine the two and store the normalized instant separately from the human-facing local time. Skip that separation and travel weeks will reorder themselves incorrectly in the journal, which is difficult to repair after a few thousand records.

GPSLatitude, GPSLongitude, GPSLatitudeRef, and GPSLongitudeRef are read as a single unit. Absent coordinates stay null. They do not become zero, because zero is a real place in the Gulf of Guinea and it will pollute every map query built later.

For each accepted file, persist: the 64-character hexadecimal SHA-256 digest, original filename, byte count, pixel dimensions, capture timestamp, timezone offset, latitude, and longitude. When DateTimeOriginal is missing entirely, filesystem modification time serves as a fallback only if it is flagged as such — a field like timestamp_source: filesystem keeps the compromise visible rather than laundering it into apparent camera data.

During testing, round-trip one northern/eastern sample and one southern/western sample to confirm that N, S, E, and W references convert to the correct signs. Sign errors are silent and they mirror locations across hemispheres.

Dedup Blind Spots

Exact hashing and strict timestamp grouping catch burst sequences from a single camera app cleanly. They miss visually identical copies once a third-party app has resized the pixels, rewritten the EXIF block, or shifted the capture time. Plan on a manual pass for images that arrived through messaging apps.

What a Local Vision Model Actually Delivers

Read the staged image from the binary property, strip any data-URI prefix, and send the base64 payload to the local Ollama API with a LLaVA-family model. The request targets http://127.0.0.1:11434/api/generate with model, prompt, images, and stream set to false. The base64 string belongs in the images array, never embedded in the text prompt.

Set a request timeout of 90–300 seconds during initial testing. First-run model loading takes substantially longer than a warm request on the same hardware, and a tight timeout will make a working pipeline look broken.

Prompt design should be governed by future retrieval fields rather than prose quality. Ask for JSON with the keys summary, objects, visible_text, setting, and activity. Cap the summary at 25–40 words. Require an empty visible_text value when no characters are legible, which stops the model from hallucinating signage. Explicitly prohibit names, relationships, and motive.

That prohibition is partly a privacy choice and partly an accuracy one. Untuned local vision models handle object detection and OCR competently; they cannot reliably identify specific individuals or infer nuanced emotional states, so generic person labels keep the archive honest. If memory pressure forces a resize, send a derived copy with a longest edge of 1280–1600 pixels to the model while the original file stays untouched in the archive.

Front Matter, Asset Paths, and Atomic Writes

The final transformation node merges normalized EXIF fields, the source digest, and parsed vision output into YAML front matter plus a short Markdown body.

Name each record from local capture time and append the first eight digest characters: YYYY-MM-DD-HHmmss--hash8.md. The shorter minute-resolution pattern collides constantly, because burst cameras routinely write several files inside a single minute — the collision log from any first import says as much.

A practical front matter set covers captured_at, timezone_offset, latitude, longitude, source_hash, objects, visible_text, model, and prompt_version. Including the model name and prompt version costs nothing and makes it possible, two years later, to identify which records were generated under which instructions.

Store the image at assets/YYYY/MM/original-name--hash8.jpg and embed it with ![[assets/YYYY/MM/original-name--hash8.jpg]]. Relative embeds survive when the entire vault is moved as one directory, which is the whole point of an offline-first archive.

One last detail that saves considerable frustration: write record.tmp first and rename it to record.md only after the file handle closes. Obsidian indexes aggressively and will happily parse half-written front matter, leaving broken metadata in the vault cache.

The Thirty-Day Batch Before the Full Import

Resist the urge to run the historical library on day one. The prompt will need revision, and revising it after 40,000 records, give or take, means reprocessing 40,000 records.

Select one consecutive 30-day period rather than a curated sample. Consecutive days preserve real clusters — commutes, meals, screenshots, evening interiors, weekend travel, in the proportions the pipeline will actually face. Review 60–120 generated records from that batch, deliberately including daylight, low light, indoor, outdoor, screenshots, signs, food, groups of people, GPS-present, and GPS-absent cases.

The Thirty-Day Batch Before the Full Import

Audit retrieval failures, not caption elegance. Mark missing OCR, invented objects, incorrect timestamps, mishandled null GPS, and duplicate burst entries. Then run 2–4 prompt iterations, rerunning identical groups of 25–50 images after each change so that model behavior, parser failures, and hardware timing stay comparable across versions.

Keep the Manifest

Retain a test manifest listing source hash, expected capture day, expected visible text where applicable, generated filename, and prompt version. Hold it for at least the duration of the historical import — it is the only way to prove a regression rather than suspect one.

Build the no-egress boundary first and the captions second. A pipeline that produces mediocre descriptions but never leaks a coordinate can be improved with a better prompt next month; a pipeline that produces beautiful captions by shipping images to a remote endpoint has already given away the thing worth protecting, and no amount of tuning gets it back. Start with the firewall log clean, then make the model smarter.

Join Our Newsletter

Be the first to know.

No spam, just thoughtful updates.

Join the Conversation

No comments.

Write a Comment

Cookie settings