docs: publish-pipeline-vorbereitung + bild-metadaten-konvention

- spec publish-pipeline aktualisiert: blossom-only (rsync-legacy-pfad raus)
- plan publish-pipeline (24 tasks in 12 phasen), blaupausen-tauglich:
  alle projekt-konstanten via env (BUNKER_URL, AUTHOR_PUBKEY_HEX,
  BOOTSTRAP_RELAY, CONTENT_ROOT, CLIENT_TAG, MIN_RELAY_ACKS)
- spec bild-metadaten-konvention (phase 1, yaml-basiert)
- wiki-entwurf deutsch + englisch: inline-markdown-konvention zur
  bildattribution mit mapping zu nip-92 imeta-tags. konvention für
  menschen, parser passt sich an; bei ambiguität eskalation zur
  redaktion statt stillem raten
- redaktions-checkliste für bild-durchgang (abgearbeitet, bleibt
  zum abgleich bei späteren migrationen)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jörg Lohrer 2026-04-16 15:14:02 +02:00
parent c023b59769
commit 931ef9f03f
6 changed files with 4410 additions and 111 deletions

View File

@ -0,0 +1,602 @@
# Redaktion: Bild-Metadaten-Durchgang
**Zweck:** 91 Bilder in 18 Posts visuell prüfen und Alt-Texte, Lizenzen, Autor:innen-Angaben gegen das echte Bild abgleichen.
**Arbeitsweise:**
- Pro Bild: Checkbox `[ ]``[x]` wenn geprüft.
- Im **NOTIZ**-Feld: freie Änderungswünsche, Korrekturen, Klarstellungen.
- Bei `UNKNOWN`: Recherche-Ergebnis eintragen oder „bleibt UNKNOWN".
- Ich mache den Abgleich am Ende und schreibe alle Änderungen ins Frontmatter zurück.
**Links:**
- `📝 Frontmatter` → öffnet `index.md` zum Direkt-Editieren
- `🖼 Bild` → öffnet die Bilddatei lokal im Finder/Preview (mit `file://`)
---
## 2013-02-07 — premium-freemium-mium-mium-mium
📝 [Frontmatter](../content/posts/2013-02-07-premium-freemium-mium-mium-mium/index.md)
- [X] **my-very-hungry-caterpillar.jpg** (Cover, FREMD)
🖼 [Bild](../content/posts/2013-02-07-premium-freemium-mium-mium-mium/my-very-hungry-caterpillar.jpg)
Alt: „Kleine Raupe aus Papier gefaltet, Anspielung auf das Kinderbuch 'Die kleine Raupe Nimmersatt'"
Lizenz: CC BY-NC-SA 3.0 · Autor: Relly Annett-Baker · Quelle: flickr.com/photos/fizzkitten/4454153264
**NOTIZ:**
Bild ist leider nicht mehr verfügbar online - ich hoffe jedoch es gibt keine Abmahnung weil nicht mehr nachweisbar das cc-lizenz
---
## 2013-05-29 — erlebnispadagogik-im-handbuch-jugend-evangelische-perspektive
📝 [Frontmatter](../content/posts/2013-05-29-erlebnispadagogik-im-handbuch-jugend-evangelische-perspektive/index.md)
_Keine lokalen Bilder. Body enthält tote Amazon-Hotlinks zu Buchcovern (Affiliate-Programm 2018 eingestellt)._
- [X] **Amazon-Hotlinks entfernen / durch Text ersetzen?** Entscheidung später.
**NOTIZ:**
Ja Hotlink entfernen
---
## 2017-10-23 — telegram-octopi
📝 [Frontmatter](../content/posts/2017-10-23-telegram-octopi/index.md)
- [ ] **octopi1.png** (Cover)
🖼 [Bild](../content/posts/2017-10-23-telegram-octopi/octopi1.png)
Alt: „Screenshot der OctoPrint-Plugin-Verwaltung während der Installation des Telegram-Plugins — Fortschrittsanzeige läuft"
**NOTIZ:**
- [ ] **octopi2.png**
🖼 [Bild](../content/posts/2017-10-23-telegram-octopi/octopi2.png)
Alt: „Screenshot der Konfigurationsmaske des OctoPrint-Telegram-Plugins mit Eingabefeld für den Telegram-Bot-Token"
**NOTIZ:**
- [ ] **octopi3.png**
🖼 [Bild](../content/posts/2017-10-23-telegram-octopi/octopi3.png)
Alt: „Screenshot der OctoPrint-Telegram-Plugin-Oberfläche nach erfolgreichem Token-Eintrag — Benutzerliste wird angezeigt, Rechte fehlen noch"
**NOTIZ:**
- [ ] **octopi4.png**
🖼 [Bild](../content/posts/2017-10-23-telegram-octopi/octopi4.png)
Alt: „Screenshot der Benutzer-Rechte-Konfiguration mit gesetzten Häkchen bei 'Command' und 'Notify'"
**NOTIZ:**
Alle CC0
---
## 2017-10-31 — lutherkuerbis
📝 [Frontmatter](../content/posts/2017-10-31-lutherkuerbis/index.md)
- [ ] **kuerbis-titelbild.jpg** (Cover)
🖼 [Bild](../content/posts/2017-10-31-lutherkuerbis/kuerbis-titelbild.jpg)
Alt: „Fertig geschnitzter Kürbis mit dem Muster einer Lutherrose, innen beleuchtet — glüht warm in der Dunkelheit"
**NOTIZ:**
- [ ] **lutherrose.png** (Vektorschablone, Vorlage aus dem Web)
🖼 [Bild](../content/posts/2017-10-31-lutherkuerbis/lutherrose.png)
Alt: „Schwarz-weiße Vektorgrafik der Lutherrose: Kreuz im Herzen, umgeben von fünfblättriger Rose in einem Ring — als Schnitzschablone aufbereitet"
Caption: „Vektorisierte Schablone, abgeleitet von einer Fotovorlage aus dem Web"
Modifications: „Vektorisierung per online-convert.com aus gemeinfreier Fotovorlage; Originalurheber der Fotovorlage unbekannt"
**NOTIZ:**
- [ ] **kuerbis-aufschneiden.jpg**
🖼 [Bild](../content/posts/2017-10-31-lutherkuerbis/kuerbis-aufschneiden.jpg)
Alt: „Hände schneiden mit großem Messer den Deckel von einem orangenen Kürbis ab"
**NOTIZ:**
- [ ] **kuerbis-entkernen.jpg**
🖼 [Bild](../content/posts/2017-10-31-lutherkuerbis/kuerbis-entkernen.jpg)
Alt: „Mit einem Löffel wird das Fruchtfleisch und die Kerne aus dem Inneren des aufgeschnittenen Kürbis herausgekratzt"
**NOTIZ:**
- [ ] **schablone-aufbringen.jpg**
🖼 [Bild](../content/posts/2017-10-31-lutherkuerbis/schablone-aufbringen.jpg)
Alt: „Papier-Schablone mit Lutherrosen-Motiv wird auf die Außenhaut des entkernten Kürbis geklebt"
**NOTIZ:**
- [ ] **kuerbis-ausschneiden.jpg**
🖼 [Bild](../content/posts/2017-10-31-lutherkuerbis/kuerbis-ausschneiden.jpg)
Alt: „Mit einem Schnitzwerkzeug wird die Lutherrose entlang der Schablone aus der Kürbishaut herausgeschnitten"
**NOTIZ:**
Alle CC0
---
## 2019-03-26 — Pflanzenschild-QR-Code
📝 [Frontmatter](../content/posts/2019-03-26-Pflanzenschild-QR-Code/index.md)
- [ ] **cura-plugin-change-filment-at-z.png** (Cover)
🖼 [Bild](../content/posts/2019-03-26-Pflanzenschild-QR-Code/cura-plugin-change-filment-at-z.png)
Alt: „Screenshot des Cura-Slicers mit aktiviertem 'Change Filament at Z'-Plugin — Konfiguration eines Filamentwechsels in bestimmten Layern"
**NOTIZ:**
- [ ] **qr-code-pflanzenschild.jpg**
🖼 [Bild](../content/posts/2019-03-26-Pflanzenschild-QR-Code/qr-code-pflanzenschild.jpg)
Alt: „Dreieckiges 3D-gedrucktes Pflanzenschild mit aufgedrucktem zweifarbigem QR-Code, steckt in einem Pflanztopf"
**NOTIZ:**
Alle CC0
---
## 2021-08-15 — virtual-reality (⚠️ 4× UNKNOWN zur Recherche)
📝 [Frontmatter](../content/posts/2021-08-15-virtual-reality/index.md)
- [ ] **04-aframe.jpg** (Cover, EIGEN)
🖼 [Bild](../content/posts/2021-08-15-virtual-reality/04-aframe.jpg)
Alt: „Screenshot einer A-Frame-WebVR-Szene: 3D-Objekte in einem Browser-Viewport, erstellt mit A-Frame-Framework"
Quelle: codepen.io/joerglohrer/full/dyXQqWG
**NOTIZ:**
- [ ] **01-immersion-wikipedia.jpg** (⚠️ UNKNOWN)
🖼 [Bild](../content/posts/2021-08-15-virtual-reality/01-immersion-wikipedia.jpg)
Alt: „Screenshot des Wikipedia-Artikels 'Immersive learning' mit Einstiegsdefinition"
Lizenz: UNKNOWN · Autor: UNKNOWN · Quelle: en.wikipedia.org/wiki/Immersive_learning
Wikipedia-Text ist CC BY-SA — soll ich das so setzen?
**NOTIZ:**
- [ ] **02-mittelalterliche-kirche.jpg** (FREMD, Sketchfab)
🖼 [Bild](../content/posts/2021-08-15-virtual-reality/02-mittelalterliche-kirche.jpg)
Alt: „Screenshot eines 3D-Modells einer mittelalterlichen Kirche (Calatrava la Nueva, Spanien) auf Sketchfab, erstellt aus 76 Laser-Scans und 4100 Fotos"
Lizenz: CC BY-NC 4.0 · Autor: UNKNOWN · Quelle: sketchfab.com/3d-models/medieval-church-…
Im Post-Body Zeile 120122 genannt: „Processed in Reality Capture from 76 Faro laser scans and 4100 photographs" — aber kein Urhebername. Recherche möglich?
**NOTIZ:**
- [ ] **03-avatare-erstellen.jpg** (⚠️ UNKNOWN, Ready Player Me)
🖼 [Bild](../content/posts/2021-08-15-virtual-reality/03-avatare-erstellen.jpg)
Alt: „Screenshot der Avatar-Erstellung im Ready Player Me Web-Interface"
Lizenz: UNKNOWN · Autor: UNKNOWN
**NOTIZ:**
- [ ] **05-pupillendistanz.jpg** (⚠️ UNKNOWN, EyeMeasure-App)
🖼 [Bild](../content/posts/2021-08-15-virtual-reality/05-pupillendistanz.jpg)
Alt: „Screenshot der iOS-App 'EyeMeasure' bei der Messung des Pupillenabstands mittels iPhone-Kamera"
Lizenz: UNKNOWN · Autor: UNKNOWN
**NOTIZ:**
- [ ] **06-vr-adapter-3ddruck.jpg** (EIGEN)
🖼 [Bild](../content/posts/2021-08-15-virtual-reality/06-vr-adapter-3ddruck.jpg)
Alt: „3D-gedruckter Adapter zur Befestigung einer VIVE Deluxe Audio Strap an der Oculus Quest 2, frisch aus dem 3D-Drucker"
**NOTIZ:**
- [ ] **07-vive-straps-3ddruck.jpg** (EIGEN)
🖼 [Bild](../content/posts/2021-08-15-virtual-reality/07-vive-straps-3ddruck.jpg)
Alt: „3D-gedruckte Halterungen der VIVE Deluxe Audio Strap, montiert an der Oculus Quest 2"
**NOTIZ:**
---
## 2021-11-17 — WordPress-Werkstatt
📝 [Frontmatter](../content/posts/2021-11-17-WordPress-Werkstatt/index.md)
- [ ] **04-termine-neu.png** (Cover)
🖼 [Bild](../content/posts/2021-11-17-WordPress-Werkstatt/04-termine-neu.png)
Alt: „Screenshot der WordPress-Beitragsübersicht mit eingefügtem Shortcode [relilab_termine], der eine Terminliste als Block rendert"
**NOTIZ:**
- [ ] **01-json-import.png**
🖼 [Bild](../content/posts/2021-11-17-WordPress-Werkstatt/01-json-import.png)
Alt: „Screenshot der ACF-Plugin-Oberfläche beim Import einer JSON-Datei mit Feldgruppen-Definitionen"
(Hinweis: im Body fälschlich `![](h01-json-import.png)` mit Tippfehler — Body-Fix später)
**NOTIZ:**
- [ ] **02-terminfelder.png**
🖼 [Bild](../content/posts/2021-11-17-WordPress-Werkstatt/02-terminfelder.png)
Alt: „Screenshot eines WordPress-Beitrags mit zwei neuen ACF-Terminfeldern 'Startet am' und 'Endet am' als Datum-/Zeit-Picker"
**NOTIZ:**
- [ ] **03-kategorien.png**
🖼 [Bild](../content/posts/2021-11-17-WordPress-Werkstatt/03-kategorien.png)
Alt: „Screenshot der WordPress-Kategorieverwaltung mit neu angelegter Kategorie 'Termine' samt Unterkategorien"
**NOTIZ:**
- [ ] **05-php-storm.png**
🖼 [Bild](../content/posts/2021-11-17-WordPress-Werkstatt/05-php-storm.png)
Alt: „Screenshot der PhpStorm-IDE mit geöffneter PHP-Datei zum add_shortcode()-Aufruf"
**NOTIZ:**
- [ ] **06-termine-listen.png**
🖼 [Bild](../content/posts/2021-11-17-WordPress-Werkstatt/06-termine-listen.png)
Alt: „Screenshot des PHP-Codes für die Funktion 'termineAusgeben' mit get_posts()-Abfrage und Shortcode-Registrierung"
**NOTIZ:**
- [ ] **07-external-library.png**
🖼 [Bild](../content/posts/2021-11-17-WordPress-Werkstatt/07-external-library.png)
Alt: „Screenshot der PhpStorm-Konfiguration zur Einbindung von WordPress als External Library für Auto-Complete"
**NOTIZ:**
---
## 2021-12-03 — bibelfussball
📝 [Frontmatter](../content/posts/2021-12-03-bibelfussball/index.md)
- [ ] **bibelfussball1.png** (Cover)
🖼 [Bild](../content/posts/2021-12-03-bibelfussball/bibelfussball1.png)
Alt: „Tafel-Skizze eines Fußballfeldes mit Mittellinie, Strafräumen und zwei Toren — Magnetknopf markiert die aktuelle Ballposition"
**NOTIZ:**
---
## 2022-02-16 — Moodle-Iomad-Linux
📝 [Frontmatter](../content/posts/2022-02-16-Moodle-Iomad-Linux/index.md)
- [ ] **title-gif.gif** (Cover)
🖼 [Bild](../content/posts/2022-02-16-Moodle-Iomad-Linux/title-gif.gif)
Alt: „Animiertes Titelbild des Artikels zur Moodle-Server-Installation mit Iomad unter Ubuntu"
**NOTIZ:**
- [ ] **01-netzwerkbruecke.png**
🖼 [Bild](../content/posts/2022-02-16-Moodle-Iomad-Linux/01-netzwerkbruecke.png)
Alt: „Screenshot der VirtualBox-Netzwerkeinstellungen mit aktivierter Netzwerkbrücke für die Ubuntu-VM"
**NOTIZ:**
- [ ] **02-hosts-eintragen.png**
🖼 [Bild](../content/posts/2022-02-16-Moodle-Iomad-Linux/02-hosts-eintragen.png)
Alt: „Terminal-Screenshot mit geöffneter /etc/hosts-Datei im nano-Editor, neuer Eintrag 'moodle.local' wird hinzugefügt"
**NOTIZ:**
- [ ] **03-config generieren.png** (Datei mit Leerzeichen im Namen!)
🖼 [Bild](<../content/posts/2022-02-16-Moodle-Iomad-Linux/03-config generieren.png>)
Alt: „Screenshot des Moodle-Installationsassistenten beim automatischen Generieren der config.php"
**NOTIZ:**
---
## 2022-03-19 — OB-virtualcam (31 Bilder)
📝 [Frontmatter](../content/posts/2022-03-19-OB-virtualcam/index.md)
- [ ] **29-autostartordner.jpg** (Cover)
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/29-autostartordner.jpg)
Alt: „Screenshot des Windows-Autostart-Ordners mit verknüpften OBS- und Zoom-Startlinks für automatischen Start beim Systemstart"
**NOTIZ:**
- [ ] **01-deutsche-tastatur-ubuntu.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/01-deutsche-tastatur-ubuntu.png)
Alt: „Screenshot der Ubuntu-Terminal-Dialog zur Konfiguration der deutschen Tastatur via dpkg-reconfigure"
**NOTIZ:**
- [ ] **02-chrome-remote-desktop.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/02-chrome-remote-desktop.png)
Alt: „Screenshot der Chrome-Remote-Desktop-Installation im Ubuntu-Terminal"
**NOTIZ:**
- [ ] **03-status-chrome-remote.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/03-status-chrome-remote.png)
Alt: „Screenshot des systemctl-Status des chrome-remote-desktop-Dienstes als 'active (running)'"
**NOTIZ:**
- [ ] **04-remotezugriff.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/04-remotezugriff.png)
Alt: „Screenshot der Chrome-Remote-Desktop-Konfigurationsseite mit SSH-Befehl und PIN-Eingabe"
**NOTIZ:**
- [ ] **05-systemctl-status.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/05-systemctl-status.png)
Alt: „Screenshot der systemctl-status-Ausgabe für chrome-remote-desktop mit aktivem Dienst"
**NOTIZ:**
- [ ] **06-cannot-open-video-device.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/06-cannot-open-video-device.png)
Alt: „Terminal-Screenshot der Fehlermeldung 'Cannot open device /dev/video0' bei v4l2-ctl --list-devices"
**NOTIZ:**
- [ ] **07-jetzt-v412-ctl.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/07-jetzt-v412-ctl.png)
Alt: „Terminal-Screenshot der erfolgreichen v4l2-ctl-Geräteliste nach Installation von v4l2loopback"
**NOTIZ:**
- [ ] **08-dummy-video-device.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/08-dummy-video-device.png)
Alt: „Terminal-Screenshot nach Reboot: virtuelle Kamera fehlt, Dummy-Video-Device muss neu geladen werden"
**NOTIZ:**
- [ ] **09-relilab-technical-host.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/09-relilab-technical-host.png)
Alt: „Screenshot der Chrome-Remote-Desktop-Geräteübersicht mit dem VM-Eintrag 'relilab-technical-host'"
**NOTIZ:**
- [ ] **10-pin-remote-desktop.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/10-pin-remote-desktop.png)
Alt: „Screenshot des Chrome-Remote-Desktop-PIN-Eingabefelds für die Remote-Verbindung"
**NOTIZ:**
- [ ] **11-keyboard-tastatur-umstellen.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/11-keyboard-tastatur-umstellen.png)
Alt: „Screenshot der Linux-Keyboard-Einstellungen mit Umstellung auf deutsche Tastaturbelegung"
**NOTIZ:**
- [ ] **12-apps-verknuepfen.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/12-apps-verknuepfen.png)
Alt: „Screenshot der Cinnamon-Desktop-Umgebung mit Drag-and-Drop-Verknüpfung von Anwendungen auf den Desktop"
**NOTIZ:**
- [ ] **13-startvirtualcam.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/13-startvirtualcam.png)
Alt: „Screenshot der OBS-Verknüpfung mit dem Zusatzparameter --startvirtualcam im Startbefehl"
**NOTIZ:**
- [ ] **14-OBS-deutsch-umstellen.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/14-OBS-deutsch-umstellen.png)
Alt: „Screenshot der OBS-Studio-Einstellungen beim Umschalten der Benutzeroberfläche auf Deutsch"
**NOTIZ:**
- [ ] **15-obs-mit-virtual-cam-starten.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/15-obs-mit-virtual-cam-starten.png)
Alt: „Screenshot der OBS-Startbefehl-Konfiguration mit --startvirtualcam-Parameter für automatischen Kamera-Start"
**NOTIZ:**
- [ ] **16-startup-application.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/16-startup-application.png)
Alt: „Screenshot der Cinnamon-Startup-Applications-Verwaltung mit neu hinzugefügtem OBS-Eintrag"
**NOTIZ:**
- [ ] **17-i-will-only-be-using-OBS.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/17-i-will-only-be-using-OBS.png)
Alt: „Screenshot des OBS-Auto-Configuration-Wizard mit ausgewählter Option 'I will only be using the virtual camera'"
**NOTIZ:**
- [ ] **18-video1920.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/18-video1920.png)
Alt: „Screenshot der OBS-Video-Einstellungen mit Auflösung 1920x1080"
**NOTIZ:**
- [ ] **19-szenensammlung-importieren-OBS.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/19-szenensammlung-importieren-OBS.png)
Alt: „Screenshot des OBS-Menüs 'Szenensammlung importieren' mit Auswahl einer JSON-Datei"
**NOTIZ:**
- [ ] **20-chrome-einrichten.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/20-chrome-einrichten.png)
Alt: „Screenshot des Ubuntu-Keyring-Passwort-Dialogs beim ersten Chrome-Start"
**NOTIZ:**
- [ ] **21-chrome-standard.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/21-chrome-standard.png)
Alt: „Screenshot der Google-Chrome-Einstellungen mit gesetzter Option 'Als Standardbrowser festlegen'"
**NOTIZ:**
- [ ] **22-chrome-anmeldung.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/22-chrome-anmeldung.png)
Alt: „Screenshot der Google-Account-Anmeldung in Chrome mit aktiviertem Sync"
**NOTIZ:**
- [ ] **23-zoom-anmeldung.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/23-zoom-anmeldung.png)
Alt: „Screenshot der Zoom-Client-Anmeldemaske unter Linux"
**NOTIZ:**
- [ ] **24-zoom-sprache-aendern.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/24-zoom-sprache-aendern.png)
Alt: „Screenshot des Zoom-Tray-Menüs mit Sprachauswahl-Untermenü zur Umstellung auf Deutsch"
**NOTIZ:**
- [ ] **25-slides-emojis.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/25-slides-emojis.png)
Alt: „Screenshot einer Präsentationsfolie im Chrome-Browser mit fehlenden Emoji-Zeichen als leere Platzhalter"
**NOTIZ:**
- [ ] **26-keyring-problem.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/26-keyring-problem.png)
Alt: „Screenshot der Ubuntu-GUI-Fehlermeldung beim Versuch, sich als Root einzuloggen"
**NOTIZ:**
- [ ] **27-startvirtualcam-verknuepft-OBS.jpg**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/27-startvirtualcam-verknuepft-OBS.jpg)
Alt: „Screenshot der Windows-Eigenschaften einer OBS-Desktop-Verknüpfung mit --startvirtualcam-Parameter"
**NOTIZ:**
- [ ] **28-shell-startup.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/28-shell-startup.png)
Alt: „Screenshot des Windows-Run-Dialogs mit Befehl 'shell:startup' zum Öffnen des Autostart-Ordners"
**NOTIZ:**
- [ ] **v412-ctl-fehlermeldung.png**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/v412-ctl-fehlermeldung.png)
Alt: „Terminal-Screenshot der v4l2-ctl-Fehlermeldung beim Öffnen des Video-Gerätes"
**NOTIZ:**
- [ ] **virtueller-desktop-titelbild.jpg**
🖼 [Bild](../content/posts/2022-03-19-OB-virtualcam/virtueller-desktop-titelbild.jpg)
Alt: „Stilisiertes Titelbild: virtueller Desktop-Arbeitsplatz mit mehreren Bildschirmen und Remote-Verbindung"
**NOTIZ:**
---
## 2023-02-26 — jojos-schoko-zimt-schnecken
📝 [Frontmatter](../content/posts/2023-02-26-jojos-schoko-zimt-schnecken/index.md)
- [ ] **schneckennudeln-titel.jpg** (Cover)
🖼 [Bild](../content/posts/2023-02-26-jojos-schoko-zimt-schnecken/schneckennudeln-titel.jpg)
Alt: „Goldbraun gebackene Hefeschnecken in einer Kuchenform, Titelbild des Rezepts"
**NOTIZ:**
- [ ] **Hefeteig-mit-Fuellung.jpg**
🖼 [Bild](../content/posts/2023-02-26-jojos-schoko-zimt-schnecken/Hefeteig-mit-Fuellung.jpg)
Alt: „Ausgerollter Hefeteig, bestrichen mit cremiger Kakao-Zimt-Zucker-Füllung, bereit zum Einrollen"
**NOTIZ:**
- [ ] **16-Schneckennudeln.jpg**
🖼 [Bild](../content/posts/2023-02-26-jojos-schoko-zimt-schnecken/16-Schneckennudeln.jpg)
Alt: „16 dicht an dicht aufgestellte, rohe Hefeschnecken in einer runden Kuchenform"
**NOTIZ:**
- [ ] **hefeschnecken-in-capelle-backform.jpg**
🖼 [Bild](../content/posts/2023-02-26-jojos-schoko-zimt-schnecken/hefeschnecken-in-capelle-backform.jpg)
Alt: „Gegangene, mit Eimilch bestrichene Hefeschnecken in Kapellen-Backform, bereit für den Ofen"
**NOTIZ:**
- [ ] **schneckennudeln-im-ofen.jpg**
🖼 [Bild](../content/posts/2023-02-26-jojos-schoko-zimt-schnecken/schneckennudeln-im-ofen.jpg)
Alt: „Hefeschnecken im Ofen während des Backens, Oberseite beginnt goldbraun zu werden"
**NOTIZ:**
- [ ] **schneckennudeln-fertig.jpg**
🖼 [Bild](../content/posts/2023-02-26-jojos-schoko-zimt-schnecken/schneckennudeln-fertig.jpg)
Alt: „Fertig gebackene, goldbraune Hefeschnecken in der Kuchenform, bereit zum Servieren"
**NOTIZ:**
---
## 2023-03-23 — saemann (Midjourney, CC BY-SA 3.0 DE)
📝 [Frontmatter](../content/posts/2023-03-23-saemann/index.md)
- [ ] **saemann-title.jpg** (Cover, Collage)
🖼 [Bild](../content/posts/2023-03-23-saemann/saemann-title.jpg)
Alt: „Titelbild zum Gleichnis vom Sämann: Collage der fünf KI-generierten Illustrationen im Stil von Eric Carle"
Modifications: „Collage aus Midjourney-generierten Bildern im Stil von Eric Carle, Prompts siehe Artikel"
**NOTIZ:**
- [ ] **bild1-saemann.jpeg**
🖼 [Bild](../content/posts/2023-03-23-saemann/bild1-saemann.jpeg)
Alt: „Illustration im Stil von Eric Carle: Ein freundlicher Bauer streut Samen in einem offenen Feld, im Hintergrund vier Böden — felsig, dornig, vogelreich und fruchtbar"
**NOTIZ:**
- [ ] **bild1-alternativ-saemann.jpeg**
🖼 [Bild](../content/posts/2023-03-23-saemann/bild1-alternativ-saemann.jpeg)
Alt: „Alternative Illustration im Stil von Eric Carle: Bauer beim Säen mit verschiedenen Bodenarten im Hintergrund"
**NOTIZ:**
- [ ] **bild2-saemann.jpeg**
🖼 [Bild](../content/posts/2023-03-23-saemann/bild2-saemann.jpeg)
Alt: „Illustration im Stil von Eric Carle: Kleine, schwache Pflanzen, die mit wenig Erde auf felsigem Boden zu wachsen beginnen"
**NOTIZ:**
- [ ] **bild2-alternativ-saemann.jpeg**
🖼 [Bild](../content/posts/2023-03-23-saemann/bild2-alternativ-saemann.jpeg)
Alt: „Alternative Illustration im Stil von Eric Carle: Keimende Pflanzen auf steinigem Grund"
**NOTIZ:**
- [ ] **bild3-saemann.jpeg**
🖼 [Bild](../content/posts/2023-03-23-saemann/bild3-saemann.jpeg)
Alt: „Illustration im Stil von Eric Carle: Junge Pflanzen werden von Dornen umklammert und erstickt"
**NOTIZ:**
- [ ] **bild4-saemann.jpeg**
🖼 [Bild](../content/posts/2023-03-23-saemann/bild4-saemann.jpeg)
Alt: „Illustration im Stil von Eric Carle: Fröhliche Vögel picken Samen vom Boden und fressen sie, bevor sie keimen können"
**NOTIZ:**
- [ ] **bild5-saemann.jpeg**
🖼 [Bild](../content/posts/2023-03-23-saemann/bild5-saemann.jpeg)
Alt: „Illustration im Stil von Eric Carle: Große, gesunde Pflanzen tragen reiche Früchte auf fruchtbarem Boden, der Bauer steht lächelnd daneben"
**NOTIZ:**
- [ ] **screen-chatgpt-saemann.png**
🖼 [Bild](../content/posts/2023-03-23-saemann/screen-chatgpt-saemann.png)
Alt: „Screenshot des ChatGPT-Dialogs: Eingabe der Anfrage zum Gleichnis vom Sämann für einen 8-Jährigen und KI-generierte Antwort in fünf Bildbeschreibungen"
**NOTIZ:**
---
## 2023-04-07 — Dampfnudeln
📝 [Frontmatter](../content/posts/2023-04-07-Dampfnudeln/index.md)
- [ ] **Hefefreuden.jpg** (Cover)
🖼 [Bild](../content/posts/2023-04-07-Dampfnudeln/Hefefreuden.jpg)
Alt: „Titelbild: Dampfnudeln und Hefezopf auf einem Tisch, frisch aus Dampfgarer und Ofen"
**NOTIZ:**
- [ ] **Hefeteig.jpg**
🖼 [Bild](../content/posts/2023-04-07-Dampfnudeln/Hefeteig.jpg)
Alt: „Aufgegangener Hefeteig in einer Rührschüssel, glatt und elastisch, nach 30 Minuten Ruhezeit"
**NOTIZ:**
- [ ] **Dampfnudeln-auf-Lochblech.jpg**
🖼 [Bild](../content/posts/2023-04-07-Dampfnudeln/Dampfnudeln-auf-Lochblech.jpg)
Alt: „Sechs runde Hefeteigstücke zum Dampfgaren auf einem gelochten Dampfgarblech"
**NOTIZ:**
- [ ] **Dampfnudeln-im-Dampfgarer.jpg**
🖼 [Bild](../content/posts/2023-04-07-Dampfnudeln/Dampfnudeln-im-Dampfgarer.jpg)
Alt: „Gegarte, aufgegangene Dampfnudeln im geöffneten Dampfgarer, glänzend und flaumig"
**NOTIZ:**
- [ ] **Dampfnudel-mit-Vanillesosse.jpg**
🖼 [Bild](../content/posts/2023-04-07-Dampfnudeln/Dampfnudel-mit-Vanillesosse.jpg)
Alt: „Dampfnudel auf Teller angerichtet, übergossen mit goldgelber Vanillesoße"
**NOTIZ:**
- [ ] **Hefezopf.jpg**
🖼 [Bild](../content/posts/2023-04-07-Dampfnudeln/Hefezopf.jpg)
Alt: „Frisch gebackener, dreifach geflochtener Hefezopf, goldbraun glänzend nach dem Einpinseln mit Ei"
**NOTIZ:**
---
## 2023-07-25 — wordpress-statt-padlet-oder-taskcards
📝 [Frontmatter](../content/posts/2023-07-25-wordpress-statt-padlet-oder-taskcards/index.md)
- [ ] **wordpress-horizontales-scrollen.gif** (Cover)
🖼 [Bild](../content/posts/2023-07-25-wordpress-statt-padlet-oder-taskcards/wordpress-horizontales-scrollen.gif)
Alt: „Animierter Screenshot: WordPress-Seite mit horizontal scrollbaren Spalten, die Beiträge im Kanban-Stil nebeneinander zeigen"
**NOTIZ:**
- [ ] **spalten-als-posts-block.png**
🖼 [Bild](../content/posts/2023-07-25-wordpress-statt-padlet-oder-taskcards/spalten-als-posts-block.png)
Alt: „Screenshot des Stackable 'posts block'-Plugins in WordPress mit Spaltenansicht nach Kategorien"
**NOTIZ:**
- [ ] **posts-per-drag-and-drop-sortieren.png**
🖼 [Bild](../content/posts/2023-07-25-wordpress-statt-padlet-oder-taskcards/posts-per-drag-and-drop-sortieren.png)
Alt: „Screenshot der WordPress-Beitragsliste mit aktiviertem 'Intuitive Custom Post Order'-Plugin — Beiträge werden per Drag & Drop sortiert"
**NOTIZ:**
---
## 2024-01-16 — offenheit-das-wesentliche (Midjourney, CC0)
📝 [Frontmatter](../content/posts/2024-01-16-offenheit-das-wesentliche/index.md)
- [ ] **offenheit-wesentlich.png** (Cover)
🖼 [Bild](../content/posts/2024-01-16-offenheit-das-wesentliche/offenheit-wesentlich.png)
Alt: „KI-generierte Aquarell-Illustration: Silhouetten von Menschen aller Geschlechter und Altersgruppen, die ineinander übergehen und sich überlappen — Symbol einer Community of Trust"
Modifications: „KI-generiert mit Midjourney v6.0, Prompt: A Community of Trust based on Openness, silhouettes of people of all genders and ages that merge into each other and overlap, watercolors --v 6.0 --seed 1235164279"
**NOTIZ:**
---
## 2024-03-05 — bottomup-markdown
📝 [Frontmatter](../content/posts/2024-03-05-bottomup-markdown/index.md)
- [ ] **bottomup-markdown.png** (Cover)
🖼 [Bild](../content/posts/2024-03-05-bottomup-markdown/bottomup-markdown.png)
Alt: „Titelbild zur OER-Camp-Session 'BottomUp MarkDown' — Symbol für die 5V-Freiheiten von Open Content in Verbindung mit der Markdown-Sprache"
**NOTIZ:**
---
## 2024-04-03 — kibedenken-bewusstsein (Midjourney, CC0)
📝 [Frontmatter](../content/posts/2024-04-03-kibedenken-bewusstsein/index.md)
- [ ] **kibedenken.png** (Cover)
🖼 [Bild](../content/posts/2024-04-03-kibedenken-bewusstsein/kibedenken.png)
Alt: „Ein junger Roboterjunge mit gesenktem Kopf betrachtet seine Spiegelung im Wasser, im fotorealistischen Stil einer Canon EOS 5D Mark IV"
Caption: „Referenziert auf Narziss aus der griechischen Mythologie und die Illustration von Caravaggio (siehe [Wikipedia #Narziss](https://de.wikipedia.org/wiki/Narziss#))"
Modifications: „KI-generiert mit Midjourney v6.0, Prompt: photographed with the Canon EOS 5D Mark IV a young robot boy with his head down, looking at his reflection in water --v6.0"
**NOTIZ:**
---
## 2025-03-04 — dezentrale-oep-oer (3 Autoren, CC BY 4.0)
📝 [Frontmatter](../content/posts/2025-03-04-dezentrale-oep-oer/index.md)
- [ ] **dezentrale-oep-oer.png** (Cover)
🖼 [Bild](../content/posts/2025-03-04-dezentrale-oep-oer/dezentrale-oep-oer.png)
Alt: „Ein in den Sand gezeichneter Strauß mit den Buchstaben 'OER' — Sinnbild für offene Bildung und freien Wissensaustausch, gleichzeitig Wortspiel-Verbindung zu Nostr (Ostrich = Strauß)"
Caption: „Analog zum Ichthys-Fisch als geheimem Erkennungszeichen: Symbol einer Gemeinschaft, die Wissen offen, unabhängig und widerstandsfähig teilt"
Autoren: Jörg Lohrer, Steffen Rörtgen, Bastian Granas
**NOTIZ:**
---
## Globale Anmerkungen / Änderungswünsche
_Alles was nicht bildspezifisch ist (Lizenz-Defaults, Regeln für UNKNOWN, Generalvorschläge) kann hier rein:_
**NOTIZ:**
---
## Zusammenfassung
- **91 Bilder** in 18 Posts
- **1 Post** ohne lokale Bilder (Erlebnispädagogik, tote Amazon-Hotlinks)
- **4 UNKNOWN-Einträge** zur Recherche (alle im VR-Post)
- **1 Fremdbild** (Flickr, CC BY-NC-SA, Raupe)
- **1 teilfremdes Bild** (Sketchfab, CC BY-NC, Fotograf UNKNOWN)
- **Rest Eigenaufnahmen** (CC0 oder CC BY-SA)

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,10 @@
# Publish-Pipeline für Nostr-Events — Design-Spec
**Datum:** 2026-04-15
**Datum:** 2026-04-15 (aktualisiert 2026-04-16: Blossom für alle Bilder, kein All-Inkl-rsync-Pfad mehr)
**Status:** Entwurf, ausstehende User-Freigabe
**Scope:** Toolchain, die Markdown-Posts aus `content/posts/*/index.md` in signierte Nostr-Events (`kind:30023`, NIP-23) umwandelt, zu Relays publiziert, und die zugehörigen Bilder zum Asset-Host (All-Inkl für Altposts, Blossom für neue) hochlädt.
**Scope:** Toolchain, die Markdown-Posts aus `content/posts/*/index.md` in signierte Nostr-Events (`kind:30023`, NIP-23) umwandelt, zu Relays publiziert, und die zugehörigen Bilder zu Blossom hochlädt.
**Designentscheidung 2026-04-16:** Alle Bilder (auch die der 18 Altposts) werden zu Blossom hochgeladen. Kein rsync-Legacy-Pfad, kein `image_source`-Flag im Frontmatter. Die SPA rendert alle Posts über denselben Code-Pfad (Event-Text → Bild-URLs aus Blossom). Repo = Source-of-Truth für Content, Pipeline = Nostr-Export-Routine.
Diese Spec ist die Schwester-Spec zu [`2026-04-15-nostr-page-design.md`](2026-04-15-nostr-page-design.md) und teilt sich mit ihr den Event-Kontrakt für `kind:30023` und die Konfiguration über `kind:10002` / `kind:10063`.
@ -34,23 +36,22 @@ Diese Spec ist die Schwester-Spec zu [`2026-04-15-nostr-page-design.md`](2026-04
│ (Git-Diff oder force) │
│ 3. Pro Post: │
│ a. Frontmatter parsen │
│ b. Markdown transform │
│ c. Bilder upload │
│ (legacy/blossom) │
│ b. Bilder aus Ordner → │
│ Blossom upload │
│ c. Markdown body: bild- │
│ pfade → Blossom-URLs │
│ d. Event bauen │
│ e. Via NIP-46 signieren │
│ f. Zu Relays pushen │
└──────┬──────────────────────┘
┌──────────┼──────────────┬──────────────┐
▼ ▼ ▼ ▼
Amber Public Blossom- All-Inkl
(NIP-46 Nostr- Server (rsync
Signer Relays (primal, over SSH,
via aus später eigen) Altbilder
Relay) kind:10002 aus der 18
kind:10063) Migrations-
posts)
┌──────────┼──────────────┐
▼ ▼ ▼
Amber Public Blossom-
(NIP-46 Nostr- Server
Signer Relays aus kind:10063
via aus (primal,
Relay) kind:10002 später eigener)
```
### Kernprinzipien
@ -152,34 +153,14 @@ Einmalig manuell publizieren. Phase-1-Inhalt: ein Server.
Phase-5-Erweiterung (eigener Blossom-Server): zusätzliches `["server", "https://blossom.joerg-lohrer.de"]` wird vorne in die Liste aufgenommen, neues Event publiziert.
### 2.5 SSH-Deploy-Key für All-Inkl
1. Lokal Keypair erzeugen, **dediziert für Deploys**, nicht persönlicher SSH-Key:
```
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_joerglohrerde_deploy -C "deploy-joerglohrerde"
```
Ohne Passphrase (CI braucht non-interactive Zugang).
2. Public-Key-Inhalt (`*.pub`) in All-Inkl-KAS unter „SSH-Zugänge" → „Authorized Keys" eintragen.
3. Verbindung testen: `ssh -i ~/.ssh/id_ed25519_joerglohrerde_deploy w00xxxxx@ssh.all-inkl.com`
4. Private-Key bereitstellen:
- **Lokal:** liegt in `~/.ssh/` und wird von rsync automatisch genutzt.
- **CI:** als GitHub-Actions-Secret `SSH_DEPLOY_KEY` (Inhalt der privaten Key-Datei). Im Workflow wird er in `~/.ssh/id_ed25519` gechrieben und `chmod 600` gesetzt.
### 2.6 All-Inkl Deploy-Root
Nach Tarifwechsel auf Premium: Pfad im KAS unter „Dateiverwaltung" ablesen. Typisch: `w00xxxxx@ssh.all-inkl.com:joerg-lohrer.de/`.
- **Lokal:** in `.env` als `ALLINKL_DEPLOY_ROOT`
- **CI:** als GitHub-Actions-Secret
### 2.7 `deno task check`
### 2.5 `deno task check`
Dieser Subcommand verifiziert alle obigen Punkte:
- `BUNKER_URL` gesetzt, Bunker antwortet auf Ping, Pubkey stimmt mit `AUTHOR_PUBKEY_HEX` überein.
- `kind:10002` auf Bootstrap-Relay gefunden, mindestens 1 Relay eingetragen.
- `kind:10063` auf Bootstrap-Relay gefunden, mindestens 1 Server eingetragen.
- SSH-Verbindung zu `ALLINKL_DEPLOY_ROOT` erfolgreich (`ssh ... echo ok`).
- Blossom-Server aus `kind:10063` antwortet auf HEAD / (Healthcheck).
- Deno-Version und benötigte Permissions.
Bei jedem Fehler: klare Text-Meldung, was zu tun ist (z. B. „kind:10002 fehlt — publiziere es manuell mit folgendem Schema: ...").
@ -249,72 +230,47 @@ Slug kommt als **lowercase String** aus dem Frontmatter-Feld `slug:`. Ist bereit
### 4.2 Bild-URL-Transformation
Ziel: alle relativen Bild-Referenzen im Markdown-Body werden zu absoluten URLs.
Ziel: alle relativen Bild-Referenzen im Markdown-Body werden durch Blossom-URLs ersetzt. Ablauf:
**Erkannte Muster:**
- `![alt](filename)` — reguläre Markdown-Bild-Syntax.
- `[![alt](filename)](link)` — Bild-in-Link-Konstrukt.
- `![alt](filename =WxH)` — mit Größen-Suffix (Obsidian/PaperMod-Erweiterung).
1. Pipeline sammelt alle Bilder aus dem Post-Ordner (Datei-Scan nach gängigen Bild-Extensions).
2. Jedes Bild wird zu allen Servern aus `kind:10063` hochgeladen (siehe §5).
3. Blossom liefert eine hash-basierte URL zurück (Format: `<server>/<sha256>` oder `<server>/<sha256>.<ext>`).
4. Pipeline baut eine Mapping-Tabelle `<dateiname> → <blossom-url>`.
5. Markdown-Body wird traversiert, alle erkannten Bild-Patterns werden ersetzt:
- `![alt](filename.png)``![alt](<blossom-url>)`
- `[![alt](filename.png)](link)``[![alt](<blossom-url>)](link)`
- `![alt](filename.png =WxH)``![alt](<blossom-url>)` (Größen-Suffix entfernt; SPA skaliert per CSS)
6. Wenn `filename` bereits ein Schema enthält (`http://`, `https://`, `//`), bleibt die URL unverändert — ist schon absolut.
**Regeln:**
1. Wenn `filename` ein Schema enthält (`http://`, `https://`, `//`), nicht transformieren — ist schon absolut.
2. Ansonsten zu absoluter URL machen; URL-Kodierung pro Pfad-Segment via `encodeURIComponent()`.
3. `=WxH`-Suffix entfernen; die SPA skaliert Bilder per CSS responsiv.
**Konsequenz:** Es gibt nur **einen** Upload-Pfad (Blossom). Kein Legacy-Pfad mehr. Kein `image_source`-Flag, keine Datum-basierten URL-Strukturen.
**Basis-URL je nach `image_source`-Frontmatter:**
### 4.3 Cover-Image-Tag
- Wenn `image_source: legacy``https://joerg-lohrer.de/<YYYY>/<MM>/<DD>/<dtag>.html/<encoded-filename>`
- `YYYY/MM/DD` aus `date:`-Frontmatter, nicht aus dem Signatur-Zeitpunkt.
- `<dtag>` ist identisch mit `slug`.
- Wenn `image_source` fehlt oder `image_source: blossom` → Blossom-URL; siehe Abschnitt 5.
### 4.3 `image_source`-Flag
**Einmaliger Migrationsschritt (vor erstem Publish-Lauf):** Die 18 Altposts bekommen `image_source: legacy` ins Frontmatter geschrieben. Das ist ein separater Commit, kein Pipeline-Feature.
**Neue Posts:** kein Flag nötig, Default = `blossom`. Wenn ein zukünftiger Post explizit auf All-Inkl zeigen soll (außergewöhnlich), kann `image_source: legacy` gesetzt werden.
### 4.4 Cover-Image-Tag
Das `image`-Tag im Event (für Listen-Previews/OG-Vorschau in Nostr-Clients) kommt aus dem Frontmatter (nicht aus dem Markdown-Body):
Das `image`-Tag im Event (für Listen-Previews/OG-Vorschau in Nostr-Clients) kommt aus dem Frontmatter:
- Quelle: `cover.image:` (Hugo-Page-Bundle-Konvention); Fallback `image:` auf Top-Level.
- Ist typischerweise ein relativer Dateiname.
- Wird durch denselben URL-Bauer wie die Body-Bilder geschickt (Abschnitt 4.2), aber der Input ist ein direkter Dateiname aus YAML, nicht aus Markdown-Syntax. Keine `=WxH`-Suffix-Erkennung nötig.
- Ergebnis: absolute URL gemäß `image_source`-Policy.
- Ist typischerweise ein relativer Dateiname, der als Bild auch im Post-Ordner liegt und damit ohnehin zu Blossom hochgeladen wird.
- Wird nach dem Upload über die Mapping-Tabelle auf die Blossom-URL umgeschrieben.
- Wenn der Wert bereits absolut ist (http/https), bleibt er unverändert.
---
## 5. Upload-Pfade
## 5. Upload-Pfad
### 5.1 Legacy-Upload (All-Inkl)
### 5.1 Blossom-Upload (einheitlich für alle Posts)
Betrifft: die 18 Altposts, Bilder darin.
**Mechanik:** `rsync` over SSH via `Deno.Command("rsync", [...])`.
**Befehlsschema:**
```
rsync -avz --no-perms --no-times \
-e "ssh -i $DEPLOY_KEY_PATH -o StrictHostKeyChecking=accept-new" \
<post-folder>/*.{png,jpg,jpeg,gif,webp,svg} \
$ALLINKL_DEPLOY_ROOT<YYYY>/<MM>/<DD>/<dtag>.html/
```
- **Idempotent:** rsync überträgt nur neue/geänderte Dateien.
- **Nicht-löschend:** ohne `--delete`. Alte Bilder bleiben auf dem Server liegen, keine automatische Bereinigung. Manueller Aufräum-Bedarf wird hingenommen (Tote Dateien verursachen keinen Schaden, Storage ist billig).
- **Zielordner erzeugen:** rsync legt fehlende Ordner per `--mkpath` oder (wenn Version zu alt) per vorgeschaltetem `ssh ... mkdir -p` an.
**Neuer Post-Edit mit alten Bildern:** falls jemand mal einen Post editiert, der `image_source: legacy` hat und neue Bilder hinzufügt → diese werden auch zu All-Inkl geschoben. Das ist okay. Das Flag steuert nur den URL-Basispfad, nicht die Intention „nie wieder All-Inkl".
### 5.2 Blossom-Upload
Betrifft: alle neuen Posts (`image_source: blossom` oder fehlend).
Betrifft: alle Bilder aller Posts, ohne Unterscheidung zwischen Alt- und Neu-Post.
**Mechanik:** BUD-01 HTTP-Upload zu allen Servern aus `kind:10063`-Liste, parallel.
**Schritte pro Bild:**
**Ablauf pro Post:**
1. Alle Dateien im Post-Ordner mit Bild-Extensions (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.svg`) sammeln.
2. Hugo-generierte Resize-Varianten (`*_hu_*.png` etc.) werden **ignoriert** — das sind Derivate, keine Originale. Nur die Originaldateien, wie sie im Markdown referenziert werden, zählen.
3. Pro Bild SHA-256 berechnen, zu allen Servern parallel hochladen.
4. Mapping `<filename> → <primary-blossom-url>` aufbauen (primär = erster Server aus Liste).
**Schritte pro Bild (intern):**
1. SHA256-Hash der Datei berechnen.
2. Authorization-Event (`kind:24242`) bauen und via Bunker signieren (enthält Hash, Verb `upload`, Expiration).
@ -329,6 +285,17 @@ Betrifft: alle neuen Posts (`image_source: blossom` oder fehlend).
**Retry:** 2 Versuche pro Server mit exponentiellem Backoff.
**Idempotenz:** Blossom dedupliziert per SHA-256. Ein erneuter Upload derselben Datei ist ein No-Op (Server antwortet 200 mit derselben URL). Daher ist wiederholtes `--force-all` unproblematisch.
### 5.2 Kein Legacy-Upload mehr
Frühere Versionen dieser Spec sahen einen rsync-Pfad zu All-Inkl für Altposts vor. Das ist entfallen. Begründung:
- Repo ist Source-of-Truth; alle Bilder liegen in `content/posts/<ordner>/`.
- Einheitlicher Render-Pfad in der SPA (keine Sonderlogik für Altposts).
- Blossom dedupliziert per Hash; wiederholter Upload ist billig.
- Nach Cutover verwaisen die alten `joerg-lohrer.de/YYYY/MM/DD/…`-URLs — das ist akzeptiert, da sie nur in der weggehenden Hugo-Site referenziert sind.
---
## 6. Change-Detection und Workflow
@ -406,13 +373,9 @@ Pro Post wird das signierte Event an alle Relays aus der `kind:10002`-Liste para
### 7.2 Blossom-Upload
Siehe Abschnitt 5.2. Pro Server 2 Retries, mindestens 1 Server muss akzeptieren.
Siehe Abschnitt 5.1. Pro Server 2 Retries, mindestens 1 Server muss akzeptieren.
### 7.3 Legacy-Upload
rsync-Aufruf wird bei Exit-Code != 0 einmal wiederholt (1 Retry, 3 s Pause). Bleibt der Aufruf fehlerhaft, wird der Post als failed markiert und die Pipeline fährt mit dem nächsten fort.
### 7.4 Bunker-Signing
### 7.3 Bunker-Signing
- Timeout 30 Sekunden pro Signatur-Request (Handy-Wake-up berücksichtigen).
- 1 Retry bei Timeout.
@ -470,7 +433,6 @@ publish/
│ │ ├── signer.ts # NIP-46 Bunker-Wrapper
│ │ ├── relays.ts # loadOutboxRelays, publishEvent
│ │ ├── blossom.ts # loadServerList, uploadBlob
│ │ ├── legacy-upload.ts # rsync SSH wrapper
│ │ ├── change-detection.ts # gitDiff, allPostFiles, forceMode
│ │ └── log.ts # structured logger + JSON writer
│ └── subcommands/
@ -548,25 +510,18 @@ jobs:
with:
deno-version: v2.x
- name: Setup SSH-Deploy-Key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_DEPLOY_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan ssh.all-inkl.com >> ~/.ssh/known_hosts
- name: Pre-Flight Check
env:
BUNKER_URL: ${{ secrets.BUNKER_URL }}
ALLINKL_DEPLOY_ROOT: ${{ secrets.ALLINKL_DEPLOY_ROOT }}
AUTHOR_PUBKEY_HEX: ${{ secrets.AUTHOR_PUBKEY_HEX }}
BOOTSTRAP_RELAY: ${{ secrets.BOOTSTRAP_RELAY }}
run: deno task check
- name: Publish
env:
BUNKER_URL: ${{ secrets.BUNKER_URL }}
ALLINKL_DEPLOY_ROOT: ${{ secrets.ALLINKL_DEPLOY_ROOT }}
AUTHOR_PUBKEY_HEX: ${{ secrets.AUTHOR_PUBKEY_HEX }}
BOOTSTRAP_RELAY: ${{ secrets.BOOTSTRAP_RELAY }}
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
run: |
if [ "${{ inputs.force_all }}" = "true" ]; then
@ -594,7 +549,7 @@ Diese Publish-Pipeline und die SPA sind komplementär, aber voneinander entkoppe
- `kind:30023` Event-Schema — Publish produziert, SPA konsumiert.
- `kind:10002` Relay-Liste — Publish liest, SPA liest.
- `kind:10063` Blossom-Liste — Publish liest beim Upload, SPA liest für Bild-Fallback (zukünftig).
- Bild-URL-Konvention für Altposts `/YYYY/MM/DD/<dtag>.html/<file>` — Publish schreibt, SPA erwartet.
- Alle Bild-URLs zeigen auf Blossom (hash-basiert) — einheitlich für alle Posts.
**Unabhängige Entwicklung möglich:**
@ -604,7 +559,7 @@ Diese Publish-Pipeline und die SPA sind komplementär, aber voneinander entkoppe
**Abhängigkeit beim Cutover (SPA-Migrationsschritte C + D):**
- SPA kann erst live gehen, wenn die 18 Altposts als Events auf Relays liegen.
- **Schritt C** der SPA-Migration bedeutet konkret: einmaliger lokaler Lauf `deno task publish --force-all` mit dem vollständigen Altbestand. Dieser Schritt liegt zeitlich **vor** Schritt D (dem tatsächlichen Cutover auf All-Inkl).
- **Schritt C** der SPA-Migration bedeutet konkret: einmaliger lokaler Lauf `deno task publish --force-all` mit dem vollständigen Altbestand. Dieser Schritt liegt zeitlich **vor** Schritt D (dem tatsächlichen Cutover der Hauptdomain).
- Voraussetzung ist, dass die Publish-Pipeline zu diesem Zeitpunkt vollständig implementiert und durch `deno task check` validiert ist.
**Laufender Betrieb:**
@ -621,12 +576,11 @@ Diese Publish-Pipeline und die SPA sind komplementär, aber voneinander entkoppe
|---|---|---|---|
| Amber offline während CI | mittel | hoch (Pipeline bricht ab) | Clear Error; Nutzer retriggert manuell nachdem Handy verfügbar |
| Bunker-Secret leakt (Repo-Secret) | niedrig | mittel | Secret rotierbar: in Amber Pairing löschen, neu pairen, Secret aktualisieren |
| SSH-Deploy-Key leakt | niedrig | mittel | Dedicated Key, in All-Inkl-KAS revokebar |
| `kind:10002` versehentlich überschrieben (Relay-Liste leer) | niedrig | hoch | check-Subcommand prüft vor jedem Run; Pipeline bricht bei leerer Liste ab |
| Relay-Zensur (Events werden gelöscht) | niedrig | mittel | Multi-Relay-Push; zusätzlich bezahltes nostr.wine als Durability-Anker |
| Git-Diff übersieht Post (Rebase, Force-Push) | niedrig | niedrig | `--force-all` als Fallback, dokumentiert |
| Blossom-Server löscht Bild | mittel | mittel | Multi-Upload zu mehreren Servern sobald kind:10063 erweitert ist |
| `encodeURIComponent` vs. All-Inkl Apache: URL-Matching fällt auseinander | niedrig | mittel | Tests gegen reale URLs; Normalisierungs-Regel (lowercase Slugs, ASCII-Filenames bevorzugt) |
| Blossom-Server löscht Bild | mittel | mittel | Multi-Upload zu mehreren Servern sobald kind:10063 erweitert ist; `nak blossom mirror` als Ausgleich |
| Blossom-Server komplett weg, kein Mirror | niedrig | hoch | eigener Blossom-Server auf Optiplex (Phase 5) als dauerhafter Anker |
| Privater Schlüssel-Recovery | niedrig | **katastrophal** | Amber hat Backup-Mechanismus; `nsec` zusätzlich offline auf Hardware sichern |
---
@ -636,7 +590,7 @@ Diese Publish-Pipeline und die SPA sind komplementär, aber voneinander entkoppe
**Jetzt (Bunker-Stufe Amber, Phase 1 Blossom):**
- Handy mit Amber als einziger Signer, online während Publish-Runs.
- Ein Blossom-Server in `kind:10063` (primal).
- Legacy-Bilder auf All-Inkl für die 18 Altposts.
- Alle Bilder (auch die der 18 Altposts) auf Blossom.
- Relay-Liste mit 4 Public-Relays.
**Bunker-Stufe Optiplex (sobald Proxmox-Container läuft):**
@ -659,7 +613,7 @@ Diese Publish-Pipeline und die SPA sind komplementär, aber voneinander entkoppe
- `deno task check` ohne Fehler.
- 18 Altposts via einmaligem `deno task publish --force-all` publiziert.
- Jeder Post in mindestens 2 Public-Relays abrufbar, in Habla.news korrekt gerendert.
- Bilder der 18 Posts via `/YYYY/MM/DD/<dtag>.html/<bildname>` auf All-Inkl erreichbar.
- Alle Bilder auf Blossom erreichbar (Hash-URL liefert die Datei).
- Ein neuer Test-Post via CI auf `main`-Push publiziert in unter 90 Sekunden ab Push.
- `publish-log.json` enthält aussagekräftige Einträge pro Post.
- Pipeline läuft ohne nsec-Exposition in irgendeiner Umgebung.

View File

@ -0,0 +1,277 @@
# Konvention: Bild-Metadaten im Post-Frontmatter (Phase 1)
**Datum:** 2026-04-16
**Status:** Phase-1-Minimal — fokussiert auf sichere Attribution und `alt`-Vollständigkeit. Caption-Rendering, Reverse-Routine, License-Katalog und strikte Validierung sind explizit Phase 2.
**Scope:** YAML-Frontmatter-Schema für Bildmetadaten in Markdown-Posts. Wird von der Publish-Pipeline in `kind:30023`-Events (NIP-23) plus `imeta`-Tags (NIP-92) + `license`-Tag abgebildet.
## Ziele
1. **Sichere Attribution** — keine stille Fehlattribuierung. Fehlende Kenntnis wird explizit als `UNKNOWN` markiert, nie implizit geerbt.
2. **Menschlich lesbares, minimal-invasives YAML** — Defaults kommen aus Env, Frontmatter enthält nur das Abweichende.
3. **Blaupausen-Tauglichkeit** — funktioniert für beliebige Repos mit 1..n Autoren, Eigen- und Fremdbildern.
4. **Eine Datenstruktur pro Konzept** — Cover ist nur ein Bild mit Rolle. Kein paralleler Schema-Zweig.
---
## 1. Post-Ebene
```yaml
---
title: "Schokoschnecken"
slug: "jojos-schoko-zimt-schnecken"
date: 2023-02-26
# Lizenz des Post-TEXTES. Gilt NICHT automatisch für Bilder.
license: "https://creativecommons.org/publicdomain/zero/1.0/deed.de"
# Text-Autoren. Weglassen, wenn DEFAULT_AUTHORS aus Env gelten soll.
# Immer Array, auch bei einem Autor.
authors:
- name: "Jörg Lohrer"
url: "https://joerg-lohrer.de/" # optional
orcid: "..." # optional, frei erweiterbar
---
```
**Regeln:**
- `license` fehlt → Env-Default `DEFAULT_LICENSE` greift für den Text.
- `authors` fehlt → Env-Default `DEFAULT_AUTHORS` greift für den Text.
- **Diese Werte gelten ausschließlich für den Post-TEXT.** Für Bilder gibt es keine automatische Vererbung. Bilder haben eigene Lizenz- und Autor-Felder (siehe Abschnitt 2).
### 1.1 `date`
Erlaubtes Format: `YYYY-MM-DD` (wird als `00:00:00 UTC` interpretiert) oder ISO-8601 mit Uhrzeit (`YYYY-MM-DDTHH:MM:SSZ`). Zeitzone immer UTC, keine lokale TZ. Die Pipeline leitet daraus `published_at` (Unix-Sekunden) ab, stabil über Edits.
---
## 2. Bilder — einheitliche Liste
**Alle** Bilder eines Posts (Cover wie Body-Bilder) leben in einer einzigen `images`-Liste. Das Cover ist ein Bild mit `role: cover`.
```yaml
images:
# Cover-Bild
- file: cover.jpg
role: cover
alt: "Goldbraune Hefeschnecken auf Kuchenblech, frisch gebacken"
license: "https://creativecommons.org/publicdomain/zero/1.0/deed.de"
authors:
- name: "Jörg Lohrer"
# Body-Bild, eigenes Foto
- file: Hefeteig-mit-Fuellung.jpg
alt: "Hefeteig mit Kakao-Zimt-Zucker-Füllung, ausgerollt auf Backpapier"
license: "https://creativecommons.org/publicdomain/zero/1.0/deed.de"
authors:
- name: "Jörg Lohrer"
# Body-Bild, Herkunft unklar (Altpost, noch zu recherchieren)
- file: altes-bild.jpg
alt: "Screenshot der Startseite eines Lern-Portals"
license: UNKNOWN
authors: UNKNOWN
# Body-Bild, Fremdbild mit vollen Angaben
- file: fremdfoto.jpg
alt: "Osterküken mit Osterei"
authors:
- name: "Vera Kratochvil"
source_url: "https://www.publicdomainpictures.net/de/view-image.php?image=13188"
license: "https://creativecommons.org/publicdomain/zero/1.0/"
modifications: "beschnitten" # optional (das B in TULLU-BA)
```
### 2.1 Feld-Referenz
| Feld | Pflicht | Wert | Semantik |
|---|---|---|---|
| `file` | ja | String | Dateiname relativ zum Post-Ordner. Datei muss existieren. |
| `role` | nein | `cover` | Genau ein Bild pro Post darf `role: cover` haben. Dessen URL landet im Event-`image`-Tag. Kein `role` → Body-Bild. |
| `alt` | ja | String | Accessibility-Beschreibung. Leerstring `""` ist erlaubt (Dekorationsbild), fehlendes Feld ist ein Validierungsfehler. |
| `caption` | nein | String | Optionaler menschlicher Kontext (z. B. „Teig vor dem Einrollen"). Wird in Phase 1 nur in `imeta` als `caption`-Feld eingetragen. |
| `license` | ja | URL \| `UNKNOWN` | Volle URL im schema.org-Stil **oder** `UNKNOWN` als expliziter Marker. Kein Inheritance. |
| `authors` | ja | Array \| `UNKNOWN` | Array von `{name, url?, orcid?, ...}` **oder** `UNKNOWN`. Kein Inheritance. |
| `source_url` | nein | URL | Originalquelle / Fundstelle des Bildes. |
| `modifications` | nein | String | Freitext-Beschreibung einer Bearbeitung („beschnitten", „Kontrast angehoben", …). |
### 2.2 `UNKNOWN`-Semantik
`UNKNOWN` ist ein **einzelner** sauberer Marker — kein leeres Feld, kein `null`, kein Weglassen. Nutzen:
- Pipeline schreibt das Feld **nicht** in den `imeta`-Tag.
- Pipeline **loggt eine Warnung** pro `UNKNOWN`-Vorkommen (mit Post-Slug + Dateiname) — dient als Recherche-Liste.
- In Phase 1 ist `STRICT_MODE` default `false`: Events werden trotzdem publiziert.
- In Phase 2 kann `STRICT_MODE=true` Events mit `UNKNOWN` blockieren.
### 2.3 Bilder im Body
Im Markdown-Body werden Bilder weiterhin schlicht referenziert:
```markdown
![](Hefeteig-mit-Fuellung.jpg)
```
oder (für Migration tolerant):
```markdown
![Hefeteig mit Füllung](Hefeteig-mit-Fuellung.jpg)
```
Der Alt-Text im Markdown ist **niedriger priorisiert** als `alt` aus `images[]`. Er dient nur als Fallback für Bilder, die nicht in `images[]` stehen.
**Reihenfolge:** `images[]` ist ein Metadaten-Lookup per `file`, **keine** Sequenz. Die YAML-Reihenfolge muss nicht der Body-Reihenfolge entsprechen. Die Pipeline sortiert für Log-Output alphabetisch nach `file`.
### 2.4 Body-Captions aus Altposts
Bestehende in-body-Captions (z. B. Lead-in-Sätze vor Bildern, italic-Attributionen nach Bildern) bleiben unberührt. Phase 1 injiziert **nichts** in den Body. Redundanz oder Entfernen ist eine Phase-2-Entscheidung.
---
## 3. Abbildung auf das Nostr-Event (kind:30023)
### 3.1 Pflicht- und Standard-Tags (NIP-23)
| Tag | Quelle |
|---|---|
| `["d", slug]` | Frontmatter `slug` |
| `["title", title]` | Frontmatter `title` |
| `["published_at", unix]` | Frontmatter `date` (stabil über Edits) |
| `["summary", ...]` | Frontmatter `description` |
| `["image", url]` | URL des Bildes mit `role: cover` nach Blossom-Upload |
| `["t", tag]` | je ein Eintrag aus Frontmatter `tags[]` |
### 3.2 Lizenz und Autoren (Post-Text-Ebene)
| Tag | Quelle |
|---|---|
| `["license", url]` | Post-`license` (einmal pro Event, nur für Text-Lizenz) |
| `["p", pubkey, relay-hint, role]` | optional, wenn Text-Autoren einen Nostr-Pubkey haben — Phase 2 |
Für Phase 1 wird **nur** der `license`-Tag des Post-Textes geschrieben.
### 3.3 `imeta`-Felder pro Bild (NIP-92 plus Extensions)
Pro hochgeladenem Bild ein Tag:
```
["imeta",
"url <blossom-url>",
"m <mime>",
"x <sha256>",
"alt <alt>", // nur wenn nicht leer
"caption <caption>", // nur wenn vorhanden
"license <url>", // nur wenn konkrete URL (nicht UNKNOWN)
"author <name>", // eins pro Autor, nur wenn konkret (nicht UNKNOWN)
"source_url <url>", // nur wenn vorhanden
"modifications <text>" // nur wenn vorhanden
]
```
**Regeln:**
- `url`, `m`, `x` sind Pflicht und kommen aus dem Blossom-Upload.
- `UNKNOWN`-Werte werden **weggelassen** (kein Feld im Tag).
- Leerer `alt` wird weggelassen.
- Mehrere Autoren → mehrere `author`-Einträge im selben Tag.
### 3.4 NIP-89 `client`-Tag
Wenn Env `CLIENT_TAG` gesetzt ist: `["client", "<name>"]`. Default leer → kein Tag. Opt-in für Blaupausen, die Provenance markieren wollen.
### 3.5 Referenzen (`a`, `e`) — Phase 2
Aus optionalem Frontmatter `references:` (Array von `nostr:naddr…` / `nostr:nevent…`) werden `a`/`e`-Tags dekodiert. In Phase 1 nicht implementiert.
### 3.6 Body-Caption-Injektion — Phase 2
Automatische Injektion menschenlesbarer Attribution unter jedes Bild im Event-`content`. In Phase 1 nicht implementiert — reine `imeta`-Tags reichen für NIP-23-konforme Clients. Ob/wie in Phase 2 gebaut, wird anhand konkreter Client-Lücken entschieden.
### 3.7 Reverse-Routine — Phase 2
Rekonstruktion von strukturierten `images[]`-Einträgen aus nacktem Markdown mit injizierten Captions. In Phase 1 nicht benötigt.
---
## 4. Env-Defaults (Blaupause)
| Env | Default | Zweck |
|---|---|---|
| `DEFAULT_LICENSE` | `https://creativecommons.org/publicdomain/zero/1.0/deed.de` | Post-Text-Lizenz, wenn Frontmatter `license` fehlt |
| `DEFAULT_AUTHORS` | `[]` | Post-Text-Autoren als JSON-Array `[{"name":"…"}]`, wenn Frontmatter `authors` fehlt |
| `CLIENT_TAG` | *(leer)* | NIP-89 client-Provenance, opt-in |
| `STRICT_MODE` | `false` | Phase 1: Warnungen statt Fehler bei `UNKNOWN`. Phase 2: kann auf `true` gesetzt werden |
**Wichtig:** Env-Defaults greifen nur für die **Post-Text-Lizenz und Post-Text-Autoren**. Sie greifen **nicht** für Bilder. Bilder brauchen explizite `license` und `authors` pro Eintrag (oder `UNKNOWN`).
---
## 5. Validierung (Phase 1 — minimal)
Der `validate-post`-Subcommand prüft:
1. Jedes Bild in `images[]` hat ein `alt`-Feld (Leerstring erlaubt, fehlendes Feld verboten).
2. Jeder `file`-Wert referenziert eine existierende Datei im Post-Ordner.
3. Jedes im Body mit `![](filename)` referenzierte Bild existiert als Datei.
4. Maximal ein Bild hat `role: cover`.
**Explizit NICHT geprüft in Phase 1:**
- `license` vorhanden oder well-formed (Env-Default für Text greift; Bilder dürfen `UNKNOWN` sein)
- `authors` vorhanden oder non-empty (dito)
- URL-Wohlgeformtheit über `string.startsWith('http')` hinaus
- Orphan-Bilder (Bilder im Ordner, die nicht in `images[]` stehen und nicht im Body referenziert sind)
---
## 6. Migrations-Workflow (die 18 Altposts)
**Vor** der Pipeline-Implementierung wird einmalig ein Redaktions-Durchlauf gemacht, Claude-assistiert. Pro Post:
1. Bestehendes Frontmatter lesen.
2. Bilder im Post-Ordner listen. Hugo-Derivate (`*_hu_*.ext`) ignorieren.
3. Body-Kontext extrahieren (Text vor/nach jedem Bild + Dateiname).
4. Für jedes Bild schlägt Claude vor:
- `alt` (aus Kontext + Dateiname abgeleitet)
- `role: cover` für das Frontmatter-Cover-Bild
- `license` + `authors` = Eigenwerte, **wenn** der Kontext klar auf Eigenaufnahme hindeutet; sonst `UNKNOWN` mit Notiz
5. Jörg reviewt, korrigiert, nickt ab.
6. Pipeline-Autor schreibt Frontmatter-Patch.
7. Commit pro Post oder gebündelt nach Batch.
**Minimaler Fall pro Post:**
```yaml
---
# bisheriges Frontmatter bleibt
# ergänzt wird:
images:
- file: cover.jpg
role: cover
alt: "..."
license: "https://creativecommons.org/publicdomain/zero/1.0/deed.de"
authors:
- name: "Jörg Lohrer"
- file: bild1.jpg
alt: "..."
license: "https://creativecommons.org/publicdomain/zero/1.0/deed.de"
authors:
- name: "Jörg Lohrer"
---
```
Fremdbilder bekommen `source_url`, Bilder mit unklarer Provenienz `UNKNOWN`.
---
## 7. Was in Phase 2 entschieden wird
- **Caption-Rendering-Format** (Kurzform-Katalog, Host-Extraktion, Locale-Normalisierung)
- **Body-Caption-Injektion** oder Verzicht
- **Reverse-Routine** aus Caption → YAML
- **`STRICT_MODE=true`** als Standard
- **Orphan-Bild-Detection** in der Validierung
- **`references:`-Feld** für `a`/`e`-Cross-References
- **`p`-Tags** für Text-Autoren mit Nostr-Pubkeys

View File

@ -0,0 +1,274 @@
# Structured Image Metadata for Markdown-Sourced Nostr Long-Form Content
**Status:** Working draft — a practice convention, not (yet) a NIP.
**Scope:** Authors who maintain Markdown long-form posts (`kind:30023`, NIP-23) in a git repository and publish them to Nostr via a build pipeline. The convention defines how image metadata (author, license, source, alt text, caption) lives in the repository, how it becomes `imeta` tags (NIP-92) in the event, and how to round-trip between the two.
**Goal:** Zero data loss between repository and event. Human-readable in raw Markdown. Machine-readable in the published event. Safe defaults against accidental misattribution.
---
## Why this exists
Markdown's native image syntax — `![alt](file.png)` — only carries two fields: the target and an alt text. Everything else a properly attributed image needs (author, license, license link, source, modifications — the "TULLU-BA" rule in German copyright practice) has nowhere to go.
Authors have three unsatisfying options today:
1. **Stuff everything into a visible caption line** under each image. Good for human readers, bad for machine parsing, risky because easily forgotten or inconsistent.
2. **Inline HTML `<figure>` blocks** with `<figcaption>`. Breaks Markdown lint tooling, hard to re-edit.
3. **Lose the metadata entirely.** Silent misattribution risk when the post is re-published without provenance.
NIP-92's `imeta` tag fixes the event-side machine-readability problem (url, mime, sha256, alt, etc. per image). But it doesn't answer where the data lives *before* the event exists.
This convention proposes: **structured YAML frontmatter as source of truth, free-form Markdown body for prose, deterministic bidirectional mapping between them.**
---
## The convention in one example
```yaml
---
title: "Schoko-Zimt-Schnecken"
slug: "schoko-schnecken"
date: 2023-02-26
# Text license (the post body). Image licenses are set per image.
license: "https://creativecommons.org/publicdomain/zero/1.0/"
# Post text authors (array, even for single author).
authors:
- name: "Jane Doe"
url: "https://jane.example/"
images:
- file: cover.jpg
role: cover
alt: "Golden baked yeast buns in a round pan, fresh from the oven"
license: "https://creativecommons.org/publicdomain/zero/1.0/"
authors:
- name: "Jane Doe"
- file: dough-filling.jpg
alt: "Rolled-out yeast dough, spread with cocoa-cinnamon-sugar filling"
license: "https://creativecommons.org/publicdomain/zero/1.0/"
authors:
- name: "Jane Doe"
# Foreign image with full TULLU-BA attribution:
- file: flickr-buns.jpg
alt: "Basket of freshly baked cinnamon rolls"
caption: "On a market stall in Lyon"
authors:
- name: "Max Mustermann"
source_url: "https://www.flickr.com/photos/mustermann/12345/"
license: "https://creativecommons.org/licenses/by-sa/4.0/"
modifications: "cropped"
---
Roll out the dough and spread the filling evenly:
![](dough-filling.jpg)
Slice into 16 pieces and arrange in the pan...
```
The Markdown body stays clean. The YAML carries the truth.
---
## Field reference
### Post-level (applies to the post text, not images)
| Field | Required | Type | Semantics |
|---|---|---|---|
| `license` | yes | URL | License of the post **text**. Does **not** cascade to images. |
| `authors` | yes | Array of `{name, url?, orcid?, ...}` | Authors of the post text. Array even with one author. |
Pipeline implementations may provide env-level defaults (`DEFAULT_LICENSE`, `DEFAULT_AUTHORS`) so single-author blogs don't repeat the same block on every post.
### Per-image (under the `images:` list)
| Field | Required | Type | Semantics |
|---|---|---|---|
| `file` | yes | String | Filename relative to the post directory. Must exist on disk. |
| `role` | no | `cover` | At most one image per post may carry `role: cover`. Its URL becomes the event's `image` tag. |
| `alt` | yes | String | Accessibility description. Empty string is allowed (decorative image); missing field is a validation error. |
| `caption` | no | String | Optional human context beyond the alt text. |
| `license` | yes | URL or `UNKNOWN` | Full schema.org-style license URL, or the literal `UNKNOWN`. No cascading from post-level. |
| `authors` | yes | Array or `UNKNOWN` | Author list, or the literal `UNKNOWN`. No cascading from post-level. |
| `source_url` | no | URL | Where the image was originally sourced (Flickr, Sketchfab, self-reference, etc.). |
| `modifications` | no | String | Free-text description of any derivative work ("cropped", "color-adjusted", "AI-generated with prompt: ..."). The "BA" in TULLU-BA. |
### Why no cascading
Cascading license/author from post to images was rejected after early prototypes: it makes **silent misattribution** the easy default. If a post is tagged `license: CC0` and a contributor adds a foreign image without noticing, the image inherits CC0 implicitly and ships to Nostr with a false attribution.
Explicit per-image fields cost a few extra lines of YAML and prevent an entire class of attribution bugs.
### `UNKNOWN` as an explicit value
For legacy content where provenance has been lost:
```yaml
- file: old-screenshot.png
alt: "Screenshot of a now-defunct learning portal's homepage"
license: UNKNOWN
authors: UNKNOWN
```
Pipeline behavior:
- Fields set to `UNKNOWN` are **not** written into the `imeta` tag (they are simply absent, not wrongly stated).
- A warning is logged per `UNKNOWN` field with post slug + filename — this becomes a research backlog.
- A strict mode can block publication when `UNKNOWN` values are present (opt-in).
---
## Mapping to the Nostr event
A post with this frontmatter produces a `kind:30023` event containing:
### Standard NIP-23 tags
- `["d", "<slug>"]`
- `["title", "<title>"]`
- `["published_at", "<unix-seconds>"]`
- `["summary", "<description>"]` if present
- `["image", "<cover-blossom-url>"]` — from the image marked `role: cover`
- `["t", "<tag>"]` per entry in `tags:`
### Text license
- `["license", "<url>"]` — once per event, from post-level `license`
### Per-image `imeta` (NIP-92 + extensions)
Each uploaded image yields one `imeta` tag:
```
["imeta",
"url <blossom-url>",
"m <mime-type>",
"x <sha256>",
"alt <alt>", if non-empty
"caption <caption>", if present
"license <url>", if set (not UNKNOWN)
"author <name>", one entry per author, if set (not UNKNOWN)
"source_url <url>", if present
"modifications <text>" if present
]
```
NIP-92 explicitly allows implementers to add fields beyond its core set; clients ignore unknown fields. `license`, `author`, `source_url`, `modifications` are extensions this convention uses to carry TULLU-BA data inline with the image reference.
### Markdown body transformation
The Markdown body is traversed: each `![alt](filename.png)` is replaced with `![alt](<blossom-url>)` after the image has been uploaded. Size hints (`![alt](file.png =300x200)`) are stripped. Absolute URLs in the source are preserved.
---
## Round-trip: YAML ↔ Markdown
The convention is designed so authors can work in **either direction**:
### Forward: YAML → published event
1. Pipeline parses frontmatter.
2. For each `images[]` entry, uploads `file` to Blossom, receives `{url, sha256}`.
3. Builds mapping `filename → blossom-url`.
4. Rewrites Markdown body image references.
5. Assembles `imeta` tags from the structured fields + upload results.
6. Signs and publishes.
### Reverse: "flat" Markdown → YAML
Some authors write Markdown with visible attribution lines underneath images, like:
```markdown
![Yeast dough with filling](dough-filling.jpg)
*Photo: Jane Doe, [CC0](https://creativecommons.org/publicdomain/zero/1.0/)*
![Cinnamon rolls at the market](flickr-buns.jpg)
*Photo: Max Mustermann via [Flickr](https://www.flickr.com/photos/mustermann/12345/), [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/), cropped*
```
A round-trip parser can reconstruct the `images[]` YAML from this pattern because it follows a predictable shape:
```
![<alt>](<file>)
*Photo: <name>{, <name2>}{ via [<source-label>](<source-url>)}, [<license-label>](<license-url>){, <modifications>}.*
```
**Recognizable tokens** for the reverse parser:
- Image reference: standard Markdown `![alt](file)` on its own line.
- Attribution line: starts on the next line, wrapped in `*...*`, begins with a role word (`Photo`, `Foto`, `Image`, `Abb.`, etc.), ends with a period.
- **Authors**: comma-separated names between the role word and either `via` or the license bracket.
- **Source**: `via [<label>](<url>)`. The label is derived from the hostname if generated forward; on reverse, it's discarded and only the URL is kept.
- **License**: `[<short>](<url>)`. On reverse, only the URL is kept.
- **Modifications**: a trailing fragment after the license link, before the final period.
### Canonical caption format
Forward generation (YAML → caption string) uses a deterministic template:
```
{caption + ". "}Photo: {authors joined by " / "}{ via [<source-host>](<source_url>)}, [<license-short>]({license_url}){, <modifications>}.
```
With a license short-form catalog:
| License URL prefix | Short form |
|---|---|
| `https://creativecommons.org/publicdomain/zero/1.0/` | `CC0` |
| `https://creativecommons.org/licenses/by/4.0/` | `CC BY 4.0` |
| `https://creativecommons.org/licenses/by-sa/4.0/` | `CC BY-SA 4.0` |
| `https://creativecommons.org/licenses/by-nd/4.0/` | `CC BY-ND 4.0` |
| `https://creativecommons.org/licenses/by-nc/4.0/` | `CC BY-NC 4.0` |
| `https://creativecommons.org/licenses/by-nc-sa/4.0/` | `CC BY-NC-SA 4.0` |
| `https://creativecommons.org/licenses/by-nc-nd/4.0/` | `CC BY-NC-ND 4.0` |
| *anything else* | hostname of the URL |
Locale suffixes (`/deed.de`, `/deed.en`) are collapsed to the base URL for short-form lookup.
---
## Why this is forward-safe
Three properties make the convention robust over time:
1. **Events are replaceable.** A post re-published with improved metadata (better alt text, filled-in `UNKNOWN` fields) simply overrides the previous event via NIP-23's `d`-tag identity.
2. **`imeta` extensions degrade gracefully.** Clients that don't read `license`/`author`/`source_url` in `imeta` ignore them; they still get the standard `url`/`m`/`x`/`alt` fields.
3. **Reverse parsing is optional.** A pipeline can publish without ever supporting the reverse direction; the YAML is always the source of truth.
---
## What this convention does **not** do
- **Does not inject captions into the event body.** Early drafts did; it turned into a fragile regex workout across Markdown variants (link-wrapped images, list-embedded images, block quotes). Recommended approach: let clients render attribution from `imeta` fields. Inject body captions only if a concrete client gap makes it necessary.
- **Does not define new Nostr kinds.** It uses `kind:30023` (NIP-23), `kind:10063` (Blossom user server list, BUD-03), and `kind:10002` (NIP-65 relay list) as-is.
- **Does not mandate Blossom.** The convention maps cleanly to any content-addressed image host. Blossom is just the most interoperable option in the Nostr ecosystem today.
---
## Open questions for the community
1. **License in `imeta` — convention or its own tag?** Should per-image license info live in `imeta` as a non-standard field, or should there be a companion `license` tag per image with an `x <sha256>` back-reference? The `imeta` approach keeps everything per-image in one tag. A separate tag decouples concerns but duplicates the binding.
2. **Multiple licenses per image.** CC dual-licensing exists (e.g. "CC BY-SA or GFDL"). Should the spec allow `license` as an array, or repeat the `license` field multiple times in `imeta`?
3. **Canonical short-form catalog.** The table above is practical but not authoritative. Should a registry of license-URL-to-short-form mappings live somewhere reference-able?
4. **Attribution in languages other than English.** The reverse-parser pattern uses role words like `Photo`, `Foto`, `Image`. A language-agnostic marker (e.g. a leading emoji or a structured sigil like `⸻ credit ⸻`) would sidestep i18n, at the cost of readability.
5. **Machine-readable attribution in client rendering.** Long-form clients (Habla, Flycat, etc.) vary in how (and whether) they surface `imeta.license` / `imeta.author`. Adoption of this convention is only valuable if clients pick it up — a reference renderer implementation would lower the bar.
---
## References
- [NIP-23 — Long-form Content](https://github.com/nostr-protocol/nips/blob/master/23.md)
- [NIP-92 — Media Attachments (`imeta`)](https://github.com/nostr-protocol/nips/blob/master/92.md)
- [NIP-65 — Relay List Metadata (`kind:10002`)](https://github.com/nostr-protocol/nips/blob/master/65.md)
- [Blossom BUD-01 — Server Requirements](https://github.com/hzrd149/blossom/blob/master/buds/01.md)
- [Blossom BUD-03 — User Server List (`kind:10063`)](https://github.com/hzrd149/blossom/blob/master/buds/03.md)
- [TULLU / TULLU-BA attribution rule (German, Wikimedia practice)](https://commons.wikimedia.org/wiki/Commons:Lizenzhinweisgenerator)
- [schema.org/CreativeWork — `license` field convention](https://schema.org/license)

View File

@ -0,0 +1,283 @@
# Strukturierte Bild-Metadaten für Markdown-basierte Nostr-Langform-Beiträge
**Status:** Arbeitsentwurf — eine Praxis-Konvention, (noch) kein NIP.
**Scope:** Eine Inline-Markdown-Konvention zur Bildattribution (Urheber, Lizenz, Quelle, Bearbeitung), die in jedem Markdown-Editor direkt nutzbar ist und sich verlustfrei auf NIP-92-`imeta`-Tags in `kind:30023`-Events abbilden lässt.
**Ziel:** Ein einheitliches, menschlich lesbares und maschinell parsbares Attributions-Format für Bilder in Nostr-Langform-Beiträgen. TULLU-BA-konform. Zero-Tool: funktioniert ohne Build-Pipeline. Zero-Loss: bidirektional konvertierbar zu `imeta`-Tags, sobald Publishing dazukommt.
---
## Warum es das braucht
Markdowns native Bild-Syntax — `![alt](datei.png)` — trägt nur zwei Felder: das Ziel und einen Alt-Text. Alles andere, was ein korrekt attribuiertes Bild braucht (Urheber, Lizenz, Link zur Lizenz, Quelle, Bearbeitungen — die TULLU-BA-Regel aus der deutschen Urheberrechtspraxis), hat keinen Platz.
Autor:innen haben heute drei unbefriedigende Optionen:
1. **Attribution als freier Fließtext** unter jedem Bild. Gut für Menschen, nicht parsbar.
2. **Inline-HTML-`<figure>`-Blöcke** mit `<figcaption>`. Bricht Markdown-Lint-Tools, schwer editierbar.
3. **Metadaten weglassen.** Risiko stiller Fehlattribution.
NIP-92s `imeta`-Tag löst die Event-seitige Maschinenlesbarkeit (url, mime, sha256, alt usw. pro Bild). Diese Konvention liefert das fehlende Gegenstück: **wie dieselben Informationen bereits im Markdown stehen können — einheitlich, lesbar, parsbar**.
---
## Konvention zur Bildattribution
### Maximale Beispiel-Darstellung
![Rhabarberpflanze mit großen grünen Blättern und roten Stielen in einem Gartenbeet mit Mulch](https://inaturalist-open-data.s3.amazonaws.com/photos/71812633/medium.jpg)
[garden rhubarb, Speise-Rhabarber](https://www.inaturalist.org/photos/71812633), [John Sankey](https://www.inaturalist.org/users/2831535), [CC0](https://creativecommons.org/publicdomain/zero/1.0/), beschnitten
### Maximale Beispiel-Konstruktion
```markdown
![alt](imageUrl)
[title](sourceUrl), [author](authorUrl), [licence](licenceUrl), modification
```
Die Caption-Zeile steht **auf der Zeile direkt nach dem Bild** (Zeilenumbruch, kein Leerzeichen dazwischen).
---
## Regeln
1. **Reihenfolge der Felder:** `alt`, `imageUrl`, `title`, `sourceUrl`, `author`, `authorUrl`, `licence`, `licenceUrl`, `modification`. Die Reihenfolge ist **normativ**, damit Parser sich darauf verlassen können.
2. **Trenner:** Komma + Leerzeichen (`, `) zwischen den Caption-Feldern. Einheitlich, kein Mix aus „von", „via", Pipe usw.
3. **Verlinkungen:**
- `title``sourceUrl`
- `author``authorUrl`
- `licence``licenceUrl`
4. **URL-Disziplin:** Alle URL-Felder sind absolut (`https://…`), niemals relativ.
5. **CC0 / Public Domain:** `sourceUrl` darf entfallen. Urheber:in und Lizenz bleiben aus Transparenzgründen empfohlen.
6. **Bearbeitungen:** Bei CC-BY-Lizenzen ist die Änderung anzugeben, sobald das Werk verändert wurde (Zuschnitt, Farbe, Skalierung, Kombination usw.). Bei CC0 optional.
7. **Barrierefreiheit:** `alt` ist formal optional, aber für WCAG/BITV-Konformität faktisch Pflicht. Leere eckige Klammern `![]` nur bei rein dekorativen Bildern.
---
## (Pflicht-)Felder
| Feld | Status | Bedeutung / Form |
|---|---|---|
| `licence` | **Pflicht** | Lizenz-Kurzform (`CC0`, `CC BY`, `CC BY-SA`, `©`, …) |
| `licenceUrl` | **Pflicht** | Kanonische Lizenz-URL, z. B. `https://creativecommons.org/publicdomain/zero/1.0/` |
| `imageUrl` | **Pflicht** | Absolute URL zur Bilddatei (sonst nicht renderbar) |
| `sourceUrl` | **Pflicht** außer bei CC0 | URL zur Quellseite (Link in `title`) |
| `author` | **Pflicht** außer bei CC0 | Name der Urheber:in |
| `authorUrl` | optional | Profil-/Homepage-URL der Urheber:in |
| `modification` | optional (Pflicht bei Bearbeitung von CC-BY-Werken) | Freitext zur Bearbeitung |
| `title` | optional | Titel des Werks |
| `alt` | optional (faktisch Pflicht für Accessibility) | Screen-Reader-Beschreibung |
---
## Minimale Beispiel-Darstellung
![](https://inaturalist-open-data.s3.amazonaws.com/photos/71812633/medium.jpg)
[CC0](https://creativecommons.org/publicdomain/zero/1.0/)
### Minimale Beispiel-Konstruktion
```markdown
![](imageUrl)
[licence](licenceUrl)
```
Die harte Mindestanforderung: **Bild + Lizenz-Link**. Alles andere darf weg, wenn es die Lizenz erlaubt (z. B. CC0).
---
## Zwischenformen
Zwischen Minimum und Maximum sind alle Teilmengen erlaubt, solange die Reihenfolge eingehalten wird und die Pflichtfelder der jeweiligen Lizenz erfüllt sind.
**CC0-Eigenbild mit Urheberangabe (empfohlen für Transparenz):**
```markdown
![Hase auf Wiese](cover.jpg)
Comenius-Institut, [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
```
**CC-BY-Fremdbild ohne Titel:**
```markdown
![Schlüssel mit Schild "Ermutigung"](ermutigung.jpg)
[Jörg Lohrer](https://www.flickr.com/photos/empeiria/8553607289/), [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
```
---
## Parsing-Regeln (für Tooling)
Die Konvention ist für **Menschen** geschrieben. Parser haben die Aufgabe, sich daran möglichst anzupassen — **nicht umgekehrt**. Sonderzeichen, Rollen-Wörter oder sprach-abhängige Marker werden bewusst nicht vorgeschrieben, weil sie den Schreibfluss behindern würden.
Ein Parser erkennt eine Attributions-Caption anhand dieser Merkmale:
- **Position:** direkt nach einer Markdown-Bild-Zeile (`![alt](imageUrl)`), auf der nächsten Zeile ohne Leerzeile dazwischen.
- **Struktur:** eine oder mehrere `[label](url)`-Markdown-Links, getrennt durch `, `, optional abschließender Freitext-Teil für `modification`.
- **Feld-Zuordnung** nach Position in der Reihenfolge gemäß Regel 1:
- Erster Link vor einem eventuellen Personennamen-Link = `title` + `sourceUrl`
- Zweiter Link (Personenname) = `author` + `authorUrl`
- Dritter Link (CC-Kürzel) = `licence` + `licenceUrl`
- Alles danach (ohne Klammer-Syntax) = `modification`
**Eindeutige Fälle:**
- **Drei Links**`title`/`sourceUrl`, `author`/`authorUrl`, `licence`/`licenceUrl` in dieser Reihenfolge. Der letzte Link muss auf ein Lizenz-URL-Pattern matchen.
- **Zwei Links**, zweiter matcht Lizenz-Pattern → `author`/`authorUrl` + `licence`/`licenceUrl`. Ein Titel ohne Autor:in wird konventionell nicht vergeben — das erste `[Text](url)` ist in zwei-Link-Fällen immer `author`.
- **Ein Link + unverlinkter Text + Lizenz-Link** → unverlinkter Text ist `author`, Link vor der Lizenz wäre `title+sourceUrl`.
- **Nur ein Link**, matcht Lizenz-Pattern → `licence`/`licenceUrl`. Minimal-Form.
- **Unverlinkter String vor der Lizenz**`author` (ohne URL).
- **Freitext nach der Lizenz**`modification`.
**Mehrdeutige Fälle** (z. B. `[Etwas](url), [CC0](url)` — Autor oder Titel?):
- **Parser-Empfehlung:** LLM-gestützter Parser nimmt Kontext dazu (Bild-Alt-Text, Body-Kontext, Plattform-Muster der URL) und ordnet zu.
- **Reiner Regex-Parser:** markiert die Caption als **ambigue** und eskaliert zur redaktionellen Prüfung (statt zu raten).
- **Schreibende:** können Mehrdeutigkeit jederzeit selbst auflösen, indem sie beide Felder setzen (`[Titel](url), [Autor](url), [Lizenz](url)`). Ein Titel ohne Autor:in ist die Ausnahme; wer Eindeutigkeit braucht, ergänzt die Urheber:in.
Der Parser bricht nie stillschweigend. Eine Caption ist entweder eindeutig geparst, eindeutig Minimal-Form, oder **wird als prüfbedürftig markiert** — nie still falsch interpretiert.
---
## Abbildung auf das Nostr-Event (`imeta`, NIP-92)
Jedes Bild im Beitrag wird als eigener `imeta`-Tag im `kind:30023`-Event codiert:
```
["imeta",
"url <imageUrl>",
"m <mime>",
"x <sha256>",
"alt <alt>", wenn nicht leer
"title <title>", wenn vorhanden
"source_url <sourceUrl>", wenn vorhanden
"author <author>", wenn vorhanden; ein Eintrag pro Autor:in
"author_url <authorUrl>", wenn vorhanden
"license <licenceUrl>", Pflicht
"modification <modification>" wenn vorhanden
]
```
**Normativ:**
- `url`, `m`, `x`, `license` sind **Pflicht** im `imeta`.
- `license` ist immer die volle URL, nicht die Kurzform (maschinenlesbar, Clients können daraus die Kurzform zur Anzeige ableiten).
- `m` (mime) und `x` (sha256) kommen nicht aus der Caption, sondern werden beim Upload zum Blob-Host (z. B. Blossom) ermittelt.
**Erweiterung über NIP-92 hinaus:** Die Felder `title`, `source_url`, `author`, `author_url`, `modification` sind keine NIP-92-Kernfelder. NIP-92 erlaubt Implementierenden ausdrücklich, zusätzliche Felder einzuführen; Clients ignorieren unbekannte Felder. Diese Konvention nutzt diese Erweiterungsmöglichkeit, um TULLU-BA-Daten direkt beim Bild mitzuführen.
---
## Bidirektionale Abbildung (Markdown ↔ `imeta`)
### Hinweg: Markdown → `imeta`
1. Parser findet `![alt](imageUrl)` im Body.
2. Nächste Zeile wird als Caption interpretiert, Felder nach Reihenfolge-Regel extrahiert.
3. Bild wird hochgeladen (z. B. Blossom), `url`/`mime`/`sha256` werden aus der Upload-Antwort ergänzt.
4. `imeta`-Tag wird aus Caption-Feldern + Upload-Daten gebaut.
5. Markdown-Body wird angepasst: ursprüngliche `imageUrl` → Upload-URL. Die Caption-Zeile bleibt erhalten (oder wird entfernt, wenn der Client sie aus `imeta` rendert — Entscheidung des Publishing-Tools).
### Rückweg: `imeta` → Markdown
1. Client liest Event, extrahiert pro `imeta`-Tag die Felder.
2. Rendert `![alt](url)` mit `alt` aus dem Tag.
3. Rendert darunter eine Caption-Zeile mit den vorhandenen Feldern in der normativen Reihenfolge aus Regel 1.
4. `license` (URL) wird über einen Kurzform-Katalog (siehe Anhang) in eine lesbare Kurzform übersetzt (`CC0`, `CC BY 4.0`, …).
Weil die Reihenfolge normativ ist und die Trennzeichen einheitlich, lässt sich beides verlustfrei ineinander übersetzen.
---
## Beispiel: End-to-End
### Markdown im Editor
```markdown
![Rhabarberpflanze mit großen grünen Blättern und roten Stielen in einem Gartenbeet mit Mulch](https://inaturalist-open-data.s3.amazonaws.com/photos/71812633/medium.jpg)
[garden rhubarb, Speise-Rhabarber](https://www.inaturalist.org/photos/71812633), [John Sankey](https://www.inaturalist.org/users/2831535), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), beschnitten
```
### Geparst
| Feld | Wert |
|---|---|
| `alt` | Rhabarberpflanze mit großen grünen Blättern … |
| `imageUrl` | https://inaturalist-open-data.s3.amazonaws.com/photos/71812633/medium.jpg |
| `title` | garden rhubarb, Speise-Rhabarber |
| `sourceUrl` | https://www.inaturalist.org/photos/71812633 |
| `author` | John Sankey |
| `authorUrl` | https://www.inaturalist.org/users/2831535 |
| `licence` | CC BY-SA 4.0 |
| `licenceUrl` | https://creativecommons.org/licenses/by-sa/4.0/ |
| `modification` | beschnitten |
### Als `imeta`-Tag im `kind:30023`-Event (nach Blossom-Upload)
```
["imeta",
"url https://blossom.example/abc123…def.jpg",
"m image/jpeg",
"x abc123…def",
"alt Rhabarberpflanze mit großen grünen Blättern und roten Stielen in einem Gartenbeet mit Mulch",
"title garden rhubarb, Speise-Rhabarber",
"source_url https://www.inaturalist.org/photos/71812633",
"author John Sankey",
"author_url https://www.inaturalist.org/users/2831535",
"license https://creativecommons.org/licenses/by-sa/4.0/",
"modification beschnitten"
]
```
### Beim Rendern in einem Nostr-Client
Der Client, der dieses `imeta` versteht, rekonstruiert die Caption nach derselben Konvention:
```markdown
![Rhabarberpflanze …](https://blossom.example/abc123…def.jpg)
[garden rhubarb, Speise-Rhabarber](https://www.inaturalist.org/photos/71812633), [John Sankey](https://www.inaturalist.org/users/2831535), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), beschnitten
```
Ein Client, der die erweiterten `imeta`-Felder nicht kennt, zeigt immerhin `![alt](url)` korrekt an und ignoriert den Rest — Graceful Degradation.
---
## Anhang: Lizenz-URL → Kurzform-Katalog
| Lizenz-URL-Präfix | Kurzform |
|---|---|
| `https://creativecommons.org/publicdomain/zero/1.0/` | `CC0` |
| `https://creativecommons.org/publicdomain/mark/1.0/` | `Public Domain` |
| `https://creativecommons.org/licenses/by/4.0/` | `CC BY 4.0` |
| `https://creativecommons.org/licenses/by-sa/4.0/` | `CC BY-SA 4.0` |
| `https://creativecommons.org/licenses/by-nd/4.0/` | `CC BY-ND 4.0` |
| `https://creativecommons.org/licenses/by-nc/4.0/` | `CC BY-NC 4.0` |
| `https://creativecommons.org/licenses/by-nc-sa/4.0/` | `CC BY-NC-SA 4.0` |
| `https://creativecommons.org/licenses/by-nc-nd/4.0/` | `CC BY-NC-ND 4.0` |
| *alles andere* | Host der URL als Kurzform-Fallback |
Locale-Suffixe (`/deed.de`, `/deed.en`) werden bei der Kurzform-Auflösung auf die Basis-URL reduziert. Für Versionen (`3.0` statt `4.0`) wird die Version mit angezeigt.
---
## Offene Fragen an die Community
1. **Reihenfolge normativ oder locker?** Die normative Reihenfolge macht den Parser einfach. Eine lockere Variante (Felder an beliebiger Position, Erkennung per URL-Pattern) wäre toleranter, aber fragiler. Empfehlung: normativ. Meinungen?
2. **Mehrere Autor:innen pro Bild.** Ein Bild mit Ko-Autorenschaft: `[Jane Doe](…) / [John Doe](…)`? Oder Komma-getrennt `[Jane Doe](…), [John Doe](…)`? Letzteres kollidiert mit dem Feld-Trenner. Empfehlung: `/` als Autor:innen-Trenner innerhalb des `author`-Slots.
3. **Mehrere Lizenzen pro Bild.** CC-Dual-Licensing (z. B. „CC BY-SA **oder** GFDL") — `[CC BY-SA](url) / [GFDL](url)` analog zu Autor:innen?
4. **Kanonischer Kurzform-Katalog.** Die Tabelle ist praktikabel, aber nicht normativ. Eine Registry von Lizenz-URL-zu-Kurzform-Mappings, referenzierbar an einer Stelle, würde Interop erleichtern.
5. **Sprach-Rollen-Wörter.** Diese Konvention verzichtet auf einleitende Wörter wie „Foto:", „Photo:", „Bild:". Das macht sie sprach-agnostisch. Will jemand ein optionales Rollen-Wort erlauben (`*Foto: [title](url), …*`), damit Attributionen in langen Texten klarer identifizierbar sind?
6. **Repo-Workflow-Ergänzung.** Wer Markdown in einem Git-Repo mit Build-Pipeline pflegt, möchte manchmal Metadaten **strukturiert im YAML-Frontmatter** statt im Body. Ein paralleler YAML-Mapping (gleiche Felder, gleiche Semantik, Array unter `images:`) kann als Ergänzung leben, wobei die Inline-Markdown-Form die Basis bleibt und beides bidirektional konvertierbar ist.
---
## Referenzen
- [NIP-23 — Long-form Content](https://github.com/nostr-protocol/nips/blob/master/23.md)
- [NIP-92 — Media Attachments (`imeta`)](https://github.com/nostr-protocol/nips/blob/master/92.md)
- [Blossom BUD-01 — Server Requirements](https://github.com/hzrd149/blossom/blob/master/buds/01.md)
- [TULLU / TULLU-BA Attributions-Regel (Wikimedia Deutschland)](https://commons.wikimedia.org/wiki/Commons:Lizenzhinweisgenerator)
- [schema.org/CreativeWork — `license`-Feld](https://schema.org/license)
- [WCAG 2.1 — Accessible Alt Text](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html)