Files
abdulive/docs/projects/clip-shorts.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

7.3 KiB
Raw Blame History

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).

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:

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:

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:

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:

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).