# 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). Kein Python, keine GPU nötig — auf CPU braucht ein 30-Sekunden-Clip unter einer Minute. **Transkribiert wird ausschließlich die Mikrofonspur.** OBS nimmt getrennte Audiospuren auf (Mikro auf Spur 2, Spielton auf Spur 3, Mischung auf Spur 1). Whisper bekommt nur Spur 2 — bei GTA-Motoren, Schüssen und Spielmusik ist das der Unterschied zwischen einem brauchbaren und einem unbrauchbaren Transkript. Ins fertige Short kommt weiterhin die Mischung von Spur 1. ```bash # Mikrofonspur herausziehen, 16 kHz Mono, so wie whisper.cpp es erwartet ffmpeg -i raw.mp4 -map 0:a:1 -ac 1 -ar 16000 -c:a pcm_s16le mic.wav whisper-cli -m /models/ggml-small.bin -l de -oj -osrt -f mic.wav ``` `-map 0:a:1` ist der zweite Audiostrom der Datei — welcher das ist, hängt davon ab, wie OBS die Spuren schreibt. Die Zuordnung steht in `config/config.yaml` unter `clipper.audio` und wird beim ersten Clip mit `ffprobe` gegengeprüft. 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/// 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).