Files
klarbild/README.md
T

119 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Klarbild
Verwandelt Screenshots und Fundstücke in saubere, druckfertige Bilder — Rahmen und
Shop-Oberflächen entfernen, exakte Fotoformate (30×40 cm etc.), The-Frame-Querformat,
für den Cricut freistellen, und automatisch nach Picdrop ausliefern.
**Claim:** „Screenshot rein, sauberes Bild raus."
## Stack
Astro 5 (SSR, Node standalone) + React-Inseln · Postgres · S3 (MinIO) · pg-boss ·
sharp · pdf-lib (Druckbogen) · grammY (Telegram) · OKLCH-Tokens, kein Tailwind.
Deploy: Gitea → Coolify → Hetzner.
## Entwicklung
```bash
npm install
cp .env.example .env # Werte eintragen
npm run dev
```
Migrationen und Seeds laufen beim Serverstart automatisch. Erststart-Passwörter über
`SEED_TILL_PASSWORD` / `SEED_LEA_PASSWORD`.
## Funktionen (Kurzüberblick)
- **Modi:** Bereinigen (Screenshot säubern/freistellen/formatieren), Umwandeln (ein Bild + Text,
z. B. Foto → Ölgemälde), Kombinieren (mehrere Bilder + Text), Neu erzeugen (nur Text).
- **Formate:** feste cm-/DIN-/Screen-Formate **und frei definierbare** — Schlüssel `WxH` (z. B.
`25x35`) oder `sticker<N>` (`sticker5` = 5×5 cm). Parsing in `src/lib/format.ts`, keine Migration nötig.
- **Dateiformat:** PNG (verlustfrei, Transparenz) oder JPG (klein, Picdrop-freundlich). Global über
`settings.output_ext`, pro Rezept via `recipes.output_ext`, pro Umwandlung im Studio. JPG nur ohne Alpha.
- **Auslieferung:** Picdrop (SFTP/FTPS) + beliebige Zusatzziele; NAS/Backup-Ziele; optionaler
`.md`-Metadaten-Beileger **pro Ziel** schaltbar. Nicht-transparente Ergebnisse gehen als JPG an Picdrop.
- **Bibliothek:** Ordner, Farbmarkierungen, Prompt-Anzeige, Vorher/Nachher, Vollbild + „In Fotos sichern".
- **Rechte/Datenschutz:** Sichtbarkeit (eigene/alle), anonyme Generierungen, private Sessions, NSFW-Gate.
- **Telegram-Bot** (grammY-Webhook) als vollwertiger Website-Ersatz. **API-Token** (`Bearer`) für `/api/*`.
- **Drucken (`/drucken`, ohne KI):** Bilder exakt auf physische Maße bringen (interaktiver Zuschnitt mit
Zoom/Verschieben) und mehrere davon in 100-%-Größe mit **Schnittmarken** auf einen Bogen setzen —
Passbildsatz, Kita-Satz, Sticker. Papier DIN A6–A2 inkl. **A3+**, Fotopapier 9×13–20×30, Letter/Legal
oder freies Maß. Marken: Eckmarken (0,25 pt, außerhalb des Endformats) oder durchgehende Linien.
Beschnittzugabe 0–10 mm. Ausgabe: PDF in 1:1 (`pdf-lib`) bzw. Einzelbild mit dpi-Metadaten.
Layout: gleiche Größen → exaktes Raster, gemischte Größen → MaxRects-Packer.
Alle Formate der KI-Generierung sind auch hier wählbar; passt das Bild nicht zum Format,
entscheidet man je Bild zwischen **Zuschneiden** und **Rand lassen** (mit Randfarbe) — nie verzerren.
86 Bildmaße in 10 Gruppen (Passbild bis 100 × 140 cm), 19 Papierformate.
**Größer als der Bogen** (20 × 30 cm auf A4): je Zelle `oversize` = `fit` (melden) ·
`overflow` (mittig aufs ganze Blatt, Verlust wird vorab beziffert — hier genau 3 mm) ·
`tile` (Posterdruck auf mehrere Blätter mit gestricheltem Klebefalz und Blattnummer).
**Beschriftung** frei: aus, automatisch oder eigener Text an einer von sechs Stellen;
sie entfällt automatisch, wenn an der Kante kein Platz frei ist.
Dasselbe Bild darf mehrfach als Zelle vorkommen („Duplizieren"), um es in zwei Maßen zu drucken.
Kern: `src/lib/paper.ts` (Formate), `src/lib/printlayout.ts` (reine Mathematik, getestet),
`src/lib/printoversize.ts` (Überstand & Kachelung, getestet), `src/lib/printrender.ts`
(sharp + pdf-lib). Browser-Vorschau und Server-PDF benutzen denselben Rechenkern.
Auch per Telegram („🖨 Drucken") und MCP (`exact_size`, `print_sheet`).
Öffentliche Vorstellungsseite ohne Login: `/vorstellung`.
- **Dateinamen (`src/lib/naming.ts`, abhängigkeitsfrei):** `JJJJ-MM-TT_HHMMSS_<motiv>_<format>_<kürzel>.<ext>`,
z. B. `2026-08-20_143207_lea-badge_the-frame_a3f9.jpg`. Zeitstempel vorn → jede Dateiliste sortiert
chronologisch; Kürzel aus der Item-ID → zwei Bilder können sich beim Ausliefern **nicht mehr überschreiben**.
Zeitzone `KLARBILD_TZ` (Standard `Europe/Berlin`). Nachbenennung der Bibliothek und erneute Auslieferung
im Admin unter „Dateinamen" (`src/lib/renames.ts`), idempotent.
- **Teilen (`/s/<slug>`, öffentlich):** ein Bild oder eine Sammlung ohne Konto zugänglich machen —
ansehen und in voller Auflösung herunterladen. Voreinstellungen bewusst vorsichtig: **Ablauf**
(Standard 30 Tage), **widerrufbar**, optionales **Zugangswort** (argon2), **Download abschaltbar**,
`noindex`. Zugriff hängt am **Link**, nicht am Bild (fremde `item`-Kennung ⇒ 404); private Sitzungen
lassen sich nie teilen. Verwaltung unter `/teilen` mit Aufrufen und Downloads. Gezählt wird *wann*
und *womit*, nicht *wer*: IP nur gehasht, gesalzen mit Server-Geheimnis **und** Link-Kennung, eine
Zeile je Besucher und Stunde, Löschung nach 90 Tagen. Kern: `src/lib/shares.ts`.
- **Weitergeben:** per E-Mail aus Klarbild heraus (mehrere Empfänger, **kein Anhang** — ein Link lässt
sich zurückziehen), über das eigene Mailprogramm oder per `wa.me`-Link, der WhatsApp mit fertigem
Text öffnet. (Server-seitiger WhatsApp-Versand bräuchte die Business-API: Meta-Konto, genehmigte
Vorlagen, Gebühren je Konversation.)
- **E-Mail (`src/lib/mail.ts`):** SMTP über das **eigene Postfach** — SPF/DKIM stimmen dadurch von
selbst und kein Versanddienst sieht mit. Zugang AES-256-GCM in `settings`. Trägt Passwort-Reset
(Einmal-Token, nur Hash gespeichert, 60 min, gleiche Antwort für unbekannte Adressen),
Auftragshinweise (je Nutzer, `/konto`) und den Versand von Teilen-Links.
- **Sitzungen:** signiertes Cookie **plus** serverseitige Prüfung (`session_epoch`). Passwortwechsel und
Rollenänderung entwerten alle offenen Anmeldungen; die Rolle kommt aus der Datenbank, nicht aus dem
Cookie.
- **KI-Kennzeichnung (`src/lib/provenance.ts`):** IPTC `DigitalSourceType` als XMP direkt in der Datei
(`trainedAlgorithmicMedia` bzw. `compositeWithTrainedAlgorithmicMedia`) samt Modell — von Hand in
PNG-`iTXt` und JPEG-`APP1` geschrieben, ohne neue native Abhängigkeit. Mit exiftool prüfbar.
- **Sicherheits-Header:** CSP mit echten Hashes für jedes inline-Skript (`experimental.csp`), dazu
`nosniff`, `referrer-policy`, `X-Frame-Options`, `permissions-policy`. `style-src` bleibt offen, weil
React Stile als Attribute setzt und Attribute sich nicht hashen lassen.
- **Admin:** Key/Picdrop/Modelle/Nutzer/Kostendeckel/Presets/Speicherverwaltung, E-Mail, Teilen-Links,
Dateinamen, Picdrop-Diagnose.
## Datenmodell / Migrationen
Nummerierte SQL in `migrations/`, beim Start automatisch (kein ORM-Push). Wichtige Spalten:
`items.has_alpha`, `jobs.status` (inkl. `failed`), `jobs.finished_at`,
`settings.output_ext` / `recipes.output_ext`, `settings.*_metadata_sidecar`, `delivery_targets.*`,
`print_presets` (Bogen-Vorlagen, `013`), `shares`/`share_items`/`share_views` + `settings.smtp_*` +
`users.email`/`notify_jobs` (`014`), `users.session_epoch` (`015`).
## Tests
`npm test` — Node-Testrunner mit nativem Type-Stripping (`node --experimental-strip-types`).
**Kein `tsx`, kein `playwright` in `package.json`** — der Docker-Build installiert devDependencies,
und deren native postinstall-Schritte haben den Deploy schon zweimal zerlegt. Testwerkzeug gehört
außerhalb des Projekts installiert. Deckt die Druck-Layoutmathematik ab (Maß-Parsing,
mm → px exakt, Überlappungsfreiheit, Ränder, Beschnittzugabe, Markengeometrie, Mehrseitigkeit).
## Stolpersteine & Lösungen (Learnings)
- **„Bild bei Picdrop ✓, aber nicht in der Galerie":** Fehldiagnose vermeiden — die Dateien lagen die ganze
Zeit **korrekt** per SFTP in `/POSTER LEA` (per Admin → Picdrop → „Ordner anzeigen" verifizierbar). Der
eigentliche Grund war die **Sortierung in der Picdrop-Weboberfläche**, nicht das Format. Unabhängig davon
liefern wir nicht-transparente Ergebnisse jetzt als JPG aus (`src/lib/delivery.ts`, `deliverableBuffer`):
~25 MB PNG → ~2 MB JPG, schneller und handlicher — plus wählbares Ausgabeformat (`output_ext`).
- **„Unbekanntes Format: sticker5":** Seed legte ein Rezept mit Formatschlüssel `sticker5` an, den der
Resolver nicht kannte → jeder Lauf schlug fehl. Lösung: generisches Parsen von `sticker<N>`/`WxH`.
- **Auftrag trotz Fehler grün:** `jobs.status` kannte kein `failed`; alle Positionen fehlgeschlagen wurde
trotzdem `done`. Lösung: Status `failed`, wenn `done=0 && failed>0` (`src/worker.ts` `maybeComplete`).
- **Doppelte Datums-/UUID-Namen:** `\b`-Wortgrenzen greifen nicht an Unterstrichen. Lösung: `\b` entfernt,
Datum/UUID/Hex global ersetzt (`src/lib/pipeline.ts` `cleanMotif`).
- **Content-Type bei gemischten PNG/JPG-Ergebnissen:** `file.ts` snifft jetzt die Magic Bytes.
- **`.tmp-`-Reste auf Picdrop:** früher Temp-Rename-Bug; `cleanupTmp` + Admin-Knopf „Reste aufräumen".
## Status
Reihenfolge und Nachweise siehe `CLAUDE.md`. Anforderungen im Handover-Paket
(`Imagetool-Lea/00..07`). Führend: `01-anforderungen.md`.