Recipes / Meeting notesSign inGet an API key

Meeting notes

Turn a recorded meeting into speaker-labelled notes with talk time per person — in the language the meeting was held in.

What it does

  1. Uploads the recording and transcribes it with diarization: true and mode: "codemix", so English terms stay readable.
  2. Merges consecutive segments from the same speaker into turns.
  3. Writes Markdown: speakers with their talk time, then the transcript with a timestamp per turn.
python meeting_notes.py standup.m4a hi-IN > notes.md

Make it yours

  • Pass names={"speaker_0": "Asha"} to to_markdown once you know who is who.
  • Send the transcript to your own summariser to add action items; the speaker turns give it clean structure.
  • For recordings in several languages, transcribe with the main language — code-mixed mode keeps English words intact.

The script

meeting_notes.py
"""Meeting notes: a speaker-labelled, timestamped transcript of a recording, written as Markdown.

    python meeting_notes.py meeting.m4a hi-IN > notes.md

Needs TWENTYSIX_API_KEY and TWENTYSIX_BASE_URL. Consecutive segments from the same speaker are merged into turns.
"""
import sys

from twentysix import Client


def clock(s):
    s = int(s)
    return f"{s // 3600:d}:{s // 60 % 60:02d}:{s % 60:02d}" if s >= 3600 else f"{s // 60:d}:{s % 60:02d}"


def turns(segments, gap_s=2.0):
    """Merge consecutive segments by the same speaker (unless separated by a long pause)."""
    out = []
    for s in segments:
        who = s.get("speaker", "speaker_0")
        if out and out[-1]["speaker"] == who and s.get("start", 0) - out[-1]["end"] <= gap_s:
            out[-1]["text"] += " " + s["text"]
            out[-1]["end"] = s.get("end", out[-1]["end"])
        else:
            out.append({"speaker": who, "start": s.get("start", 0.0), "end": s.get("end", 0.0), "text": s["text"]})
    return out


def to_markdown(result, names=None):
    names = names or {}
    lines = [f"# Meeting notes", "", f"_{clock(result['duration_seconds'])} · {', '.join(result['languages'])}_", ""]
    speakers = sorted({t["speaker"] for t in turns(result["segments"])})
    talk = {sp: sum(t["end"] - t["start"] for t in turns(result["segments"]) if t["speaker"] == sp) for sp in speakers}
    lines += ["## Speakers", ""] + [f"- **{names.get(sp, sp.replace('speaker_', 'Speaker '))}** — {clock(talk[sp])} talking"
                                     for sp in speakers] + ["", "## Transcript", ""]
    for t in turns(result["segments"]):
        lines.append(f"**{names.get(t['speaker'], t['speaker'].replace('speaker_', 'Speaker '))}** · {clock(t['start'])}  ")
        lines.append(t["text"].strip())
        lines.append("")
    if result.get("gaps"):
        lines += ["> Some stretches could not be transcribed: " +
                  ", ".join(f"{clock(g['start'])}–{clock(g['end'])}" for g in result["gaps"])]
    return "\n".join(lines)


if __name__ == "__main__":
    path, language = sys.argv[1], (sys.argv[2] if len(sys.argv) > 2 else "hi-IN")
    with Client() as client:
        result = client.transcribe_file(path, language, mode="codemix", diarization=True)
    print(to_markdown(result))
Scriba is a product of 26AI Private Limited, Pune, India.© 2026 26AI Private Limited