---
name: youtube-headless-pipeline
description: "Vollautomatische YouTube-Shorts-Pipeline: Reddit-Stories → LLM-generierte deutsche Kurzgeschichte → Conrad TTS → Pexels B-Roll → fertiges 9:16 Short. Keine Kosten, null manuelle Schritte."
version: 2.0.0
author: Ahmed & Kiwi
metadata:
  hermes:
    tags: [YouTube, Shorts, Automation, Video, TTS, Reddit, Pexels, LLM]
---

# YouTube Headless Pipeline

Komplett automatisierte Video-Produktion für einen faceless YouTube-Kanal. Zwei Modi:

**Shorts-Modus (Standard)** — Reddit-Post → LLM generiert eigene deutsche Kurzgeschichte (~50s) → Conrad TTS → Pexels B-Roll → vertikales 1080×1920 Short

**Long-Form-Modus (veraltet)** — Siehe `references/long-form-legacy.md`. Nur noch bei explizitem Wunsch nutzen.

## Architektur (Shorts)

```
Reddit API → Story (Titel + englischer Text)
    ↓
LLM (gemma3:4b) → komplett eigene deutsche Kurzgeschichte, max. 100 Wörter
    ↓
edge-tts → MP3 Audio (de-DE-ConradNeural), Ziel: 45-55 Sekunden
    ↓
Pexels API → 40-60 vertikale B-Roll Clips
    ↓
ffmpeg → Clips normalisieren + looped → 55s B-Roll-Video (1080×1920)
    ↓
ffmpeg → Audio + B-Roll merged → fertiges .mp4 Short
```

## Voraussetzungen

- **Static ffmpeg mit libx264** (`/DATA/.local/bin/ffmpeg-static`) — ZimaOS-Build hat KEIN libx264
- **edge-tts** installiert (`/DATA/.local/bin/edge-tts`)
- **Python 3** mit stdlib (keine externen Pakete nötig)
- **Internet** für Reddit, Pexels, Ollama Cloud
- **Ollama Cloud API Key** (Pro-Tier, $20/mo) — Base URL `https://ollama.com/v1`
- **gemma3:4b** als Story-Modell (kein Reasoning-Modell → liefert direkt Text, siehe Pitfalls)
- Pexels benötigt **KEINEN** API-Key — läuft mit Demo-Auth `Authorization: test`

## Verwendung

### Vollautomatisch (ein Befehl)

```bash
cd /DATA/AppData/hermes/Projekte/youtube-kanal
python3 scripts/pipeline.py
```

### Mit bestimmtem Subreddit

```bash
python3 scripts/pipeline.py --topic beichten     # r/Beichten
python3 scripts/pipeline.py --topic confessions  # r/confessions
python3 scripts/pipeline.py --topic stories      # r/stories
```

### Einzelne Schritte

```bash
python3 scripts/pipeline.py --steps story         # Nur Story + LLM
python3 scripts/pipeline.py --steps story,audio   # Story + Audio
python3 scripts/pipeline.py --steps render,merge  # Nur Video rendern (braucht vorhandene Clips + Audio)
```

### Video finden

Videos landen in:
```
/DATA/AppData/hermes/Projekte/youtube-kanal/output/<timestamp>.mp4
```

## Output-Verzeichnis

- `output/` — Fertige Shorts (YYYYMMDD_HHMMSS.mp4)
- `audio/` — TTS-Audio (.mp3) + generierte Story (.txt)
- `videos/broll/` — Heruntergeladene Stock-Clips
- `videos/normalized/` — Auf einheitliches Format gebrachte Clips

## Video-Specs (Shorts)

- **Format:** 1080×1920 (9:16 vertikal) — YouTube/TikTok/Reels
- **Dauer:** ~50 Sekunden (max. 55s, Ziel 45-50s)
- **Video:** H.264, 30fps, YUV420p
- **Audio:** AAC 128kbps
- **Kein Intro, kein Outro, kein Branding** — purer Story-Inhalt

## LLM-Story-Generator

Das LLM (deepseek-v4-pro) bekommt den Reddit-Post und ein deutsches Prompt:

- Prompt-Sprache: Deutsch
- Ziel: **Eigene Kurzgeschichte**, NICHT Übersetzung
- Struktur: Situation → Konflikt → Pointe/Twist
- Max. 120 Wörter (≈50 Sekunden gesprochen)
- Spannender erster Satz (Hook in den ersten 5 Wörtern)
- Umgangssprachlich, emotional, authentisch
- NUR die Story — kein "Willkommen zu...", kein "Like & Abo"

## Quellen

- **Reddit:** r/confessions, r/Beichten, r/stories, r/tifu
- **B-Roll:** Pexels (lizenzfrei, kein API-Key nötig)
- **Stimme:** Microsoft Edge TTS (de-DE-ConradNeural, männlich)
- **Story-Generator:** gemma3:4b via Ollama Cloud

## Typische Laufzeit (Shorts)

- Story fetch + LLM: 5-15 Sekunden
- Audio-Generierung: 5-10 Sekunden
- B-Roll Download: 30-60 Sekunden (40 Clips)
- Video-Normalisierung + Rendern: 1-3 Minuten
- Merge: 10-30 Sekunden
- **Gesamt: ~2-5 Minuten pro Short**

## Pitfalls

### Reasoning-Modelle vs. Nicht-Reasoning-Modelle (!!)
- **deepseek-v4-pro und kimi-k2.6 sind Reasoning-Modelle.** Sie verbrauchen Tokens fürs interne Denken. Bei zu wenigen `max_tokens` ist der `content` leer und nur `reasoning` gefüllt.
- Selbst mit 1024 `max_tokens` kann deepseek-v4-pro den Prompt selbst als „Story" ausgeben, statt eine echte Geschichte zu schreiben.
- **Deshalb: gemma3:4b für Story-Generierung.** Kein Reasoning-Modell → liefert direkt Text ohne Denk-Overhead. 400 `max_tokens` reichen.
- **Fallback im Skript:** Prüft `msg.get("content", "") or msg.get("reasoning", "")` — falls content leer, reasoning nehmen.

### Ollama Cloud Timeouts
- Ollama Cloud kann zeitweise nicht erreichbar sein → curl bekommt leeren Response.
- Skript hat derzeit KEINEN Timeout-Fallback. Bei Netzproblemen: Warten und neu starten.

### Pexels API
- **Query-Encoding-Falle:** `+` in Search-Queries (z.B. `nature+landscape`) führt zu "Invalid API key" — NUR Einzelwörter verwenden (`nature`, `landscape`, `city`)
- Pexels API ist langsam bei vielen Requests → `per_page=10` und 0.3s Delay
- **Vertikale Clips präferieren:** Scoring-Logik gibt Bonus für h>w, sucht nach 1080×1920-nähen Clips

### ffmpeg: ZimaOS-Build hat kein libx264
ZimaOS-ffmpeg: kein libx264. Workaround: **Static Build von johnvansickle.com** nach `/DATA/.local/bin/ffmpeg-static`. System-ffprobe (`/usr/bin/ffprobe`) reicht für Metadaten. Details: `references/ffmpeg-zimaos.md`

### Concat + Rendering: Die harte Lektion (mehrere Fehlversuche)
- **NICHT `-stream_loop` mit concat verwenden!** Kracht bei unterschiedlichen Codecs.
- **NICHT concat + filter in einem ffmpeg-Aufruf kombinieren!**
- ✅ **Richtiger Ansatz (2-Phasen):**
  1. **Phase 1 — Normalisieren:** Jeden Clip einzeln re-encoden → `libx264, ultrafast, crf 23, 1080×1920, 30fps, fixed GOP`
  2. **Phase 2 — Concatenieren:** Concat-Liste aus normalisierten Clips bauen (für Loops: Clips mehrfach in die Liste schreiben), dann direkt re-encoden

### Guards im Skript
- **Div-by-Zero:** Falls 0 Clips → schwarzes Blank-Video
- **ffprobe kann leeren String liefern** → try/except mit Fallback-Duration (15s)
- **Dateigröße prüfen:** `file.stat().st_size > 1000` nach jedem Download
- **edge-tts:** kein offizielles Rate-Limit, aber zu viele Requests → 429
- **Video-Codec:** H.264 + AAC → YouTube-kompatibel

## Nächste Schritte (manuell)

- YouTube-Upload (Browser → studio.youtube.com)
- Titel + Beschreibung + Tags setzen
- Thumbnail (später automatisierbar mit Pillow)
- Hashtags: #Shorts #Storytime #RedditStories
