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
- Uploads the recording and transcribes it with
diarization: trueandmode: "codemix", so English terms stay readable. - Merges consecutive segments from the same speaker into turns.
- 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# Meeting notes
_14:32 · hi-IN_
## Speakers
- **Speaker 0** — 8:10 talking
- **Speaker 1** — 5:47 talking
## Transcript
**Speaker 0** · 0:00
आज का agenda छोटा है, पहले release की बात करते हैं।Make it yours
- Pass
names={"speaker_0": "Asha"}toto_markdownonce 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))