CHRISTIAN OHLE
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.

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

Meine Einschätzung

Ich sage es ehrlich: ElevenLabs ist teuer, und ich habe mehrfach versucht, auf günstigere Alternativen umzusteigen. Open-Source-TTS wie Coqui-XTTS klingt auf Deutsch leider immer noch nach Roboter, und OpenAI TTS hat bei deutschen Umlauten und Fachbegriffen regelmäßig Aussetzer. Bis sich das ändert, bleibt ElevenLabs für mich der pragmatische Standard. Was mich bei Whisper lokal überrascht hat: Die DSGVO-Argumentation war mein ursprünglicher Grund, aber der eigentliche Gewinn ist die Unabhängigkeit von API-Limits und Latenz. In meiner Praxis rate ich jedem, der eine Voice-Pipeline baut, zum lokalen Whisper-Setup — die einmalige Investition in GPU-Hardware rechnet sich innerhalb von drei Monaten.

Quellen

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.