Infrastructure for capturing paired screencast and keyboard/mouse input data.
crowd-cast is a single-binary agent that embeds libobs for screen capture and recording, eliminating the need to install OBS Studio separately.
Key components:
- Embedded libobs - Screen/window capture with hardware encoding (via libobs-rs)
- Sync Engine - Coordinates recording with input capture, filters by frontmost app
- Input Capture - Cross-platform keyboard/mouse capture (rdev/evdev)
- System Tray - Control recording from the menu bar
┌─────────────────────────────────────────────────────────────────┐
│ crowd-cast Agent (Rust) │
│ ┌──────────┐ ┌────────────────┐ ┌─────────────────────────┐ │
│ │ Tray UI │ │ Embedded libobs│ │ Sync Engine │ │
│ │ │ │ (libobs-rs) │ │ - Frontmost app detect │ │
│ └──────────┘ │ │ │ - Input filtering │ │
│ │ ┌───────────┐ │ │ - Event buffering │ │
│ │ │mac-capture│ │ └───────────┬─────────────┘ │
│ │ │obs-x264 │ │ │ │
│ │ │obs-ffmpeg │ │ ┌─────┴─────┐ │
│ │ └───────────┘ │ │ rdev/evdev│ │
│ └────────────────┘ └───────────┘ │
│ │ │ │
│ Video Output Input Events │
│ │ │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ Uploader │ │
│ └─────┬─────┘ │
└────────────────────────────────────┼────────────────────────────┘
│
▼
┌─────────────┐
│ Lambda + S3 │
└─────────────┘
- Single binary: No external OBS installation required (libobs is embedded)
- Privacy-aware capture: Only records when selected applications are in the foreground
- Automatic updates: Sparkle framework keeps the app up to date in the background
- Idle detection: Automatically pauses recording when you step away, resumes on return
- Hardware acceleration: Uses native encoding (VideoToolbox on macOS)
- Efficient uploads: Streaming uploads via pre-signed S3 URLs with retry/backoff
- Easy setup: Wizard handles permissions and application selection
Download CrowdCast.dmg from the latest release, open it and follow the instructions in the wizard.
# Clone the repository
git clone https://github.com/p-doom/crowd-cast.git
cd crowd-cast
# Build (endpoint required at build time)
CROWD_CAST_API_GATEWAY_URL="https://your-api-gateway.execute-api.region.amazonaws.com/prod/presign" \
cargo build --release
# Run the setup wizard
./target/release/crowd-cast-agent --setupOn macOS, build.rs automatically installs OBS binaries via cargo-obs-build during
cargo build. Set CROWD_CAST_SKIP_OBS_INSTALL=1 to skip this behavior.
- Grant Accessibility permission to the agent (System Settings → Privacy & Security → Accessibility)
First-time setup on a release machine:
scripts/setup-macos-signing.sh \
--p12 /path/to/developer-id.p12For a full release (build, sign, notarize, publish to GitHub Releases + upload appcast to S3):
scripts/build-and-publish-macos.sh \
--github-repo p-doom/crowd-cast \
--s3-bucket crowd-cast-bucket \
--identity "Developer ID Application: Your Name (TEAMID)" \
--notarize \
--version 1.0.0 \
--build-number 1055 \
--sparkle-public-ed-key "YOUR_PUBLIC_KEY" \
--sparkle-private-ed-key-file /path/to/private-key.txtAuto-updates are delivered via Sparkle using an appcast hosted on S3.
Support coming soon...
Most settings are managed through the setup wizard and the tray menu. The configuration file is at:
- macOS:
~/Library/Application Support/dev.crowd-cast.agent/config.toml
Key settings:
[capture]
target_apps = ["org.mozilla.firefox", "com.apple.Terminal"]
capture_all = false
idle_timeout_secs = 120 # Pause after 2 min of inactivity
single_active_app_capture = true # One app captured at a time (multi-scene)
[recording]
autostart_on_launch = true
notify_on_start_stop = true
segment_duration_secs = 300 # 5-minute recording segments
[upload]
delete_after_upload = trueUpload endpoint is set at build time via CROWD_CAST_API_GATEWAY_URL.
Input logs are stored in MessagePack format. Each file contains an array of [timestamp_us, [event_type, event_data]] tuples:
[0, ["ContextChanged", ["com.apple.Terminal"]]]
[1234000, ["KeyPress", [0, "KeyA"]]]
[1334000, ["KeyRelease", [0, "KeyA"]]]
[1500000, ["MouseMove", [12.5, -3.2]]]
[2000000, ["MousePress", ["Left", 540.0, 320.0]]]
[2100000, ["MouseRelease", ["Left", 540.0, 320.0]]]
[2500000, ["MouseScroll", [0.0, -3.0, 540.0, 320.0]]]
[3999000, ["ContextChanged", ["UNCAPTURED"]]]
Event types:
ContextChanged: app switch (bundle ID orUNCAPTUREDfor untracked apps)KeyPress/KeyRelease:[key_code, key_name]MouseMove:[delta_x, delta_y]MousePress/MouseRelease:[button, x, y]MouseScroll:[delta_x, delta_y, x, y]
Timestamps are microseconds relative to the segment start. Video and input files share the same session/segment IDs for alignment.
Overlay keylogs on top of a screen capture:
python scripts/overlay_keylogs.py --video capture.mp4 --input input.msgpack --output capture_with_keys.mp4To just generate subtitles (ASS):
python scripts/overlay_keylogs.py --input input.msgpack --ass-out keylogs.assThe agent expects a Lambda endpoint that returns pre-signed S3 URLs. Example Lambda handler:
import boto3
import json
s3 = boto3.client('s3')
BUCKET = 'your-bucket'
def handler(event, context):
body = json.loads(event['body'])
file_name = body['fileName']
version = body['version']
user_id = body['userId']
key = f"uploads/{version}/{user_id}/{file_name}"
content_type = (
"application/msgpack" if file_name.endswith(".msgpack") else "video/mp4"
)
upload_url = s3.generate_presigned_url(
'put_object',
Params={'Bucket': BUCKET, 'Key': key, 'ContentType': content_type},
ExpiresIn=3600
)
return {
'statusCode': 200,
'body': json.dumps({
'uploadUrl': upload_url,
'key': key,
'contentType': content_type,
})
}This section is for contributors who want to modify crowd-cast.
crowd-cast-agent --setupThis runs the interactive setup wizard that guides you through configuration.
crowd-cast-agentThe agent will:
- Bootstrap OBS libraries (downloads if needed)
- Initialize embedded libobs for capture
- Show in your system tray
- Capture input when selected apps are in foreground
crowd-cast-agent [OPTIONS]
OPTIONS:
-h, --help Print help message
-s, --setup Run the setup wizard (re-select apps, etc.)
ENVIRONMENT:
RUST_LOG Set log level (e.g., debug, info, warn)
CROWD_CAST_LOG_PATH
Override log directory (default: ~/Library/Logs/crowd-cast on macOS)
CROWD_CAST_API_GATEWAY_URL
Lambda endpoint for pre-signed S3 URLs (set at build time)
macOS (Apple Silicon):
brew install simde # Required for ARM builds
brew install create-dmg # Required for release DMG packaging# 1. Clone with submodules
git clone --recursive https://github.com/p-doom/crowd-cast.git
cd crowd-cast
# 2. Build the agent (macOS auto-installs OBS binaries in build.rs)
cargo build
# 3. Run tests
cargo testThe agent uses libobs-rs to embed OBS functionality. Key crates:
libobs- Raw FFI bindings to libobslibobs-wrapper- Safe Rust wrapperlibobs-bootstrapper- Downloads OBS binaries at runtime (macOS)cargo-obs-build- Downloads OBS binaries at build time
The fork at libobs-rs/ includes macOS support from PR #53.
To add support for new capture types, implement them in src/capture/sources.rs:
pub fn new_window_capture(ctx: &ObsContext, window_name: &str) -> Result<ObsSourceRef> {
// Use libobs-wrapper to create window capture source
}MIT License, see LICENSE.md
Contributions welcome! Please open an issue first to discuss proposed changes.
