ipcam
A command-line tool for managing IP cameras (Tapo, Reolink) via RTSP and vendor APIs.
Features
- Capture snapshots from individual cameras or all cameras in parallel
- Assemble multi-camera snapshots into a single tiled grid image
- Record video clips and build timelapses using ffmpeg
- Print or pipe RTSP stream URLs (main and sub streams)
- Pan/tilt/zoom control via ONVIF (Tapo) or Reolink API
- Poll motion detection events and watch for status changes
- Health-check all cameras and run a hook command on status change
- End-to-end camera test (network reachability, RTSP URL, snapshot)
- Discover cameras on the local network via ONVIF WS-Discovery
- Interactive
initwizard that auto-discovers and configures cameras - Frigate NVR integration: list events and fetch latest snapshots
- go2rtc restream proxy support for cameras behind a proxy
- JSON output for every command (
--json) - Shell completions for bash, zsh, and fish
Installation
Prerequisites
Build and install
Quick Start
Run the interactive setup wizard to discover cameras on your network and generate a config file:
Or use --auto to skip prompts and generate a config from detected cameras:
Once configured, a few common commands:
# List configured cameras
# Capture a snapshot
# Check all cameras are reachable
# Run an end-to-end test on all cameras
Configuration
The config file is TOML and lives at:
- macOS:
~/Library/Application Support/ipcam/config.toml - Linux:
~/.config/ipcam/config.toml
Run ipcam config to print the exact path on your system.
Example config
[]
= "192.168.1.10"
= 8554 # default
[]
= "192.168.1.11"
= 5001 # default
[[]]
= "front-door"
= "reolink"
= "192.168.1.101"
= "admin"
= "your-password"
[[]]
= "backyard"
= "tapo"
= "192.168.1.102"
= "admin"
= "your-password"
# Optional: override RTSP port (default: 554)
= 554
# Optional: override ONVIF port (default: 2020 for Tapo, 8000 for Reolink)
= 2020
# Optional: use a go2rtc restream instead of direct RTSP
= "backyard"
# Optional: Frigate camera name if it differs from the config name
= "backyard_cam"
Config fields
| Field | Required | Description |
|---|---|---|
name |
yes | Unique identifier used in all commands |
type |
yes | tapo or reolink |
host |
yes | IP address of the camera |
username |
no | Camera username (default: admin) |
password |
no | Camera password |
rtsp_port |
no | RTSP port (default: 554) |
onvif_port |
no | ONVIF port (default: 2020 for Tapo, 8000 for Reolink) |
go2rtc_stream |
no | go2rtc stream name when using a restream proxy |
frigate_name |
no | Frigate camera name (default: config name with - replaced by _) |
Commands
| Command | Description |
|---|---|
init |
Interactive setup wizard; discovers cameras via ONVIF and writes config |
list |
List all configured cameras |
info <camera> |
Show camera model and firmware (Reolink) or basic info (Tapo) |
snapshot <camera> |
Capture a JPEG snapshot |
snapshot --all |
Capture snapshots from all cameras in parallel |
snapshot --grid |
Capture from all cameras and tile into a single image |
snapshot --every <interval> |
Capture snapshots on a repeating interval |
snapshot-all |
Alias for snapshot --all |
stream <camera> |
Print the RTSP URL, or pipe to a file with --output |
record <camera> |
Record a video clip (default 30 s) |
timelapse <camera> |
Capture frames at an interval and encode to MP4 |
status [camera] |
Check which cameras are online and report latency |
test [camera] |
End-to-end test: network, RTSP URL, snapshot |
watch |
Poll camera health continuously; run a hook on status changes |
events <camera> |
Show motion detection status; use --watch to poll continuously |
ptz <camera> <action> |
Pan/tilt/zoom control: left, right, up, down, stop, preset |
discover |
Scan the network for ONVIF cameras |
frigate events |
List recent Frigate NVR events |
frigate snapshot <camera> |
Fetch the latest snapshot from Frigate |
config |
Show the config file path and whether it exists |
completions <shell> |
Print a shell completion script |
All commands accept --json for machine-readable output and --config <path> to override the config file location.
Examples
Snapshot
# Single camera, default filename (<camera>_<timestamp>.jpg)
# Single camera, custom output path
# All cameras saved to a directory
# Tiled grid of all cameras
# Capture every 5 minutes, save timestamped files
Status and test
# Check all cameras
# Check a single camera
# Full end-to-end test (network + RTSP + snapshot)
# Test a single camera, JSON output
Watch
# Poll every 30 s, print status changes to stdout
# Poll every minute, run a script on any status change
The --exec command receives these environment variables:
| Variable | Value |
|---|---|
CAMERA_NAME |
Camera name from config |
CAMERA_HOST |
Camera IP address |
CAMERA_STATUS |
online or offline |
CAMERA_DETAIL |
Human-readable detail (model or error message) |
Timelapse
# 1-hour capture with a snapshot every 30 s, encoded to timelapse.mp4
# Keep individual frames as well
Shell completions
# zsh
# bash
# fish
PTZ control
# Pan left at speed 5 (default)
# Tilt up at speed 8
# Go to preset position 1
# Stop movement
Supported Cameras
Tapo (TP-Link)
- Snapshots captured via ffmpeg over RTSP (sub-stream by default)
- PTZ control via ONVIF SOAP over HTTP (port 2020 by default)
- RTSP streams:
stream1(main),stream2(sub) - Motion detection: not supported (Tapo does not expose an API for this)
- Health check: TCP connect to the RTSP port
Reolink
- Snapshots fetched via the Reolink HTTP/JSON API (
/cgi-bin/api.cgi?cmd=Snap) - PTZ control via the Reolink
PtzCtrlAPI - Motion detection via
GetMdStateAPI - RTSP streams:
h264Preview_01_main(main),h264Preview_01_sub(sub) - Health check:
GetDevInfoAPI call (also returns model name) - Device info (model, firmware) available via
GetDevInfo
Integration
go2rtc
If your cameras are behind a go2rtc restream proxy, add a [go2rtc] section to your config and set go2rtc_stream on each camera. ipcam will use the proxy RTSP URL (rtsp://<host>:<port>/<stream>) instead of connecting to the camera directly.
Sub-stream URLs are constructed by appending _sub to the stream name (e.g. backyard_sub).
Frigate NVR
Add a [frigate] section to your config pointing at your Frigate instance. Then use the frigate subcommand:
# List the 20 most recent events across all cameras
# Filter by camera
# Save the latest Frigate snapshot for a camera
Frigate camera names use underscores (e.g. front_door). Set frigate_name in the camera config if the name differs from your ipcam name.
License
MIT