Files
dora-webcam/README.md
T
cristhian 6f76074709
validate / validate (push) Successful in 59m15s
name no consumer of this node
A source that documents itself through a specific downstream node depends on
that node to be understood, and that node already depends on this one for
frames. The README pointed at one by name and the CI workflow still carried a
comment about the dataflow that wired it, which no longer exists.

The output is documented by its type instead: wire `image` into anything that
takes std/media/v1/Image. PREVIEW covers seeing the frames without naming
anyone. Generic words like "a viewer" or "a consumer" stay, since they describe
a role rather than a repository.
2026-09-15 06:13:06 -03:00

123 lines
4.3 KiB
Markdown

# dora-webcam
Dora node that captures frames from a webcam and publishes them as
`std/media/v1/Image`. A source for a vision pipeline: it takes a tick and produces
frames.
Based on [`opencv-video-capture`](https://github.com/dora-rs/dora-hub/tree/main/node-hub/opencv-video-capture)
from dora-hub, brought up to the dora 1.0 node contract, narrowed to Linux camera
selection, and changed to publish the typed Arrow image struct rather than a flat
buffer plus metadata.
## Usage
Add to your dataflow:
```yaml
nodes:
- id: camera
git: ssh://git@gitea.liria.cl:5001/liria/dora-webcam.git
tag: v0.1.0
build: uv pip install .
path: dora-webcam
inputs:
tick: dora/timer/millis/33
outputs:
- image
```
Run the dataflow with `--uv` (`dora run dataflow.yml --uv`): the build command is
`uv pip install .`, so the node gets its own managed environment and never installs into
an ambient one.
### Runnable dataflow
[`dataflow.yml`](dataflow.yml) is the camera on its own, and is what CI runs:
```bash
dora run dataflow.yml --uv
```
Nothing consumes the frames, so to actually see them set `PREVIEW`:
```bash
PREVIEW=true dora run dataflow.yml --uv
```
A window opens showing the frames as they are published. Press `q` to stop. That is
enough to check a camera, an encoding or a flip without wiring a second node — which is
why there is one dataflow here rather than one per consumer.
For a real pipeline, wire `image` into any node that takes `std/media/v1/Image`, from
your own dataflow. This repository names no consumer: it is a source, and depending on
something downstream of it only to demonstrate it would be a cycle.
## Output
| Output | Payload | Metadata |
|---|---|---|
| `image` | `std/media/v1/Image` struct | none — the struct describes itself |
The struct carries `width: UInt32`, `height: UInt32`, `encoding: Utf8` and
`data: LargeBinary`, so a receiver needs nothing from the message metadata. For a
compressed encoding, `width` and `height` are the image's dimensions, not the length of
`data`.
## Inputs
| Input | Description |
|---|---|
| `tick` | Publishes one frame. Wire to `dora/timer/millis/N`; the node stops when it closes. |
## Environment variables
| Variable | Default | Meaning |
|---|---|---|
| `CAMERA_ID` | unset | Substring of a `/dev/v4l/by-id/` entry. Wins over `CAPTURE_PATH`. |
| `CAPTURE_PATH` | `0` | Device path (`/dev/video1`) or index. A numeric string is an index. |
| `IMAGE_WIDTH` | native | Requested capture width; frames are also resized to it. |
| `IMAGE_HEIGHT` | native | Requested capture height; frames are also resized to it. |
| `ENCODING` | `bgr8` | One of `bgr8`, `rgb8`, `yuv420`, `jpeg`/`jpg`/`jpe`, `png`, `bmp`, `webp`. |
| `JPEG_QUALITY` | `95` | Quality 0-100, used only by the JPEG encodings. |
| `FLIP` | unset | `VERTICAL`, `HORIZONTAL` or `BOTH`. |
| `PREVIEW` | unset | `true` opens a window of the published frames; `q` stops the node. |
| `CAPTURE_FRAMES` | `0` | Stop after this many frames. 0 runs until the tick closes. |
Resizing only happens when both `IMAGE_WIDTH` and `IMAGE_HEIGHT` are set.
`CAMERA_ID` is worth preferring on a robot: a by-id name is stable across reboots and
across which USB port the camera is in, and an index is not.
`CI=true` makes a failed read publish a labelled black frame instead of raising, which
is what lets the dataflow run on a machine with no camera. It is not a configuration
knob.
## Build
```bash
uv sync
```
Every dependency ships prebuilt wheels for linux-x86_64, linux-aarch64 and macos-arm64,
so the node installs with no system packages and no compiler.
**Requirements:** dora 1.0+ (verified with `dora-cli 1.0.1`), Python 3.11+, `uv`, and a
V4L2 camera. `PREVIEW` also needs a display. On a bare Linux image `opencv-python` needs
the libGL shared libraries (`libgl1`, `libglib2.0-0`), which it links at import time.
## Test
```bash
uv run pytest
```
Unit tests over device resolution, encoding and the image struct; they need neither a
camera nor a dataflow. The headless `dataflow.yml` run covers the wiring and the event
loop.
## Camera selection on other platforms
Selecting by `CAMERA_ID` reads `/dev/v4l/by-id/`, so it is Linux only. Upstream also
enumerates cameras through macOS `system_profiler` and Windows PowerShell; that is not
carried here. `CAPTURE_PATH` works everywhere.