Speaker diarization
Diarization answers "who spoke when." Speech Revolutions labels who spoke each segment and attaches a speaker to every word, so you can render a transcript as a back-and-forth conversation instead of one wall of text.
Where Zephyr leads
Diarization is a headline strength: on our public benchmark suite Zephyr ranks #1 on diarization error rate (DER) across every subset, well ahead of the field — and some providers can't diarize at all, including OpenAI's gpt-4o-transcribe (as of the model available on 2026-07-23; check OpenAI's current docs before relying on this). See the benchmarks page for the full provider-by-provider numbers and methodology.
Turn it on
speaker_labels is on by default. diarize is a Deepgram-compatible alias for the same flag, so code ported from Deepgram works unchanged.
# speaker_labels defaults to True; shown explicitly here.
result = client.transcribe("meeting.mp3", speaker_labels=True)
# diarize is a Deepgram-compatible alias for speaker_labels
result = client.transcribe("meeting.mp3", diarize=True)Speaker turns: .utterances
The most convenient view is result.utterances — an AssemblyAI-style list of contiguous speaker turns. Each utterance carries its speaker, its text, the start/end of the turn, and the words that make it up.
result = client.transcribe("meeting.mp3", speaker_labels=True)
for u in result.utterances:
print(f"[{u.start:.1f}s] Speaker {u.speaker}: {u.text}")
# [0.5s] Speaker SPEAKER_0: Hi, thanks for joining.
# [3.2s] Speaker SPEAKER_1: Happy to be here.Per-word speakers: .words
Every entry in result.words also carries a speaker alongside its timestamps. Utterances are simply runs of consecutive words with the same speaker, grouped for you — but the per-word labels are there when you need finer control (e.g. highlighting the current speaker word by word).
for w in result.words[:4]:
print(w.speaker, w.word, w.start, w.end)
# SPEAKER_0 Hi 0.50 0.68
# SPEAKER_0 thanks 0.68 0.99Render speaker turns
To display a conversation, iterate utterances and print a new block whenever the speaker changes. Since utterances are already grouped by turn, this is a direct loop — optionally mapping raw labels like SPEAKER_0 to friendly names.
NAMES = {"SPEAKER_0": "Host", "SPEAKER_1": "Guest"}
def format_ts(seconds: float) -> str:
m, s = divmod(int(seconds), 60)
return f"{m:02d}:{s:02d}"
for u in result.utterances:
name = NAMES.get(u.speaker, u.speaker)
print(f"{format_ts(u.start)} {name}\n {u.text}\n")Speaker label format
Speakers are stable string ids within a job (e.g. SPEAKER_0, SPEAKER_1). If you export a Deepgram-shaped response with result.to_deepgram(), those ids are mapped to integer speaker indices to match Deepgram's schema.
Migrating from Deepgram
Beyond the diarize alias, result.to_deepgram() returns a Deepgram pre-recorded response shape — including results.utterances with integer speaker indices — so existing parsing code keeps working while you migrate. See the migration playbook.
Related
Timestamps covers the per-word start/end that diarization builds on. The cookbook has a copy-pasteable speaker-labels recipe.