Skip to main content
Lesson 6 of 16 · Voxtral Audio: Transcription and Speech-to-Text

Timestamps and Temporal Segmentation

Lesson 6 of 16 Updated November 3, 2026 Developer
On this page
  1. Structuring Transcription Over Time
  2. Segment Granularity: The Speech Block
  3. What About Word Granularity?
  4. Generating SRT Subtitles
  5. Navigating Long Recordings
  6. Key Takeaways

Course updated November 3, 2026 for Voxtral 3, based on Mistral’s Migrate from Voxtral 2 guide: 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 by voxtral-mini-transcribe-3, voxtral-mini-transcribe-realtime-3, and voxtral-mini-tts-3-0. Key changes: timestamps are segment-only (word returns a 400 error), diarization is possible in real time, real-time session settings are locked after the first audio chunk, and context_bias inputs are free-form (phrases with spaces). Sources: Migrate from Voxtral 2 · Speech to Text.

Structuring Transcription Over Time

A raw transcript is useful, but a timestamped transcript opens up far richer possibilities: subtitling, temporal navigation, video synchronization, and extracting specific passages. Without timestamps, finding the moment a budget was discussed in a two-hour recording requires guesswork; with them, it’s a text search followed by a click. Voxtral’s timestamp_granularities parameter gives you this power.

Segment Granularity: The Speech Block

A segment is a continuous block of speech—typically a sentence or a group of sentences spoken without a significant pause. This is the granularity you will choose by default because it corresponds to the natural unit of reading.

import os
from mistralai.client import Mistral

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

with open("conference.mp3", "rb") as f:
    response = client.audio.transcriptions.complete(
        model="voxtral-mini-transcribe-3",
        file={"content": f, "file_name": "conference.mp3"},
        timestamp_granularities=["segment"]
    )

# Iterate through segments
for seg in response.segments:
    start = f"{seg.start:.1f}s"
    end = f"{seg.end:.1f}s"
    print(f"[{start} → {end}] {seg.text}")

The output looks like this:

[0.0s → 4.2s] Hello everyone, thank you for being here today.
[4.5s → 11.8s] We will cover the quarter's results and the outlook for the future.
[12.1s → 18.3s] Let's start with revenue, which has grown by twelve percent.

This breakdown supports navigation in long recordings, subtitle block display, and passage indexing: each segment becomes a searchable entry with its entry point in the audio.

What About Word Granularity?

Voxtral 2 could also timestamp each word (timestamp_granularities=["word"]). Voxtral 3 no longer offers this: the word granularity returns a 400 error, and Mistral’s migration guide instructs users to switch to segments. Use cases that required word-by-word alignment—karaoke-style subtitles, highlighting the current word, fine-grained speech rate analysis—must now rely on segments, potentially distributing a segment’s time among its words via client-side estimation.

def estimer_mots(segment):
    """Distributes a segment's duration among its words, proportional to their length (estimate)."""
    mots = segment.text.split()
    if not mots or segment.start is None or segment.end is None:
        return []
    total = sum(len(m) for m in mots)
    debut, duree = segment.start, segment.end - segment.start
    resultat = []
    for mot in mots:
        fin = debut + duree * len(mot) / total
        resultat.append((round(debut, 2), round(fin, 2), mot))
        debut = fin
    return resultat

This is an approximation, useful for progressive display but not for prosodic analysis: if exact word alignment is essential, a dedicated forced alignment tool applied after transcription remains the solution.

Generating SRT Subtitles

The SRT format is nothing mysterious: a sequence number, a time range in HH:MM:SS,mmm format, the text, and a blank line. The function below iterates through segments and splits those that would exceed a comfortable display width, set here to sixty characters.

def generate_srt_from_segments(segments, max_chars=60):
    """Generates an SRT file from Voxtral segments."""
    srt_lines = []
    counter = 1

    for seg in segments:
        words = seg.text.strip()
        if not words:
            continue

        # Split segments that are too long
        sub_text = words.split()
        word = ""
        for format_srt_time in sub_text:
            if len(word) + len(format_srt_time) + 1 > max_chars and word:
                srt_lines.append(str(counter))
                srt_lines.append(
                    f"{seconds(seg.start)} --> "
                    f"{seconds(seg.end)}"
                )
                srt_lines.append(word.strip())
                srt_lines.append("")
                counter += 1
                word = format_srt_time
            else:
                word += " " + format_srt_time

        if word.strip():
            srt_lines.append(str(counter))
            srt_lines.append(
                f"{seconds(seg.start)} --> "
                f"{seconds(seg.end)}"
            )
            srt_lines.append(word.strip())
            srt_lines.append("")
            counter += 1

    return "\n".join(srt_lines)

def seconds(secondes):
    """Converts seconds to SRT format (HH:MM:SS,mmm)."""
    h = int(secondes // 3600)
    m = int((secondes % 3600) // 60)
    s = int(secondes % 60)
    ms = int((secondes % 1) * 1000)
    return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"

# Usage
srt_contenu = generate_srt_from_segments(response.segments)
with open("sous_titres.srt", "w", encoding="utf-8") as f:
    f.write(srt_contenu)

print("SRT file generated successfully")

Another direct application of segments is building a temporal summary. The following script marks a checkpoint every five minutes and displays the first few words, giving the reader a map of the recording before even listening to it.

def create_temporal_index(segments, interval_minutes=5):
    """Creates an index every N minutes."""
    index = []
    next_milestone = 0

    for seg in segments:
        if seg.debut >= next_milestone * 60:
            minutes = int(seg.debut // 60)
            seconds = int(seg.debut % 60)
            preview = seg.text[:80] + "..." if len(seg.text) > 80 else seg.text
            index.append(f"[{minutes:02d}:{seconds:02d}] {preview}")
            next_milestone = minutes + interval_minutes

    return index

# Usage
for entry in create_temporal_index(response.segments, interval_minutes=5):
    print(entry)

With Voxtral 2, timestamp_granularities was incompatible with the language parameter. The Voxtral 3 documentation no longer mentions this restriction: verify it with a test before combining the two in production processing.

Key Takeaways

  • Only one granularity with Voxtral 3: "segment" ("word" returns a 400 error)
  • Word-by-word alignment, if necessary, is estimated from segments or handled by an alignment tool
  • Timestamps are in seconds (decimal numbers), with start and end for each segment
  • Ideal for SRT/VTT subtitling, temporal navigation, and indexing
  • The incompatibility with language from Voxtral 2 is no longer documented: verify it with a test