diff --git a/ki-video/PLAN.md b/ki-video/PLAN.md index 35ea1bbf..6f205e66 100644 --- a/ki-video/PLAN.md +++ b/ki-video/PLAN.md @@ -362,13 +362,228 @@ Empfehlung: Erst aufbauen wenn 3080-Rig voll ausgelastet. Alternative: verkaufen Nicht aktiv aufbauen. Falls 3080-Rig irgendwann an Kapazitaetsgrenzen stoesst: 1-Karten-Test mit Whisper. Wenn selbst das nicht lohnt: verkaufen und in NVMe-Storage oder RAM investieren. +## Produktions-Datenbank + Orchestrierung + +Zentrale Steuerung: **eine SQLite-Datei** (`production.db`) auf ki-tower. +Alles passiert hier: Kanaele, Videos, Szenen, Jobs, Assets, Status. + +### Datenbank-Schema + +```sql +-- Kanaele (je einer pro YouTube-Kanal) +CREATE TABLE channels ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, -- "Geld & Imperien Stil" + slug TEXT UNIQUE NOT NULL, -- "kanal-a" + avatar_path TEXT NOT NULL, -- "/data/channels/kanal-a/avatar.png" + voice_path TEXT NOT NULL, -- "/data/channels/kanal-a/voice-sample.wav" + style TEXT NOT NULL, -- "pip_20" oder "fullscreen" + prompt_template TEXT, -- System-Prompt fuer GPT-5.4 Skripte (Ton, Stil, Persoenlichkeit) + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Videos (ein Eintrag pro Video) +CREATE TABLE videos ( + id INTEGER PRIMARY KEY, + channel_id INTEGER REFERENCES channels(id), + title TEXT NOT NULL, -- "Irans Hyperschall-Schlag" + topic TEXT, -- Thema + Recherche-Notizen + status TEXT DEFAULT 'draft', -- draft → script → scenes → producing → assembly → review → published + script TEXT, -- Fertiges Skript (aus GPT-5.4 + Review) + scene_plan TEXT, -- JSON: Szenenplan (aus Qwen 14B) + voiceover_path TEXT, -- Pfad zur fertigen WAV + avatar_path TEXT, -- Pfad zum fertigen Avatar-Video + subtitle_path TEXT, -- Pfad zur SRT-Datei + final_path TEXT, -- Pfad zum fertigen MP4 + thumbnail_path TEXT, -- Pfad zum Thumbnail + yt_title TEXT, -- YouTube-Titel + yt_description TEXT, -- YouTube-Description + yt_tags TEXT, -- YouTube-Tags (JSON array) + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + published_at DATETIME +); + +-- Szenen (aus dem Szenenplan, je eine Zeile pro Szene) +CREATE TABLE scenes ( + id INTEGER PRIMARY KEY, + video_id INTEGER REFERENCES videos(id), + scene_nr INTEGER NOT NULL, + scene_type TEXT NOT NULL, -- "hero", "standard", "karte", "infografik" + prompt TEXT NOT NULL, -- Bildprompt (EN) + overlay TEXT, -- Text-Overlay (oder NULL) + duration_s REAL NOT NULL, -- Geschaetzte Dauer in Sekunden + image_path TEXT, -- Pfad zum generierten Bild (nach Produktion) + upscaled BOOLEAN DEFAULT 0, + status TEXT DEFAULT 'pending' -- pending → generating → done → failed +); + +-- GPU-Jobs (jede Aufgabe die ein Worker erledigt) +CREATE TABLE jobs ( + id INTEGER PRIMARY KEY, + video_id INTEGER REFERENCES videos(id), + job_type TEXT NOT NULL, -- "tts", "sdxl", "flux", "sadtalker", "whisper", "esrgan", "assembly" + gpu TEXT, -- "3090", "3080_0", "3080_1", "3080_2", "3080_3" + status TEXT DEFAULT 'queued', -- queued → running → done → failed → retry + input_data TEXT, -- JSON: was der Worker braucht + output_path TEXT, -- Pfad zum Ergebnis + error TEXT, -- Fehlermeldung falls failed + queued_at DATETIME DEFAULT CURRENT_TIMESTAMP, + started_at DATETIME, + finished_at DATETIME +); +``` + +### Verzeichnisstruktur + +``` +/data/ki-video/ +├── production.db # SQLite — zentrale Steuerung +│ +├── channels/ # Pro Kanal +│ ├── kanal-a/ +│ │ ├── avatar.png # Referenz-Portraet (FLUX-generiert) +│ │ ├── voice-sample.wav # XTTS Voice-Cloning Referenz (~30s) +│ │ ├── style.json # { "avatar": "pip_20", "position": "bottom_right" } +│ │ └── prompt.md # GPT-5.4 System-Prompt (Persoenlichkeit, Ton, Stil) +│ └── kanal-b/ +│ ├── avatar.png # Anderes Gesicht +│ ├── voice-sample.wav # Andere Stimme +│ ├── style.json # { "avatar": "fullscreen" } +│ └── prompt.md # Anderer Stil/Persoenlichkeit +│ +├── videos/ # Pro Video ein Ordner +│ ├── 2026-03-16-iran-hyperschall/ +│ │ ├── script.md # Fertiges Skript +│ │ ├── scenes.json # Szenenplan +│ │ ├── images/ +│ │ │ ├── hero-001.png +│ │ │ ├── scene-002.png +│ │ │ ├── scene-003.png +│ │ │ └── ... # 80-120 Bilder +│ │ ├── audio/ +│ │ │ ├── voiceover.wav # XTTS v2 Output +│ │ │ └── music.mp3 # Hintergrundmusik +│ │ ├── avatar/ +│ │ │ ├── clip-001.mp4 # SadTalker 30s-Clips +│ │ │ ├── clip-002.mp4 +│ │ │ └── full.mp4 # Zusammengefuegt +│ │ ├── subtitles.srt # Whisper → SRT +│ │ ├── thumbnail.png # FLUX hero + Text +│ │ └── final.mp4 # FERTIGES VIDEO +│ └── 2026-03-18-oelpreis-analyse/ +│ └── ... +│ +└── templates/ + ├── ffmpeg-pip20.sh # FFmpeg-Preset: 20% Avatar unten rechts + ├── ffmpeg-fullscreen.sh # FFmpeg-Preset: 100% Avatar + ├── ffmpeg-kenburns.sh # Ken-Burns-Effekt Presets + └── music/ # Lizenzfreie Hintergrundmusik + ├── ambient-01.mp3 + └── ambient-02.mp3 +``` + +### Produktions-Ablauf (Statusmaschine) + +``` + ┌──────────────────────────────────────────────────┐ + │ │ + ┌─────────┐ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ + │ draft │──────┼─►│ script │───►│ scenes │───►│ producing │ │ + │ │ │ │ │ │ │ │ │ │ + │ Thema │ │ │ GPT-5.4 │ │ Qwen 14B │ │ 5 GPUs │ │ + │ anlegen │ │ │ + Review │ │ Szenen- │ │ parallel │ │ + └─────────┘ │ └──────────┘ │ plan │ └─────┬─────┘ │ + │ └──────────┘ │ │ + │ ▼ │ + │ ┌───────────┐ ┌──────────┐ ┌──────────┐ │ + │ │ published │◄───│ review │◄──│ assembly │ │ + │ │ │ │ │ │ │ │ + │ │ YouTube │ │ Mensch │ │ FFmpeg │ │ + │ │ Upload │ │ schaut │ │ PiP/Full │ │ + │ └───────────┘ └──────────┘ └──────────┘ │ + │ │ + └──────────────────────────────────────────────────┘ + +Status-Uebergaenge: + draft → script : Mensch gibt Thema ein, startet GPT-5.4 Skript + script → scenes : Mensch reviewt Skript, bestaetigt, Qwen generiert Szenenplan + scenes → producing : Orchestrator erstellt Jobs fuer alle 5 GPUs + producing → assembly : Alle GPU-Jobs fertig (TTS + Avatar + Bilder + Untertitel) + assembly → review : FFmpeg Assembly fertig, Video liegt als MP4 vor + review → published : Mensch schaut Video, gibt frei, Upload +``` + +### Orchestrator-Logik (Python, laeuft auf ki-tower) + +``` +Alle 10 Sekunden: + 1. Checke videos WHERE status = 'scenes' + → Erstelle GPU-Jobs: + - 1x TTS-Job (3080 #0) + - N x SDXL-Jobs (3080 #0 nach TTS, #1) + - M x FLUX-Jobs (3090, nur hero-Szenen) + - 1x Whisper-Job (3080 #3, nach TTS) + - 1x ESRGAN-Job (3080 #3, nach Bilder) + - 1x SadTalker-Job (3080 #2, nach TTS) + + 2. Checke jobs WHERE status = 'queued' + → Pruefe ob Abhaengigkeiten erfuellt: + - SadTalker wartet auf TTS (voiceover.wav muss existieren) + - Whisper wartet auf TTS + - ESRGAN wartet auf Bilder + → Sende an Worker via HTTP REST + + 3. Checke jobs WHERE status = 'running' + → Polle Worker-Status + → Bei done: speichere output_path, update status + → Bei failed: retry (max 3x), dann menschliche Aufmerksamkeit + + 4. Checke videos WHERE status = 'producing' + AND alle zugehoerigen jobs.status = 'done' + → Starte Assembly-Job (FFmpeg) + → Status → 'assembly' + + 5. Checke videos WHERE status = 'assembly' + AND assembly-job.status = 'done' + → Status → 'review' + → Benachrichtigung an Telegram (Hausmeister-Bot!) +``` + +### CLI fuer den Alltag + +```bash +# Neues Video anlegen +./produce.py new --channel kanal-a --topic "Irans Hyperschall-Schlag" + +# Status aller Videos +./produce.py status +# ID | Kanal | Thema | Status | Fortschritt +# 42 | kanal-a | Irans Hyperschall-Schlag | producing | 3/5 Jobs done +# 43 | kanal-b | Oelpreis-Analyse 2026 | script | Review pending + +# Skript bestaetigen und Produktion starten +./produce.py approve-script 42 + +# Fertiges Video reviewen und freigeben +./produce.py publish 42 --yt-upload + +# GPU-Auslastung +./produce.py gpus +# GPU | Status | Job | Video | Progress +# 3090 | busy | flux-hero | #42 | 8/15 images +# 3080 #0 | busy | sdxl-batch | #42 | 23/50 images +# 3080 #1 | busy | sdxl-batch | #42 | 31/50 images +# 3080 #2 | busy | sadtalker | #42 | 12:30/24:00 +# 3080 #3 | idle | — | — | waiting for images +``` + ## Worker-Architektur ``` ki-tower (3090, Chef) gpu-worker (4x 3080, Worker) gpu-reserve (RX 6600 XT) ┌─────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────┐ │ Orchestrator (Python) │ │ Debian 12 + Docker + CUDA │ │ Reserve/Whisper │ -│ ├── Job Queue (SQLite) │ Tailscale │ │ +│ ├── production.db │ Tailscale │ │ │ ├── /api/submit-job │◄────────────►│ GPU #0: xtts+sdxl :8501 │ │ Whisper :8601 │ │ ├── /api/job-status │ │ GPU #1: sdxl-worker :8502 │ │ CPU-Batch :8602 │ │ └── /api/get-result │ Tailscale │ GPU #2: sadtalker :8503 │ │ (nur bei Bedarf)│ @@ -379,12 +594,6 @@ ki-tower (3090, Chef) gpu-worker (4x 3080, Worker) gpu- └─────────────────────────┘ └──────────────────────────────┘ ``` -Prinzipien: -- 1 Container = 1 GPU = 1 Aufgabe. Feste Zuordnung, kein dynamisches Scheduling. -- SQLite als Job-Queue. Ein User, nicht tausend. Eine Datei reicht. -- HTTP-APIs pro Worker. Orchestrator ruft per REST auf, pollt Status. -- Kein Service-Mesh, kein Kubernetes. Tailscale verbindet die zwei Maschinen. - ## VRAM-Budget (ki-tower, sequentiell) ```