/ concept-collection / commonroom-recorder
Sign in
concept-collection / commonroom-recorder
commonroom-recorder / README.md
99 lines · 4.3 KBCodeBlameHistory
200df8aRecord commonroom audio and chat from the command lineJeremy Magland 1# commonroom-recorder
3Record a [commonroom](https://github.com/concept-collection/commonroom) call
4from the command line, for transcribing the meeting afterwards.
6The recorder joins a room as an ordinary, **visible** participant (default
7name "Recorder") that stays muted the whole time. It receives every other
8participant's audio and writes one WAV file per participant β€” so a transcript
9with speaker attribution needs no diarization β€” plus the room chat and a
10machine-readable event log. By default it also posts a one-line chat notice to
11each participant so everyone knows the meeting is being recorded.
13## Usage
aaf4452Distribute via npx from a GitHub Pages tarballJeremy Magland 15No install needed β€” run it straight from this repo's GitHub Pages tarball
16(nothing is published to npm):
18```
19npx https://concept-collection.github.io/commonroom-recorder/commonroom-recorder.tgz <room> [options]
20```
22(`npx github:concept-collection/commonroom-recorder` works too, but builds
23from source on first run.) Or from a clone:
26npm install
27npm run build
28node dist/cli.js <room> [options]
29```
31Options:
33```
34--name <name> Display name in the room (default: Recorder)
35--out <dir> Output directory (default: ./recordings/<room>-<timestamp>)
36--duration <sec> Stop automatically after this many seconds
37--notice <text> Chat line sent to each participant on connect
38 (default: "πŸ”΄ This meeting is being recorded.")
39--no-notice Don't send any recording notice
40```
42Stop with Ctrl-C. Requires Node >= 22 (built-in WebSocket). The WebRTC stack
43is [`@roamhq/wrtc`](https://github.com/WonderInventions/node-webrtc), which
44ships prebuilt binaries for Linux and macOS.
46## Output
48```
49<out>/
50 audio/<name>-<peer8>-segN.wav one file per participant per connection
51 (48 kHz mono s16 PCM, typically)
52 chat.txt human-readable chat + join/left log
53 events.jsonl every event with ISO timestamps: join, left,
54 chat, mute/unmute, segment start/end
55 manifest.json session summary: room, participants,
56 segments with start/end times and durations
57```
59Everything is written incrementally (`tail -f chat.txt` works live; the
60manifest is rewritten at every segment boundary and every 30 s), so a crash
61loses at most about a second of audio. A file only starts when a participant's
62first real audio arrives β€” someone who never unmutes produces no file. If a
63participant disconnects and returns, they get a new numbered segment; the
64manifest's per-segment start times let a transcript interleave speakers on one
65timeline. During a segment, silence is padded by wall clock, so a sample's
66position in the file always tracks elapsed time.
68To transcribe: run each `audio/*.wav` through your transcriber of choice
69(e.g. whisper), offset each result by its segment's `startedAt` from
70`manifest.json`, and merge.
72## How it works
74The p2p layer is commonroom's, ported to Node: the same nostr
75presence/signaling topics (knowing the room name IS the key), the same
76schnorr-signed events (with a fresh ephemeral keypair per run), the same
77deterministic-initiator WebRTC mesh and control data channel (hello, mute
78notices, chat, bye). To the browsers in the room the recorder is
79indistinguishable from a participant whose mic and camera are muted β€” it
80counts toward the room cap of 8 and appears in the participant list.
82Two deliberate deviations from the browser client:
84- **Receive-only media.** The video m-line is negotiated `sendonly` from the
85 recorder's side (a placeholder track that never produces a frame), so no
86 video is ever sent to the recorder β€” with up to 7 participants that saves
87 several Mbit/s and all the decode CPU. Audio is symmetric (a silent
88 placeholder goes out, like any muted mic).
89- **Files instead of tiles.** Each remote audio track feeds an `RTCAudioSink`
90 whose PCM goes straight to an incrementally-written WAV.
92## Testing
94`npm run test:loopback` runs an end-to-end test with no browser: it starts the
95recorder and a synthetic participant that "speaks" a 440 Hz sine and sends a
96chat message, then verifies the WAV really contains the tone and the chat made
97it to disk. It uses the real public nostr relays, so it needs network access.
98For a real-world test, run the recorder and join the same room at
99https://concept-collection.github.io/commonroom/ from a browser.
↑↓ move↡openescclose