christianohle
Zurück zu Bauen

Bauen

Voice-Agent + Subtitle-Agent: Word-Level-Timing mit Whisper

Build-in-Public Teil 6: Wie zwei Agents zusammenarbeiten — Voice-Agent erzeugt MP3 mit ElevenLabs, Subtitle-Agent transkribiert mit Whisper auf Word-Level.

6 Min Lesezeit voice agent · subtitle agent · whisper word-level timing · elevenlabs deutsch · ki video voiceover · multi-agent pipeline
Hero-Image: Voice-Agent + Subtitle-Agent: Word-Level-Timing mit Whisper

TL;DR — Was du nach diesem Artikel weißt

  • Wie der Voice-Agent mit ElevenLabs Turbo v2.5 deutsche Voiceovers pro Szene generiert.
  • Welche VoiceSettings fü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=True war 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:

  1. Pro Szene ~3–5 Sekunden Render-Zeit statt 15+ Sekunden bei Multilingual
  2. ~30 % günstiger pro Zeichen
  3. IVC-Voice (Instant Clone, 1-Min Training-Audio) reicht für meine Brand-Voice
  4. 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.

Porträt von Christian Ohle

Geschrieben von

Christian Ohle

Builder · Schmied der christianohle

Seit 2005 mit dem Web. Online-Marketing, Coding, lokale KI. Schreibt auf christianohle über Agents, MCP, lokale LLMs und Workflow-Automation — alles selbst getestet. Wöchentlicher Newsletter mit aktuellen News & Tutorials.