Aller au contenu principal

Timestamps et Segmentation Temporelle

Mis à jour le 29 juillet 2026

Structurer la transcription dans le temps

Une transcription brute est utile, mais une transcription horodatée ouvre des possibilités bien plus riches : sous-titrage, navigation temporelle, synchronisation avec la vidéo, extraction d’un passage précis. Sans timestamps, retrouver dans un enregistrement de deux heures le moment où le budget a été évoqué relève de l’écoute au jugé ; avec eux, c’est une recherche textuelle suivie d’un clic. Le paramètre timestamp_granularities de Voxtral vous donne ce pouvoir.

Granularité segment : le bloc de parole

Un segment est un bloc de parole continu — typiquement une phrase ou un groupe de phrases prononcées sans pause significative. C’est la granularité que vous choisirez par défaut, parce qu’elle correspond à l’unité de lecture naturelle.

import os
from mistralai 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-latest",
        file={"content": f, "file_name": "conference.mp3"},
        timestamp_granularities=["segment"]
    )

# Parcourir les segments
for seg in response.segments:
    debut = f"{seg.start:.1f}s"
    fin = f"{seg.end:.1f}s"
    print(f"[{debut}{fin}] {seg.text}")

La sortie ressemble à ceci :

[0.0s → 4.2s] Bonjour à tous, merci d'être présents aujourd'hui.
[4.5s → 11.8s] Nous allons aborder les résultats du trimestre et les perspectives pour la suite.
[12.1s → 18.3s] Commençons par le chiffre d'affaires qui a progressé de douze pour cent.

Ce découpage sert la navigation dans un enregistrement long, l’affichage de blocs de sous-titres et l’indexation par passage : chaque segment devient une entrée consultable, avec son point d’entrée dans l’audio.

Granularité mot : le calage fin

À la granularité "word", chaque mot est horodaté individuellement, avec son début et sa fin.

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

# Parcourir les mots
for mot in response.words:
    print(f"[{mot.start:.2f}s → {mot.end:.2f}s] {mot.text}")

La précision descend au centième de seconde :

[0.00s → 0.45s] Bonjour
[0.48s → 0.62s] à
[0.63s → 0.98s] tous

Ce niveau de détail est indispensable dès que le mot doit être synchronisé visuellement : sous-titrage précis façon karaoké, mise en surbrillance du mot en cours pendant la lecture, analyse prosodique du débit et des pauses. En contrepartie, la réponse est nettement plus volumineuse — un discours d’une heure représente plusieurs milliers d’entrées.

Rien ne vous oblige à choisir : les deux granularités peuvent être demandées dans le même appel, ce qui vous donne la structure de lecture et le calage fin en une seule requête.

response = client.audio.transcriptions.complete(
    model="voxtral-mini-latest",
    file={"content": f, "file_name": "audio.mp3"},
    timestamp_granularities=["segment", "word"]
)

# Segments disponibles
print(f"Nombre de segments : {len(response.segments)}")

# Mots disponibles
print(f"Nombre de mots : {len(response.words)}")

Générer des sous-titres SRT

Le format SRT n’a rien de mystérieux : un numéro d’ordre, une plage horaire au format HH:MM:SS,mmm, le texte, une ligne vide. La fonction ci-dessous parcourt les segments et découpe ceux qui dépasseraient la largeur d’affichage confortable, fixée ici à soixante caractères.

def generer_srt_depuis_segments(segments, max_chars=60):
    """Génère un fichier SRT à partir des segments Voxtral."""
    lignes_srt = []
    compteur = 1

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

        # Découper les segments trop longs
        mots = texte.split()
        sous_texte = ""
        for mot in mots:
            if len(sous_texte) + len(mot) + 1 > max_chars and sous_texte:
                lignes_srt.append(str(compteur))
                lignes_srt.append(
                    f"{formater_temps_srt(seg.start)} --> "
                    f"{formater_temps_srt(seg.end)}"
                )
                lignes_srt.append(sous_texte.strip())
                lignes_srt.append("")
                compteur += 1
                sous_texte = mot
            else:
                sous_texte += " " + mot

        if sous_texte.strip():
            lignes_srt.append(str(compteur))
            lignes_srt.append(
                f"{formater_temps_srt(seg.start)} --> "
                f"{formater_temps_srt(seg.end)}"
            )
            lignes_srt.append(sous_texte.strip())
            lignes_srt.append("")
            compteur += 1

    return "\n".join(lignes_srt)


def formater_temps_srt(secondes):
    """Convertit des secondes en format SRT (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}"


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

print("Fichier SRT généré avec succès")

Autre exploitation directe des segments : construire un sommaire temporel. Le script suivant relève un repère toutes les cinq minutes et en affiche les premiers mots, ce qui donne au lecteur une carte de l’enregistrement avant même de l’écouter.

def creer_index_temporel(segments, intervalle_minutes=5):
    """Crée un index toutes les N minutes."""
    index = []
    prochain_jalon = 0

    for seg in segments:
        if seg.start >= prochain_jalon * 60:
            minutes = int(seg.start // 60)
            secondes = int(seg.start % 60)
            apercu = seg.text[:80] + "..." if len(seg.text) > 80 else seg.text
            index.append(f"[{minutes:02d}:{secondes:02d}] {apercu}")
            prochain_jalon = minutes + intervalle_minutes

    return index

# Utilisation
for entree in creer_index_temporel(response.segments, intervalle_minutes=5):
    print(entree)

Une contrainte vient toutefois limiter vos combinaisons : le paramètre timestamp_granularities est incompatible avec le paramètre language. Si vous avez besoin de forcer la langue et d’obtenir des timestamps, il vous faudra deux appels séparés, ou accepter la détection automatique — qui fonctionne très bien pour le français et vous fera gagner une requête.

Points clés à retenir

  • Deux granularités : "segment" (phrases/blocs) et "word" (mot par mot)
  • Les deux peuvent être combinées dans un même appel
  • Les timestamps sont en secondes (float) avec une précision au centième
  • Idéal pour le sous-titrage SRT/VTT, la navigation temporelle, l’indexation
  • Incompatible avec le paramètre language — utilisez la détection automatique