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
176 lines
7.3 KiB
Markdown
176 lines
7.3 KiB
Markdown
# 1 — Clip-to-Shorts-Pipeline (`apps/clipper`)
|
||
|
||
**Wirkrichtung:** Reichweite. Der einzige Kanal mit nichtlinearer Reichweite für
|
||
einen Kanal dieser Größe.
|
||
|
||
## Der ehrliche Engpass
|
||
|
||
Nicht das Zuschneiden ist schwer, sondern **die Auswahl**. Ein automatisch
|
||
erzeugter, mittelmäßiger Clip pro Stream bringt nichts; drei gute pro Woche
|
||
bringen viel. Die Pipeline ist deshalb so gebaut, dass sie **Kandidaten
|
||
produziert und rankt**, nicht dass sie blind veröffentlicht. Abdu sieht eine
|
||
Liste fertig gerenderter 9:16-Videos und wählt aus. Das dauert zwei Minuten
|
||
statt zwei Stunden — genau das ist der Hebel.
|
||
|
||
Zweiter ehrlicher Punkt: Bei 10 Zuschauern sind Chat-Spikes ein **schwaches
|
||
Signal**. Deshalb ist die Reihenfolge der Trigger:
|
||
|
||
1. **`!clip` im Chat** — funktioniert ab Tag 1, jeder Zuschauer kann auslösen
|
||
2. **Hotkey / Streamdeck** durch Abdu selbst — bestes Signal überhaupt
|
||
3. **Chat-Spike** — wird von Anfang an mitgeschrieben und protokolliert, aber
|
||
erst scharf geschaltet, wenn genug Material für eine Kalibrierung da ist
|
||
|
||
## Zwei Quellwege
|
||
|
||
### Weg A: OBS-Replay-Buffer (Primärweg)
|
||
|
||
Replay-Buffer in OBS auf 90 s. Trigger → `SaveReplayBuffer` über obs-websocket
|
||
→ MP4 in voller Aufnahmequalität landet lokal.
|
||
|
||
**Vorteile:** Originalqualität statt Twitch-Transcode. Kein API-Delay. Kein
|
||
Rate Limit. Und — der eigentliche Gewinn — man kann in OBS eine **zweite
|
||
Aufnahmespur oder eine dedizierte Vertical-Szene** einrichten, in der Facecam
|
||
und Gameplay bereits sauber getrennt sind. Dann entfällt das fehleranfällige
|
||
Zurückschneiden aus dem komposite Bild komplett.
|
||
|
||
**Nachteil:** Braucht Festplattenplatz und eine korrekte OBS-Konfiguration.
|
||
|
||
### Weg B: Twitch-Clip über Helix (Zweitweg)
|
||
|
||
`POST /helix/clips` → warten → `GET /helix/clips?id=` → MP4 über die aus
|
||
`thumbnail_url` abgeleitete URL (siehe
|
||
[`../02-twitch-integration.md`](../02-twitch-integration.md)).
|
||
|
||
**Wird trotzdem gebraucht,** weil ein Twitch-Clip eine teilbare URL hat, im
|
||
Kanal auffindbar ist und von Zuschauern weiterverbreitet werden kann. Deshalb:
|
||
**beide Wege gleichzeitig auslösen.** Twitch-Clip für die Plattform, OBS-Datei
|
||
für das Short.
|
||
|
||
## Layout und ffmpeg
|
||
|
||
Zielformat: 1080×1920, H.264, höchstens 60 s.
|
||
|
||
### Eingemessen am 2026-09-09
|
||
|
||
Aus einem VOD-Screenshot (GTA 5, Szene „Gameplay") ergaben sich diese Werte,
|
||
festgehalten in [`config/layout.yaml`](../../config/layout.yaml):
|
||
|
||
| Element | Lage im Streambild 1920×1080 |
|
||
|---|---|
|
||
| Facecam | bündig oben links, **440×330**, Seitenverhältnis 4:3 |
|
||
| Chat-Overlay | ins Bild eingebrannt, beginnt bei **x = 1270** |
|
||
| Minimap | unten links, endet bei x ≈ 360 |
|
||
| Handy-/Kontakt-Overlay | rechts unten, ab x ≈ 1540 |
|
||
|
||
Die Kanten der Facecam sind über die gesamte Höhe bzw. Breite konstant
|
||
(rechte Kante x = 439 bei y = 120/200/280/360; untere Kante y = 391 bei
|
||
x = 40/140/240/340/420) — ein sauberes Rechteck, also ein fester Crop.
|
||
|
||
**Zwei Befunde, die das ursprünglich geplante Layout verändert haben:**
|
||
|
||
**1. Der Chat ist ins Streambild eingebrannt.** Ein zentrierter Gameplay-Crop
|
||
hätte mitten durch die Chat-Zeilen geschnitten — abgeschnittene Buchstaben am
|
||
rechten Rand. Das nutzbare Band liegt zwischen Facecam (endet bei 440) und Chat
|
||
(beginnt bei 1270), also **828 px breit**. Das ist schmaler als die 888 px, die
|
||
für ein zentrales Drittel nötig wären, und legt damit die Bandaufteilung fest.
|
||
|
||
**2. Die Facecam ist 4:3, das Zielband ist deutlich breiter.** Ein 4:3-Bild auf
|
||
ein 16:9-Band zu skalieren zieht das Gesicht um ein Drittel in die Breite.
|
||
Deshalb wird aus der Cam ein **Streifen mit der Zielproportion** geschnitten
|
||
(440×208 ab y = 100), nicht das ganze Bild verzerrt. Der Versatz y = 100 sitzt
|
||
so, dass Kopf und Kinn vollständig drin sind und oben etwas Luft bleibt.
|
||
|
||
Daraus folgt die Aufteilung **512 px Cam + 1408 px Gameplay = 1920**:
|
||
|
||
```bash
|
||
ffmpeg -i in.mp4 -filter_complex "
|
||
[0:v]crop=440:208:0:100,scale=1080:512,setsar=1[cam];
|
||
[0:v]crop=828:1080:440:0,scale=1080:1408,setsar=1[game];
|
||
[cam][game]vstack=inputs=2[v]
|
||
" -map "[v]" -map 0:a \
|
||
-c:v libx264 -crf 20 -preset veryfast -pix_fmt yuv420p \
|
||
-c:a aac -b:a 160k -movflags +faststart out.mp4
|
||
```
|
||
|
||
Nachmessen und Vorschau rendern:
|
||
|
||
```powershell
|
||
docker compose run --rm toolkit calibrate
|
||
```
|
||
|
||
Das legt drei Bilder in `data/clips/_calibration/` ab: das Einzelbild, dasselbe
|
||
Bild mit eingezeichneten Ausschnitten, und die fertige 9:16-Vorschau.
|
||
|
||
Für andere Spiele lässt sich pro Kategorie ein eigenes Profil in
|
||
`layout.yaml` hinterlegen.
|
||
|
||
### Was das Layout verbessern würde
|
||
|
||
Der Crop skaliert die Facecam von 440 px auf 1080 px Breite — Faktor 2,45. Das
|
||
ist sichtbar weich. **Die eigentliche Lösung liegt in OBS, nicht in ffmpeg:**
|
||
eine zweite Aufnahmespur oder eine dedizierte Vertical-Szene, in der Cam und
|
||
Gameplay in voller Auflösung nebeneinander vorliegen. Dann entfällt sowohl der
|
||
Qualitätsverlust als auch der ganze Filtergraph. Das ist eine Aufgabe für die
|
||
Zeit nach dem MVP — der Crop-Weg funktioniert vorher gut genug.
|
||
|
||
Kleinigkeit mit großer Wirkung: **GTA-Untertitel im Spiel ausschalten.** Sie
|
||
laufen über die volle Bildbreite und werden vom Gameplay-Crop rechts
|
||
abgeschnitten. Die eigenen Whisper-Untertitel liegen ohnehin darüber.
|
||
|
||
Bei Quelle = getrennte Vertical-Szene aus OBS entfällt der ganze Filtergraph.
|
||
|
||
## Untertitel
|
||
|
||
`whisper.cpp` (`whisper-cli`), Modell `ggml-small` (deutsch, reicht; `medium`
|
||
bei Bedarf), auf der extrahierten Audiospur (16 kHz Mono WAV). Kein Python,
|
||
keine GPU nötig — auf CPU braucht ein 30-Sekunden-Clip unter einer Minute.
|
||
Ausgabe als SRT, Umwandlung in ASS mit Karaoke-Timing
|
||
(wortweises Highlight — der Stil, der auf Shorts funktioniert), dann einbrennen:
|
||
|
||
```bash
|
||
ffmpeg -i out.mp4 -vf "ass=subs.ass" -c:a copy -c:v libx264 -crf 20 final.mp4
|
||
```
|
||
|
||
Der Untertitel-Stil (Font, Größe, Position, Highlight-Farbe) liegt in
|
||
`config/subtitle-style.ass`. Sicherheitsabstand nach unten mindestens 320 px,
|
||
sonst verdeckt die TikTok-UI den Text.
|
||
|
||
## Ranking der Kandidaten
|
||
|
||
`score` aus: Spike-Höhe, Anzahl unterschiedlicher Chatter im Fenster,
|
||
Emote-Dichte, Lautstärkespitze im Audio (`ebur128`), Trigger-Art
|
||
(Hotkey > `!clip` > Spike), Menge erkannter Sprache im Transkript.
|
||
|
||
## Ausgabe
|
||
|
||
```
|
||
data/clips/<stream-datum>/<clip-id>/
|
||
raw.mp4 Quelle (OBS oder Twitch)
|
||
vertical.mp4 9:16 ohne Untertitel
|
||
final.mp4 9:16 mit eingebrannten Untertiteln <- Upload-Kandidat
|
||
subs.srt / subs.ass
|
||
transcript.txt
|
||
meta.json Score, Trigger, Chat-Ausschnitt, Titelvorschläge
|
||
thumb.jpg
|
||
```
|
||
|
||
Plus ein Eintrag in `clips` mit `status='rendered'`. Das Dashboard zeigt die
|
||
Kandidaten des letzten Streams, sortiert nach Score, mit Vorschau und den
|
||
Knöpfen *Übernehmen* / *Verwerfen*.
|
||
|
||
## Ausdrücklich nicht in Ausbaustufe 1
|
||
|
||
Automatischer Upload zu TikTok/YouTube/Reels. Die Upload-APIs sind aufwendig
|
||
(die TikTok Content Posting API braucht eine App-Review), und der Nutzen ist
|
||
gering, solange die Auswahl ohnehin manuell erfolgt. Ausbaustufe 2.
|
||
|
||
## Abnahmekriterien
|
||
|
||
- `!clip` im Chat, innerhalb von 3 Minuten liegt ein fertiges `final.mp4` vor.
|
||
- Untertitel sind zeitlich um weniger als 300 ms versetzt und bei normaler
|
||
Sprechlautstärke deutsch korrekt.
|
||
- Facecam-Crop sitzt bei drei verschiedenen OBS-Szenen richtig.
|
||
- Ein Clip läuft durch die Pipeline, während der Stream weiterläuft, ohne dass
|
||
die Encoding-Last Frames im Stream kostet (`-preset veryfast`, niedrige
|
||
Prozesspriorität, bei Bedarf NVENC).
|