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
40f6397Document npx URL caching; link versioned tarball on the landing pageJeremy Magland 23from source on first run.) Note that npx caches the download per URL β after
24a new release, use the versioned tarball listed at
25[the landing page](https://concept-collection.github.io/commonroom-recorder/)
26or `rm -rf ~/.npm/_npx`. Or from a clone:
aaf4452Distribute via npx from a GitHub Pages tarballJeremy Magland 27
29npm install
30npm run build
31node dist/cli.js <room> [options]
32```
34Options:
36```
37--name <name> Display name in the room (default: Recorder)
38--out <dir> Output directory (default: ./recordings/<room>-<timestamp>)
39--duration <sec> Stop automatically after this many seconds
40--notice <text> Chat line sent to each participant on connect
41 (default: "π΄ This meeting is being recorded.")
42--no-notice Don't send any recording notice
43```
45Stop with Ctrl-C. Requires Node >= 22 (built-in WebSocket). The WebRTC stack
46is [`@roamhq/wrtc`](https://github.com/WonderInventions/node-webrtc), which
47ships prebuilt binaries for Linux and macOS.
cbaa294Add transcribe subcommand: merged speaker-attributed transcriptJeremy Magland 49## Transcribing
51```
52npx https://concept-collection.github.io/commonroom-recorder/commonroom-recorder.tgz transcribe <recording-dir>
53```
55produces `transcript.md` β a merged, speaker-attributed transcript of the
56meeting with the chat and join/left events interleaved on one timeline:
58```
59**[10:00:02] Alice:** So the agenda today...
61> [10:00:15] π¬ **Alice:** here's the doc link
63*[10:00:19] β Bob joined*
65**[10:00:20] Bob:** Sorry I'm late...
66```
68(plus `transcript.json` with the same items, structured). Speech recognition
69runs locally through whichever engine is found (`--engine` to force one):
70[faster-whisper](https://github.com/SYSTRAN/faster-whisper)
71(`pip install faster-whisper`), whisper.cpp's `whisper-cli` (pass the ggml
72model file via `--model`), or the
73[openai-whisper](https://github.com/openai/whisper) CLI. `--model` defaults to
74`small`; `--language` forces a language instead of auto-detecting. Raw
75per-file ASR output is cached in `<dir>/asr/`, so re-running is instant β
76use `--force` to re-transcribe.
80```
81<out>/
82 audio/<name>-<peer8>-segN.wav one file per participant per connection
83 (48 kHz mono s16 PCM, typically)
84 chat.txt human-readable chat + join/left log
85 events.jsonl every event with ISO timestamps: join, left,
86 chat, mute/unmute, segment start/end
87 manifest.json session summary: room, participants,
88 segments with start/end times and durations
89```
91Everything is written incrementally (`tail -f chat.txt` works live; the
92manifest is rewritten at every segment boundary and every 30 s), so a crash
93loses at most about a second of audio. A file only starts when a participant's
94first real audio arrives β someone who never unmutes produces no file. If a
95participant disconnects and returns, they get a new numbered segment; the
96manifest's per-segment start times let a transcript interleave speakers on one
97timeline. During a segment, silence is padded by wall clock, so a sample's
98position in the file always tracks elapsed time.
cbaa294Add transcribe subcommand: merged speaker-attributed transcriptJeremy Magland 100The `transcribe` subcommand consumes exactly these files; because each WAV is
101silence-padded to track wall-clock time, an ASR timestamp within a segment
102plus the segment's `startedAt` is already the meeting timeline.
104## How it works
106The p2p layer is commonroom's, ported to Node: the same nostr
107presence/signaling topics (knowing the room name IS the key), the same
108schnorr-signed events (with a fresh ephemeral keypair per run), the same
109deterministic-initiator WebRTC mesh and control data channel (hello, mute
110notices, chat, bye). To the browsers in the room the recorder is
111indistinguishable from a participant whose mic and camera are muted β it
112counts toward the room cap of 8 and appears in the participant list.
114Two deliberate deviations from the browser client:
116- **Receive-only media.** The video m-line is negotiated `sendonly` from the
117 recorder's side (a placeholder track that never produces a frame), so no
118 video is ever sent to the recorder β with up to 7 participants that saves
119 several Mbit/s and all the decode CPU. Audio is symmetric (a silent
120 placeholder goes out, like any muted mic).
121- **Files instead of tiles.** Each remote audio track feeds an `RTCAudioSink`
122 whose PCM goes straight to an incrementally-written WAV.
124## Testing
126`npm run test:loopback` runs an end-to-end test with no browser: it starts the
127recorder and a synthetic participant that "speaks" a 440 Hz sine and sends a
128chat message, then verifies the WAV really contains the tone and the chat made
129it to disk. It uses the real public nostr relays, so it needs network access.
130For a real-world test, run the recorder and join the same room at
131https://concept-collection.github.io/commonroom/ from a browser.