catching actuators in the act
BenchCam is a local-first, bench-side capture + event-marking workflow for documenting hardware work. You start a session, log time-stamped markers as you work (e.g. "power on", "chip lifted", "fault"), optionally record video with an off-the-shelf webcam, and end up with a self-contained folder of plain files you can read with any text editor — plus an optional auto-edited review clip that timelapses the boring parts and slows down around each marker.
Status: v0, now exercised on real bench sessions. Two real
hello-motionsessions exist — a connection-debugging session and one where the actuator was recorded spinning on command (open-loop bring-up, narrated on camera). The capture path, the physical START/MARK/STOP/MUTE buttons + status LED, and the full post-capture pipeline (transcribe → auto-chapter → speech-aware review clip) have all run on real footage. Honest caveats: the physical controls are still a breadboard rig (no custom PCB yet), the actuator sessions were bring-up/debugging rather than a polished showcase, and raw capture still records to a microSD until the external SSD is deployed. Treat it as an early, working tool, not a finished product.
- It is a software workflow: it records a session, logs markers (events with a timestamp + label), and can auto-edit a review clip. Everything is kept in local files only — no cloud sync, no accounts.
- For video it drives existing, off-the-shelf tools: an
ffmpegsubprocess capturing a USB webcam, or OBS Studio over OBS WebSocket. BenchCam does not build, sell, or include any camera, capture card, or hardware — you supply a regular webcam and (optionally) OBS or ffmpeg. - It is not a camera system and not a robotics or motion-control system. It uses off-the-shelf cameras and compute (a USB webcam, a Raspberry Pi) and does not build a camera, capture card, image sensor, or high-speed video electronics. It does have physical controls — tactile START/MARK/STOP/MUTE buttons and a status LED wired to the Pi's GPIO (a breadboard rig today; no custom PCB yet) — but it stays strictly on the observation side: it records and marks hardware work and never drives actuators or any moving hardware (it may receive external marker events, but never commands hardware).
v0 ships with a NullRecorder (records no video) so you can use and test the
session + marker workflow immediately, with or without a camera. The
FfmpegRecorder records real, audio-synced video by driving an external
ffmpeg: on Linux / Raspberry Pi it captures the webcam over V4L2 and the mic
over ALSA into capture.mkv — this is the primary bench setup — and on
Windows it captures over DirectShow (see
Recording video with ffmpeg). The ObsRecorder
drives OBS Studio's recording on Windows (see
Recording video with OBS).
In the real bench setup the work is split across two machines:
- Capture appliance — Raspberry Pi 5 (Linux). The camera, ring light, and mic
live at the bench on the Pi. It records over V4L2/ALSA, exposes the physical
GPIO controls, and runs the dashboard and the button daemon as always-on
systemdservices (both survive reboot). Marking a session never needs the laptop. - Compute / edit / upload — Windows laptop. Whisper transcription, silero-VAD,
the
editrender, and the Claudelabel/autochapterAPI calls run here (on the GPU).benchcam fetch <id>pulls a session off the Pi overscp; only the small finishedreview.mp4is uploaded.
The heavy, torch-based extras ([transcribe], [vad]) and the [label] extra are
laptop-only — the Pi install stays stdlib-only so capture has no heavy deps.
Four tactile buttons and a status LED wire to the Pi's 40-pin header (3.3V logic
only): START, MARK, STOP, and MUTE, plus a status LED (through
a ≥220Ω series resistor) that shows mute state. A small Pi-local daemon
(gpio_buttons.py, intentionally not tracked in this repo) debounces the
presses and calls the same session/marker code the CLI uses — START begins a
recording, MARK drops a frame-accurate source=gpio marker, STOP finalizes, and
MUTE marks a span to be timelapsed out of the review. It runs as the always-on
benchcam-buttons service. This is a breadboard rig, validated in real
sessions — not a custom PCB.
- Python 3.11 or newer (developed and validated on Python 3.13). The core is pure standard library — no third-party packages are required to install or run the session/marker workflow or the dashboard.
- For video recording: a working
ffmpegbinary on yourPATH(only needed if you use--recorder ffmpeg; the defaultnullrecorder needs nothing extra). - For the OBS recorder: OBS Studio 28+ and the optional
benchcam[obs]extra (only needed if you use--recorder obs). See Recording video with OBS. - A regular USB webcam if you want video (BenchCam does not provide one).
The steps below set up the laptop (editing, transcription, upload). The
Raspberry Pi capture box uses the same editable install on Linux (pip install -e ., no heavy extras) — see
Recording headless on a Raspberry Pi
for the capture side.
Open PowerShell (or Command Prompt) in the project folder.
# 1. Create and activate a virtual environment
py -3.11 -m venv .venv
.\.venv\Scripts\Activate.ps1
# 2. Install BenchCam (editable)
pip install -e .If PowerShell blocks the activation script, allow it for the current user:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSignedYou can also run BenchCam without installing the console script:
python -m benchcam --helpA session is just four commands. Run them from the same folder so they share the
same sessions\ directory.
# Create a new session (uses the NullRecorder by default)
benchcam new --profile bench-a
# Start the session clock
benchcam run
# Log markers as you work (quote labels that contain spaces)
benchcam mark "power on"
benchcam mark "chip lifted"
benchcam mark "fault observed"
# Close the session
benchcam endBy default session folders are created under .\sessions\ in the current
directory. To put all session data (markers and collected videos) somewhere
else — for example an external SSD — set the BENCHCAM_SESSIONS_ROOT
environment variable. Every command honors it, so new sessions and the videos
collected into them land there with no code change and no per-command flag:
# Persist it for your user (new shells pick it up automatically):
setx BENCHCAM_SESSIONS_ROOT "E:\benchcam-sessions"
# ...or just for the current shell:
$env:BENCHCAM_SESSIONS_ROOT = "E:\benchcam-sessions"An explicit --sessions-root PATH on any command overrides the env var, which
overrides the .\sessions\ default. No drive letter is hardcoded — point it at
whatever drive you like (the external SSD is the intended home once it's
available).
new/run/mark/end each run as a separate process, which is fine for
occasional marks but slow when your hands are busy at the bench. benchcam live
opens a single long-running shell that holds the active session in memory and
marks on one keypress — no per-mark process startup, no re-reading
markers.csv.
# Create a session first (if you don't already have an active one)
benchcam new --profile bench-a
# Enter the live shell (starts the session if it hasn't started yet)
benchcam liveInside the shell:
| Key | Action |
|---|---|
Space / Enter |
Mark now, no label. Fast path — prints index + elapsed seconds instantly. |
l |
Mark now, then type a one-line label (empty label is allowed). |
n |
Append a line to notes.md (fills the gap where notes were write-once). |
s |
Show status: session id, elapsed time, marker count so far. |
q |
Quit cleanly: stop the recorder, end the session, print a summary. |
Notes:
liveattaches to the same active session as the other commands. If the session is stillcreated, it starts it (same effect asrun, includingrecorder.start). If it has alreadyended, it refuses with a clear message.- Every mark is still appended to
markers.csvimmediately for crash safety; only the next marker index is tracked in memory. - Markers made here use
source = manual, exactly likebenchcam mark.
This produces a folder like:
sessions\2026-06-17_05-43-00\
session.json
markers.csv
notes.md
{
"session_id": "2026-06-17_05-43-00",
"created_wall_time": "2026-06-17T05:43:00.123456+00:00",
"profile": "bench-a",
"camera": "",
"microphone": "",
"recorder": "null",
"storage_path": "sessions\\2026-06-17_05-43-00",
"notes": "",
"status": "ended",
"started_wall_time": "2026-06-17T05:43:10.000000+00:00",
"ended_wall_time": "2026-06-17T05:45:00.000000+00:00"
}marker_index,elapsed_seconds,wall_time,source,label,narration
1,2.500,2026-06-17T05:43:12.500000+00:00,gpio,Power Connected,so I've got power to the board now
2,15.250,2026-06-17T05:43:25.250000+00:00,manual,chip lifted,elapsed_secondsis measured from when the session started (benchcam run).sourcerecords where the marker came from:manual(benchcam markor the dashboard),gpio(a physical button press), orauto(proposed bybenchcam autochapter).gpio/manualare ground truth and win overautoon conflict.labelis the short on-screen chapter title.narrationis the raw transcribed speech near the marker (written bybenchcam transcribe), kept in its own column so transcription and labeling never clobber each other.
A free-form Markdown file for whatever you want to jot down during the session.
| Command | What it does |
|---|---|
benchcam new |
Create a new session folder and make it the active session. |
benchcam run |
Start recording / start the session clock. |
benchcam mark "label" |
Append a time-stamped marker to the active session. |
benchcam live |
Interactive shell that marks the active session on a single keypress. |
benchcam end |
Stop recording and close the active session. |
benchcam transcribe |
Fill each marker's narration from the audio with Whisper (laptop; [transcribe] extra). |
benchcam label |
Summarize each marker's narration into a terse technical label via the Claude API (laptop; [label] extra + $ANTHROPIC_API_KEY). |
benchcam autochapter |
Propose chapters from the full-session transcript and write them as source=auto markers; merges with real markers, which win (laptop; [transcribe]+[label]). |
benchcam chapters |
Review/re-title chapters without re-watching: print, or --edit/--apply an editable chapters.txt (updates only the label column). |
benchcam edit |
Render a marker-aware review.mp4 (timelapse + normal-speed windows + persistent chapter tags). Flags: --vad, --auto, --preview, --fetch. |
benchcam publish |
Export a paste-ready YouTube chapter block (remapped review timestamps) to youtube_chapters.txt. Read-only; never uploads. |
benchcam fetch |
Pull a recorded session off the Pi over scp to the laptop and open it (run on the laptop). |
benchcam dashboard |
Local web UI to run a whole session (start/mark/stop/review) from a browser. |
Useful options:
benchcam new --profile NAME --camera DESC --microphone DESC --recorder {null,obs,ffmpeg} --notes "..."benchcam mark "label" --source externalbenchcam edit --session ID --pre 3 --post 5 --speed 30 [--vad] [--auto] [--preview] [--fetch] --font PATH--sessions-root PATH(on any command) to use a different sessions directory.
Roughly, a full session flows: capture (Pi: run/mark/end or the GPIO
buttons) → fetch to the laptop → transcribe + label or autochapter
→ edit → optionally chapters to re-title → publish the chapter block.
benchcam edit --auto runs the transcribe → autochapter → render chain in one
command, skipping any step whose output already exists.
The "active" session is tracked by a small pointer file at
sessions\.active, so run, mark, and end know which session to use.
- NullRecorder (
null, default): records no video. Lets you exercise the session and marker workflow, and pairs well with capturing video manually in a separate app. - ObsRecorder (
obs): drives OBS Studio's recording over OBS WebSocket v5 (optionalbenchcam[obs]extra). See Recording video with OBS. - FfmpegRecorder (
ffmpeg): records one capture file per session by driving an externalffmpegbinary. Two paths are supported and selected by platform: Windows (DirectShow / dshow, video-onlycapture.mp4) and Linux (V4L2 + ALSA,capture.mkvwith audio — e.g. a Raspberry Pi capturing from a C920). See Recording video with ffmpeg.
See src/benchcam/recorders/ for the recorder code.
The ffmpeg recorder captures one file into the session folder (capture.mp4
on Windows, capture.mkv on Linux). It starts when the session starts, so the
video timecode lines up with each marker's elapsed_seconds. ffmpeg is run as an
external binary — BenchCam adds no Python ffmpeg dependency, so you must have
ffmpeg on your PATH (winget install Gyan.FFmpeg, or download from
ffmpeg.org and add it to PATH; on Debian/RPi
OS sudo apt install ffmpeg).
The DirectShow device name (for a Logitech C920S it is usually
HD Pro Webcam C920S) must be passed exactly. List the devices on your machine:
ffmpeg -list_devices true -f dshow -i dummyLook under "DirectShow video devices" for the quoted name of your webcam.
Pass the device name with --camera; it is stored in session.json and used as
the source of truth for the device. (You can also set the BENCHCAM_CAMERA
environment variable; an explicit --camera on the session wins.)
benchcam new --recorder ffmpeg --camera "HD Pro Webcam C920S" --profile bench-a
benchcam livebenchcam live (or benchcam run) launches ffmpeg in the background and returns
immediately; quitting live (or benchcam end) sends q to ffmpeg so the MP4
is finalized and playable, force-killing only if it does not exit in time. If
ffmpeg is missing from PATH, or no camera name is configured, the command
fails with a clear message instead of silently recording nothing.
Defaults are tuned for the C920S: 1080p30 over MJPEG, H.264 (libx264), video
only (no audio). An ffmpeg.log is written next to the video for troubleshooting.
On Linux the recorder captures over V4L2 and writes capture.mkv. The
C920 emits MJPEG natively, so the stream is copied (-c:v copy) — the Pi
never transcodes — and Matroska is used because MJPEG stream-copy is unreliable
in .mp4. ALSA audio (e.g. a Yeti Nano) is muxed into the same file.
The defaults match a confirmed C920 + Yeti Nano setup, so a plain session works out of the box:
benchcam new --recorder ffmpeg --profile bench-pi
benchcam run # captures /dev/video0 (mjpeg 1920x1080@30) + Yeti via ALSA
# ... benchcam mark "label" ...
benchcam end # sends 'q' so capture.mkv is finalizedEverything is configurable (nothing is hardcoded in the command builder):
| Setting | Default | Override |
|---|---|---|
| Video device | /dev/video0 |
--camera (stored in session.json) or BENCHCAM_CAMERA |
| Audio device | plughw:CARD=Nano,DEV=0 |
--microphone (stored in session.json) or BENCHCAM_MICROPHONE (set empty to disable audio) |
| Input format | mjpeg |
BENCHCAM_INPUT_FORMAT |
| Resolution | 1920x1080 |
BENCHCAM_VIDEO_SIZE (WxH) |
| Frame rate | 30 |
BENCHCAM_FRAMERATE |
Discover V4L2 devices with v4l2-ctl --list-devices and ALSA capture devices
with arecord -L. The underlying command is equivalent to the manually
validated:
ffmpeg -f v4l2 -input_format mjpeg -framerate 30 -video_size 1920x1080 \
-i /dev/video0 -f alsa -ac 2 -ar 44100 -i plughw:CARD=Nano,DEV=0 \
-c:v copy -c:a aac capture.mkvHarmless
Dequeued v4l2 buffer contains corrupted datawarnings at stream startup are expected; they are logged toffmpeg.logand are not fatal.
macOS note: the avfoundation input path is still a TODO stub in
build_ffmpeg_command.
The obs recorder lets BenchCam drive OBS Studio's recording over OBS
WebSocket v5. OBS stays your live dashboard (preview, framing, focus) while
BenchCam tells it when to start and stop. Because BenchCam triggers the start,
marker elapsed_seconds lines up with the OBS video timecode automatically.
OBS writes the video to its own configured recording folder (BenchCam can't
change where OBS records). So when the session ends, BenchCam collects that
video — it moves the file into the session folder as capture.<ext> (the
extension is whatever OBS wrote, .mkv or .mp4), so the video ends up right
next to markers.csv / session.json / notes.md and each session folder is
self-contained. The obs_recording.txt pointer and a notes.md line are updated
to the new in-folder path.
If the move can't happen (file still locked, drive missing, etc.), the session
still ends cleanly and the pointer keeps the original OBS path — collection
degrades to "pointer to the external file", never to lost footage. The move is
cross-drive safe (it falls back to copy-then-delete), so it works even when OBS
records on C: and your session root is on an external SSD.
Single-app camera constraint: with the OBS recorder, OBS owns the camera and provides the live preview. Do not also run the ffmpeg recorder against the same C920S — only one app can hold the camera at a time.
- Install OBS Studio 28 or newer (WebSocket v5 is built in — no plugin needed).
- In OBS: Tools → WebSocket Server Settings.
- Check Enable WebSocket server. Note the Server Port (default
4455). - Click Show Connect Info to see / copy the Server Password (auth is on by default).
The OBS client (obsws-python) is an optional dependency — the core BenchCam
install needs nothing third-party. Install the extra and pass the password via an
environment variable (never commit it):
pip install -e ".[obs]"
# Connection config (constructor arg > env vars > defaults). Set at least the
# password; host/port default to localhost/4455.
$env:BENCHCAM_OBS_PASSWORD = "<the password from Show Connect Info>"
# Optional overrides:
# $env:BENCHCAM_OBS_HOST = "localhost"
# $env:BENCHCAM_OBS_PORT = "4455"If obsws-python isn't installed, or OBS isn't running / reachable, the obs
recorder fails with a clear, actionable error instead of silently recording
nothing.
benchcam new --recorder obs --profile bench-a
benchcam liveEntering live (or benchcam run) connects to OBS and sends StartRecord;
quitting live (or benchcam end) sends StopRecord, captures the file path OBS
wrote, and disconnects. If OBS is already recording when you start, BenchCam
refuses (so you don't end up with a second, misaligned recording).
After the session, the OBS video is sessions\<id>\capture.mkv (or .mp4) next
to markers.csv, obs_recording.txt records its final path, and the marker
elapsed_seconds values map directly onto that recording's timecode.
benchcam edit turns a recorded session into a YouTube-ready "build log"
review.mp4 with no manual editing:
- The stretches between events are timelapsed (default
--speed 30for 30x). - Around each marker the clip drops to normal speed for a window — default
--pre 3seconds before and--post 5seconds after. Overlapping or adjacent windows merge into one normal-speed segment. - With
--vad, actual speech (detected by silero-VAD) also drives normal speed, so a timelapse never cuts you off mid-sentence and a chapter never flips before you start talking. A marker still forces a normal-speed window even when silent. - MUTE spans (from
mute_spans.csv, written by the MUTE button) are force-timelapsed — the mirror of a marker — even over speech, so asides you don't want in the video compress into the montage. - Audio is kept full-precision in the normal-speed windows (your narration) and dropped in the timelapsed stretches (no chipmunk audio).
- Each chapter's label renders as a persistent on-screen chapter tag (top-left, monospace) that stays from its marker until the next chapter begins — a running "where am I now" indicator, drawn across timelapse segments too, not just a brief caption.
- If the capture is unfinalized/headerless (a card-full or hard-killed
session with no duration header),
editdoes a one-time lossless remux to a sidecar and proceeds instead of refusing.
It reads the session's capture.* and markers.csv and writes review.mp4 into
the same session folder. It needs ffmpeg (and ffprobe, which ships with
ffmpeg) on your PATH — see Recording video with ffmpeg
for install instructions.
One-command pipeline. benchcam edit --auto orchestrates full-session
transcribe → autochapter → render in a single command, skipping any expensive
step whose output already exists (so re-rendering after a title tweak does zero
Whisper/API work). --preview writes a fast 720p review.preview.mp4 for
iterating on chapter titles without a full render, and --fetch pulls the session
from the Pi first. These compose with --vad/--speed.
# Edit the newest session with the defaults (3s pre / 5s post / 30x):
benchcam edit
# ...or a specific session, with custom pacing:
benchcam edit --session 2026-06-18_05-43-00 --pre 2 --post 6 --speed 12
# Custom caption font (optional):
benchcam edit --font "C:\Windows\Fonts\arial.ttf"Chapter tags are rendered by ffmpeg's drawtext. BenchCam escapes the text and
the font path for ffmpeg's filtergraph automatically (so Windows paths like
C:\Windows\Fonts\consola.ttf and labels containing :, ', ,, \, or %
render correctly), and passes the filtergraph via a temp filterscript. Tags
default to a monospace "engineering-terminal" font (Consolas on Windows,
DejaVu Sans Mono on Linux) for the aligned look; if the chosen --font isn't
found it falls back to a known system monospace font, and finally to ffmpeg's
default — the render never dies on a missing font. Pass --font to override.
benchcam edit first prints the segment plan (which stretches are timelapsed
vs. normal speed, and which captions land where) so you can sanity-check the
pacing before watching, then renders sessions\<id>\review.mp4. Re-running
overwrites review.mp4 cleanly; capture.* is never modified or deleted.
Notes:
- No markers → a straight
--speedtimelapse of the whole video (still a quick way to skim a session);editsays so. - Marker times past the end of the video are clamped to its length.
- The review is timelapsed, so a marker's raw
elapsed_secondsis not its timestamp inreview.mp4.editprints the raw→review time map in its segment plan, andbenchcam publishturns that map into a paste-ready YouTube chapter block — don't hand YouTube the raw marker times. - If
capture.*is missing (e.g. an OBS session where collection failed and onlyobs_recording.txtremains),editfollows that pointer to the original file; if it still can't be found, it fails with a clear message.
benchcam dashboard starts a small local web server (stdlib http.server,
bound to 127.0.0.1:8765, no accounts, no external exposure) and opens your
browser to a one-page UI. From there you can start a session, mark events (click
or labeled), add notes, stop, and render the review clip — without typing any
terminal commands.
benchcam dashboardThe page has a clear ● RECORDING / ○ IDLE indicator so you always know the
state, a big MARK button (plus a label field), a notes field, Stop, and a
Session library with per-session actions. It calls the same BenchCam logic as
the CLI — start = new + run, mark = mark, stop = recorder stop + collect +
end, review = edit. If OBS isn't running, starting an OBS session shows a
clear error rather than silently failing; a second start is refused; stopping
when nothing is active is a no-op with a message.
Naming sessions: the Start screen has an optional Session name field.
When set, the session folder becomes <timestamp>_<slug> (e.g.
2026-06-18_14-41-31_moteus-first-spin) — the timestamp prefix is kept for
sorting/uniqueness — and the friendly name is stored in session.json so the UI
shows it nicely. With no name, the folder is timestamp-only as before; older
sessions without a name fall back to showing their folder name.
Session library: a list of every session under the sessions root (newest
first) showing the name, date/time, marker count, length, and whether a
review.mp4 exists. Per row you can Open video (the capture in your default
player), Make review / Open review (render with the pre/post/speed
controls, or open the existing one), Open folder (in the file explorer), and
rename the session inline. Renaming an ended session renames the folder on
disk to <original-timestamp>_<new-slug> (the timestamp prefix is kept) and
updates session.json (and repoints obs_recording.txt/.active if needed), so
File Explorer reflects the new name. The currently recording session can't be
renamed — stop it first. The library just scans the session folders — there's no
separate database.
The dashboard's MARK button is a click convenience. For hands-busy marking,
benchcam livein a terminal (single keypress per marker) is still the fastest path — the dashboard does not replace it.
With the OBS recorder, OBS itself is your live camera preview next to the dashboard; BenchCam just drives OBS's record start/stop so markers stay aligned.
The recorder dropdown defaults to the sensible choice for the machine: ffmpeg on Linux (the Raspberry Pi capture box) and OBS on Windows — so you don't have to pick it from the dropdown every time. Every recorder is still selectable.
By default the dashboard binds to 127.0.0.1, so it's reachable only from the
machine it runs on. To open it from a phone on the same Wi-Fi (e.g. standing
at the bench by the Pi), bind it to the LAN — this is opt-in because the
dashboard has no authentication:
# On the Pi (headless): expose on the LAN, don't try to open a local browser.
benchcam dashboard --lan --no-browser
# Equivalent: benchcam dashboard --host 0.0.0.0
# Or set it once: export BENCHCAM_DASHBOARD_HOST=0.0.0.0On startup it prints the actual phone-reachable URLs (the Pi's LAN IP and its
http://<hostname>.local:8765/ mDNS name) — open one of those on your phone. It
never prints the bare 0.0.0.0 wildcard. Only enable LAN access on a network
you trust, since anyone on that network can reach the (unauthenticated) UI.
You don't need to set BENCHCAM_OBS_PASSWORD before launching. When the OBS
recorder is selected, the Start screen shows an OBS WebSocket password field
(and optional host/port). Enter the password from OBS's Tools → WebSocket
Server Settings → Show Connect Info once and start a session — BenchCam saves
it to a local, git-ignored file at .benchcam\config.json (under the project
folder), so you won't have to re-enter it on future launches (leave the field
blank to reuse the saved one). The password resolves in order:
- the field you typed on the Start screen,
- the saved
.benchcam\config.json, - the
BENCHCAM_OBS_PASSWORDenvironment variable,
and if none is available the start fails with the same clear "authentication
enabled but no password provided" error as the CLI. The .benchcam\ folder (and
.env) are git-ignored, so the password is never committed. Host/port work the
same way (defaulting to localhost:4455).
For a clean double-click that just opens the dashboard — no console window,
no flash — use scripts\benchcam-dashboard.vbs. It runs benchcam dashboard
with the venv's pythonw.exe (the windowless Python), so nothing visible appears
while the server runs in the background for your session.
Desktop shortcut (a):
- In File Explorer, open the project's
scripts\folder. - Right-click
benchcam-dashboard.vbs→ Show more options → Send to → Desktop (create shortcut). Double-clicking it now opens the dashboard with no console window.
Pin to the taskbar (b): A shortcut to a .vbs can't be pinned directly, but
a shortcut to wscript.exe can (it's an .exe). So point the shortcut at
wscript.exe and pass the script as an argument:
-
Right-click the desktop → New → Shortcut.
-
For the location, enter (use YOUR full path to the repo):
wscript.exe "C:\path\to\benchcam\scripts\benchcam-dashboard.vbs" -
Name it BenchCam, finish, then right-click it → Pin to taskbar (or Pin to Start). Because the target is
wscript.exe, pinning is allowed.
Custom icon (c): Right-click the shortcut → Properties → Change
Icon… → browse to your .ico → OK. The icon shows on the desktop shortcut
and on the pinned taskbar button.
Stopping it: because there's no window, stop the dashboard from Task
Manager → end the background pythonw.exe process running BenchCam (or just
leave it; it's a tiny local server). If a double-click seems to do nothing, the
dashboard is probably already running — open http://127.0.0.1:8765 in your
browser, or stop the existing pythonw.exe first.
The launcher assumes a
.venvin the project root with BenchCam installed (py -3 -m venv .venv→ activate →pip install -e ., pluspip install -e ".[obs]"if you use the OBS recorder).
scripts\benchcam-dashboard.bat (and the PowerShell scripts\benchcam-dashboard.ps1)
activate the venv and run benchcam dashboard too, but they keep a console
window open (closing it stops the dashboard, which is handy). To use it: open
scripts\, right-click benchcam-dashboard.bat → Send to → Desktop
(create shortcut), and optionally set the shortcut's Run: to Minimized.
Prefer the .vbs launcher above for a no-console, pinnable experience.
- (OBS recorder) Open OBS with your C920S as a source so you have a live preview, and make sure its WebSocket server is enabled (see Recording video with OBS).
- Double-click the desktop shortcut → the dashboard opens in your browser.
- Pick the recorder (OBS by default), optionally type a profile and a Session name, and — the first time — paste the OBS WebSocket password into the OBS WebSocket password field (it's saved locally afterward). Click Start session; the indicator turns red ● RECORDING.
- Work at the bench: click MARK now for quick markers, or type a label and click Mark + label; add free-form notes with the note field. Markers appear in the live list with their elapsed time.
- Click Stop session → the indicator returns to ○ IDLE and the session appears at the top of the Session library.
- In the library row, click Make review → BenchCam renders the marker-aware review clip into the session folder, opens it, and the row flips to Open review for next time. Open video plays the raw capture.
From the project folder, in PowerShell, with BenchCam installed:
benchcam new --profile quicktest
benchcam liveThen, inside the live shell:
- Press
Spacetwice — you should seemarker #1andmarker #2with elapsed seconds. - Press
l, typechip lifted, pressEnter— you should seemarker #3 ... chip lifted. - Press
n, typelooks good, pressEnter— confirms a note was appended. - Press
s— prints the session id, elapsed time, andmarkers 3. - Press
q— prints a summary and exits.
Finally, open the newest folder under sessions\ and confirm markers.csv has
three rows (two blank labels, one chip lifted) and notes.md ends with
looks good.
pip install -e ".[dev]"
pytest- BenchCam writes only to your local
sessions\directory. - Video/media files (each session's
capture.mp4/capture.mkv, whether written by ffmpeg or collected from OBS) and thesessions\directory are git-ignored and must not be committed. Video is large — keep recordings on your external SSD (setBENCHCAM_SESSIONS_ROOT), not in git.
src/benchcam/
cli.py argparse CLI (new/run/mark/live/end/edit/transcribe/
label/autochapter/chapters/publish/fetch/dashboard)
session.py session model + on-disk layout
markers.py markers.csv reading/writing (incl. the narration column)
live.py interactive single-keypress marking shell
keypress.py cross-platform single-key reader (msvcrt / termios)
transcribe.py Whisper narration -> the marker `narration` column ([transcribe])
label.py narration -> terse technical label via the Claude API ([label])
autochapter.py full-session transcript -> faithful source=auto chapters
chapters.py review / re-title chapters (chapters.txt) without re-watching
editor.py marker-aware auto-edit -> review.mp4 (timelapse, VAD, chapter tags)
publish.py export a YouTube chapter block from the review remap
dashboard.py local web UI (stdlib http.server) over the existing logic
config.py local gitignored config (.benchcam/config.json; OBS creds)
clock.py time helpers
recorders/
base.py Recorder interface
null.py NullRecorder (default)
obs.py ObsRecorder (OBS Studio via OBS WebSocket v5)
ffmpeg.py FfmpegRecorder (ffmpeg subprocess; Linux V4L2/ALSA + Windows dshow)
collect.py move an external recording into the session folder
scripts/
benchcam-dashboard.vbs no-console Windows launcher (recommended; pinnable)
benchcam-dashboard.bat Windows launcher (keeps a console window)
benchcam-dashboard.ps1 PowerShell launcher
reviewer.py laptop review hub (list / open / watch / make-review)
tests/ unit tests
MIT © 2026 Harrison Powe.