streamcraft 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamcraft-0.1.0/LICENSE +21 -0
- streamcraft-0.1.0/PKG-INFO +358 -0
- streamcraft-0.1.0/README.md +330 -0
- streamcraft-0.1.0/pyproject.toml +48 -0
- streamcraft-0.1.0/setup.cfg +4 -0
- streamcraft-0.1.0/streamcraft/__init__.py +52 -0
- streamcraft-0.1.0/streamcraft/devices.py +517 -0
- streamcraft-0.1.0/streamcraft/pipeline.py +334 -0
- streamcraft-0.1.0/streamcraft/webrtc.py +523 -0
- streamcraft-0.1.0/streamcraft.egg-info/PKG-INFO +358 -0
- streamcraft-0.1.0/streamcraft.egg-info/SOURCES.txt +15 -0
- streamcraft-0.1.0/streamcraft.egg-info/dependency_links.txt +1 -0
- streamcraft-0.1.0/streamcraft.egg-info/requires.txt +3 -0
- streamcraft-0.1.0/streamcraft.egg-info/top_level.txt +1 -0
- streamcraft-0.1.0/tests/test_devices.py +414 -0
- streamcraft-0.1.0/tests/test_pipeline.py +662 -0
- streamcraft-0.1.0/tests/test_webrtc.py +408 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 gst-tools contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streamcraft
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fluent Python wrappers for GStreamer — build pipelines, control cameras, and manage WebRTC sessions without the boilerplate.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/ranaweerasupun/streamcraft
|
|
7
|
+
Project-URL: Repository, https://github.com/ranaweerasupun/streamcraft
|
|
8
|
+
Project-URL: Issues, https://github.com/ranaweerasupun/streamcraft/issues
|
|
9
|
+
Keywords: gstreamer,webrtc,video,audio,streaming,pipeline,v4l2,ptz,camera,raspberry-pi
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Multimedia :: Video
|
|
19
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Provides-Extra: aiohttp
|
|
26
|
+
Requires-Dist: aiohttp>=3.9; extra == "aiohttp"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# streamcraft
|
|
30
|
+
|
|
31
|
+
Fluent Python wrappers for GStreamer — build pipelines, control cameras, and manage WebRTC sessions without the boilerplate.
|
|
32
|
+
|
|
33
|
+
[](https://github.com/ranaweerasupun/streamcraft/actions/workflows/test.yml)
|
|
34
|
+
[](https://www.python.org/downloads/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
[](https://www.kernel.org/)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## The problem
|
|
41
|
+
|
|
42
|
+
Writing a GStreamer pipeline in Python looks like this:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# 45 lines just to describe: camera → decode → encode → RTP
|
|
46
|
+
pipeline = Gst.Pipeline.new("video")
|
|
47
|
+
|
|
48
|
+
v4l2src = Gst.ElementFactory.make("v4l2src", "v4l2src")
|
|
49
|
+
v4l2src.set_property("device", "/dev/video0")
|
|
50
|
+
v4l2src.set_property("do-timestamp", True)
|
|
51
|
+
mjpeg_caps = Gst.Caps.from_string("image/jpeg,width=1280,height=720,framerate=30/1")
|
|
52
|
+
|
|
53
|
+
queue = Gst.ElementFactory.make("queue", "queue_v1")
|
|
54
|
+
queue.set_property("max-size-buffers", 3)
|
|
55
|
+
queue.set_property("max-size-time", 0)
|
|
56
|
+
queue.set_property("max-size-bytes", 0)
|
|
57
|
+
queue.set_property("leaky", 2)
|
|
58
|
+
|
|
59
|
+
jpegdec = Gst.ElementFactory.make("jpegdec", "jpegdec")
|
|
60
|
+
videoconv = Gst.ElementFactory.make("videoconvert", "videoconvert")
|
|
61
|
+
|
|
62
|
+
x264enc = Gst.ElementFactory.make("x264enc", "x264enc")
|
|
63
|
+
x264enc.set_property("tune", "zerolatency")
|
|
64
|
+
x264enc.set_property("speed-preset", "ultrafast")
|
|
65
|
+
x264enc.set_property("bitrate", 2000)
|
|
66
|
+
x264enc.set_property("key-int-max", 30)
|
|
67
|
+
|
|
68
|
+
# ... 20 more lines of pipeline.add() and link() calls ...
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With streamcraft the same pipeline is:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from streamcraft import PipelineBuilder
|
|
75
|
+
|
|
76
|
+
pipeline, elems = (
|
|
77
|
+
PipelineBuilder(name="video-pipeline")
|
|
78
|
+
.element("v4l2src", name="src", device="/dev/video0", do_timestamp=True)
|
|
79
|
+
.caps("image/jpeg,width=1280,height=720,framerate=30/1")
|
|
80
|
+
.element("queue", name="buffer", max_size_buffers=3, leaky=2)
|
|
81
|
+
.element("jpegdec")
|
|
82
|
+
.element("videoconvert")
|
|
83
|
+
.element("x264enc", name="encoder", tune="zerolatency",
|
|
84
|
+
speed_preset="ultrafast", bitrate=2000)
|
|
85
|
+
.element("h264parse", config_interval=1)
|
|
86
|
+
.caps("video/x-h264,stream-format=byte-stream,alignment=au")
|
|
87
|
+
.element("rtph264pay", name="pay", pt=96, config_interval=1, mtu=1200)
|
|
88
|
+
.build()
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# pipeline is a real Gst.Pipeline — do anything you want with it
|
|
92
|
+
encoder = elems["encoder"]
|
|
93
|
+
encoder.set_property("bitrate", 4000)
|
|
94
|
+
pipeline.set_state(Gst.State.PLAYING)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The topology is the code. Each line is one element, in the order data flows through it.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## What's in the library
|
|
102
|
+
|
|
103
|
+
### `PipelineBuilder`
|
|
104
|
+
|
|
105
|
+
A fluent builder for linear GStreamer pipelines. It handles the repetitive `ElementFactory.make()` → `set_property()` → `link()` / `link_filtered()` loop for you, with clear error messages when something goes wrong.
|
|
106
|
+
|
|
107
|
+
The builder only handles linear topologies by design. For elements with dynamic pads (`decodebin`, `webrtcbin`) or branching topologies (`tee`), `build()` returns a real `Gst.Pipeline` that you extend with the standard GStreamer API — the two approaches compose naturally and neither gets in the way of the other.
|
|
108
|
+
|
|
109
|
+
One detail that saves a constant small friction: GStreamer property names use hyphens (`speed-preset`, `key-int-max`, `max-size-buffers`) but Python keyword arguments must use underscores. The builder converts underscores to hyphens automatically, so you write Python-idiomatic code and the right thing happens.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from streamcraft import PipelineBuilder
|
|
113
|
+
|
|
114
|
+
pipeline, elems = (
|
|
115
|
+
PipelineBuilder()
|
|
116
|
+
.element("audiotestsrc", num_buffers=100)
|
|
117
|
+
.element("audioconvert")
|
|
118
|
+
.caps("audio/x-raw,channels=1,rate=48000")
|
|
119
|
+
.element("opusenc", name="enc", bitrate=128000, complexity=5)
|
|
120
|
+
.element("fakesink", sync=False)
|
|
121
|
+
.build()
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
enc = elems["enc"]
|
|
125
|
+
print(enc.get_property("bitrate")) # 128000
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `require_elements`
|
|
129
|
+
|
|
130
|
+
Checks that all the GStreamer plugins your pipeline needs are installed before you try to build anything. If something is missing, you get a clear error message with the exact `apt install` command to fix it — not a cryptic `None` return buried ten lines into your startup code.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from streamcraft import require_elements
|
|
134
|
+
|
|
135
|
+
require_elements(
|
|
136
|
+
"webrtcbin", "v4l2src", "jpegdec", "videoconvert",
|
|
137
|
+
"x264enc", "h264parse", "rtph264pay",
|
|
138
|
+
"alsasrc", "opusenc", "rtpopuspay",
|
|
139
|
+
)
|
|
140
|
+
# Raises EnvironmentError with an apt install hint if anything is missing.
|
|
141
|
+
# Returns None silently if everything is present.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `check_v4l2_device` and `list_v4l2_devices`
|
|
145
|
+
|
|
146
|
+
Verifies that a V4L2 camera device exists, is readable, and actually opens — catching "device busy" and permission errors before you commit to building a pipeline for a real connection.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from streamcraft import check_v4l2_device, list_v4l2_devices
|
|
150
|
+
|
|
151
|
+
print(list_v4l2_devices()) # ['/dev/video0', '/dev/video2']
|
|
152
|
+
|
|
153
|
+
ok, msg = check_v4l2_device("/dev/video0")
|
|
154
|
+
if not ok:
|
|
155
|
+
raise SystemExit(f"Camera not available: {msg}")
|
|
156
|
+
# e.g. "Device '/dev/video0' exists but is not readable.
|
|
157
|
+
# Try: sudo usermod -aG video $USER (then re-login)"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `V4L2PTZCamera`
|
|
161
|
+
|
|
162
|
+
Controls pan, tilt, and zoom on any V4L2 camera that exposes those controls — Obsbot, Logitech PTZ Pro, and similar. It uses `v4l2-ctl` to auto-detect the camera's valid ranges at startup, so you never have to hardcode min/max values per camera model.
|
|
163
|
+
|
|
164
|
+
If no PTZ camera is connected, all operations are silent no-ops that return `False`. This means your application starts and streams video correctly even when the PTZ camera isn't plugged in.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from streamcraft import V4L2PTZCamera
|
|
168
|
+
|
|
169
|
+
cam = V4L2PTZCamera("/dev/video0")
|
|
170
|
+
|
|
171
|
+
if cam.available:
|
|
172
|
+
cam.set_pan(90_000) # pan right (units are arc-seconds × 100)
|
|
173
|
+
cam.set_tilt(-50_000) # tilt down
|
|
174
|
+
cam.set_zoom(200) # zoom in (range is camera-specific)
|
|
175
|
+
cam.reset() # back to center, minimum zoom
|
|
176
|
+
|
|
177
|
+
print(cam.status.to_dict())
|
|
178
|
+
# {'available': True, 'pan': 0, 'tilt': 0, 'zoom': 100,
|
|
179
|
+
# 'ranges': {'pan': {'min': -522000, 'max': 522000}, ...}}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `WebRTCSession`
|
|
183
|
+
|
|
184
|
+
Manages the entire WebRTC signaling lifecycle for a GStreamer pipeline: SDP offer/answer exchange, ICE candidate buffering (including the race condition where candidates arrive before the remote description is set), GLib→asyncio thread bridging, and pipeline state transitions.
|
|
185
|
+
|
|
186
|
+
Decoupled from any web framework — `send_json` is any async callable that accepts a dict, so it works with aiohttp, FastAPI, Starlette, or raw `websockets` with no modification.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from streamcraft import WebRTCSession
|
|
190
|
+
from aiohttp import web
|
|
191
|
+
import json
|
|
192
|
+
|
|
193
|
+
async def handle_ws(request):
|
|
194
|
+
ws = web.WebSocketResponse()
|
|
195
|
+
await ws.prepare(request)
|
|
196
|
+
|
|
197
|
+
pipeline = build_my_pipeline() # your PipelineBuilder call
|
|
198
|
+
session = WebRTCSession(pipeline)
|
|
199
|
+
session.connect_ice_sender(ws.send_json) # forwards local ICE candidates
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
async for msg in ws:
|
|
203
|
+
if msg.type == web.WSMsgType.TEXT:
|
|
204
|
+
await session.handle_message(json.loads(msg.data), ws.send_json)
|
|
205
|
+
finally:
|
|
206
|
+
session.stop() # releases camera, mic, and all GStreamer resources
|
|
207
|
+
|
|
208
|
+
return ws
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Installation
|
|
214
|
+
|
|
215
|
+
streamcraft depends on GStreamer's Python bindings, which come from the system package manager and are not available on PyPI. Install the system packages first, then install streamcraft into a virtual environment.
|
|
216
|
+
|
|
217
|
+
**Step 1 — system dependencies (Debian / Ubuntu / Raspberry Pi OS):**
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
sudo apt update && sudo apt install -y \
|
|
221
|
+
python3-gi \
|
|
222
|
+
gir1.2-gstreamer-1.0 \
|
|
223
|
+
gir1.2-gst-plugins-base-1.0 \
|
|
224
|
+
gir1.2-gst-plugins-bad-1.0 \
|
|
225
|
+
gstreamer1.0-plugins-base \
|
|
226
|
+
gstreamer1.0-plugins-good \
|
|
227
|
+
gstreamer1.0-plugins-bad \
|
|
228
|
+
gstreamer1.0-plugins-ugly \
|
|
229
|
+
gstreamer1.0-libav \
|
|
230
|
+
gstreamer1.0-tools \
|
|
231
|
+
gstreamer1.0-alsa \
|
|
232
|
+
v4l-utils
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Step 2 — create a virtual environment with access to the system packages:**
|
|
236
|
+
|
|
237
|
+
The `--system-site-packages` flag is important — it gives the virtual environment visibility into the GStreamer bindings that `apt` installed in Step 1. Without it, `import gi` would fail inside the venv even though GStreamer is installed on the system.
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
python -m venv --system-site-packages ~/streamcraft_env
|
|
241
|
+
source ~/streamcraft_env/bin/activate
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Step 3 — install streamcraft:**
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# From GitHub:
|
|
248
|
+
pip install git+https://github.com/ranaweerasupun/streamcraft.git
|
|
249
|
+
|
|
250
|
+
# With aiohttp for the streaming server example:
|
|
251
|
+
pip install "git+https://github.com/ranaweerasupun/streamcraft.git#egg=streamcraft[aiohttp]"
|
|
252
|
+
|
|
253
|
+
# Or clone and install locally:
|
|
254
|
+
git clone https://github.com/ranaweerasupun/streamcraft.git
|
|
255
|
+
cd streamcraft
|
|
256
|
+
pip install -e ".[aiohttp]"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Example project — bidirectional streaming server
|
|
262
|
+
|
|
263
|
+
The `examples/` directory contains a complete bidirectional video and audio streaming server. Run it on a Raspberry Pi 5; connect from any browser on a device in the same [Tailscale](https://tailscale.com) network.
|
|
264
|
+
|
|
265
|
+
What it does: streams live H.264 video and Opus audio from the Pi's camera to the browser over WebRTC, receives the browser's camera and microphone back on the Pi, and exposes the camera's pan/tilt/zoom controls through sliders in the browser UI. No robot-specific code, no serial ports, no joysticks — just streaming, which is useful to almost anyone.
|
|
266
|
+
|
|
267
|
+
What streamcraft replaces in the example: the GStreamer pipeline that would have been ~130 lines of `ElementFactory.make()` and `link()` boilerplate is 12 readable lines. The WebRTC signaling handler that would have been ~140 lines of SDP parsing, ICE buffering, and GLib→asyncio threading code is one `WebRTCSession` call. The PTZ camera class that would have been ~190 lines is one `V4L2PTZCamera` line.
|
|
268
|
+
|
|
269
|
+
**Running the example:**
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
git clone https://github.com/ranaweerasupun/streamcraft.git
|
|
273
|
+
cd streamcraft
|
|
274
|
+
|
|
275
|
+
# Set up the environment
|
|
276
|
+
python -m venv --system-site-packages ~/streamcraft_env
|
|
277
|
+
source ~/streamcraft_env/bin/activate
|
|
278
|
+
pip install -e ".[aiohttp]"
|
|
279
|
+
|
|
280
|
+
# Generate a TLS certificate
|
|
281
|
+
# Option A — Tailscale HTTPS (requires a paid Tailscale plan):
|
|
282
|
+
sudo tailscale cert <your-device-fqdn>
|
|
283
|
+
|
|
284
|
+
# Option B — self-signed certificate (free, browser will show a warning once):
|
|
285
|
+
sudo mkdir -p /var/lib/tailscale/certs
|
|
286
|
+
sudo openssl req -x509 -newkey rsa:4096 -days 365 -nodes \
|
|
287
|
+
-keyout /var/lib/tailscale/certs/<your-fqdn>.key \
|
|
288
|
+
-out /var/lib/tailscale/certs/<your-fqdn>.crt \
|
|
289
|
+
-subj "/CN=<your-fqdn>"
|
|
290
|
+
|
|
291
|
+
# Grant your user read access to the certificate files
|
|
292
|
+
sudo setfacl -m u:$USER:x /var/lib/tailscale
|
|
293
|
+
sudo setfacl -m u:$USER:rx /var/lib/tailscale/certs
|
|
294
|
+
sudo setfacl -m u:$USER:r /var/lib/tailscale/certs/<your-fqdn>.crt
|
|
295
|
+
sudo setfacl -m u:$USER:r /var/lib/tailscale/certs/<your-fqdn>.key
|
|
296
|
+
|
|
297
|
+
# Edit the four config lines at the top of the server file
|
|
298
|
+
nano examples/streaming_server.py
|
|
299
|
+
|
|
300
|
+
# Run from the project root
|
|
301
|
+
python examples/streaming_server.py
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Then open `https://<your-device-fqdn>:8443` in a browser on another device in your Tailscale network.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Design philosophy
|
|
309
|
+
|
|
310
|
+
**Sit on top, not underneath.** streamcraft reduces boilerplate without hiding GStreamer concepts. You still work with real `Gst.Pipeline`, `Gst.Element`, and `Gst.Pad` objects. When you need to do something the builder doesn't support, you use the standard GStreamer API directly on the returned objects — the library never gets in the way.
|
|
311
|
+
|
|
312
|
+
**Linear pipelines only (in the builder).** The builder handles the common case: a straight chain from source to sink. This deliberate constraint keeps the builder simple and predictable. Anything more complex uses the builder for the linear parts and the raw GStreamer API for the rest.
|
|
313
|
+
|
|
314
|
+
**Fail fast with actionable messages.** Missing plugin? You get the exact `apt install` command. Bad property name? You get the GStreamer hyphenated name next to your typo, plus the `gst-inspect-1.0` command to look it up. Failed link? You get the element names and a hint about which pad is incompatible. The error messages are part of the library's contract.
|
|
315
|
+
|
|
316
|
+
**No web framework lock-in.** `WebRTCSession` takes a callable, not a framework object. Any async function that accepts a dict and sends it as JSON is compatible — aiohttp, FastAPI, Starlette, raw `websockets`, or anything else.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Running the tests
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# Software-only tests — no hardware required, runs in CI
|
|
324
|
+
pytest
|
|
325
|
+
|
|
326
|
+
# Including hardware tests — requires a camera on /dev/video0
|
|
327
|
+
pytest -m hardware
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The test suite has 93 tests. Hardware-dependent tests are marked `@pytest.mark.hardware` and skipped by default. The software-only tests use GStreamer's built-in test elements (`videotestsrc`, `audiotestsrc`, `fakesink`) and complete in under one second.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Project structure
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
streamcraft/
|
|
338
|
+
├── streamcraft/ ← the installable Python package
|
|
339
|
+
│ ├── __init__.py ← public API
|
|
340
|
+
│ ├── pipeline.py ← PipelineBuilder
|
|
341
|
+
│ ├── devices.py ← require_elements, V4L2PTZCamera, check_v4l2_device
|
|
342
|
+
│ └── webrtc.py ← WebRTCSession
|
|
343
|
+
├── examples/
|
|
344
|
+
│ ├── streaming_server.py ← bidirectional streaming server (Raspberry Pi)
|
|
345
|
+
│ └── interface.html ← browser UI served by the example server
|
|
346
|
+
├── tests/
|
|
347
|
+
│ ├── test_pipeline.py ← 37 tests for PipelineBuilder
|
|
348
|
+
│ ├── test_devices.py ← 37 tests for devices module
|
|
349
|
+
│ └── test_webrtc.py ← 19 tests for WebRTCSession
|
|
350
|
+
├── pyproject.toml
|
|
351
|
+
└── README.md
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
MIT — see [LICENSE](LICENSE) for details.
|