Skip to content

Diarization

Optional speaker diarization ("who spoke when") with pyannote.audio.

assign_speakers(segments, turns)

Label each segment with the speaker whose turns overlap it most; leave it unchanged if none do.

Parameters:

Name Type Description Default
segments list[Segment]

Segments to label in place.

required
turns list[Turn]

Speaker turns from :func:diarize.

required
Source code in src/murmurvault/diarize.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def assign_speakers(segments: list[Segment], turns: list[Turn]) -> None:
    """Label each segment with the speaker whose turns overlap it most; leave it unchanged if none do.

    Args:
        segments: Segments to label in place.
        turns: Speaker turns from :func:`diarize`.
    """
    for seg in segments:
        overlap: dict[str, float] = {}
        for start, end, label in turns:
            ov = min(seg.end, end) - max(seg.start, start)
            if ov > 0:
                overlap[label] = overlap.get(label, 0.0) + ov
        if overlap:
            seg.speaker = max(overlap, key=overlap.__getitem__)

diarize(audio, cfg)

Find speaker turns.

Parameters:

Name Type Description Default
audio ndarray

16 kHz mono float32 samples.

required
cfg dict

The [diarization] config section.

required

Returns:

Type Description
list[Turn]

(start, end, label) turns, labelled speaker 1, speaker 2, ... in order of first appearance.

Source code in src/murmurvault/diarize.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def diarize(audio: np.ndarray, cfg: dict) -> list[Turn]:
    """Find speaker turns.

    Args:
        audio: 16 kHz mono float32 samples.
        cfg: The ``[diarization]`` config section.

    Returns:
        ``(start, end, label)`` turns, labelled ``speaker 1``, ``speaker 2``, ... in order of first appearance.
    """
    import torch

    out = _pipeline(cfg)({"waveform": torch.from_numpy(np.ascontiguousarray(audio))[None], "sample_rate": SR})
    annotation = getattr(out, "speaker_diarization", out)  # pyannote 4 wraps the annotation
    raw = [(float(t.start), float(t.end), str(label)) for t, _, label in annotation.itertracks(yield_label=True)]
    names: dict[str, str] = {}
    for _, _, label in sorted(raw):
        names.setdefault(label, f"speaker {len(names) + 1}")
    return [(s, e, names[label]) for s, e, label in raw]