Streamcatcher
A cross-platform Python CLI that connects to an RTSP or RTMP video stream and views it in a small desktop window — including 360° panoramic cameras, which it reprojects into a flat pan/tilt/zoom "look-around" viewport. Pure Python, powered by OpenCV.
Status: early development, built in small, tested vertical slices. Live video playback, the 360° equirectangular look-around viewport, snapshots, recording, auto-reconnect, and an HTTP control API all work today. Video only in the live window — OpenCV does not decode audio (the
ffmpegrecord mode keeps it).
Features
- View RTSP/RTMP streams in a native window (OpenCV — video only, no audio).
- 360° support: reproject an equirectangular panorama into a flat look-around viewport.
- Look around live:
W/A/S/Dto pan and tilt,+/-to zoom,qto quit. - Snapshots: press
pin the viewer to save the current view, or grab one frame headlessly with--snapshot(current directory) or--snapshot out.jpg. - Recording: capture a stream to a file while you watch with
--record— anopencvmode (video only, no extra dependency) or a lossless, audio-keepingffmpegmode. Cap the length with--duration SECONDS. - Auto-reconnect: when a live stream drops, reconnect with exponential
backoff (retries forever by default;
--no-reconnectto exit on the first drop). - Offline-first: the package and the entire test suite run with no network,
no credentials, and no live stream. OpenCV is lazy-imported only on the live
path, and an offline
stubbackend (-b stub) needs nothing at all. - Secrets-safe: stream URLs (which may embed
user:pass@host) are wrapped inSecretStrand scrubbed from logs by a redacting filter.
Install
Requires Python 3.12+.
pip install streamcatcher
Or install the latest unreleased code straight from source:
pip install git+https://github.com/ThugipanSivanesan/Streamcatcher
The live viewer pulls in opencv-python; no separate system media player (VLC,
ffmpeg app, …) is required.
Headless / server install
On a server, container, or CI box you don't need the desktop window — and the
default GUI build's system libraries (libGL.so.1 on Linux) may be missing.
Swap in the headless OpenCV build so serve and --snapshot run without them:
pip install streamcatcher
pip uninstall opencv-python
pip install opencv-python-headless
streamcatcher serve and streamcatcher play --snapshot work fully headless.
Only the live play window needs the default opencv-python build; on a
headless build it exits with a clear message instead of a cryptic OpenCV error.
(The two wheels both provide cv2 and can't coexist, so it's a swap, not an extra.)
Usage
# View a plain 2D stream in a window
streamcatcher play rtsp://camera.local:554/stream1
# View a 360° equirectangular camera with a look-around viewport
streamcatcher play rtsp://123.456.7.890/live/live -p equirect
# Capture a single frame in the current directory and exit — no window (respects -p)
streamcatcher play rtsp://cam/live --snapshot
# Or choose the exact output path
streamcatcher play rtsp://cam/live --snapshot shot.jpg
# Record the stream to a file while you watch it (bare --record → timestamped .mp4)
streamcatcher play rtsp://cam/live --record capture.mp4
# Record losslessly with audio (needs the ffmpeg binary), capped at 60 seconds
streamcatcher play rtsp://cam/live --record capture.mp4 --record-mode ffmpeg --duration 60
In the viewer window: W/A/S/D (or drag the mouse) aim · +/- zoom · p snapshot · q quit.
The p hotkey writes a timestamped streamcatcher-snapshot-YYYYMMDD-HHMMSS.jpg
in the current directory.
| Flag | Values | Env var |
|---|---|---|
--backend / -b |
opencv (live window, default), stub (offline no-op) |
STREAMCATCHER_BACKEND |
--projection / -p |
flat (default), equirect |
STREAMCATCHER_PROJECTION |
--snapshot |
optional PATH — save one frame and exit; defaults to a timestamped JPEG in the current directory |
— |
--record |
optional PATH — record while playing; defaults to a timestamped .mp4 in the current directory. Mutually exclusive with --snapshot |
— |
--record-mode |
opencv (default, video only), ffmpeg (lossless + audio) |
STREAMCATCHER_RECORD_MODE |
--duration |
SECONDS — stop recording and playback after the first frame; requires --record |
STREAMCATCHER_RECORD_DURATION |
--reconnect / --no-reconnect |
auto-reconnect on drop (default on) | STREAMCATCHER_RECONNECT_ENABLED |
play opens a real OpenCV window by default; pass -b stub (or set
STREAMCATCHER_BACKEND=stub) for the offline no-op backend used in development
and tests.
How it works
- Playback: OpenCV (
cv2.VideoCapture+ highgui) opens its own native window from a plain Python CLI. RTSP is forced over TCP to reduce dropped packets. - 360° reprojection: pure-NumPy equirectangular→pinhole remap tables are fed
to
cv2.remap. The math is deterministic and unit-tested with no GPU, window, or live stream needed. - Headless control core:
StreamSessiondrives open / read / render / look-around / close with no window attached — the same core the HTTP control API sits on. - CLI: Typer.
HTTP control API
Install the [api] extra and run the server so another program — or an AI agent —
can open sessions, drive the look-around, and pull frames:
pip install 'streamcatcher[api]'
streamcatcher serve # binds 127.0.0.1:8000
streamcatcher serve --host 0.0.0.0 --port 9000 --token changeme
Interactive OpenAPI docs are served at /docs. Key endpoints:
| Endpoint | Purpose |
|---|---|
POST /session |
Open a session for a stream URL; returns an id (the URL is never echoed back). |
GET /session/{id}/frame |
Current look-around viewport as a JPEG — how an agent "sees" now. |
GET /session/{id}/panorama |
Raw, full-frame JPEG (before reprojection). |
GET /session/{id}/stream.mjpg |
MJPEG stream of the viewport. |
POST /session/{id}/look |
Pan/tilt/zoom by {pan, tilt, zoom} degree deltas. |
POST /session/{id}/look/{pan_left…zoom_out} |
Discrete look steps. |
GET /session/{id}/state |
Current projection and orientation. |
DELETE /session/{id} |
Close the session. |
The server binds 127.0.0.1 by default. Set --token (or STREAMCATCHER_API_TOKEN)
to require an Authorization: Bearer <token> header on every request. Stream URLs
and their credentials are never returned in any response.
Documentation
Full docs — CLI usage, the 360°/camera guide, the Python and HTTP APIs,
architecture, and an API reference generated from the source — are built with
mkdocs-material under docs/. Build them locally with
uv run mkdocs serve (see Contributing). See the
changelog for what has shipped so far.
Contributing
See CONTRIBUTING.md for the full development workflow. The short version, using uv:
uv sync --extra api # create the environment (incl. the API extra)
uv run pytest # tests (offline, no credentials)
uv run ruff check . # lint
uv run ruff format --check . # format check
uv run mkdocs serve # preview the docs (needs: uv sync --group docs)
pre-commit install # enable local hooks
Security
- CI runs gitleaks (secret scanning) and osv-scanner (dependency vulnerabilities) on every pull request.
- Pre-commit hooks scan for secrets and private keys before anything is committed.
- Stream URLs and their embedded credentials are never written to logs.
License
Metadata
Release files for streamcatcher 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| streamcatcher-0.3.0.tar.gz | 152.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| streamcatcher-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 199.9 kB
Release files / streamcatcher-0.3.0.tar.gz
| Download URL | streamcatcher-0.3.0.tar.gz |
|---|---|
| Size | 152.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f11d71a7783f03902a58d43f0c4d6ad8ff63ec2be27655bed63578534837ea50
|
|
BLAKE2b-256 checksum How to use checksums |
6c22023d483d2130deee9183e6813d796da19c6dc00b41edd9f8593b6ac8344f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.
Transparency logRelease files / streamcatcher-0.3.0-py3-none-any.whl
| Download URL | streamcatcher-0.3.0-py3-none-any.whl |
|---|---|
| Size | 47.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5fa7c873e28b8545f84a6ca5d945d309423902de1cdc546f969d60097d3af0dc
|
|
BLAKE2b-256 checksum How to use checksums |
faad82256564afcf12759d3857e8a2f002405eae7e54fabb7f06b6ff2cbe6d22
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.
Transparency log