Skip to main content

pyezcams

v0.1.3 · PyPI

Minimal (stdlib-only) toolkit to run a node that captures USB webcams and streams them over RTSP/WebRTC via MediaMTX. A single command (pyezcams) starts MediaMTX and one ffmpeg per camera and keeps them alive.

No Python dependencies. Relies on three system binaries: ffmpeg, v4l2-ctl and mediamtx. Linux only (v4l2 capture).

Stateless: the library stores nothing and assumes no paths. All configuration is the match file, passed explicitly and required.

Prerequisites

Binary Install
ffmpeg sudo apt install ffmpeg
v4l2-ctl sudo apt install v4l-utils
mediamtx binary from releases onto the PATH

check_prerequisites() verifies them at startup and, if any is missing, says what to do.

Install

pip install pyezcams

Usage

pyezcams --config cameras.txt                       # defaults: 720p30
pyezcams --config cameras.txt --resolution 1920x1080 --fps 25

--config is required (no default path). The command checks prerequisites, detects the encoder, starts MediaMTX and launches one ffmpeg per camera, supervising them (relaunches any that die, clean shutdown on SIGINT/SIGTERM).

Defaults

720p30 surveillance standard; everything works with no flags. Only --config is required; --resolution and --fps are optional overrides.

Parameter Default Applies to
resolution 1280x720 (max) capture (both cases)
framerate 30 (max) capture (both cases)
bitrate 4M re-encode (MJPG) only
GOP 60 (2s @30fps) re-encode (MJPG) only
RTSP output rtsp://localhost:8554/<alias> both cases

--resolution and --fps are a ceiling, not a fixed value. Each camera is probed with v4l2-ctl and captures at the largest mode it actually offers at or below them; a camera that only does MJPG 640x480@30 gets exactly that, and the downgrade is logged as a warning. Asking for a size the camera does not have would make V4L2 silently fall back to YUYV at its smallest size — that is the bug this avoids.

Per case:

  • H264 (copy) — captures at the negotiated mode and copies the native stream (-c:v copy, ~0 CPU). Bitrate does not apply (nothing is re-encoded).
  • MJPG (re-encode) — captures at the negotiated mode and re-encodes with the detected encoder at the given bitrate.

Bitrate and RTSP base are module constants in command.py; build_command also takes video_size, framerate and bitrate keyword args for per-camera overrides.

Match file

One camera per line, path = alias (blank lines and # comments ignored):

/dev/v4l/by-path/...-video-index0 = laser20w
/dev/v4l/by-path/...-video-index0 = cnc_a

Get the paths with ls -l /dev/v4l/by-path/.

API

from pyezcams import (
    parse_match, check_prerequisites, detect_encoder,
    detect_mode, detect_format, build_command, run,
)
  • parse_match(path) -> dict — read the match file into {alias: usb_path}.
  • check_prerequisites() -> None — verify the three binaries; RuntimeError if any is missing.
  • detect_encoder() -> str | None — first H.264 encoder that passes a real 1-frame test (h264_nvenc > h264_qsv > h264_vaapi > h264_v4l2m2m > libx264). Hang-proof: each test is capped by ENCODER_TEST_TIMEOUT (10s). A hardware encoder that hangs (broken driver/firmware) is discarded on timeout and the detection falls through to the next candidate instead of blocking node startup; software libx264 always works as the final fallback.
  • detect_mode(usb_path, max_width, max_height, max_fps) -> Mode | None — the camera's best Mode(fmt, width, height, fps) at or below the ceiling: preferred format first (H264 > MJPG), then the largest frame size that fits, then the highest fps not above max_fps (or the lowest available if the camera only offers more). None if it has no usable format. Only Size: Discrete modes are parsed; a stepwise-only camera yields None.
  • detect_format(usb_path) -> str | None"H264" (copy) or "MJPG" (re-encode), or None. Kept for back-compat; prefer detect_mode, since a format alone does not tell you at which sizes the camera offers it.
  • build_command(usb_path, alias, fmt, encoder, video_size=..., framerate=..., bitrate=...) -> list[str] — build (not run) the ffmpeg argv; the last three default to the 720p30/4M standard. ValueError if fmt is None. Pass a size/fps the camera really has (i.e. from detect_mode), not a wish.
  • run(config, video_size=..., framerate=...) -> None — orchestrate the node: prerequisites -> MediaMTX -> one ffmpeg per camera -> supervision -> clean shutdown. video_size/framerate are a ceiling.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyezcams-0.1.3.tar.gz (12.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyezcams-0.1.3-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

File details

Details for the file pyezcams-0.1.3.tar.gz.

File metadata

  • Download URL: pyezcams-0.1.3.tar.gz
  • Upload date:
  • Size: 12.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for pyezcams-0.1.3.tar.gz
Algorithm Hash digest
SHA256 9aa1777dc45ed5708df9b59df958c3d4243536ef6aa68e8a94d42081a2f701da
MD5 7850ac985a85a605da4a1ac1f39897a0
BLAKE2b-256 1d9e780dc5e1efaefd4c02c41ccb7b9cde4d131d59f25c7e79a0a868b6c7e834

See more details on using hashes here.

File details

Details for the file pyezcams-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: pyezcams-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 14.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for pyezcams-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 12a3050b66fdbb0f90de18c8f984b1e6b41bbbae6ebe39e3b43b78eae36ab4e9
MD5 419507e125a96cf18520b8f09f334011
BLAKE2b-256 2fb0fa338b303a0d6b0bf236571016eb069cc27d98764f055e2b328156468219

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page