Free · Open source · MIT
Every stream, its own file,
on one cue.
Rheocles records every camera, microphone, display and window on your Mac at once — each to its own file, started on one cue and stamped with time-of-day timecode, so an editor syncs them in one click.
No compositor. The layout is a decision for the edit, not for the moment the light goes red. Arm what you might want, cue once, cut later.
Loopback only. An HTTP and WebSocket API, one bearer token, zero clicks to pair.
coming soonbrew install --cask artisan-build/tap/rheocles

Why it exists
OBS records one stream.
A compositor decides the layout before you record and hands you one file afterwards. That is fine for a live stream, where the layout is the show.
It is the wrong shape for a take you are going to cut. Pteroprompter wanted the camera, the prompter screen, the microphone and the system audio as four files, started together, with no compositor in the way — so the edit could decide what goes where, and a scene change on the day would not be a decision baked into the footage.
Rheocles is that. Arm what you want, cue once, and every armed stream lands as its own file with its own time-of-day timecode. The camera writes at native resolution and rate; the microphone writes Broadcast Wave; nothing is scaled, mixed or laid out on the way to disk.
The distinction
It is not a compositor.
| a compositor | Rheocles | |
|---|---|---|
| Gives you | one file | one file per stream |
| The layout is decided | before you record | in the edit |
| Files sync | by eye | by timecode |
| A crash leaves | a corrupt file | playable files and a truthful manifest |
If a composited stream is what you want, OBS is excellent and free, and you should use it. Rheocles is built around the moment after the take, not the moment during it.
Arming
Armed means live.
An armed stream has its capture session running — frames flowing, discarded — so that when the cue comes, the writer starts on frames that already exist rather than waiting hundreds of milliseconds for a device to spin up. That is the entire reason there is an arm button.
- arm / disarm
- Device live or not. Never stamps anything, never writes.
- join / leave
- While a take is active. Join starts that stream's writer now, adds its file to the manifest, and stamps it with the time it actually began. Leave finalises that file; the stream stays armed and the take continues for the others.
- the cue
starton a take joins every armed stream at once. A stream that joins four minutes in is stamped four minutes in, and an editor places it at four minutes.
Armed costs you
Armed streams use CPU and hold their devices while armed. That is the trade, and the menu bar icon makes armed state obvious so nobody leaves six cameras live overnight by accident.
Late joining is not a script
Late joining is for saving CPU on a heavy stream you already know will not need edit flexibility. It is not a way to script a take. Arm everything you might want, record, and cut in the edit; a script that toggles windows in and out of a take mid-read has misunderstood the tool.
Timecode
Every file carries its own time.
A time-of-day timecode track in every video file. A Broadcast Wave time reference in every audio file. Both stamped from the host clock at the stream's first written frame.
So the files sync in an editor without the manifest, and the manifest agrees with the files. Time-of-day rather than take-relative because it is the production standard: everything from one day shares a timeline, and two NTP-synced capture boxes produce files that line up — the remote scenario, solved as a side effect.
Drift is measured, not assumed. Frames written × frame duration against host elapsed, per stream, in status events and in the manifest. An unmeasured drift is reported as absent, never as zero.
Midnight. A stream that joins after midnight into a take that started before it gets a time-of-day reference near zero, and an editor syncing by timecode will place it before the take rather than after. Every camera and field recorder shares this; there is no clean fix inside the files. The manifest records absolute UTC and is unaffected, and is the authoritative source across midnight. Broadcast Wave files stop at 4 GB — many hours at 48 kHz, 24-bit, and said here so it is not a surprise.
The manifest
The manifest is the take.
manifest.json in the take folder, written at create and rewritten atomically — temp file, then rename — on every state change. At any instant it is either the previous complete version or the next. A dead process leaves playable files and a truthful manifest.
{ "take": { "id": "tk_7f3a", "name": "ep12", "state": "recording", "created": "2026-09-11T14:02:09Z", "started": "2026-09-11T14:02:17Z" }, "streams": [ { "id": "cam-1a2b", "kind": "camera", "name": "FaceTime HD Camera", "path": "ep12/cam-facetime.mov", "codec": "hevc", "started": "2026-09-11T14:02:17.004Z", "timecode": "14:02:17:00", "frames": 21540, "driftMs": -3 }, { "id": "win-9c0d", "kind": "window", "name": "Keynote", "path": "ep12/window-keynote.mov", "codec": "hevc", "started": "2026-09-11T14:06:17.021Z", "timecode": "14:06:17:00", "frames": 7200, "driftMs": null } ], "markers": [ { "t": 38.7, "label": "cold-open out" } ]}Everything downstream consumes the manifest; nothing guesses from a folder listing. Take IDs are the handle, paths are the answer, and every path is relative to the output root.
The API in one glance
Create, cue, stop.
POST /takes reserves every file path and writes the manifest before frame one — so a client knows where the files will live. start is the cue.
TOKEN="$(cat ~/Library/Application\ Support/Rheocles/token)"H="Authorization: Bearer $TOKEN"
curl -s -H "$H" -X POST localhost:7447/takes -d '{"name":"ep12"}'# → { "id": "tk_7f3a", "files": { "cam-1a2b": "ep12/cam-facetime.mov", … } }# paths reserved, manifest written. Nothing is recording.
T=localhost:7447/takes/tk_7f3acurl -s -H "$H" -X POST $T/start # the cuecurl -s -H "$H" -X POST $T/markers -d '{"label":"cold-open out"}'curl -s -H "$H" -X POST $T/stop// the same command set, both transports live at onceconst es = new EventSource('http://127.0.0.1:7447/events') // SSEconst ws = new WebSocket('ws://127.0.0.1:7448/') // full duplexA bearer token, always, on every transport. It is provisioned to a file — mode 0600 — so any app running as you is paired with zero clicks; anything that cannot read the file gets it as a pairing code in the popover. Loopback only in the MVP: the threat is a web page in your browser talking to localhost:7447, and a token is the defence.
The name
Known for the flow.
ῥέω
Rheō is "to flow". It comes down from the Proto-Indo-European root *srew-, which came through Germanic as the English word stream. So rheo- does not resemble "stream"; it is "stream", two thousand years upstream.
The -cles is kleos, renown — the same suffix as its sibling Sonocles. Known for the flow.
Rhea, the Titaness, is attached to rheō by Plato, and Plato was making a pun. We are borrowing it, not asserting it. Both halves are Greek this time, which Sonocles could not say, and will hear about.

Anyway
Here is a Titaness
with a rig.
Six of them, each in their own cradle, all started on the same word. The one on the end arrived four minutes late and has been placed, correctly, four minutes in.
