Files
abdulive/docs/07-mvp-plan.md
T
ZeasyAlexandClaude Opus 5 d09e2b945a 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
2026-09-09 08:56:09 +02:00

208 lines
9.5 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.
# 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`](06-hebel-analyse.md).
Detailspezifikation: [`projects/clip-shorts.md`](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`](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](decisions/ADR-0004-docker.md) |
## Fortschritt
| Scheibe | Stand |
|---|---|
| S0 Container + Toolchain | in Arbeit |
| Layout eingemessen | **fertig** — `config/layout.yaml`, vorgezogen aus S3 |
| S1–S6 | offen |
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`](projects/clip-shorts.md) dokumentiert und in
`config/layout.yaml` aufgelöst.
## Umfang
### Drin
- Trigger: `!clip` im Chat **und** OBS-Hotkey (über `ReplayBufferSaved`)
- 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>/` mit `final.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 — Auth und Datenaufnahme · ~2 Tage
OAuth-Flow (`auth`-Kommando, Callback auf Port 4780), verschlüsselter
Token-Store mit automatischem Refresh, Helix-Client mit Rate-Limit-Bucket,
DB-Migrationen für `streams`, `users`, `user_streams`, `chat_messages`,
`viewer_samples`, `clips`. IRC-Client, der jede Nachricht schreibt.
Stream-Sessions über Helix-Polling im Minutentakt.
**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`](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
Audiospur extrahieren (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.
**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`](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 | S0, S1 | Läuft im Container, schreibt jeden Stream mit |
| 2 | S2, S3 | Knopfdruck erzeugt ein fertiges 9:16-Video |
| 3 | S4, S5, S6 | Untertitel, Sichtungsseite, Begrüßung |
Rund neun Arbeitstage. 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:
1. Container läuft von Streambeginn bis -ende durch, auch über einen
Twitch-Reconnect hinweg.
2. Fünfmal ausgelöst (dreimal `!clip`, zweimal Hotkey) ergibt fünf
`final.mp4` mit sitzendem Crop und brauchbaren Untertiteln.
3. Kein Auslöser bleibt unbestätigt, kein Clip geht verloren.
4. Die Sichtungsseite zeigt alle fünf, sortiert nach Score.
5. Das Encoding kostet den Stream keine sichtbaren Frames (Kontrolle über
die OBS-Statistik: keine übersprungenen Frames wegen Encoder-Überlast).
6. 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 | mittel | Falls OBS getrennte Audiospuren aufnimmt, nur die Mikrofonspur transkribieren. Sonst Rauschgatter vorschalten. |
| 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
1. Docker Desktop installieren, WSL2-Backend aktivieren
2. Zwei Twitch-Apps anlegen (siehe [`05-setup.md`](05-setup.md))
3. In OBS: obs-websocket aktivieren (Port 4455, Passwort), Wiedergabepuffer auf
90 Sekunden, Hotkey auf „Wiedergabepuffer speichern" legen
4. **Einen Screenshot der laufenden Szene** schicken — daraus wird der
Facecam-Crop eingemessen
5. Klären, ob OBS Mikrofon und Spiel auf getrennte Audiospuren aufnimmt (macht
die Untertitel deutlich besser)
## 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`](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.