TL;DR — Was du nach diesem Artikel weißt
- Wie der Voice-Agent mit ElevenLabs Turbo v2.5 deutsche Voiceovers pro Szene generiert.
- Welche
VoiceSettingsfür deutsche KI-Erklärvideos funktionieren (mit Code).- Wie der Subtitle-Agent mit Whisper Large v3 Word-Level-Timing extrahiert.
- Warum die Re-Segmentierung auf 7 Worte/3,5 s entscheidend für moderne Untertitel ist.
- Wie zwei Agents über Filesystem-Pfade Hand-in-Hand arbeiten — ohne direkte Schnittstelle.
Im vorherigen Teil habe ich den B-Roll-Agent gezeigt — einen schmalen Mini-Agent für visuelle Cutaways. Heute zwei Agents auf einmal: Voice-Agent und Subtitle-Agent, die in der christianohle-Multi-Agent-Pipeline Hand in Hand arbeiten. Der eine produziert Audio, der andere transkribiert genau dieses Audio zurück — und beide kennen sich nicht direkt, sondern kommunizieren über Filesystem-Pfade.
Das ist eine architektonische Pointe, die ich erst nach Wochen Pipeline-Betrieb verstanden habe. Voice und Subtitle sind gekoppelt im Datenfluss, aber entkoppelt im Code. Beide können einzeln getestet, ersetzt, optimiert werden. Genau das macht eine echte Multi-Agent-Architektur aus.
“Diese ‘gekoppelt im Datenfluss, entkoppelt im Code’-Logik ist mein Mantra für Pipeline-Design geworden. Jeder Agent kennt nur sein Input- und Output-Schema. Der Voice-Agent weiß nicht, dass irgendjemand seine MP3s später transkribiert. Der Subtitle-Agent weiß nicht, woher die MP3s kommen. Beide laufen, weil das Filesystem-Format konstant ist.”
Was der Voice-Agent konkret tut
Eingang: ein VideoScript mit 14–20 Szenen aus dem Script-Generator-Agent. Ausgang: 14–20 MP3-Files in data/raw/<topic_id>/voiceover/scene_NNN.mp3.
Vereinfacht:
def synthesize_scenes(scenes: list, output_dir: Path) -> list[Path]:
"""Iteriert über Scene-Liste, generiert pro Szene ein MP3."""
output_dir.mkdir(parents=True, exist_ok=True)
paths = []
for s in scenes:
out = output_dir / f"scene_{s.scene_id:03d}.mp3"
synthesize(s.narration, out)
paths.append(out)
return paths
Pro Szene ein synthesize()-Call gegen die ElevenLabs-API. Das Modell und die Voice-Settings stehen in einer einzigen Stelle — bewusst minimaler, weil hier nicht viel zu tunen ist:
audio = client.text_to_speech.convert(
voice_id=ELEVENLABS_VOICE_ID,
text=text,
model_id="eleven_turbo_v2_5",
output_format="mp3_44100_128",
voice_settings=VoiceSettings(
stability=0.55,
similarity_boost=0.75,
style=0.15,
use_speaker_boost=True,
),
)
Diese Settings sind das Resultat von ~15 Test-Renderings. stability=0.55 ist der Sweet-Spot für deutsche Erklärvideos: niedriger klingt unruhig, höher klingt flach. similarity_boost=0.75 hält die Stimme klar als meine eigene wiedererkennbar (Voice-ID zeigt auf meinen IVC-Clone). style=0.15 bringt minimale Sprech-Variation rein, ohne in Theatralik zu kippen.
“
use_speaker_boost=Truewar der entscheidende Schalter. Ohne ihn klingt die Stimme distanziert, mit ihm präsent. Habe ich erst nach drei Tagen entdeckt — bis dahin dachte ich, das Modell sei einfach so.”
Warum Turbo v2.5 statt Multilingual v2
ElevenLabs hat zwei deutsche Modelle, die in Frage kommen:
- eleven_turbo_v2_5 — schneller, günstiger, kompatibel mit IVC (Instant Voice Clones)
- eleven_multilingual_v2 — höhere Qualität, langsamer, nur kompatibel mit PVC (Professional Voice Clones)
Mein Setup nutzt Turbo v2.5, weil:
- Pro Szene ~3–5 Sekunden Render-Zeit statt 15+ Sekunden bei Multilingual
- ~30 % günstiger pro Zeichen
- IVC-Voice (Instant Clone, 1-Min Training-Audio) reicht für meine Brand-Voice
- PVC (Professional Clone, 30+ Min Training-Audio) habe ich noch nicht gemacht
“Wenn ich christianohle nochmal anfange, würde ich die ersten 30 Min hochwertigen Voice-Trainings-Audio aufnehmen und PVC nutzen. Die Qualität-Differenz ist hörbar. Aber für Phase 1 — Authority Building — reicht Turbo + IVC völlig. Der Trade-off ist: 30 Min Aufnahme-Zeit für 2 % bessere Voice-Qualität, das lohnt sich nicht in der ersten Iteration.”
Was der Subtitle-Agent dann tut
Sobald der Voice-Agent alle 14 MP3-Files geschrieben hat, übernimmt der Subtitle-Agent. Sein Job: aus den MP3s ein *.srt-File mit präzisem Word-Level-Timing erzeugen.
Im Pipeline-Flow läuft das nach der Assembly-Stage, weil der Subtitle-Agent das finale Master-MP4 als Input nimmt — nicht die einzelnen Szenen-MP3s. Damit umgeht er das Problem, dass Whisper bei einzelnen kurzen Audio-Schnipseln schlechter performt als bei einem zusammenhängenden 6-Min-Track.
Der Code-Aufruf nutzt Whisper Large v3 lokal:
import whisper
model = whisper.load_model("large-v3")
result = model.transcribe(
str(master_mp4),
language="de",
word_timestamps=True, # ← der Schlüssel
verbose=False,
)
Was word_timestamps=True macht: jedes einzelne Wort kriegt eigene Start- und End-Zeitstempel. Die Transcription für 6 Min Audio sieht dann so aus:
{
"segments": [
{
"start": 0.32,
"end": 2.45,
"text": "Ein Agent ist kein Zauber. Es sind vier Teile.",
"words": [
{"word": "Ein", "start": 0.32, "end": 0.51},
{"word": "Agent", "start": 0.52, "end": 0.83},
{"word": "ist", "start": 0.84, "end": 0.99},
{"word": "kein", "start": 1.0, "end": 1.18},
...
]
},
...
]
}
Re-Segmentierung auf 7-Worte-Chunks
Standard-Whisper-Segmente sind oft 8–15 Sekunden lang — viel zu lang für moderne Untertitel. Der Subtitle-Agent re-segmentiert die Word-Timings auf maximal 7 Wörter oder 3,5 Sekunden pro Chunk:
def resegment_to_chunks(segments, max_words=7, max_duration=3.5):
chunks = []
current_words = []
current_start = None
for seg in segments:
for w in seg["words"]:
if not current_words:
current_start = w["start"]
current_words.append(w)
duration = w["end"] - current_start
if len(current_words) >= max_words or duration >= max_duration:
chunks.append({
"start": current_start,
"end": current_words[-1]["end"],
"text": " ".join(c["word"] for c in current_words).strip(),
})
current_words = []
current_start = None
if current_words:
chunks.append({
"start": current_start,
"end": current_words[-1]["end"],
"text": " ".join(c["word"] for c in current_words).strip(),
})
return chunks
Das Resultat: 80–120 Untertitel-Chunks für ein 6-Min-Video, jeder maximal 3,5s lang, jeder maximal 7 Worte. Lesbar im modernen YouTube-Stil — der Zuschauer kann beim Hören den Untertitel einfach mitlesen.
“7 Worte / 3,5 Sekunden ist nicht beliebig. Ich hab das Limit ausprobiert: 5 Worte fühlt sich gehetzt an, 10 Worte ist zu viel zum Erfassen. 7 trifft den Sweet-Spot. Jede Untertitel-Generation, die das nicht enforct, wirkt heute schon dated.”
SRT-Format-Output
Whisper-Output wird in das SRT-Format konvertiert (Standard für YouTube-Subtitle-Upload):
1
00:00:00,320 --> 00:00:01,180
Ein Agent ist kein
2
00:00:01,180 --> 00:00:02,450
Zauber. Es sind vier Teile.
3
00:00:02,450 --> 00:00:04,300
Heute zerlegen wir die Architektur
Das .srt-File wird neben das Master-MP4 geschrieben. YouTube zieht es beim Upload automatisch.
Der Subtitle-Burn-In auf das Master-Video
Optional brennt der Subtitle-Agent die Untertitel direkt in das Video (für Plattformen, die SRT nicht ziehen — Reddit, LinkedIn-Reupload). ffmpeg-Filter mit Style-Spec:
subtitle_filter = (
f"subtitles={srt_path}:"
f"force_style='Fontname=Manrope,Fontsize=22,"
f"PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&,"
f"Outline=2,Shadow=1,MarginV=70,Alignment=2'"
)
Schwarzer Outline mit 2px, weißer Text in Manrope (passt zur christianohle-Brand-Typo), 70px MarginV (also ~70px vom unteren Rand entfernt). Das Burn-In macht das Video selbständig sehbar, auch wenn der Player keine SRT-Tracks unterstützt.
Architektur-Pointe: gekoppelt im Datenfluss, entkoppelt im Code
Hier kommt die Multi-Agent-Linse zur Geltung. Voice-Agent und Subtitle-Agent kennen sich nicht direkt:
- Der Voice-Agent schreibt MP3-Files an einen Standard-Pfad.
- Der Subtitle-Agent (über die Assembly-Stage) liest das Master-MP4, das aus diesen MP3-Files entstanden ist.
- Der Subtitle-Agent weiß nicht, dass das Audio aus ElevenLabs kommt. Es könnte genauso gut von einem menschlichen Sprecher aufgenommen worden sein.
Was diese Entkopplung ermöglicht: ich kann morgen ElevenLabs durch Coqui-TTS (lokal, Open-Source) ersetzen — der Subtitle-Agent merkt nichts. Solange die MP3s im selben Pfad-Format liegen, läuft alles weiter.
Das ist nicht über-engineered. Das ist Multi-Agent-Architektur im Production-Sinn.
Was beide Agents zusammen kosten
- Voice-Agent (ElevenLabs Turbo v2.5): ~600 Wörter pro Video × 0,30 USD/1k chars = ~0,40 €
- Subtitle-Agent (Whisper lokal): 0 € (lokale GPU, nur Strom)
Gesamt: ~0,40 € pro Video. Voice ist die teuerste Stage nach Fal-Video — aber nicht zu vermeiden, wenn man eine konsistente Brand-Voice will. Whisper lokal ist eine der schönsten Sparbüchsen der ganzen Pipeline: API-Whisper würde nochmal 0,15 €/Video kosten, lokal kostet es nichts.
“Mein erstes Setup hatte API-Whisper. Habe nach 3 Wochen auf lokal umgestellt — nicht primär aus Kostengründen, sondern weil ich kein deutsches Skript-Material an externe Server senden wollte. Die GPU-Last ist überschaubar (~90 Sek pro Video), die DSGVO-Story sauber, die Kostenersparnis ein Bonus.”
Was als nächstes in der Serie kommt
Im nächsten Teil schaue ich mir den Assembly-Agent an — der die einzelnen Szenen-Visuals und MP3s zu einem zusammenhängenden Master-MP4 zusammensetzt, mit FPS-Normalisierung, Concat-Demuxer-Konsistenz und Sidechain-Ducking-BGM. Spoiler: das ist der Agent mit den meisten ffmpeg-Tricks.
Hat dir der Artikel geholfen? Teile ihn.


