Diarization: Identifying Who Is Speaking
On this page
Course updated November 3, 2026 for Voxtral 3, based on the Mistral guide Migrate from Voxtral 2: Voxtral 2 models (
voxtral-mini-2602,voxtral-mini-transcribe-realtime-2602,voxtral-mini-tts-2603), deprecated on October 31, 2026 and retired on April 30, 2027, are replaced in examples byvoxtral-mini-transcribe-3,voxtral-mini-transcribe-realtime-3, andvoxtral-mini-tts-3-0. Key changes: timestamps are segment-only (wordreturns a 400 error), diarization is available in real time, real-time session settings are locked after the first audio chunk, andcontext_biasinputs are free-form (phrases with spaces). Sources: Migrate from Voxtral 2 · Speech to Text. Addition based on the Diarize speakers guide: labels start atspeaker_1(notspeaker_0), the field is namedspeaker_id, reference clips (speaker_references) let you name known speakers, and diarization works in real time, contrary to what the previous version stated. Sources: Diarize speakers · Speakers and diarization.
What is diarization
Diarization (speaker diarization) is the ability to identify and separate different speakers in an audio recording. The model automatically assigns an identifier to each distinct voice and segments the transcription accordingly.
The value gap is significant. A five-person meeting transcribed without diarization produces a block of text where no one knows who made which commitment; the same meeting, diarized, becomes an attributable record. That is why this feature is essential for any multi-speaker recording: meetings, interviews, phone calls, debates, and panel discussions.
Enable diarization
A single parameter is enough: diarize=True.
import os
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
with open("reunion_equipe.mp3", "rb") as f:
response = client.audio.transcriptions.complete(
model="voxtral-mini-transcribe-3",
file={"content": f, "file_name": "reunion_equipe.mp3"},
diarize=True,
timestamp_granularities=["segment"]
)
Diarization works hand in hand with segment-level timestamps: the segment carries the information. Each returned segment now includes a speaker_id field identifying the speaker; without segment-level timestamps, the request fails.
Use the results
The simplest display loops through segments and prefixes each utterance with its author and timestamp.
for seg in response.segments:
temps = f"{seg.start:.1f}s"
print(f"[{temps}] {seg.speaker_id} : {seg.text}")
The output looks like this:
[0.0s] speaker_1 : Bonjour à tous, on commence la réunion hebdomadaire.
[3.2s] speaker_1 : Julien, tu veux commencer avec le point technique ?
[5.8s] speaker_2 : Oui, alors cette semaine on a finalisé la migration.
[12.4s] speaker_2 : Tout est déployé en production depuis mercredi.
[15.1s] speaker_3 : Super, et côté performances, on a vu une amélioration ?
[18.3s] speaker_2 : Oui, le temps de réponse a baissé de trente pour cent.
Speakers are identified as speaker_1, speaker_2, and so on. These labels are valid only within a single transcription: speaker_1 in one file and speaker_1 in the next are not necessarily the same person. Without references, the model does not know names—you map these identifiers to real people, for example after listening to the first few seconds, using a simple dictionary.
# Map identifiers to real names
noms_locuteurs = {
"speaker_1": "Marie (Manager)",
"speaker_2": "Julien (Tech Lead)",
"speaker_3": "Sarah (Product)",
}
for seg in response.segments:
nom = noms_locuteurs.get(seg.speaker_id, seg.speaker_id)
minutes = int(seg.start // 60)
secondes = int(seg.start % 60)
print(f"[{minutes:02d}:{secondes:02d}] {nom}")
print(f" {seg.text}\n")
Once the mapping is in place, segment durations become metrics. Summing seg.end - seg.start per speaker gives you each person’s speaking time—a measure that meeting facilitators and call-center quality managers rely on.
from collections import defaultdict
temps_parole = defaultdict(float)
for seg in response.segments:
duree = seg.end - seg.start
temps_parole[seg.speaker_id] += duree
print("=== Temps de parole ===")
total = sum(temps_parole.values())
for locuteur, duree in sorted(temps_parole.items()):
nom = noms_locuteurs.get(locuteur, locuteur)
pourcentage = (duree / total) * 100
minutes = int(duree // 60)
secondes = int(duree % 60)
print(f"{nom} : {minutes}m{secondes:02d}s ({pourcentage:.1f}%)")
=== Temps de parole ===
Marie (Manager) : 8m23s (28.4%)
Julien (Tech Lead) : 12m45s (43.2%)
Sarah (Product) : 8m22s (28.4%)
Generate a structured report
Diarization combined with segments lets you produce a directly readable report. The trick in the following script is one line: it writes a new header only when the speaker changes, grouping consecutive turns by the same person instead of fragmenting them.
def generer_compte_rendu(segments, noms):
"""Generate a Markdown report from diarized segments."""
lignes = ["# Compte-rendu de réunion\n"]
locuteur_actuel = None
for seg in segments:
nom = noms.get(seg.speaker_id, seg.speaker_id)
if seg.speaker_id != locuteur_actuel:
locuteur_actuel = seg.speaker_id
minutes = int(seg.start // 60)
secondes = int(seg.start % 60)
lignes.append(f"\n**{nom}** ({minutes:02d}:{secondes:02d}) :")
lignes.append(f" {seg.text}")
return "\n".join(lignes)
compte_rendu = generer_compte_rendu(response.segments, noms_locuteurs)
with open("compte_rendu.md", "w", encoding="utf-8") as f:
f.write(compte_rendu)
print("Compte-rendu généré")
Go all the way to full analysis
Here is the script that brings everything together: diarized transcription, per-speaker statistics, and JSON output, ready to feed a dashboard or a summarization LLM.
import os
import json
from collections import defaultdict
from mistralai.client import Mistral
def analyser_reunion(chemin_audio: str, noms: dict = None):
"""Transcribe and analyze a full meeting."""
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
# Transcribe with diarization
with open(chemin_audio, "rb") as f:
response = client.audio.transcriptions.complete(
model="voxtral-mini-transcribe-3",
file={"content": f, "file_name": chemin_audio},
diarize=True,
timestamp_granularities=["segment"]
)
noms = noms or {}
# Statistics
stats = defaultdict(lambda: {"duree": 0.0, "segments": 0, "mots": 0})
for seg in response.segments:
stats[seg.speaker_id]["duree"] += seg.end - seg.start
stats[seg.speaker_id]["segments"] += 1
stats[seg.speaker_id]["mots"] += len(seg.text.split())
# Result
resultat = {
"transcription": response.text,
"nb_locuteurs": len(stats),
"duree_totale": max(s.end for s in response.segments),
"statistiques": {
noms.get(loc, loc): {
"duree_secondes": round(data["duree"], 1),
"nb_segments": data["segments"],
"nb_mots": data["mots"]
}
for loc, data in stats.items()
}
}
return resultat
# Usage
resultat = analyser_reunion("reunion.mp3", {
"speaker_1": "Marie",
"speaker_2": "Julien",
"speaker_3": "Sarah"
})
print(json.dumps(resultat["statistiques"], indent=2, ensure_ascii=False))
What diarization does not do
Keep a few limits in mind before promising a result to a client. Without reference clips, the model distinguishes voices, not people, and the mapping remains your responsibility. It does, however, detect the number of speakers on its own—you do not need to specify it. Two similar voices can be confused, which justifies reviewing critical passages. Simultaneous speech by two speakers is not supported in the current version, according to Mistral. Finally, diarization is also available in real time with voxtral-mini-transcribe-realtime-3 (diarize in session.update, labels carried by transcription.segment events), but a single session cannot combine diarization and turn detection.
Name speakers with reference clips
When you know in advance who is speaking—for example, the agent and the customer in a support call—you can send a short clip of each voice under a name: segments from that voice will then carry that name in speaker_id, and the same name will refer to the same person across transcriptions.
import base64
def en_base64(chemin):
with open(chemin, "rb") as f:
return base64.b64encode(f.read()).decode()
with open("appel_support.wav", "rb") as f:
response = client.audio.transcriptions.complete(
model="voxtral-mini-transcribe-3",
file={"content": f, "file_name": "appel_support.wav"},
diarize=True,
timestamp_granularities=["segment"],
speaker_references={
"conseiller": en_base64("ref_conseiller.wav"),
"client": en_base64("ref_client.wav"),
},
)
According to Mistral’s documentation, each clip should be 3 to 15 seconds long and contain at least 85% speech; you can provide up to 8, and the total number of speakers, referenced or detected, must not exceed 10. WAV, Opus, MP3, and FLAC are accepted, with lossless formats yielding the best results; the closer the recording conditions of the clip match those of the audio, the better the match. A speaker without a reference keeps a generic label. References require diarize=True, also work in real time (speaker_references in session.update), and a model that does not accept them returns an error.
Key takeaways
- Enable diarization with
diarize=True—a single parameter timestamp_granularities=["segment"]is required with diarization- Each segment includes a
speaker_idfield (speaker_1,speaker_2, etc.) - Map identifiers to real names, or name known voices with
speaker_references - Leverage statistics: speaking time, number of turns, word count
- Diarization works offline and in real time, but not with turn detection in the same session