Zugangsdaten kommen jetzt aus der App statt aus einer .env: ein Einrichtungs-Assistent auf http://localhost:4792/setup nimmt Client-ID, Secret und OBS-Passwort entgegen, der OAuth-Rueckruf laeuft auf denselben Server. Gespeichert wird verschlueselt (AES-256-GCM) in einem Volume, bewusst nicht in der Datenbank - die wird auf die Windows-Seite gesichert. Siehe ADR-0005. Technikbasis vereinfacht: Node 24 bringt node:sqlite, WebSocket, fetch und die Ausfuehrung von TypeScript mit. Das Projekt hat damit null Laufzeitabhaengigkeiten und keinen Build-Schritt; pnpm-Workspaces und die Paketmanifeste sind entfallen, Importe laufen ueber die imports-Map der Wurzel-package.json. erasableSyntaxOnly verhindert Syntax, die das Type Stripping stillschweigend entfernen wuerde. Siehe Nachtrag 2 in ADR-0001. Enthalten: - packages/core: Config, Layout, Logger mit Secret-Maskierung, Retry, YAML - packages/db: Migrationen, Repositories, alle SQL-Anweisungen an einem Ort - packages/twitch: Secret-Speicher, OAuth, Helix mit Token-Bucket, IRC - apps/ingest: Daemon, Einrichtungsseite, Spike-Erkennung - 20 Tests (IRC-Parser, Spike-Detektor, Datenbankschicht) Der Chat verbindet sich anonym, solange nichts hinterlegt ist - damit laesst sich die Datenaufnahme pruefen, bevor die Twitch-Apps existieren. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
12 KiB
MVP-Paket: „Clip-Knopf"
Ziel in einem Satz: Nach jedem Stream liegen fertige 9:16-Videos mit eingebrannten Untertiteln zur Auswahl bereit — Abdu klickt zwei davon an und lädt sie hoch.
Begründung der Auswahl: 06-hebel-analyse.md.
Detailspezifikation: projects/clip-shorts.md.
Warum genau dieser Zuschnitt
Der spürbare Effekt entsteht nicht dadurch, dass ein Clip entsteht — das kann Twitch selbst. Er entsteht dadurch, dass die Strecke von „das war lustig" bis „fertiges Short liegt bereit" von 30 Minuten Handarbeit auf 0 Minuten fällt. Genau diese Strecke ist der MVP. Alles, was nicht auf dieser Strecke liegt, fliegt raus.
Das MVP ist bewusst ein einziger Prozess in einem einzigen Container. Die in
01-architecture.md beschriebene Aufteilung in Ingest,
Overlay-Server und Konsumenten kommt erst, wenn es einen zweiten Konsumenten
gibt. Vorher ist sie nur Ballast.
Rahmenbedingungen (geklärt)
| Punkt | Stand |
|---|---|
| Zielmaschine | Abdus Stream-PC, dort wo OBS läuft — alles lokal |
| OBS-Layout | Facecam bündig oben links, 440×330 — eingemessen, siehe unten |
| GPU | keine NVIDIA-Karte → CPU-Encoding mit libx264 |
| Untertitel | whisper.cpp (Binary, kein Python) statt faster-whisper |
| Betrieb | Docker Compose, siehe ADR-0004 |
Fortschritt
| Scheibe | Stand |
|---|---|
| S0 Container + Toolchain | fertig — Image 258 MB, check-env ohne harte Fehler |
| Layout eingemessen | fertig — config/layout.yaml, vorgezogen aus S3 |
| S1 Einrichtung + Auth + DB + IRC-Ingest | fertig — 20 Tests grün, Daemon läuft |
| S2–S6 | offen |
S1 abgenommen am 2026-09-09. Der Daemon läuft, hängt am Chat, die
Einrichtung ist unter http://localhost:4792/setup erreichbar.
Beim Bauen hat sich die Technikbasis vereinfacht: Node 24 bringt SQLite,
WebSocket, fetch und die Ausführung von TypeScript mit, deshalb hat das
Projekt null Laufzeitabhängigkeiten und keinen Build-Schritt. pnpm-Workspaces
und die Paketmanifeste sind entfallen. Siehe Nachtrag 2 in
ADR-0001.
Nachgewiesen:
- 20 Tests grün (IRC-Parser, Spike-Detektor, Datenbankschicht)
- Chat verbindet sich anonym, solange keine Zugangsdaten hinterlegt sind — damit lässt sich die Datenaufnahme prüfen, bevor die Twitch-Apps existieren
- Zugangsdaten überstehen die Runde durch die Einrichtungsseite, liegen
verschlüsselt mit Rechten
0600im Volume, tauchen weder im HTML noch in der Datenbank noch in einer Volltextsuche im Klartext auf - Was noch aussteht: die Strecke IRC → Datenbank im Livebetrieb. Die Teile sind einzeln getestet, die Verdrahtung wird beim ersten echten Stream sichtbar.
S0 abgenommen am 2026-09-09. Im Image: Node 22.23, ffmpeg 5.1.9 mit
libx264 und libass, whisper.cpp v1.7.4, Sprachmodell ggml-small geladen.
check-env prüft Werkzeuge, Volumes, Layout-Geometrie, OBS-Erreichbarkeit und
Zugangsdaten; die verbleibenden Warnungen (OBS nicht erreichbar, noch keine
Zugangsdaten hinterlegt) sind auf dieser Entwicklungsmaschine erwartbar.
calibrate wurde mit einem echten 1920×1080-Bild durchgespielt: 1080×1920
heraus, Ausschnitte an der richtigen Stelle. Damit ist der Filtergraph für S3
bereits verifiziert, bevor S3 anfängt.
Das Einmessen der Facecam war eigentlich Teil von S3, ließ sich aber aus dem
gelieferten Screenshot sofort erledigen. Dabei kamen zwei Dinge heraus, die das
geplante Layout verändert haben — der Chat ist ins Streambild eingebrannt
und die Facecam ist 4:3, nicht 16:9. Beides ist in
projects/clip-shorts.md dokumentiert und in
config/layout.yaml aufgelöst.
Umfang
Drin
- Trigger:
!clipim Chat und OBS-Hotkey (überReplayBufferSaved) - Quelle: OBS-Replay-Buffer, parallel dazu ein Twitch-Clip für die teilbare URL
- Render: 1080×1920, Facecam oben, Gameplay unten, fester Crop aus Config
- Untertitel: whisper.cpp deutsch → SRT → ASS mit wortweisem Highlight → eingebrannt
- Ablage:
data/clips/<datum>/<clip-id>/mitfinal.mp4, Transkript, Metadaten - Sichtung: eine statische HTML-Seite mit Vorschau, Score und zwei Knöpfen
- Nebenbei: Chat, Zuschauerzahlen und Nutzer werden ab Tag 1 in die DB geschrieben, auch wenn im MVP noch nichts damit passiert
Bewusst draußen
| Nicht im MVP | Warum |
|---|---|
| EventSub | Für den Clip-Weg reicht IRC plus Helix-Polling. Spart einen ganzen Verbindungstyp mit Reconnect-Logik. |
| Event-Bus über WebSocket | Es gibt nur einen Prozess. Funktionsaufrufe genügen. |
| Dashboard mit Framework | Eine statische HTML-Seite reicht für zehn Videos. |
| Spike-Trigger scharf | Läuft mit und protokolliert, löst aber nichts aus — Schwelle wird erst an echten Daten kalibriert. |
| Automatischer Upload | Die Auswahl ist ohnehin manuell. Kein Gewinn, viel API-Aufwand. |
| Overlay, Punkte, Recap, Raid | Eigene Meilensteine. |
Die sieben Scheiben
Jede Scheibe ist einzeln lauffähig und einzeln überprüfbar. Am Ende jeder Scheibe gibt es etwas, das man anschauen kann.
S0 — Container und Toolchain · ~1 Tag
docker/Dockerfile (Node 22, ffmpeg mit libx264 + libass, whisper.cpp),
docker-compose.yml mit Volumes und Ports, Modell-Download beim ersten Start
in ein eigenes Volume, scripts/check-env als Selbsttest im Container.
Fertig, wenn: docker compose run --rm toolkit check-env meldet ffmpeg,
whisper.cpp, Modell und die Schreibrechte auf allen Volumes als OK.
S1 — Einrichtung, Auth und Datenaufnahme · ~3 Tage
Einrichtungs-Assistent auf http://localhost:4792/setup: Client-ID und
-Secret werden dort eingegeben, nicht in eine Datei. Verschlüsselter
Secret-Speicher im Volume, OAuth-Flow mit Rücksprung auf denselben Port,
Token-Refresh. Siehe ADR-0005.
Dazu Helix-Client mit Rate-Limit-Bucket, DB-Migrationen für streams, users,
user_streams, chat_messages, viewer_samples, clips, ein IRC-Client, der
jede Nachricht schreibt, und Stream-Sessions über Helix-Polling im Minutentakt.
Der Assistent war ursprünglich nicht eingeplant und kostet etwa einen Tag mehr. Dafür entfällt der
.env-Schritt aus der Inbetriebnahme komplett.
Fertig, wenn: Nach einem echten Stream stehen alle Nachrichten in
chat_messages und eine korrekte Zeile in streams.
Diese Scheibe ist der Teil ohne sichtbaren Effekt — und trotzdem der, den man nicht verschieben darf. Was hier nicht mitgeschrieben wird, ist für immer weg.
S2 — OBS-Anbindung und Trigger · ~1 Tag
obs-websocket v5 über host.docker.internal:4455, Reconnect-Schleife.
SaveReplayBuffer auslösen, auf ReplayBufferSaved warten, Dateipfad vom
Windows-Pfad auf den Container-Mount abbilden. Trigger: !clip im Chat
(Cooldown 60 s global, 300 s pro Nutzer, nur Follower) und der OBS-Hotkey.
Parallel POST /helix/clips, Ergebnis-ID in clips nachtragen.
Bestätigung im Chat, genau eine pro Auslösung.
Fertig, wenn: !clip im laufenden Stream erzeugt binnen 5 s eine
raw.mp4 im Ablageordner und eine Chat-Rückmeldung.
S3 — 9:16-Render · ~1,5 Tage
calibrate-Kommando: zieht einen Frame aus einer Replay-Datei, schreibt ihn
als PNG raus, nimmt die vier Crop-Werte entgegen und legt sie in
config/layout.yaml ab. Render-Schritt mit dem Filtergraph aus
projects/clip-shorts.md, Kopie der Rohdatei ins
Arbeitsvolume vor der Verarbeitung.
Fertig, wenn: Aus einer Replay-Datei entsteht eine vertical.mp4 mit
korrekt sitzender Facecam oben und Gameplay unten.
S4 — Untertitel · ~1,5 Tage
Nur die Mikrofonspur extrahieren (OBS nimmt getrennt auf: Mikro Spur 2,
Spielton Spur 3, Mischung Spur 1) als 16 kHz Mono WAV, whisper.cpp mit
ggml-small und --language de, Ausgabe als SRT mit Wort-Zeitstempeln,
Umwandlung in ASS mit wortweisem Highlight, Stil aus
config/subtitle-style.ass, Einbrennen. Sicherheitsabstand nach unten
mindestens 320 px. Ins Video kommt weiterhin die Mischung von Spur 1.
Fertig, wenn: final.mp4 hat lesbare, zeitlich passende deutsche
Untertitel, und der Text wird auf dem Handy von der TikTok-Oberfläche nicht
verdeckt.
S5 — Sichtung · ~1 Tag
Ein kleiner HTTP-Server auf Port 4792 liefert eine Seite mit allen Kandidaten
des letzten Streams: Videovorschau, Score, Auslöser, der Chat-Ausschnitt aus
dem Clip-Fenster, Transkript, und die Knöpfe Übernehmen / Verwerfen.
Übernommene Clips wandern nach data/clips/_upload/.
Fertig, wenn: Nach einem Stream lässt sich in unter zwei Minuten entscheiden, welche Clips hochgeladen werden.
S6 — Erstchatter-Begrüßung · ~0,5 Tage · optional
Der IRC-Client aus S1 liegt ohnehin da, das first-msg-Tag ebenso. Eine
Begrüßung für Erstchatter, eine für Rückkehrer nach mehr als 21 Tagen, mit
Cooldown und Obergrenze pro Stream. Details in
projects/community-memory.md.
Diese Scheibe steht am Ende, weil sie unabhängig ist und notfalls entfällt. Sie kostet fast nichts und ist das Einzige im MVP, das die Zuschauer direkt merken — deshalb ist sie trotzdem drin.
Zeitplan
| Woche | Scheiben | Ergebnis |
|---|---|---|
| 1 | Einrichtung im Browser, schreibt jeden Stream mit | |
| 2 | S2, S3 | Knopfdruck erzeugt ein fertiges 9:16-Video |
| 3 | S4, S5, S6 | Untertitel, Sichtungsseite, Begrüßung |
Rund zehn Arbeitstage (S0 erledigt, S1 durch den Einrichtungs-Assistenten einen Tag teurer). Realistisch drei Kalenderwochen, wenn nebenher noch etwas anderes läuft.
Definition of Done für das gesamte MVP
Ein echter Stream, ohne Eingriff von außen:
- Container läuft von Streambeginn bis -ende durch, auch über einen Twitch-Reconnect hinweg.
- Fünfmal ausgelöst (dreimal
!clip, zweimal Hotkey) ergibt fünffinal.mp4mit sitzendem Crop und brauchbaren Untertiteln. - Kein Auslöser bleibt unbestätigt, kein Clip geht verloren.
- Die Sichtungsseite zeigt alle fünf, sortiert nach Score.
- Das Encoding kostet den Stream keine sichtbaren Frames (Kontrolle über die OBS-Statistik: keine übersprungenen Frames wegen Encoder-Überlast).
- Chat und Zuschauerzahlen des Streams sind vollständig in der DB.
Risiken
| Risiko | Wahrscheinlichkeit | Gegenmaßnahme |
|---|---|---|
| Encoding kostet Frames im Stream | mittel | -preset veryfast, CPU-Limit im Compose-File, Schalter renderWhileLive: false — dann läuft alles nach Streamende |
| whisper.cpp versteht Spielgeräusche statt Sprache | OBS nimmt getrennte Spuren auf; transkribiert wird nur das Mikrofon (Spur 2). | |
| Twitch ändert die Ableitung der Clip-MP4-URL | niedrig | Betrifft nur den Zweitweg; die Rohdatei kommt aus OBS. Gekapselt in einer Datei. |
| Replay-Buffer-Pfad-Abbildung Windows ↔ Container | mittel | In S2 als Erstes lösen, mit einem Testlauf vor allem anderen |
| Docker Desktop belastet den Stream-PC | mittel | .wslconfig mit Speicher- und CPU-Grenze; wenn es stört, ist ein Betrieb ohne Container jederzeit möglich, weil nur ffmpeg und whisper.cpp fehlen |
Was Abdu vorher tun muss
- Docker Desktop installieren, WSL2-Backend aktivieren
- Zwei Twitch-Apps anlegen, Redirect-URI
http://localhost:4792/callback(siehe05-setup.md). Client-ID und Secret nicht irgendwo eintragen — die kommen in den Einrichtungs-Assistenten. - In OBS: obs-websocket aktivieren (Port 4455, Passwort), Wiedergabepuffer auf 90 Sekunden, Hotkey auf „Wiedergabepuffer speichern" legen
Screenshot der laufenden Szene schicken— erledigt, Crop ist eingemessen und steht inconfig/layout.yamlGetrennte Audiospuren klären— erledigt, OBS nimmt getrennt auf; Whisper bekommt nur die Mikrofonspur- In GTA die spieleigenen Untertitel ausschalten (werden vom Ausschnitt rechts abgeschnitten, und die eigenen liegen ohnehin darüber)
Danach
Wenn das MVP steht, ist der teuerste Teil erledigt: Auth, DB, Chat-Anbindung
und Container laufen. Die nächsten Meilensteine aus
04-roadmap.md setzen darauf auf und werden dadurch
erheblich billiger — das Analytics-Dashboard wertet dann bereits mehrere Wochen
echter Daten aus, statt bei null anzufangen.