zoom-transcript-to-markdown

Convert the transcript panel of a Zoom cloud-recording share page into clean Markdown: one block per speaker turn, any chat messages shown alongside the recording, how long the recording runs, and a check for missing transcript rows.

Zoom lets you play a shared recording but often not download its transcript. The share page does render the whole transcript into the DOM (unlike Teams, the list is not virtualised), so saving that one element's HTML is enough.

Step 1: capture the panel HTML

Open the recording's share page with the transcript panel visible, and save the panel's outer HTML to a file, e.g. with the copy-page-html userscript or DevTools (Copy โ†’ Copy outerHTML). The element to pick is the one holding both the Audio Transcript list and, if present, the Chat Messages list: on the 2025 share page that is section > div > div:nth-of-type(3) > div:nth-of-type(2). Anything that contains the transcript-list-item rows works; the rest of the page is ignored.

Step 2: convert

zoom-transcript-to-markdown.py is a single-file uv script with no third-party dependencies:

uv run zoom-transcript-to-markdown.py recording.html [more.html ...] [-o OUTDIR] [--gap SECONDS]

Each X.html becomes X.md, next to it or in OUTDIR. A one-line summary per file goes to the terminal.

What it reads

Every transcript row is an <li class="transcript-list-item" id="transcript-list-item-N"> whose aria-label is Speaker, 0 hours 9 minutes 49 seconds, text. Zoom only renders the visible speaker name and timestamp when the speaker changes, so the aria-label is the source for every row, and the visible header is used to cross-check it. Chat messages are chat-list-item elements with the same aria-label shape.

Length and completeness

The dump contains no recording duration, so the reported length is the offset of the last transcript row: a lower bound, usually within seconds of the end. The span from first to last row is reported too; a late first row usually means the recording started before anyone spoke.

The completeness check flags, and the script exits 1 on:

Silences longer than --gap seconds (default 60) are listed but do not fail the run: with all ids present they are genuine silence or speech Zoom did not transcribe.

Contents

๐Ÿ“„README.md2.8 KB
๐Ÿ“„zoom-transcript-to-markdown.py7.7 KB