Struktur, Doku und S0-Toolchain fuer das abdulive-Toolkit
Monorepo-Geruest fuer neun Streamer-Tools plus den Ingest-Daemon, der unter sechs davon liegt. Enthaelt: - docs/: Ueberblick, Architektur, Twitch-Integration, Datenmodell, Roadmap, Setup, Hebel-Analyse und den MVP-Plan "Clip-Knopf" - docs/projects/: je eine Spezifikation pro Tool - docs/decisions/: vier ADRs (TS-Monorepo, SQLite, EventSub/IRC, Docker) - docker/: Image mit ffmpeg und whisper.cpp, abhaengigkeitsfreier Entrypoint mit check-env, fetch-model und calibrate - config/layout.yaml: aus einem VOD-Screenshot eingemessener Facecam-Ausschnitt (440x330 oben links, Chat-Overlay ab x=1270) Noch kein Anwendungscode - Scheibe S0 aus docs/07-mvp-plan.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
This commit is contained in:
commit
d09e2b945a
98 files changed
+3273
No files matched your search
@@ -0,0 +1,39 @@
|
||||
# ADR-0001: TypeScript-Monorepo statt gemischter Sprachen
|
||||
|
||||
**Status:** akzeptiert · **Datum:** 2026-09-09
|
||||
|
||||
## Kontext
|
||||
|
||||
Neun Tools, die sich Twitch-Anbindung, Datenbank und Event-Strom teilen. Teile
|
||||
davon sind Medienverarbeitung (ffmpeg, Whisper), was klassisch nach Python
|
||||
riecht; Teile sind Browser-Overlays, was TypeScript nahelegt.
|
||||
|
||||
## Entscheidung
|
||||
|
||||
Alles in TypeScript auf Node 22, als pnpm-Workspace-Monorepo. ffmpeg und
|
||||
whisper.cpp werden als **CLI-Prozesse** aufgerufen, nicht über Sprachbindings.
|
||||
|
||||
> **Nachtrag 2026-09-09:** Ursprünglich war faster-whisper (Python) vorgesehen.
|
||||
> Der Check der Zielmaschine ergab Python 3.14, für das `ctranslate2` noch keine
|
||||
> verlässlichen Wheels liefert. Da ohnehin nur ein Kommandozeilenaufruf
|
||||
> gebraucht wird, fällt die Wahl auf **whisper.cpp** — eine einzelne Binary
|
||||
> ohne Laufzeitabhängigkeiten. Damit entfällt Python vollständig aus dem
|
||||
> Projekt. Siehe [ADR-0004](ADR-0004-docker.md).
|
||||
|
||||
## Begründung
|
||||
|
||||
- Overlays sind zwingend Browser-Code. Ein Stack statt zwei bedeutet, dass
|
||||
Typen (`packages/shared`) zwischen Server und Overlay geteilt werden können.
|
||||
- Twitch-Bibliotheken sind im Node-Ökosystem am ausgereiftesten.
|
||||
- Der Whisper-Anteil ist in Wahrheit klein: Er wird als Kommandozeilenwerkzeug
|
||||
aufgerufen und liefert SRT. Dafür lohnt kein zweiter Stack mit eigener
|
||||
Umgebung, eigenem Paketmanager und eigener Prozessverwaltung.
|
||||
- Ein Entwickler. Kontextwechsel zwischen Sprachen ist der teuerste Teil.
|
||||
|
||||
## Konsequenzen
|
||||
|
||||
- `whisper-cli` und `ffmpeg` müssen im `PATH` liegen. Beide sind fest im
|
||||
Container-Image enthalten und werden beim Start geprüft (`check-env`).
|
||||
- Sollte später ein echtes ML-Modell im Prozess laufen müssen (z. B.
|
||||
Szenenerkennung), wird das ein separater Python-Dienst hinter HTTP — dann
|
||||
bewusst und isoliert, nicht schleichend.
|
||||
@@ -0,0 +1,34 @@
|
||||
# ADR-0002: SQLite statt Postgres
|
||||
|
||||
**Status:** akzeptiert · **Datum:** 2026-09-09
|
||||
|
||||
## Kontext
|
||||
|
||||
Mehrere Prozesse schreiben und lesen dieselben Daten. Datenvolumen: bei ~10
|
||||
Zuschauern und 4 Streams pro Woche etwa 20.000 Chat-Nachrichten und 15.000
|
||||
Viewer-Samples pro Monat — wenige MB.
|
||||
|
||||
## Entscheidung
|
||||
|
||||
SQLite (`better-sqlite3`) im WAL-Modus, eine Datei unter `data/db/`.
|
||||
|
||||
## Begründung
|
||||
|
||||
- Kein Server, kein Docker, kein Zustand außerhalb einer Datei. Backup ist ein
|
||||
Dateikopie-Vorgang (`VACUUM INTO`).
|
||||
- WAL erlaubt beliebig viele gleichzeitige Leser neben einem Schreiber. Genau
|
||||
unsere Topologie: Der Ingest schreibt, alle anderen lesen.
|
||||
- `better-sqlite3` ist synchron, was den Code im Ingest erheblich vereinfacht —
|
||||
keine Race Conditions zwischen Event-Verarbeitung und Schreibvorgang.
|
||||
- Die Datenmenge ist um Größenordnungen von SQLites Grenzen entfernt.
|
||||
|
||||
## Konsequenzen
|
||||
|
||||
- **Nur ein schreibender Prozess.** Der Ingest ist der einzige Schreiber für
|
||||
`events`, `chat_messages`, `viewer_samples`, `streams`, `user_streams`.
|
||||
Andere Apps schreiben ausschließlich in ihre eigenen Tabellen (`clips`,
|
||||
`posts`, `redemptions`, `raids`). Diese Aufteilung ist verbindlich.
|
||||
- Schreibende Zugriffe immer in Transaktionen bündeln, `busy_timeout` auf 5000 ms.
|
||||
- Migration auf Postgres wäre nötig, wenn das Dashboard je remote laufen soll.
|
||||
Steht nicht an; die Repository-Schicht in `packages/db` kapselt SQL genug,
|
||||
dass es machbar bliebe.
|
||||
@@ -0,0 +1,40 @@
|
||||
# ADR-0003: EventSub über WebSocket, Chat über IRC
|
||||
|
||||
**Status:** akzeptiert · **Datum:** 2026-09-09
|
||||
|
||||
## Kontext
|
||||
|
||||
Twitch bietet drei Wege an Live-Daten: EventSub über Webhooks, EventSub über
|
||||
WebSocket, und IRC für den Chat. Chat-Nachrichten gibt es inzwischen sowohl über
|
||||
IRC als auch über EventSub (`channel.chat.message`).
|
||||
|
||||
## Entscheidung
|
||||
|
||||
- **Nicht-Chat-Events über EventSub-WebSocket.**
|
||||
- **Chat über IRC** (`wss://irc-ws.chat.twitch.tv:443`).
|
||||
|
||||
## Begründung
|
||||
|
||||
**Gegen Webhooks:** Sie brauchen eine öffentlich erreichbare HTTPS-Adresse mit
|
||||
gültigem Zertifikat. Für einen Dienst, der auf Abdus Rechner läuft, hieße das
|
||||
Tunnel oder VPS — zusätzliche bewegliche Teile und eine dauerhafte Abhängigkeit
|
||||
von einem Dritten. WebSocket verbindet sich ausgehend und braucht nichts davon.
|
||||
|
||||
**Für IRC beim Chat:** Die IRCv3-Tags enthalten mehr, als der EventSub-Payload
|
||||
liefert — insbesondere `first-msg`, `returning-chatter`, `msg-id`, vollständige
|
||||
`badges` und `emotes` mit Positionen. Das Community-Gedächtnis und der
|
||||
Spike-Detektor hängen genau an diesen Feldern. Außerdem sind die Sende-Rate-
|
||||
Limits über IRC klar dokumentiert (20 Nachrichten / 30 s, 100 für Mods), was
|
||||
für den Begrüßungs-Bot relevant ist.
|
||||
|
||||
## Konsequenzen
|
||||
|
||||
- Zwei Verbindungen statt einer, beide mit eigenem Reconnect-Verhalten.
|
||||
- `session_reconnect` von EventSub muss korrekt behandelt werden: erst zur neuen
|
||||
URL verbinden, dann die alte schließen — sonst gehen Events verloren.
|
||||
- Nach jedem Reconnect wird die Lücke per Helix-Polling geschlossen
|
||||
(Stream-Status, Follower-Liste). Kurze Lücken bei Chat-Nachrichten sind
|
||||
hinnehmbar und nicht nachholbar.
|
||||
- Wenn Twitch IRC eines Tages abkündigt, ist der Umstieg auf
|
||||
`channel.chat.message` auf `packages/twitch/chat.ts` beschränkt — die
|
||||
Normalisierung auf das interne Event-Format findet dort statt.
|
||||
@@ -0,0 +1,63 @@
|
||||
# ADR-0004: Betrieb in Docker
|
||||
|
||||
**Status:** akzeptiert · **Datum:** 2026-09-09
|
||||
|
||||
## Kontext
|
||||
|
||||
Das Toolkit braucht ffmpeg, ein Whisper-Modell, Node und SQLite. Der Check der
|
||||
Zielmaschine ergab: kein ffmpeg, kein pnpm, Python 3.14 (für das
|
||||
`ctranslate2` — die Basis von faster-whisper — noch keine verlässlichen Wheels
|
||||
liefert), keine NVIDIA-GPU. Das Toolkit soll auf Abdus Stream-PC laufen, also
|
||||
auf einem Rechner, dessen Umgebung sich unabhängig vom Projekt ändert.
|
||||
|
||||
## Entscheidung
|
||||
|
||||
Alles läuft in Containern, orchestriert über `docker-compose`. Die einzige
|
||||
Voraussetzung auf dem Stream-PC ist Docker Desktop und OBS.
|
||||
|
||||
## Begründung
|
||||
|
||||
- Die gesamte Toolchain (Node 22, ffmpeg mit libx264/libass, whisper.cpp, das
|
||||
Sprachmodell) ist im Image festgeschrieben. Kein "läuft bei mir".
|
||||
- Der Python-Konflikt entfällt vollständig, weil kein Python mehr gebraucht
|
||||
wird (siehe [ADR-0001](ADR-0001-monorepo-typescript.md), aktualisiert).
|
||||
- Updates sind ein `docker compose pull && up -d`, kein Nachinstallieren auf
|
||||
einem Rechner, der gerade streamt.
|
||||
|
||||
## Konsequenzen — die vier Dinge, die man dabei falsch machen kann
|
||||
|
||||
**1. Die SQLite-Datei gehört nicht in einen Windows-Bind-Mount.**
|
||||
Dateisperren über die 9p/virtiofs-Brücke zwischen Windows und WSL2 sind
|
||||
unzuverlässig; SQLite im WAL-Modus reagiert darauf mit
|
||||
`SQLITE_BUSY`/`database is locked` und im schlimmsten Fall mit Korruption.
|
||||
Die Datenbank liegt deshalb in einem **benannten Docker-Volume**
|
||||
(`abdulive-db`). Backups werden per Skript aus dem Volume nach Windows
|
||||
kopiert, nicht durch direktes Mounten.
|
||||
|
||||
**2. Große Mediendateien nicht über den Bind-Mount verarbeiten.**
|
||||
Das OBS-Replay-Verzeichnis wird nur lesend gemountet. Die Rohdatei wird
|
||||
**einmal** in ein containerlokales Volume kopiert, dort verarbeitet, und nur
|
||||
das fertige Ergebnis wird zurück nach `data/clips/` geschrieben. Direkt auf
|
||||
dem Bind-Mount zu encodieren kostet ein Vielfaches der Zeit.
|
||||
|
||||
**3. OBS läuft auf dem Host, nicht im Container.**
|
||||
Erreichbar über `host.docker.internal:4455`. In `docker-compose.yml` steht
|
||||
`extra_hosts: ["host.docker.internal:host-gateway"]`, damit es auch außerhalb
|
||||
von Docker Desktop funktioniert.
|
||||
|
||||
**4. Ein globaler Windows-Hotkey lässt sich aus einem Container nicht abfangen.**
|
||||
Deshalb wird gar nicht erst versucht, einen eigenen Hotkey-Dienst zu bauen:
|
||||
Der Hotkey wird **in OBS** auf "Wiedergabepuffer speichern" gelegt. OBS meldet
|
||||
danach über obs-websocket das Ereignis `ReplayBufferSaved` mit dem Dateipfad —
|
||||
das ist der Trigger. Kein Agent auf dem Host, kein zusätzlicher Autostart.
|
||||
|
||||
**Kosten, ehrlich benannt:** Docker Desktop startet eine WSL2-VM, die im
|
||||
Leerlauf ein bis zwei GB RAM belegt und beim Encodieren CPU zieht, die dem
|
||||
Stream fehlt. Gegenmaßnahmen: `.wslconfig` mit `memory=4GB` und
|
||||
`processors=4`, ffmpeg mit `-preset veryfast` und niedriger Priorität, und
|
||||
Rendering standardmäßig **nach** Streamende (`clipper.renderWhileLive: false`),
|
||||
sofern die Kandidatenliste erst danach gebraucht wird.
|
||||
|
||||
**GPU:** Kein NVENC (keine NVIDIA-Karte), und Intel QSV ist aus WSL2 heraus
|
||||
nicht verlässlich erreichbar. Es bleibt bei CPU-Encoding mit libx264. Für
|
||||
Clips von 30–60 Sekunden ist das unkritisch.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Architekturentscheidungen
|
||||
|
||||
| ADR | Thema | Status |
|
||||
|---|---|---|
|
||||
| [0001](ADR-0001-monorepo-typescript.md) | TypeScript-Monorepo statt gemischter Sprachen | akzeptiert |
|
||||
| [0002](ADR-0002-sqlite.md) | SQLite statt Postgres | akzeptiert |
|
||||
| [0003](ADR-0003-eventsub-websocket.md) | EventSub ueber WebSocket, Chat ueber IRC | akzeptiert |
|
||||
| [0004](ADR-0004-docker.md) | Betrieb in Docker, whisper.cpp statt Python | akzeptiert |
|
||||
|
||||
Neue Entscheidung: Datei `ADR-<nummer>-<kurzname>.md`, Aufbau Kontext /
|
||||
Entscheidung / Begruendung / Konsequenzen.
|
||||
Reference in new issue
Block a user