Profile
Back to NewsBack
GitHub Trending 6 min
Reader Mode
steipete/camsnap: CLI to capture snapshots, short clips, and run motion detection against RTSP/ONVIF came ras

steipete/camsnap: CLI to capture snapshots, short clips, and run motion detection against RTSP/ONVIF came ras

11 hours ago

camsnap 📸 — Frame first, questions later.

CI</a> Release</a> Platforms</a> License</a> Homebrew</a>

camsnap is a command-line tool for capturing snapshots and short clips from RTSP cameras, discovering ONVIF devices, and running motion-triggered shell actions. It also captures local webcams on macOS and Linux; Windows builds support network cameras.

$ camsnap add --name kitchen --host 192.168.1.50 --user camera --pass 'camera-password'
Added camera "kitchen"
$ camsnap snap kitchen --out kitchen.jpg

Save a camera once, then address it by name for snapshots, clips, health checks, and motion detection.

Install

Homebrew is the smallest install path on macOS and Linux. The formula installs ffmpeg as a dependency.

brew install steipete/tap/camsnap

Prebuilt binaries for macOS, Linux, and Windows are available from GitHub Releases. Install ffmpeg separately when using a release binary; Windows supports RTSP cameras but not local webcams.

You can also run camsnap in a container or build it from source. See Docker usage or Development.

Quick start

Add an RTSP camera, confirm the saved configuration, then capture a frame:

camsnap add --name kitchen --host 192.168.1.50 \
  --user camera --pass 'camera-password'
camsnap list
camsnap snap kitchen --out kitchen.jpg

Replace the example host and credentials with values from your camera.

The default RTSP transport is TCP and the default stream is stream1. Use camera-specific defaults when needed:

camsnap add --name kitchen --host 192.168.1.50 \
  --user camera --pass 'camera-password' \
  --rtsp-transport udp --stream stream2 --rtsp-client gortsplib

camsnap stores configuration in ~/.config/camsnap/config.yaml, or $XDG_CONFIG_HOME/camsnap/config.yaml when XDG_CONFIG_HOME is set. The file contains camera credentials in plain text and is written with user-only permissions. Use --config to choose another path.

Commands

| Command | Purpose | | --- | --- | | add | Add or update a saved RTSP or local camera. | | list | List saved cameras without exposing passwords. | | snap | Capture one frame to a file. | | clip | Record a short clip. | | watch | Detect scene changes and run a shell action. | | discover | Find ONVIF devices on the local network. | | devices | List local video inputs. | | ptz | Control pan, tilt, and zoom on supported USB webcams. | | sweep | Capture verified frames across a local PTZ camera's pan arc. | | doctor | Check ffmpeg, connectivity, and optional capture probes. |

Run camsnap --help for the complete flags and defaults.

Clips

camsnap copies the video stream when possible. Drop audio or transcode it to AAC when a camera's source audio is not MP4-compatible.

camsnap clip kitchen --dur 5s --no-audio --out kitchen.mp4
camsnap clip kitchen --dur 5s --audio-codec aac --out kitchen.mp4

When --out is omitted, snap and clip write to a temporary file and print its path. Clip durations accept fractional seconds, for example --dur 250ms or --dur 1.5s.

Motion actions

watch uses ffmpeg scene-change detection and applies a cooldown between actions:

camsnap watch kitchen --threshold 0.2 --cooldown 5s --json \
  --action 'touch /tmp/camsnap-motion'

Actions run asynchronously with the current environment (including PATH) plus CAMSNAP_CAMERA, CAMSNAP_SCORE, and CAMSNAP_TIME. Completed action processes are reaped while monitoring continues. --json writes one JSON object per line, with camera names escaped for JSON. Pass --duration to stop after a fixed interval or --action-template to interpolate {camera}, {score}, and {time} into the command once per event.

Discovery and diagnostics

Find ONVIF cameras on the current network, then check saved cameras:

camsnap discover --info
camsnap doctor --probe

discover --info attempts ONVIF device-information requests and uses saved credentials for matching hosts. doctor --probe classifies authentication, network, local-device, and macOS Camera permission failures.

Camera sources

RTSP and RTSPS

Saved cameras can set defaults for transport, stream, RTSP client, audio handling, and an explicit stream path. Command flags override those defaults.

Use --path for cameras whose stream URL contains an opaque token instead of stream1 or stream2. See RTSP camera setup for tokenized UniFi Protect streams and tuning controls.

Local webcams

On macOS, official builds use native AVFoundation for device listing and snapshots; clips and motion detection use ffmpeg. Linux uses v4l2 through ffmpeg.

camsnap devices
camsnap snap --device 0 --out webcam.jpg
camsnap ptz status --device 0
camsnap ptz goto --device 0 --pan 12.5 --tilt -5 --zoom 50
camsnap ptz move --device 0 --pan -10 --zoom 5
camsnap ptz home --device 0
camsnap ptz goto --device 0 --pan 45 --settle 3s --timeout 6s
camsnap sweep --device 0 --from -45 --to 45 --steps 7 --out-dir panorama

On macOS, PTZ commands keep the selected camera streaming while reading or changing its position, so they require macOS Camera permission. Motion commands verify the observed position after --settle (default 2s) and fail if it does not stabilize before --timeout (default 5s).

sweep is macOS-only and keeps one native capture session open across the entire arc and writes numbered JPEG frames plus manifest.json. The manifest records requested and observed pan/tilt for each frame; failed positions are retained and reported with a non-zero exit status. Add --fail-fast to stop after the first failed position, or --json to print the manifest.

See Local webcams for stable macOS device selectors, Camera permission behavior, UVC PTZ control and sweeps, Linux device paths, and backend selection.

Platform support

| Platform | RTSP cameras | Local webcams | | --- | --- | --- | | macOS 14+ | Yes | Native snapshots; ffmpeg clips and motion | | Linux | Yes | v4l2 through ffmpeg | | Windows | Yes | No | | Docker | Yes | No direct device capture |

Development

Source builds require Go 1.27 or newer and ffmpeg on PATH; Go 1.27.3 is the preferred build toolchain.

macOS CI uses the Apple Silicon xcode-27 runner, keeps a macOS 14 deployment target for both architectures, and smoke-tests Intel binaries under Rosetta. Release CI builds snapshot archives on pull requests and main pushes. Dispatch release.yml on main with dry_run: true and the current release tag to build without publishing archives, updating the Homebrew tap, or pushing container images.

make build
make lint
make test

make build embeds the Camera usage description and signs the binary on macOS. Run go run ./cmd/camsnap --help for an unsigned source invocation that does not access a local camera.

License

MIT

Linux and Omarchy cameras

Install FFmpeg for capture. camsnap devices --json lists accessible V4L2 video capture nodes with their card names, device paths and numeric node indices; ISP subdevices and metadata/output nodes are excluded. For example:

camsnap snap --device 50 --out camera.jpg
camsnap snap --device "Hardware ISP Camera" --out camera.jpg
camsnap clip --device /dev/video50 --duration 2s --out camera.mp4
camsnap ptz status --device /dev/video50

A device path (including /dev/v4l/by-id/...) is preferable for saved camera configuration. Numeric selectors refer to the kernel video node number, not the position in the list. Omitting the camera for snap uses the first accessible capture device on Linux. Name selection rejects ambiguous matches.

PTZ uses standard writable V4L2 absolute pan, tilt and zoom controls; no separate USB access or cgo is required. Physical PTZ motion is not yet hardware-verified; please report your camera model, advertised controls, and motion results in a GitHub issue. Cameras without these controls report that PTZ is unsupported. Camera access follows the active user's device ACLs; inspect them with getfacl /dev/videoN if capture is denied. Intel IPU cameras may be exposed through a system-managed V4L2 relay: the relay and its camera HAL dependencies must work before Camsnap can receive frames.

Chat with me