bundles: meeting recorder with local transcription - #336
Merged
Conversation
added 3 commits
September 9, 2026 09:43
Records a meeting in the browser (shared tab audio plus microphone, mixed in WebAudio, uploaded every 15 s) and transcribes it on the same host with a local OpenAI-compatible endpoint, defaulting to the faster-whisper-server bundle on loopback. No cloud call, no API key, no port of its own: the page and its upload endpoints ride the gateway through panel + panelRoutes, so recordings inherit the dashboard session. - panel/meeting-recorder.js: capture UI, level meter and a silence warning so a silent recording surfaces in the first 20 seconds, notes box, recent recordings table, and an insecure-context warning (tab audio needs HTTPS) - panel/routes.js: session/chunk/finish/upload/status/sessions, auth scoped under one path prefix, bodies streamed to disk rather than buffered - server/transcribe.js: ten-minute slices with timestamps offset back into meeting time; a detached worker, so a long meeting survives a closed page - server/store.js: session directories, id validation, listing - also transcribes a recording made some other way (call-app recording, phone voice memo) through the same path Measured about 3.5x real time with large-v3 int8 on CPU.
The panel registry copies panel/*.js into <crow-home>/panels/, which breaks bundle-relative imports: a static "../server/store.js" resolves to <crow-home>/server/store.js and the router never loads. Both files now try the installed bundle path first (honouring CROW_HOME, so alternate instances find their own copy) and fall back to the repo layout, the same pattern knowledge-base-routes.js uses. Verified end to end in both layouts against a real faster-whisper server: chunked record, finish, detached worker, transcript written, panel rendered with the session table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Records a meeting in the browser and transcribes it on the same host. Built because a meeting worth referencing later should not have to pass through a cloud transcription service to become text.
What it does
The panel asks the browser for two audio sources, the meeting itself (a shared tab or window, which is the only way a page is allowed to hear another tab) and the microphone, mixes them in WebAudio, and uploads Opus every 15 seconds. A crashed tab or a closed laptop costs at most those 15 seconds. On stop, a detached worker converts the audio to 16 kHz mono, cuts it into ten-minute slices, sends each to a local OpenAI-compatible transcription endpoint, and writes a timestamped markdown transcript with any notes taken while listening.
It also takes a recording made some other way: a call app's own local recording, a phone voice memo, an old meeting. Same path from upload onward.
Shape
panel+panelRoutes, so recordings inherit the dashboard session rather than needing their own auth. No entry in the port registry.application/octet-stream, which the gateway's global 1 MB JSON parser ignores, so a long upload never passes through memory.router.use("/dashboard/meeting-recorder-api", authMiddleware)), per the mount-time check on unpathed router middleware.Requires
ffmpeg on the host, and a transcription endpoint. The
faster-whisper-serverbundle is the intended pairing and is the default (http://localhost:8004, CPU, int8, loopback). Browsers only hand over tab audio in a secure context, so the dashboard has to be reached over HTTPS or localhost; the panel says so on screen when it is not, rather than letting someone record silence.Tested
Routes mounted on a bare Express app with a stub auth middleware, against a real faster-whisper server:
.m4atook the same path to the same outputThroughput measured at about 3.5x real time with large-v3 int8 on CPU, so a ninety-minute meeting finishes roughly twenty-five minutes after it ends.
Deliberately not included
Speaker labels (diarization is a second model), a live transcript (transcription starts on stop), and any automatic summary. Every transcript carries a line saying it is a machine transcript whose names get misheard, because confident-looking text invites quotation.