A cross-platform CLI to view RTMP/RTSP video streams (incl. 360) in a small window.
Project description
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, auto-reconnect, and an HTTP control API all work today. Video only — OpenCV does not decode audio.
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. - 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
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 |
— |
--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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file streamcatcher-0.2.0.tar.gz.
File metadata
- Download URL: streamcatcher-0.2.0.tar.gz
- Upload date:
- Size: 110.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c58f5cf90631a4350e4b8876d745f6f2d58c9c8af6ca8e0de1f3a15ddf6f835d
|
|
| MD5 |
3c86472df57664fa6a02d5384766d0dd
|
|
| BLAKE2b-256 |
847c3dfc1b17f321d5cf1a3d38e07fd3e657232a478ead7abc042c86c8e24f2e
|
Provenance
The following attestation bundles were made for streamcatcher-0.2.0.tar.gz:
Publisher:
release.yml on ThugipanSivanesan/Streamcatcher
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
streamcatcher-0.2.0.tar.gz -
Subject digest:
c58f5cf90631a4350e4b8876d745f6f2d58c9c8af6ca8e0de1f3a15ddf6f835d - Sigstore transparency entry: 2212379127
- Sigstore integration time:
-
Permalink:
ThugipanSivanesan/Streamcatcher@464e24ee74d4ad0c6c098f5199b0ea59af11715e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ThugipanSivanesan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@464e24ee74d4ad0c6c098f5199b0ea59af11715e -
Trigger Event:
push
-
Statement type:
File details
Details for the file streamcatcher-0.2.0-py3-none-any.whl.
File metadata
- Download URL: streamcatcher-0.2.0-py3-none-any.whl
- Upload date:
- Size: 34.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3333b54069794689d81bf8b27ef6897a97cda9a4e06cb465460dfd98a92a836a
|
|
| MD5 |
014cfc12b1f6e0da7e32523d9e3d9f7d
|
|
| BLAKE2b-256 |
127764ca14dd1b66fe50726c92ce025417f33ae318d12a39ee5d84b23a190cf8
|
Provenance
The following attestation bundles were made for streamcatcher-0.2.0-py3-none-any.whl:
Publisher:
release.yml on ThugipanSivanesan/Streamcatcher
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
streamcatcher-0.2.0-py3-none-any.whl -
Subject digest:
3333b54069794689d81bf8b27ef6897a97cda9a4e06cb465460dfd98a92a836a - Sigstore transparency entry: 2212379144
- Sigstore integration time:
-
Permalink:
ThugipanSivanesan/Streamcatcher@464e24ee74d4ad0c6c098f5199b0ea59af11715e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ThugipanSivanesan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@464e24ee74d4ad0c6c098f5199b0ea59af11715e -
Trigger Event:
push
-
Statement type: