Skip to content

justinhuangcode/camgrab

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CamGrab

English | δΈ­ζ–‡

CI Release Crates.io License: MIT Rust Docker GitHub Stars Last Commit Issues Platform

A fast, single-binary IP camera CLI for snapshots, recording, and motion detection. πŸ“·

Why CamGrab?

Most camera tools either depend on heavy external binaries (ffmpeg) or are full-blown NVR systems you have to self-host. camgrab is a single binary with zero external dependencies β€” connect to your cameras and start working in seconds.

camgrab ffmpeg + cron motion Frigate ZoneMinder
Single binary, zero deps Yes No (ffmpeg ~50MB) No (C libs) No (Docker stack) No (LAMP stack)
RTSP snapshot + clip Built-in Manual scripting No clips Via API Via API
Native motion detection Built-in (zones + filters) Scene filter only Yes ML-based Zones
ONVIF PTZ control Built-in No No Via MQTT Limited
Multi-camera concurrent Async (tokio) Sequential scripts Per-process Yes Yes
Daemon + cron scheduler Built-in External cron Built-in Always-on Always-on
Notifications Webhook / MQTT / Email DIY Limited MQTT Email
Cloud storage (S3) Built-in DIY No No No
Resource usage ~20MB RAM ~100MB+ ~50MB+ ~2GB+ ~500MB+
Setup complexity camgrab add && camgrab snap Write scripts Edit config files Docker compose LAMP + config

Features

  • Zero external dependencies β€” Pure Rust RTSP client via retina; no ffmpeg required
  • Snapshot capture β€” Single-frame capture with H.264 decode (openh264) and MJPEG passthrough
  • Video clip recording β€” Duration-based RTSP recording with AVCC-to-Annex B conversion
  • Native motion detection β€” Frame differencing with configurable zones, sensitivity, and filters
  • Multi-camera concurrent β€” Snap or watch all cameras simultaneously with tokio async runtime
  • ONVIF discovery + PTZ β€” Auto-discover cameras on the network; pan, tilt, zoom, and preset management
  • Built-in notifications β€” Webhook, MQTT, and email alerting out of the box
  • Daemon mode with scheduler β€” Cron-based automated captures and continuous monitoring
  • Cloud storage β€” Local filesystem and S3/MinIO backends with atomic writes
  • Health checks β€” camgrab doctor verifies connectivity, stream info, and system configuration
  • JSON output β€” Pass --json on any command for machine-readable output
  • Cross-platform β€” Linux, macOS, Windows; pre-built binaries for 5 targets

Installation

Pre-built binaries (recommended)

Download the latest release for your platform from GitHub Releases:

Platform Archive
Linux x86_64 camgrab-v*-linux-x86_64.tar.gz
Linux ARM64 camgrab-v*-linux-arm64.tar.gz
macOS Intel camgrab-v*-macos-x86_64.tar.gz
macOS Apple Silicon camgrab-v*-macos-arm64.tar.gz
Windows x86_64 camgrab-v*-windows-x86_64.zip
# Example: Linux x86_64
curl -LO https://github.com/justinhuangcode/camgrab/releases/latest/download/camgrab-v1.0.0-linux-x86_64.tar.gz
tar xzf camgrab-v1.0.0-linux-x86_64.tar.gz
sudo mv camgrab /usr/local/bin/

Homebrew (macOS / Linux)

brew tap justinhuangcode/tap
brew install camgrab

Docker

docker pull ghcr.io/justinhuangcode/camgrab:latest

Via Cargo

cargo install --git https://github.com/justinhuangcode/camgrab -p camgrab-cli

From source

git clone https://github.com/justinhuangcode/camgrab.git
cd camgrab
cargo build --release
# Binary at target/release/camgrab

Requirements: Rust 1.88+

Quick Start

# 1. Add a camera (interactive)
camgrab add

# Or specify directly
camgrab add front-door \
  --host 192.168.1.100 --port 554 \
  --username admin --password secret123

# 2. Capture a snapshot
camgrab snap front-door
camgrab snap front-door --out snapshot.jpg --format jpeg

# 3. Record a 30-second clip
camgrab clip front-door --duration 30 --out clip.mp4

# 4. Watch for motion
camgrab watch front-door --threshold 0.3 --cooldown 5

# 5. Discover cameras on the network
camgrab discover

# 6. Check camera health
camgrab doctor --all

Commands

Command Description
add [NAME] Add or update a camera (interactive or flags)
list List all configured cameras with status
snap <CAMERA> Capture a single-frame snapshot
clip <CAMERA> Record a video clip for a specified duration
watch <CAMERA> Monitor a camera feed for motion events
discover Auto-discover ONVIF cameras on the local network
doctor [CAMERA] Verify camera connectivity and stream info
ptz <CAMERA> Pan-Tilt-Zoom control via ONVIF
daemon start|stop|status|logs Manage the background daemon for scheduled operations

camgrab snap

Capture a single frame from a camera and save as an image.

camgrab snap <CAMERA> [OPTIONS]
Flag Default Description
-o, --out <PATH> auto Output path for snapshot
-f, --format <FMT> jpeg Image format: jpeg, png, webp
-t, --timeout <SEC> 10 Connection timeout in seconds
--transport <PROTO> tcp RTSP transport: tcp, udp
--json false Output result as JSON
camgrab snap front-door --out /backup/snapshot.png --format png
camgrab snap front-door --json | jq '.path'

camgrab clip

Record a video clip for a specified duration from an RTSP stream.

camgrab clip <CAMERA> [OPTIONS]
Flag Default Description
-o, --out <PATH> auto Output path for video clip
-d, --duration <SEC> 30 Recording duration in seconds
-f, --format <FMT> mp4 Video format: mp4, mkv, avi
--transport <PROTO> tcp RTSP transport: tcp, udp
--json false Output result as JSON
camgrab clip front-door --duration 60
camgrab clip front-door --out evidence.mp4 --duration 120

camgrab watch

Monitor a camera feed for motion, trigger actions on detection events.

camgrab watch <CAMERA> [OPTIONS]
Flag Default Description
-t, --threshold <FLOAT> 0.3 Motion detection threshold 0.0-1.0
-c, --cooldown <SEC> 5 Cooldown between events
--sensitivity <LEVEL> medium Detection sensitivity: low, medium, high
--zones-from <PATH> β€” Path to JSON zone configuration file
--action <CMD> β€” Command template to execute on detection
--json false Output events as JSON

Environment variables in actions:

  • $CAMGRAB_CAMERA β€” Camera name
  • $CAMGRAB_SCORE β€” Motion detection score (0.0-1.0)
  • $CAMGRAB_TIME β€” Event timestamp (RFC 3339)
  • $CAMGRAB_ZONE β€” Zone name (if using zones)
# Trigger webhook on motion
camgrab watch front-door --action 'curl -X POST https://api.home.com/alert'

# Capture snapshot on motion
camgrab watch front-door \
  --action 'camgrab snap $CAMGRAB_CAMERA --out /alerts/$CAMGRAB_CAMERA-$CAMGRAB_TIME.jpg'

# Stream JSON events for log aggregation
camgrab watch front-door --json | tee -a motion_events.log

camgrab ptz

Control Pan-Tilt-Zoom cameras via ONVIF protocol.

camgrab ptz <CAMERA> [OPTIONS]
Flag Description
--pan <FLOAT> Pan position -1.0 to 1.0
--tilt <FLOAT> Tilt position -1.0 to 1.0
--zoom <FLOAT> Zoom level 0.0 to 1.0
--goto-preset <N> Move to saved preset
--save-preset <N> Save current position as preset
--preset-name <NAME> Name for saved preset
--list-presets List all available presets
--home Return to home position
--stop Stop current movement
camgrab ptz front-door --pan 0.5 --tilt 0.2 --zoom 0.8
camgrab ptz front-door --goto-preset 2
camgrab ptz front-door --list-presets

camgrab daemon

Start, stop, and manage the background daemon for scheduled operations.

camgrab daemon <SUBCOMMAND>
Subcommand Description
start Start the daemon process
stop Stop the daemon process
restart Restart the daemon
status Check daemon status
logs View daemon logs (--follow for real-time)
reload Reload configuration without restart

Docker Usage

# Pull from GitHub Container Registry
docker pull ghcr.io/justinhuangcode/camgrab:latest

# One-off snapshot
docker run --rm --network host \
  -v ./config.toml:/etc/camgrab/config.toml:ro \
  -v ./output:/var/lib/camgrab \
  ghcr.io/justinhuangcode/camgrab:latest \
  snap front-door -o /var/lib/camgrab/snap.jpg

# Daemon mode for continuous monitoring
docker run -d --name camgrab --restart unless-stopped \
  --network host \
  -v ./config.toml:/etc/camgrab/config.toml:ro \
  -v camgrab-data:/var/lib/camgrab \
  ghcr.io/justinhuangcode/camgrab:latest \
  daemon start

The Docker image supports linux/amd64 and linux/arm64.

Configuration

Configuration files are stored in XDG-compliant locations:

  • Linux/macOS: ~/.config/camgrab/config.yaml
  • Windows: %APPDATA%\camgrab\config.yaml

Supports YAML, TOML, and JSON formats.

Example (YAML)

cameras:
  front-door:
    name: "Front Door Camera"
    host: "192.168.1.100"
    port: 554
    username: "admin"
    password: "secret123"
    protocol: rtsp
    transport: tcp
    stream_type: main
    timeout_secs: 10

  back-yard:
    name: "Back Yard Camera"
    host: "192.168.1.101"
    port: 554
    username: "admin"
    password: "secret456"
    custom_path: "/stream1"

storage:
  backends:
    - type: local
      path: "/var/lib/camgrab/storage"
    - type: s3
      bucket: "camgrab-clips"
      region: "us-east-1"
      prefix: "cameras"

notifications:
  webhook:
    enabled: true
    url: "https://api.example.com/webhook"
    method: POST
    headers:
      Authorization: "Bearer token123"
  mqtt:
    enabled: true
    broker: "mqtt.example.com:1883"
    topic: "camgrab/events"

daemon:
  schedules:
    - name: "hourly-snapshots"
      camera: "front-door"
      action: "snap"
      cron: "0 * * * *"
    - name: "night-motion-watch"
      camera: "back-yard"
      action: "watch"
      cron: "0 22 * * *"
      duration_secs: 28800
      threshold: 0.25

Example (TOML)

[cameras.front-door]
name = "Front Door Camera"
host = "192.168.1.100"
port = 554
username = "admin"
password = "secret123"
protocol = "rtsp"
transport = "tcp"
stream_type = "main"
timeout_secs = 10

[storage]
[[storage.backends]]
type = "local"
path = "/var/lib/camgrab/storage"

[notifications.webhook]
enabled = true
url = "https://api.example.com/webhook"
method = "POST"

[[daemon.schedules]]
name = "hourly-snapshots"
camera = "front-door"
action = "snap"
cron = "0 * * * *"

Architecture

The project is organized as a Cargo workspace with three crates:

camgrab/
β”œβ”€β”€ Cargo.toml                    # Workspace manifest
β”œβ”€β”€ Dockerfile                    # Multi-stage Docker build (multi-arch)
β”œβ”€β”€ deny.toml                     # Cargo deny (license + advisory audit)
β”œβ”€β”€ Formula/camgrab.rb            # Homebrew formula
β”œβ”€β”€ .github/workflows/
β”‚   β”œβ”€β”€ ci.yml                    # Check, Clippy, Fmt, Test, Build, Deny
β”‚   └── release.yml               # Build β†’ Release β†’ Docker β†’ Homebrew
β”‚
β”œβ”€β”€ crates/
β”‚   β”œβ”€β”€ camgrab-cli/              # CLI binary
β”‚   β”‚   └── src/
β”‚   β”‚       β”œβ”€β”€ main.rs           # Entry point, clap command dispatch
β”‚   β”‚       └── commands/
β”‚   β”‚           β”œβ”€β”€ snap.rs       # Snapshot capture
β”‚   β”‚           β”œβ”€β”€ clip.rs       # Video clip recording
β”‚   β”‚           β”œβ”€β”€ watch.rs      # Motion detection monitor
β”‚   β”‚           β”œβ”€β”€ discover.rs   # ONVIF camera discovery
β”‚   β”‚           β”œβ”€β”€ doctor.rs     # Health check diagnostics
β”‚   β”‚           β”œβ”€β”€ ptz.rs        # PTZ control
β”‚   β”‚           β”œβ”€β”€ add.rs        # Camera configuration
β”‚   β”‚           └── list.rs       # Camera listing
β”‚   β”‚
β”‚   β”œβ”€β”€ camgrab-core/             # Core library
β”‚   β”‚   └── src/
β”‚   β”‚       β”œβ”€β”€ lib.rs
β”‚   β”‚       β”œβ”€β”€ camera.rs         # Camera model + management
β”‚   β”‚       β”œβ”€β”€ error.rs          # Error types
β”‚   β”‚       β”œβ”€β”€ config/           # YAML/TOML/JSON config (de)serialization
β”‚   β”‚       β”œβ”€β”€ rtsp/             # Pure Rust RTSP client (retina + openh264)
β”‚   β”‚       β”œβ”€β”€ motion/           # Frame differencing, zones, filters
β”‚   β”‚       β”œβ”€β”€ onvif/            # SOAP/WS-Security device + PTZ + discovery
β”‚   β”‚       β”œβ”€β”€ storage/          # Local + S3/MinIO backends
β”‚   β”‚       └── notify/           # Webhook, MQTT, email
β”‚   β”‚
β”‚   └── camgrab-daemon/           # Background daemon
β”‚       └── src/
β”‚           β”œβ”€β”€ lib.rs
β”‚           β”œβ”€β”€ server.rs         # Axum HTTP API
β”‚           └── scheduler.rs      # Cron job scheduling
β”‚
└── examples/
    β”œβ”€β”€ rtsp_snapshot.rs
    └── rtsp_clip.rs

Performance

  • Memory: ~20MB base + ~5MB per active RTSP stream
  • CPU: ~2-5% per 1080p stream (motion detection disabled)
  • Motion detection overhead: ~8-15% additional CPU per stream
  • Concurrent streams: Tested with 32+ simultaneous connections
  • Startup time: <100ms cold start

Camera Compatibility

Tested with:

  • Hikvision DS-2CD2xxx series
  • Dahua IPC-HDW/HFW series
  • Reolink RLC-xxx series
  • Amcrest IP2M/IP4M series
  • Ubiquiti UniFi Protect cameras
  • Generic ONVIF-compliant cameras

Security Considerations

  • Credentials are stored in plain text in configuration files β€” ensure proper file permissions (chmod 600)
  • RTSP streams are typically unencrypted β€” use VPN or VLAN for sensitive deployments
  • RTSPS (RTSP over TLS) support available for encrypted streams
  • ONVIF communications support WS-Security with timestamp validation
  • Motion detection actions execute shell commands β€” sanitize inputs carefully

Troubleshooting

Connection Issues

# Verify camera is reachable
ping 192.168.1.100

# Test RTSP port
nc -zv 192.168.1.100 554

# Run doctor command with verbose logging
camgrab -v doctor front-door

Motion Detection False Positives

  • Decrease sensitivity: --sensitivity low
  • Increase threshold: --threshold 0.5
  • Use motion zones to exclude problematic areas
  • Increase cooldown to reduce event spam: --cooldown 10

Performance Issues

  • Use UDP transport on local networks: --transport udp
  • Use sub-stream for lower bandwidth: stream_type: sub
  • Increase timeout for slow cameras: timeout_secs: 30

Development

git clone https://github.com/justinhuangcode/camgrab.git
cd camgrab

# Build
cargo build --release

# Test
cargo test --workspace

# Lint
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings

# Run examples
cargo run --example rtsp_snapshot
cargo run --example rtsp_clip

Code Style

  • rustfmt with default settings
  • Clippy pedantic lints enabled (configured in workspace Cargo.toml)
  • No unsafe code (#![deny(unsafe_code)])

Contributing

Contributions are welcome! Please see the guidelines:

  1. Fork the repository and create a feature branch
  2. Write tests for new functionality
  3. Run cargo fmt and cargo clippy before submitting
  4. Submit a pull request with a clear description of changes

Changelog

See CHANGELOG.md for release history.

Acknowledgments

  • retina β€” Pure Rust RTSP client library
  • openh264 β€” H.264 decoding
  • tokio β€” Asynchronous runtime
  • clap β€” Command-line argument parsing

License

MIT

About

A fast, single-binary IP camera CLI for snapshots, recording, and motion detection. πŸ“·

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages