|
|
|
@@ -1,12 +1,12 @@
|
|
|
|
|
# NIP-XX: Place Photos and Media
|
|
|
|
|
# NIP-XX: Place Photos and Videos
|
|
|
|
|
|
|
|
|
|
`draft` `optional`
|
|
|
|
|
|
|
|
|
|
## Abstract
|
|
|
|
|
|
|
|
|
|
This NIP defines a standardized event format for sharing photos, videos, and other visual media tied to specific real-world locations (e.g., OpenStreetMap POIs).
|
|
|
|
|
This NIP defines a standardized event format for sharing photos and videos tied to specific real-world locations (e.g., OpenStreetMap POIs). Each event carries exactly one media item.
|
|
|
|
|
|
|
|
|
|
While NIP-68 (Picture-first feeds) caters to general visual feeds, this NIP specifically targets map-based applications, travel logs, and location directories by mandating strict entity identifiers (`i` tags) and spatial indexing (`g` tags).
|
|
|
|
|
While [NIP-68](68.md) (Picture-first feeds) caters to general visual feeds, this NIP specifically targets map-based applications, travel logs, and location directories by mandating a strict entity identifier (`i` tag) and spatial indexing (`g` tag).
|
|
|
|
|
|
|
|
|
|
## Event Kind
|
|
|
|
|
|
|
|
|
@@ -14,40 +14,54 @@ While NIP-68 (Picture-first feeds) caters to general visual feeds, this NIP spec
|
|
|
|
|
|
|
|
|
|
## Content
|
|
|
|
|
|
|
|
|
|
The `.content` of the event SHOULD generally be empty. If a user wishes to provide a detailed description for a place, clients SHOULD encourage them to create a Place Review event (`kind: 30360`) instead.
|
|
|
|
|
The `.content` MUST be either empty or exactly the primary media `url`. Authors are encouraged to leave it empty so that photos do not surface out of context in generic feeds. Detailed place descriptions belong in a separate place-review event (a forthcoming Place Reviews NIP; not yet published). If `.content` is not empty, it MUST equal the primary `url` in the `imeta` tag.
|
|
|
|
|
|
|
|
|
|
## Tags
|
|
|
|
|
|
|
|
|
|
This NIP relies on existing Nostr tag conventions to link media to places and provide inline metadata.
|
|
|
|
|
This NIP reuses existing Nostr tags to link media (image or video, per the `m` tag) to places and provide inline metadata.
|
|
|
|
|
|
|
|
|
|
### Required Tags
|
|
|
|
|
|
|
|
|
|
#### 1. `i` — Entity Identifier
|
|
|
|
|
|
|
|
|
|
Identifies the exact place the media depicts using an external identifier (as defined in NIP-73). OpenStreetMap data is the default:
|
|
|
|
|
Identifies the exact place using an external content identifier (see [NIP-73](73.md)). This NIP defines the `osm:<type>:<id>` scheme, extending NIP-73's external content ids; it is proposed for inclusion in NIP-73. OpenStreetMap data is the default:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
["i", "osm:node:123456"]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- For OSM POIs, `<type>` MUST be one of: `node`, `way`, `relation`.
|
|
|
|
|
- For OSM POIs, `<type>` MUST be one of: `node`, `way`, `relation`. OSM element ids are unique only within a type (`node 123` ≠ `way 123`), which is why `<type>` is required.
|
|
|
|
|
- An event has exactly one `i` tag. A media item depicting multiple identifiable places uses the primary subject; this NIP does not define additional `i` tags.
|
|
|
|
|
|
|
|
|
|
#### 2. `g` — Geohash
|
|
|
|
|
|
|
|
|
|
Used for spatial indexing and discovery. Events MUST include at least one high-precision geohash. To optimize for map-based discovery across different zoom levels, clients SHOULD include geohashes at multiple resolutions:
|
|
|
|
|
The `g` tag was first defined in [NIP-52](52.md) (calendar events) and has since been reused by [NIP-99](99.md) (classified listings) and [NIP-CC](CC.md) (geocaching). This NIP reuses `g` for spatial indexing of place media.
|
|
|
|
|
|
|
|
|
|
This NIP adds the following requirements: events MUST include at least one high-precision geohash (length ≥ 9), and clients SHOULD include geohashes at multiple resolutions:
|
|
|
|
|
|
|
|
|
|
- length 4 — coarse (city-scale discovery)
|
|
|
|
|
- length 6 — medium (default query level, ~1 km)
|
|
|
|
|
- length 7 — fine (neighborhood, ~150 m)
|
|
|
|
|
- length 9 — high precision (~5 m, the required minimum)
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
["g", "thrr"] // coarse (~city)
|
|
|
|
|
["g", "thrrn5"] // medium (~1km)
|
|
|
|
|
["g", "thrrn5k"] // fine (~150m)
|
|
|
|
|
["g", "thrrn5kxyz"] // exact
|
|
|
|
|
[
|
|
|
|
|
["g", "thrr"],
|
|
|
|
|
["g", "thrrn5"],
|
|
|
|
|
["g", "thrrn5k"],
|
|
|
|
|
["g", "thrrn5kxy"]
|
|
|
|
|
]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 3. `imeta` — Inline Media Metadata
|
|
|
|
|
|
|
|
|
|
An event MUST contain exactly one `imeta` tag representing a single media item. The primary `url` MAY also be appended to the event's `.content` for backwards compatibility with clients that do not parse `imeta` tags.
|
|
|
|
|
An event MUST contain exactly one `imeta` tag representing a single media item. Field definitions are per [NIP-92](92.md) and [NIP-94](94.md).
|
|
|
|
|
|
|
|
|
|
Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `m` (MIME type), `thumb` (URL to a smaller thumbnail image), and `blurhash` where possible. Clients MAY also include `fallback` URLs if the media is hosted on multiple servers.
|
|
|
|
|
Clients SHOULD include `alt` (accessibility description — also the media's description/caption, see below), `dim` (dimensions), `m` (MIME type, image or video), `thumb` (URL to a smaller thumbnail image), and `blurhash` where possible. Clients MAY also include `fallback` URLs if the media is mirrored.
|
|
|
|
|
|
|
|
|
|
`alt` serves as the description of the media (not only accessibility text); clients SHOULD prompt authors for it and MAY display it as the media's caption. Since `.content` cannot carry a caption, `alt` is the only description channel for a kind 360 item. A generic or empty `alt` is little better than none.
|
|
|
|
|
|
|
|
|
|
Authors MAY set `.content` to the primary `url` so that clients which do not parse `imeta` can still render the media (see Content).
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
[
|
|
|
|
@@ -62,11 +76,46 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
|
|
|
|
|
]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Clients MAY additionally include a top-level `["m", "<mime>"]` tag for `#m` filtering (e.g., photo-only vs video-capable clients). If present, it MUST equal the `imeta`'s `m` value.
|
|
|
|
|
|
|
|
|
|
**Video.** A kind 360 video is a moving record of a place's appearance — layout, size, and surroundings — serving the same purpose as a photo, for place documentation. It is not short-form or social video content. [NIP-71](71.md) is referenced solely for its `imeta` field definitions: kind 360 videos MAY include NIP-71's video-specific `imeta` fields (`duration`, `bitrate`, `waveform`, `image` preview, `l`/`ov` audio tracks; as applicable). Because an event contains exactly one `imeta` tag, a kind 360 video is single-representation: one URL, no resolution ladders, no multiple audio tracks.
|
|
|
|
|
|
|
|
|
|
### Optional Tags
|
|
|
|
|
|
|
|
|
|
- `t`: Hashtags for categorization (e.g., `["t", "food"]`, `["t", "architecture"]`).
|
|
|
|
|
- `content-warning`: If the media contains NSFW or sensitive imagery.
|
|
|
|
|
- `published_at`: Unix timestamp of when the photo was originally taken or published.
|
|
|
|
|
- `content-warning`: Optional reason as defined in [NIP-36](36.md); clients MAY hide the media until the user acts. Example: `["content-warning", "nudity"]`.
|
|
|
|
|
|
|
|
|
|
For kind 360, the warning applies to the media (rendering), not `.content`: clients SHOULD hide or blur the media (e.g., via the `blurhash` placeholder) until the user acts.
|
|
|
|
|
|
|
|
|
|
## Querying
|
|
|
|
|
|
|
|
|
|
Single-item tags are filterable per [NIP-01](01.md).
|
|
|
|
|
|
|
|
|
|
**All media for a place.** Filter by kind 360 and `#i` with the full identifier:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{ "kinds": [360], "#i": ["osm:node:123456"] }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This returns every media item for the place regardless of geohash presence or precision, and does not require geohash tags.
|
|
|
|
|
|
|
|
|
|
**Spatial discovery.** Filter by kind 360 and `#g`:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{ "kinds": [360], "#g": ["thrrn4", "thrrn5", "thrrn6", "thrrn7"] }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Clients SHOULD query a single geohash precision level per request, MAY include multiple geohash values to cover a bounding box, SHOULD limit values per query (e.g., ≤ 30), and MAY reduce precision or split queries when necessary.
|
|
|
|
|
|
|
|
|
|
Relays MAY not index arbitrary tags; clients SHOULD NOT assume all relays serve `#i`/`#g` queries for this kind.
|
|
|
|
|
|
|
|
|
|
## Client Behavior
|
|
|
|
|
|
|
|
|
|
**Publishing.** Clients MUST NOT publish events that violate the required tags or the content rule above.
|
|
|
|
|
|
|
|
|
|
**Parsing.** Clients SHOULD tolerate missing optional fields and unknown tags, and MAY use the first `imeta` if multiple are present. An event whose `imeta` lacks a `url` has no renderable media. If the `i` element cannot be resolved (e.g., a deleted OSM element), clients MAY fall back to the `g` geohash for placement, or skip the event.
|
|
|
|
|
|
|
|
|
|
**Rendering.** Clients MAY skip media they cannot render, SHOULD honor `content-warning` (media, not `.content` — see above), and SHOULD use `thumb`/`blurhash` placeholders while loading. Relays: see Querying for the `#i`/`#g` indexing caveat.
|
|
|
|
|
|
|
|
|
|
## Example Event
|
|
|
|
|
|
|
|
|
@@ -81,8 +130,8 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
|
|
|
|
|
["i", "osm:node:987654321"],
|
|
|
|
|
["g", "xn0m"],
|
|
|
|
|
["g", "xn0m7h"],
|
|
|
|
|
["g", "xn0m7hwq"],
|
|
|
|
|
|
|
|
|
|
["g", "xn0m7hw"],
|
|
|
|
|
["g", "xn0m7hwkq"],
|
|
|
|
|
[
|
|
|
|
|
"imeta",
|
|
|
|
|
"url https://blossom.example.com/a9c84e183789a74288b8e05d04cc61230e74f386925a953e6b29f957e8cc3a61.jpg",
|
|
|
|
@@ -93,7 +142,6 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
|
|
|
|
|
"thumb https://example.com/c5a528e20235e16cc1c18090b8f04179de76288ea4e410b0fcb8d1487e416a2d.jpg",
|
|
|
|
|
"blurhash UHI=0o~q4T-o~q%MozM{x]t7RjRPt7oKkCWB"
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
["t", "ramen"],
|
|
|
|
|
["t", "food"]
|
|
|
|
|
]
|
|
|
|
@@ -104,16 +152,35 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
|
|
|
|
|
|
|
|
|
|
### Kind 360
|
|
|
|
|
|
|
|
|
|
Easy to remember as a 360-degree view of places.
|
|
|
|
|
Easy to remember as a figurative 360-degree view of places: media giving a comprehensive impression of a place.
|
|
|
|
|
|
|
|
|
|
### Why not use NIP-68 (Picture-first feeds)?
|
|
|
|
|
|
|
|
|
|
NIP-68 is designed for general-purpose social feeds (like Instagram). Place photos require strict guarantees about what entity is being depicted to be useful for map clients, directories, and review aggregators. By mandating the `i` tag for POI linking and the `g` tag for spatial querying, this kind ensures interoperability for geo-spatial applications without cluttering general picture feeds with mundane POI images (like photos of storefronts or menus).
|
|
|
|
|
NIP-68 is designed for general-purpose social feeds (like Instagram). Place media require strict guarantees about what entity is being depicted to be useful for map clients, directories, and review aggregators. By mandating the `i` tag for place linking and the `g` tag for spatial querying, this kind ensures interoperability for geospatial applications without cluttering general picture feeds with everyday place images and videos (footage of what a place looks like — not social or short-form video content).
|
|
|
|
|
|
|
|
|
|
### Spatial Indexing and the `geo:` Identifier
|
|
|
|
|
|
|
|
|
|
`g` is used for spatial indexing; the `geo:` identifier form of NIP-73 is not used in `i`, which identifies the entity (`osm:<type>:<id>`), not a location.
|
|
|
|
|
|
|
|
|
|
### External Content Identifier (`osm:`)
|
|
|
|
|
|
|
|
|
|
This NIP extends NIP-73 with the `osm:<type>:<id>` scheme, which is proposed for inclusion in NIP-73; the scheme may evolve with upstream registration. OSM identifiers reference a live database: elements may be moved, retyped, or deleted, so a referenced element can change or disappear. This is why the `g` geohash is also required — it keeps media discoverable and placeable even if the identifier later becomes stale.
|
|
|
|
|
|
|
|
|
|
### Separation from Place Reviews
|
|
|
|
|
|
|
|
|
|
Reviews (kind 30360) and media have different lifecycles and data models. A user might upload 10 photos of a park without writing a review, or write a detailed review without attaching photos. Keeping them as separate events allows clients to query `imeta` attachments for a specific `i` tag to quickly build a photo gallery for a place, regardless of whether a review was attached.
|
|
|
|
|
Place Reviews (kind 30360) is a work-in-progress companion NIP, not yet published; when it is, reviews and media will interoperate as follows. Reviews and media have different lifecycles and data models. A user might upload 10 photos of a park without writing a review, or write a detailed review without attaching photos. Keeping them as separate events allows clients to query `imeta` attachments for a specific `i` tag to quickly build a media gallery for a place, regardless of whether a review was attached.
|
|
|
|
|
|
|
|
|
|
### Single Photo per Event
|
|
|
|
|
### Single Media Item per Event
|
|
|
|
|
|
|
|
|
|
Restricting events to a single `imeta` attachment (one photo per event) is an intentional design choice. Batching photos into a single event forces all engagement (likes, zaps) to apply to the entire batch, rendering granular tagging and sorting impossible. Single-photo events enable per-photo engagement, fine-grained categorization (e.g., tagging one photo as "food" and another as "menu"), and richer sorting algorithms based on individual photo popularity.
|
|
|
|
|
Restricting events to a single `imeta` attachment (one media item per event) is an intentional design choice. Batching media into a single event forces all engagement (likes, zaps) to apply to the entire batch, rendering granular tagging and sorting impossible. Single-item events enable per-item engagement, fine-grained categorization (e.g., tagging one photo as "food" and another as "menu"), and richer sorting algorithms based on individual item popularity.
|
|
|
|
|
|
|
|
|
|
## Security Considerations
|
|
|
|
|
|
|
|
|
|
- **Unauthenticated place association.** Any key can publish kind 360 media for any place; presence does not imply endorsement by, or connection to, the place or its owner. Media may misrepresent a place (e.g., fake or outdated interior/menu photos).
|
|
|
|
|
- **Spam.** Clients SHOULD NOT assume all events are legitimate and SHOULD apply their own moderation/trust mechanisms.
|
|
|
|
|
- **Untrusted media.** `url`, `thumb`, and `fallback` point to arbitrary servers; clients SHOULD treat fetched media as untrusted content. Fetching media reveals the viewer's interest in a place to the hosting server.
|
|
|
|
|
- **Identifier decay.** Referenced OSM elements may be deleted or retyped; see Client Behavior for parsing fallbacks.
|
|
|
|
|
|
|
|
|
|
## Future Work
|
|
|
|
|
|
|
|
|
|
- **Capture time (`taken_at`).** A future tag for when the photo was taken (e.g., from EXIF). If introduced: MUST NOT be required; MUST NOT be published without explicit user consent; MUST offer the user the option to change or remove it. (Capture time discloses where and when the author was.)
|