rocket-welder-sdk 1.1.27__tar.gz → 1.1.28__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.
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/PKG-INFO +130 -1
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/README.md +128 -0
- rocket_welder_sdk-1.1.28/VERSION +1 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/pyproject.toml +9 -4
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/__init__.py +7 -1
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/connection_string.py +51 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/controllers.py +32 -24
- rocket_welder_sdk-1.1.28/rocket_welder_sdk/opencv_controller.py +278 -0
- rocket_welder_sdk-1.1.28/rocket_welder_sdk/periodic_timer.py +303 -0
- rocket_welder_sdk-1.1.28/rocket_welder_sdk/rocket_welder_client.py +404 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk.egg-info/PKG-INFO +130 -1
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk.egg-info/SOURCES.txt +2 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_ui_service_happy_path.py +3 -4
- rocket_welder_sdk-1.1.27/VERSION +0 -1
- rocket_welder_sdk-1.1.27/rocket_welder_sdk/rocket_welder_client.py +0 -167
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/MANIFEST.in +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/logo.png +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/bytes_size.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/external_controls/__init__.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/external_controls/contracts.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/external_controls/contracts_old.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/gst_metadata.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/py.typed +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/ui/__init__.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/ui/controls.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/ui/icons.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/ui/ui_events_projection.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/ui/ui_service.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/ui/value_types.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk.egg-info/dependency_links.txt +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk.egg-info/requires.txt +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk.egg-info/top_level.txt +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/setup.cfg +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/setup.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_bytes_size.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_connection_string.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_controllers.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_external_controls_serialization.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_external_controls_serialization_v2.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_gst_metadata.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_icons.py +0 -0
- {rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/tests/test_ui_controls.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rocket-welder-sdk
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.28
|
|
4
4
|
Summary: High-performance video streaming SDK for RocketWelder services using ZeroBuffer IPC
|
|
5
5
|
Home-page: https://github.com/modelingevolution/rocket-welder-sdk
|
|
6
6
|
Author: ModelingEvolution
|
|
@@ -17,6 +17,7 @@ Classifier: Topic :: Multimedia :: Video
|
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Classifier: License :: OSI Approved :: MIT License
|
|
19
19
|
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -93,6 +94,18 @@ shm://<buffer_name>?mode=duplex&buffer_size=10MB
|
|
|
93
94
|
- `buffer_size`: Size of the data buffer (default: 20MB, supports units: B, KB, MB, GB)
|
|
94
95
|
- `metadata_size`: Size of the metadata buffer (default: 4KB, supports units: B, KB, MB)
|
|
95
96
|
|
|
97
|
+
#### File (Video File Playback)
|
|
98
|
+
```
|
|
99
|
+
file:///path/to/video.mp4
|
|
100
|
+
file:///path/to/video.mp4?loop=true
|
|
101
|
+
file:///path/to/video.mp4?preview=true
|
|
102
|
+
file:///path/to/video.mp4?loop=true&preview=true
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Optional Parameters:**
|
|
106
|
+
- `loop`: Loop video playback when end is reached (`true` or `false`; default: `false`)
|
|
107
|
+
- `preview`: Enable preview window display (`true` or `false`; default: `false`)
|
|
108
|
+
|
|
96
109
|
#### MJPEG over HTTP
|
|
97
110
|
```
|
|
98
111
|
mjpeg+http://192.168.1.100:8080
|
|
@@ -318,6 +331,44 @@ for frame in client.frames():
|
|
|
318
331
|
print(f"Received frame: {frame.shape}")
|
|
319
332
|
```
|
|
320
333
|
|
|
334
|
+
## Preview Display
|
|
335
|
+
|
|
336
|
+
When using the `file://` protocol with `preview=true` parameter, you can display frames in a window. The `Show()` method must be called from the main thread:
|
|
337
|
+
|
|
338
|
+
### C# Preview
|
|
339
|
+
```csharp
|
|
340
|
+
var client = RocketWelderClient.FromConnectionString(
|
|
341
|
+
"file:///path/to/video.mp4?preview=true&loop=true"
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// Start processing in background
|
|
345
|
+
client.Start(frame => {
|
|
346
|
+
// Process frame
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Show preview window in main thread (blocks until 'q' pressed)
|
|
350
|
+
client.Show();
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Python Preview
|
|
354
|
+
```python
|
|
355
|
+
client = rw.Client.from_connection_string(
|
|
356
|
+
"file:///path/to/video.mp4?preview=true&loop=true"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Start processing in background
|
|
360
|
+
client.start(lambda frame: process_frame(frame))
|
|
361
|
+
|
|
362
|
+
# Show preview window in main thread (blocks until 'q' pressed)
|
|
363
|
+
client.show()
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Note**: The `Show()` method:
|
|
367
|
+
- Blocks when `preview=true` is set in the connection string
|
|
368
|
+
- Returns immediately when `preview` is not set or `false`
|
|
369
|
+
- Must be called from the main thread (X11/GUI requirement)
|
|
370
|
+
- Stops when 'q' key is pressed in the preview window
|
|
371
|
+
|
|
321
372
|
## Docker Integration
|
|
322
373
|
|
|
323
374
|
### C++ Dockerfile
|
|
@@ -385,6 +436,75 @@ COPY . .
|
|
|
385
436
|
CMD ["python", "app.py"]
|
|
386
437
|
```
|
|
387
438
|
|
|
439
|
+
### Running Docker with X11 Display Support (Preview)
|
|
440
|
+
|
|
441
|
+
When using the `preview=true` parameter with file protocol, you need to enable X11 forwarding for Docker containers to display the preview window.
|
|
442
|
+
|
|
443
|
+
#### Linux
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
# Allow X server connections from Docker
|
|
447
|
+
xhost +local:docker
|
|
448
|
+
|
|
449
|
+
# Run container with display support
|
|
450
|
+
docker run --rm \
|
|
451
|
+
-e DISPLAY=$DISPLAY \
|
|
452
|
+
-e CONNECTION_STRING="file:///data/video.mp4?preview=true&loop=true" \
|
|
453
|
+
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
|
|
454
|
+
-v /path/to/video.mp4:/data/video.mp4:ro \
|
|
455
|
+
--network host \
|
|
456
|
+
your-image:latest
|
|
457
|
+
|
|
458
|
+
# Restore X server security after running
|
|
459
|
+
xhost -local:docker
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### Windows WSL2
|
|
463
|
+
|
|
464
|
+
WSL2 includes WSLg which provides automatic X11 support:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
# WSLg sets DISPLAY automatically, just verify it's set
|
|
468
|
+
echo $DISPLAY # Should show :0 or similar
|
|
469
|
+
|
|
470
|
+
# Allow X server connections
|
|
471
|
+
xhost +local:docker 2>/dev/null || xhost +local: 2>/dev/null
|
|
472
|
+
|
|
473
|
+
# Run container with display support (same as Linux)
|
|
474
|
+
docker run --rm \
|
|
475
|
+
-e DISPLAY=$DISPLAY \
|
|
476
|
+
-e CONNECTION_STRING="file:///data/video.mp4?preview=true&loop=true" \
|
|
477
|
+
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
|
|
478
|
+
-v /mnt/c/path/to/video.mp4:/data/video.mp4:ro \
|
|
479
|
+
--network host \
|
|
480
|
+
your-image:latest
|
|
481
|
+
|
|
482
|
+
# Restore X server security
|
|
483
|
+
xhost -local:docker 2>/dev/null || xhost -local: 2>/dev/null
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
#### Helper Scripts
|
|
487
|
+
|
|
488
|
+
The SDK includes helper scripts for easy testing:
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
# Build Docker images with sample clients
|
|
492
|
+
./build_docker_samples.sh
|
|
493
|
+
|
|
494
|
+
# Test Python client with preview
|
|
495
|
+
./run_docker_x11.sh python
|
|
496
|
+
|
|
497
|
+
# Test C# client with preview
|
|
498
|
+
./run_docker_x11.sh csharp
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
These scripts automatically:
|
|
502
|
+
- Configure X11 display forwarding
|
|
503
|
+
- Use a test video from the repository's data folder
|
|
504
|
+
- Mount the video into the container
|
|
505
|
+
- Set up the connection string with preview enabled
|
|
506
|
+
- Clean up X server permissions after running
|
|
507
|
+
|
|
388
508
|
## Protocol Details
|
|
389
509
|
|
|
390
510
|
### Shared Memory Protocol (shm://)
|
|
@@ -394,6 +514,15 @@ High-performance local data transfer between processes:
|
|
|
394
514
|
- **Performance**: Minimal latency, maximum throughput
|
|
395
515
|
- **Use Cases**: Local processing, multi-container applications on same host
|
|
396
516
|
|
|
517
|
+
### File Protocol (file://)
|
|
518
|
+
|
|
519
|
+
Local video file playback with OpenCV:
|
|
520
|
+
|
|
521
|
+
- **Performance**: Controlled playback speed based on video FPS
|
|
522
|
+
- **Features**: Loop playback, preview window, frame-accurate timing
|
|
523
|
+
- **Use Cases**: Testing, development, offline processing, demos
|
|
524
|
+
- **Supported Formats**: All formats supported by OpenCV (MP4, AVI, MOV, etc.)
|
|
525
|
+
|
|
397
526
|
### MJPEG over HTTP (mjpeg+http://)
|
|
398
527
|
|
|
399
528
|
Motion JPEG streaming over HTTP:
|
|
@@ -49,6 +49,18 @@ shm://<buffer_name>?mode=duplex&buffer_size=10MB
|
|
|
49
49
|
- `buffer_size`: Size of the data buffer (default: 20MB, supports units: B, KB, MB, GB)
|
|
50
50
|
- `metadata_size`: Size of the metadata buffer (default: 4KB, supports units: B, KB, MB)
|
|
51
51
|
|
|
52
|
+
#### File (Video File Playback)
|
|
53
|
+
```
|
|
54
|
+
file:///path/to/video.mp4
|
|
55
|
+
file:///path/to/video.mp4?loop=true
|
|
56
|
+
file:///path/to/video.mp4?preview=true
|
|
57
|
+
file:///path/to/video.mp4?loop=true&preview=true
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Optional Parameters:**
|
|
61
|
+
- `loop`: Loop video playback when end is reached (`true` or `false`; default: `false`)
|
|
62
|
+
- `preview`: Enable preview window display (`true` or `false`; default: `false`)
|
|
63
|
+
|
|
52
64
|
#### MJPEG over HTTP
|
|
53
65
|
```
|
|
54
66
|
mjpeg+http://192.168.1.100:8080
|
|
@@ -274,6 +286,44 @@ for frame in client.frames():
|
|
|
274
286
|
print(f"Received frame: {frame.shape}")
|
|
275
287
|
```
|
|
276
288
|
|
|
289
|
+
## Preview Display
|
|
290
|
+
|
|
291
|
+
When using the `file://` protocol with `preview=true` parameter, you can display frames in a window. The `Show()` method must be called from the main thread:
|
|
292
|
+
|
|
293
|
+
### C# Preview
|
|
294
|
+
```csharp
|
|
295
|
+
var client = RocketWelderClient.FromConnectionString(
|
|
296
|
+
"file:///path/to/video.mp4?preview=true&loop=true"
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Start processing in background
|
|
300
|
+
client.Start(frame => {
|
|
301
|
+
// Process frame
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Show preview window in main thread (blocks until 'q' pressed)
|
|
305
|
+
client.Show();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Python Preview
|
|
309
|
+
```python
|
|
310
|
+
client = rw.Client.from_connection_string(
|
|
311
|
+
"file:///path/to/video.mp4?preview=true&loop=true"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Start processing in background
|
|
315
|
+
client.start(lambda frame: process_frame(frame))
|
|
316
|
+
|
|
317
|
+
# Show preview window in main thread (blocks until 'q' pressed)
|
|
318
|
+
client.show()
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Note**: The `Show()` method:
|
|
322
|
+
- Blocks when `preview=true` is set in the connection string
|
|
323
|
+
- Returns immediately when `preview` is not set or `false`
|
|
324
|
+
- Must be called from the main thread (X11/GUI requirement)
|
|
325
|
+
- Stops when 'q' key is pressed in the preview window
|
|
326
|
+
|
|
277
327
|
## Docker Integration
|
|
278
328
|
|
|
279
329
|
### C++ Dockerfile
|
|
@@ -341,6 +391,75 @@ COPY . .
|
|
|
341
391
|
CMD ["python", "app.py"]
|
|
342
392
|
```
|
|
343
393
|
|
|
394
|
+
### Running Docker with X11 Display Support (Preview)
|
|
395
|
+
|
|
396
|
+
When using the `preview=true` parameter with file protocol, you need to enable X11 forwarding for Docker containers to display the preview window.
|
|
397
|
+
|
|
398
|
+
#### Linux
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
# Allow X server connections from Docker
|
|
402
|
+
xhost +local:docker
|
|
403
|
+
|
|
404
|
+
# Run container with display support
|
|
405
|
+
docker run --rm \
|
|
406
|
+
-e DISPLAY=$DISPLAY \
|
|
407
|
+
-e CONNECTION_STRING="file:///data/video.mp4?preview=true&loop=true" \
|
|
408
|
+
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
|
|
409
|
+
-v /path/to/video.mp4:/data/video.mp4:ro \
|
|
410
|
+
--network host \
|
|
411
|
+
your-image:latest
|
|
412
|
+
|
|
413
|
+
# Restore X server security after running
|
|
414
|
+
xhost -local:docker
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### Windows WSL2
|
|
418
|
+
|
|
419
|
+
WSL2 includes WSLg which provides automatic X11 support:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# WSLg sets DISPLAY automatically, just verify it's set
|
|
423
|
+
echo $DISPLAY # Should show :0 or similar
|
|
424
|
+
|
|
425
|
+
# Allow X server connections
|
|
426
|
+
xhost +local:docker 2>/dev/null || xhost +local: 2>/dev/null
|
|
427
|
+
|
|
428
|
+
# Run container with display support (same as Linux)
|
|
429
|
+
docker run --rm \
|
|
430
|
+
-e DISPLAY=$DISPLAY \
|
|
431
|
+
-e CONNECTION_STRING="file:///data/video.mp4?preview=true&loop=true" \
|
|
432
|
+
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
|
|
433
|
+
-v /mnt/c/path/to/video.mp4:/data/video.mp4:ro \
|
|
434
|
+
--network host \
|
|
435
|
+
your-image:latest
|
|
436
|
+
|
|
437
|
+
# Restore X server security
|
|
438
|
+
xhost -local:docker 2>/dev/null || xhost -local: 2>/dev/null
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### Helper Scripts
|
|
442
|
+
|
|
443
|
+
The SDK includes helper scripts for easy testing:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
# Build Docker images with sample clients
|
|
447
|
+
./build_docker_samples.sh
|
|
448
|
+
|
|
449
|
+
# Test Python client with preview
|
|
450
|
+
./run_docker_x11.sh python
|
|
451
|
+
|
|
452
|
+
# Test C# client with preview
|
|
453
|
+
./run_docker_x11.sh csharp
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
These scripts automatically:
|
|
457
|
+
- Configure X11 display forwarding
|
|
458
|
+
- Use a test video from the repository's data folder
|
|
459
|
+
- Mount the video into the container
|
|
460
|
+
- Set up the connection string with preview enabled
|
|
461
|
+
- Clean up X server permissions after running
|
|
462
|
+
|
|
344
463
|
## Protocol Details
|
|
345
464
|
|
|
346
465
|
### Shared Memory Protocol (shm://)
|
|
@@ -350,6 +469,15 @@ High-performance local data transfer between processes:
|
|
|
350
469
|
- **Performance**: Minimal latency, maximum throughput
|
|
351
470
|
- **Use Cases**: Local processing, multi-container applications on same host
|
|
352
471
|
|
|
472
|
+
### File Protocol (file://)
|
|
473
|
+
|
|
474
|
+
Local video file playback with OpenCV:
|
|
475
|
+
|
|
476
|
+
- **Performance**: Controlled playback speed based on video FPS
|
|
477
|
+
- **Features**: Loop playback, preview window, frame-accurate timing
|
|
478
|
+
- **Use Cases**: Testing, development, offline processing, demos
|
|
479
|
+
- **Supported Formats**: All formats supported by OpenCV (MP4, AVI, MOV, etc.)
|
|
480
|
+
|
|
353
481
|
### MJPEG over HTTP (mjpeg+http://)
|
|
354
482
|
|
|
355
483
|
Motion JPEG streaming over HTTP:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.1.28
|
|
@@ -7,7 +7,7 @@ name = "rocket-welder-sdk"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "High-performance video streaming SDK for RocketWelder services using ZeroBuffer IPC"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.8.10"
|
|
11
11
|
license = {text = "MIT"}
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "ModelingEvolution", email = "info@modelingevolution.com"}
|
|
@@ -23,6 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
24
|
"License :: OSI Approved :: MIT License",
|
|
25
25
|
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.8",
|
|
26
27
|
"Programming Language :: Python :: 3.9",
|
|
27
28
|
"Programming Language :: Python :: 3.10",
|
|
28
29
|
"Programming Language :: Python :: 3.11",
|
|
@@ -66,7 +67,7 @@ include = ["rocket_welder_sdk*"]
|
|
|
66
67
|
rocket_welder_sdk = ["py.typed"]
|
|
67
68
|
|
|
68
69
|
[tool.mypy]
|
|
69
|
-
python_version = "3.9"
|
|
70
|
+
python_version = "3.9" # mypy 1.0+ requires 3.9, but code supports 3.8
|
|
70
71
|
strict = true
|
|
71
72
|
warn_return_any = true
|
|
72
73
|
warn_unused_configs = true
|
|
@@ -80,6 +81,7 @@ pretty = true
|
|
|
80
81
|
module = [
|
|
81
82
|
"cv2",
|
|
82
83
|
"cv2.*",
|
|
84
|
+
"cv2.typing",
|
|
83
85
|
"zerobuffer",
|
|
84
86
|
"zerobuffer.*",
|
|
85
87
|
"py_micro_plumberd",
|
|
@@ -91,12 +93,12 @@ ignore_missing_imports = true
|
|
|
91
93
|
|
|
92
94
|
[tool.black]
|
|
93
95
|
line-length = 100
|
|
94
|
-
target-version = ['py39', 'py310', 'py311', 'py312']
|
|
96
|
+
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
|
|
95
97
|
include = '\.pyi?$'
|
|
96
98
|
|
|
97
99
|
[tool.ruff]
|
|
98
100
|
line-length = 100
|
|
99
|
-
target-version = "
|
|
101
|
+
target-version = "py38"
|
|
100
102
|
|
|
101
103
|
[tool.ruff.lint]
|
|
102
104
|
select = [
|
|
@@ -116,6 +118,9 @@ select = [
|
|
|
116
118
|
ignore = [
|
|
117
119
|
"E501", # line too long (handled by black)
|
|
118
120
|
"B008", # do not perform function calls in argument defaults
|
|
121
|
+
"UP006", # Keep List[X] for Python 3.8 compatibility
|
|
122
|
+
"UP007", # Keep Union[X, Y] for Python 3.8 compatibility
|
|
123
|
+
"UP045", # Keep Optional[X] for Python 3.8 compatibility
|
|
119
124
|
"C901", # too complex
|
|
120
125
|
]
|
|
121
126
|
|
|
@@ -11,9 +11,11 @@ from .bytes_size import BytesSize
|
|
|
11
11
|
from .connection_string import ConnectionMode, ConnectionString, Protocol
|
|
12
12
|
from .controllers import DuplexShmController, IController, OneWayShmController
|
|
13
13
|
from .gst_metadata import GstCaps, GstMetadata
|
|
14
|
+
from .opencv_controller import OpenCvController
|
|
15
|
+
from .periodic_timer import PeriodicTimer, PeriodicTimerSync
|
|
14
16
|
from .rocket_welder_client import RocketWelderClient
|
|
15
17
|
|
|
16
|
-
# Alias for backward compatibility
|
|
18
|
+
# Alias for backward compatibility and README examples
|
|
17
19
|
Client = RocketWelderClient
|
|
18
20
|
|
|
19
21
|
__version__ = "1.1.0"
|
|
@@ -50,6 +52,10 @@ __all__ = [
|
|
|
50
52
|
# Controllers
|
|
51
53
|
"IController",
|
|
52
54
|
"OneWayShmController",
|
|
55
|
+
"OpenCvController",
|
|
56
|
+
# Timers
|
|
57
|
+
"PeriodicTimer",
|
|
58
|
+
"PeriodicTimerSync",
|
|
53
59
|
"Protocol",
|
|
54
60
|
# Main client
|
|
55
61
|
"RocketWelderClient",
|
{rocket_welder_sdk-1.1.27 → rocket_welder_sdk-1.1.28}/rocket_welder_sdk/connection_string.py
RENAMED
|
@@ -21,6 +21,7 @@ class Protocol(Flag):
|
|
|
21
21
|
MJPEG = auto() # Motion JPEG
|
|
22
22
|
HTTP = auto() # HTTP protocol
|
|
23
23
|
TCP = auto() # TCP protocol
|
|
24
|
+
FILE = auto() # File protocol
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class ConnectionMode(Enum):
|
|
@@ -43,12 +44,15 @@ class ConnectionString:
|
|
|
43
44
|
- shm://buffer_name?size=256MB&metadata=4KB&mode=Duplex
|
|
44
45
|
- mjpeg://192.168.1.100:8080
|
|
45
46
|
- mjpeg+http://camera.local:80
|
|
47
|
+
- file:///path/to/video.mp4?loop=true
|
|
46
48
|
"""
|
|
47
49
|
|
|
48
50
|
protocol: Protocol
|
|
49
51
|
host: str | None = None
|
|
50
52
|
port: int | None = None
|
|
51
53
|
buffer_name: str | None = None
|
|
54
|
+
file_path: str | None = None
|
|
55
|
+
parameters: dict[str, str] = field(default_factory=dict)
|
|
52
56
|
buffer_size: BytesSize = field(default_factory=lambda: BytesSize.parse("256MB"))
|
|
53
57
|
metadata_size: BytesSize = field(default_factory=lambda: BytesSize.parse("4KB"))
|
|
54
58
|
connection_mode: ConnectionMode = ConnectionMode.ONE_WAY
|
|
@@ -82,6 +86,8 @@ class ConnectionString:
|
|
|
82
86
|
# Parse based on protocol type
|
|
83
87
|
if protocol == Protocol.SHM:
|
|
84
88
|
return cls._parse_shm(protocol, remainder)
|
|
89
|
+
elif protocol == Protocol.FILE:
|
|
90
|
+
return cls._parse_file(protocol, remainder)
|
|
85
91
|
elif bool(protocol & Protocol.MJPEG): # type: ignore[operator]
|
|
86
92
|
return cls._parse_mjpeg(protocol, remainder)
|
|
87
93
|
else:
|
|
@@ -110,6 +116,7 @@ class ConnectionString:
|
|
|
110
116
|
"mjpeg": Protocol.MJPEG,
|
|
111
117
|
"http": Protocol.HTTP,
|
|
112
118
|
"tcp": Protocol.TCP,
|
|
119
|
+
"file": Protocol.FILE,
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
protocol = protocol_map.get(protocol_str, Protocol.NONE)
|
|
@@ -157,6 +164,43 @@ class ConnectionString:
|
|
|
157
164
|
timeout_ms=timeout_ms,
|
|
158
165
|
)
|
|
159
166
|
|
|
167
|
+
@classmethod
|
|
168
|
+
def _parse_file(cls, protocol: Protocol, remainder: str) -> ConnectionString:
|
|
169
|
+
"""Parse file protocol connection string."""
|
|
170
|
+
# Split file path and query parameters
|
|
171
|
+
if "?" in remainder:
|
|
172
|
+
file_path, query_string = remainder.split("?", 1)
|
|
173
|
+
params = cls._parse_query_params(query_string)
|
|
174
|
+
else:
|
|
175
|
+
file_path = remainder
|
|
176
|
+
params = {}
|
|
177
|
+
|
|
178
|
+
# Handle file:///absolute/path and file://relative/path
|
|
179
|
+
if not file_path.startswith("/"):
|
|
180
|
+
file_path = "/" + file_path
|
|
181
|
+
|
|
182
|
+
# Parse common parameters
|
|
183
|
+
connection_mode = ConnectionMode.ONE_WAY
|
|
184
|
+
timeout_ms = 5000
|
|
185
|
+
|
|
186
|
+
if "mode" in params:
|
|
187
|
+
mode_str = params["mode"].upper()
|
|
188
|
+
if mode_str == "DUPLEX":
|
|
189
|
+
connection_mode = ConnectionMode.DUPLEX
|
|
190
|
+
elif mode_str in ("ONEWAY", "ONE_WAY"):
|
|
191
|
+
connection_mode = ConnectionMode.ONE_WAY
|
|
192
|
+
if "timeout" in params:
|
|
193
|
+
with contextlib.suppress(ValueError):
|
|
194
|
+
timeout_ms = int(params["timeout"])
|
|
195
|
+
|
|
196
|
+
return cls(
|
|
197
|
+
protocol=protocol,
|
|
198
|
+
file_path=file_path,
|
|
199
|
+
parameters=params,
|
|
200
|
+
connection_mode=connection_mode,
|
|
201
|
+
timeout_ms=timeout_ms,
|
|
202
|
+
)
|
|
203
|
+
|
|
160
204
|
@classmethod
|
|
161
205
|
def _parse_mjpeg(cls, protocol: Protocol, remainder: str) -> ConnectionString:
|
|
162
206
|
"""Parse MJPEG connection string."""
|
|
@@ -195,6 +239,8 @@ class ConnectionString:
|
|
|
195
239
|
protocol_parts = []
|
|
196
240
|
if self.protocol & Protocol.SHM:
|
|
197
241
|
protocol_parts.append("shm")
|
|
242
|
+
if self.protocol & Protocol.FILE:
|
|
243
|
+
protocol_parts.append("file")
|
|
198
244
|
if self.protocol & Protocol.MJPEG:
|
|
199
245
|
protocol_parts.append("mjpeg")
|
|
200
246
|
if self.protocol & Protocol.HTTP:
|
|
@@ -215,6 +261,11 @@ class ConnectionString:
|
|
|
215
261
|
params.append(f"timeout={self.timeout_ms}")
|
|
216
262
|
|
|
217
263
|
return f"{protocol_str}://{self.buffer_name}?{'&'.join(params)}"
|
|
264
|
+
elif self.protocol == Protocol.FILE:
|
|
265
|
+
query_string = ""
|
|
266
|
+
if self.parameters:
|
|
267
|
+
query_string = "?" + "&".join(f"{k}={v}" for k, v in self.parameters.items())
|
|
268
|
+
return f"{protocol_str}://{self.file_path}{query_string}"
|
|
218
269
|
else:
|
|
219
270
|
return f"{protocol_str}://{self.host}:{self.port}"
|
|
220
271
|
|
|
@@ -9,7 +9,7 @@ import json
|
|
|
9
9
|
import logging
|
|
10
10
|
import threading
|
|
11
11
|
from abc import ABC, abstractmethod
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
13
13
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
from zerobuffer import BufferConfig, Frame, Reader, Writer
|
|
@@ -19,8 +19,12 @@ from zerobuffer.exceptions import WriterDeadException
|
|
|
19
19
|
from .connection_string import ConnectionMode, ConnectionString, Protocol
|
|
20
20
|
from .gst_metadata import GstCaps, GstMetadata
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
import numpy.typing as npt
|
|
24
|
+
|
|
25
|
+
Mat = npt.NDArray[np.uint8]
|
|
26
|
+
else:
|
|
27
|
+
Mat = np.ndarray # type: ignore[misc]
|
|
24
28
|
|
|
25
29
|
# Module logger
|
|
26
30
|
logger = logging.getLogger(__name__)
|
|
@@ -36,13 +40,15 @@ class IController(ABC):
|
|
|
36
40
|
...
|
|
37
41
|
|
|
38
42
|
@abstractmethod
|
|
39
|
-
def get_metadata(self) -> GstMetadata
|
|
43
|
+
def get_metadata(self) -> Optional[GstMetadata]:
|
|
40
44
|
"""Get the current GStreamer metadata."""
|
|
41
45
|
...
|
|
42
46
|
|
|
43
47
|
@abstractmethod
|
|
44
48
|
def start(
|
|
45
|
-
self,
|
|
49
|
+
self,
|
|
50
|
+
on_frame: Callable[[Mat], None], # type: ignore[valid-type]
|
|
51
|
+
cancellation_token: Optional[threading.Event] = None,
|
|
46
52
|
) -> None:
|
|
47
53
|
"""
|
|
48
54
|
Start the controller with a frame callback.
|
|
@@ -80,24 +86,26 @@ class OneWayShmController(IController):
|
|
|
80
86
|
)
|
|
81
87
|
|
|
82
88
|
self._connection = connection
|
|
83
|
-
self._reader: Reader
|
|
84
|
-
self._gst_caps: GstCaps
|
|
85
|
-
self._metadata: GstMetadata
|
|
89
|
+
self._reader: Optional[Reader] = None
|
|
90
|
+
self._gst_caps: Optional[GstCaps] = None
|
|
91
|
+
self._metadata: Optional[GstMetadata] = None
|
|
86
92
|
self._is_running = False
|
|
87
|
-
self._worker_thread: threading.Thread
|
|
88
|
-
self._cancellation_token: threading.Event
|
|
93
|
+
self._worker_thread: Optional[threading.Thread] = None
|
|
94
|
+
self._cancellation_token: Optional[threading.Event] = None
|
|
89
95
|
|
|
90
96
|
@property
|
|
91
97
|
def is_running(self) -> bool:
|
|
92
98
|
"""Check if the controller is running."""
|
|
93
99
|
return self._is_running
|
|
94
100
|
|
|
95
|
-
def get_metadata(self) -> GstMetadata
|
|
101
|
+
def get_metadata(self) -> Optional[GstMetadata]:
|
|
96
102
|
"""Get the current GStreamer metadata."""
|
|
97
103
|
return self._metadata
|
|
98
104
|
|
|
99
105
|
def start(
|
|
100
|
-
self,
|
|
106
|
+
self,
|
|
107
|
+
on_frame: Callable[[Mat], None], # type: ignore[valid-type]
|
|
108
|
+
cancellation_token: Optional[threading.Event] = None,
|
|
101
109
|
) -> None:
|
|
102
110
|
"""
|
|
103
111
|
Start receiving frames from shared memory.
|
|
@@ -161,7 +169,7 @@ class OneWayShmController(IController):
|
|
|
161
169
|
self._worker_thread = None
|
|
162
170
|
logger.info("Stopped controller for buffer '%s'", self._connection.buffer_name)
|
|
163
171
|
|
|
164
|
-
def _process_frames(self, on_frame: Callable[[Mat], None]) -> None:
|
|
172
|
+
def _process_frames(self, on_frame: Callable[[Mat], None]) -> None: # type: ignore[valid-type]
|
|
165
173
|
"""
|
|
166
174
|
Process frames from shared memory.
|
|
167
175
|
|
|
@@ -236,7 +244,7 @@ class OneWayShmController(IController):
|
|
|
236
244
|
logger.error("Fatal error in frame processing loop: %s", e)
|
|
237
245
|
self._is_running = False
|
|
238
246
|
|
|
239
|
-
def _on_first_frame(self, on_frame: Callable[[Mat], None]) -> None:
|
|
247
|
+
def _on_first_frame(self, on_frame: Callable[[Mat], None]) -> None: # type: ignore[valid-type]
|
|
240
248
|
"""
|
|
241
249
|
Process the first frame and extract metadata.
|
|
242
250
|
Matches C# OnFirstFrame behavior - loops until valid frame received.
|
|
@@ -342,7 +350,7 @@ class OneWayShmController(IController):
|
|
|
342
350
|
if not self._is_running:
|
|
343
351
|
break
|
|
344
352
|
|
|
345
|
-
def _create_mat_from_frame(self, frame: Frame) -> Mat
|
|
353
|
+
def _create_mat_from_frame(self, frame: Frame) -> Optional[Mat]: # type: ignore[valid-type]
|
|
346
354
|
"""
|
|
347
355
|
Create OpenCV Mat from frame data using GstCaps.
|
|
348
356
|
Matches C# CreateMat behavior - creates Mat wrapping the data.
|
|
@@ -440,7 +448,7 @@ class OneWayShmController(IController):
|
|
|
440
448
|
logger.error("Failed to convert frame to Mat: %s", e)
|
|
441
449
|
return None
|
|
442
450
|
|
|
443
|
-
def _infer_caps_from_frame(self, mat: Mat) -> None:
|
|
451
|
+
def _infer_caps_from_frame(self, mat: Mat) -> None: # type: ignore[valid-type]
|
|
444
452
|
"""
|
|
445
453
|
Infer GStreamer caps from OpenCV Mat.
|
|
446
454
|
|
|
@@ -487,11 +495,11 @@ class DuplexShmController(IController):
|
|
|
487
495
|
)
|
|
488
496
|
|
|
489
497
|
self._connection = connection
|
|
490
|
-
self._duplex_server: IImmutableDuplexServer
|
|
491
|
-
self._gst_caps: GstCaps
|
|
492
|
-
self._metadata: GstMetadata
|
|
498
|
+
self._duplex_server: Optional[IImmutableDuplexServer] = None
|
|
499
|
+
self._gst_caps: Optional[GstCaps] = None
|
|
500
|
+
self._metadata: Optional[GstMetadata] = None
|
|
493
501
|
self._is_running = False
|
|
494
|
-
self._on_frame_callback: Callable[[Mat, Mat], None]
|
|
502
|
+
self._on_frame_callback: Optional[Callable[[Mat, Mat], None]] = None # type: ignore[valid-type]
|
|
495
503
|
self._frame_count = 0
|
|
496
504
|
|
|
497
505
|
@property
|
|
@@ -499,14 +507,14 @@ class DuplexShmController(IController):
|
|
|
499
507
|
"""Check if the controller is running."""
|
|
500
508
|
return self._is_running
|
|
501
509
|
|
|
502
|
-
def get_metadata(self) -> GstMetadata
|
|
510
|
+
def get_metadata(self) -> Optional[GstMetadata]:
|
|
503
511
|
"""Get the current GStreamer metadata."""
|
|
504
512
|
return self._metadata
|
|
505
513
|
|
|
506
514
|
def start(
|
|
507
515
|
self,
|
|
508
|
-
on_frame: Callable[[Mat, Mat], None], # type: ignore[override]
|
|
509
|
-
cancellation_token: threading.Event
|
|
516
|
+
on_frame: Callable[[Mat, Mat], None], # type: ignore[override,valid-type]
|
|
517
|
+
cancellation_token: Optional[threading.Event] = None,
|
|
510
518
|
) -> None:
|
|
511
519
|
"""
|
|
512
520
|
Start duplex frame processing.
|
|
@@ -662,7 +670,7 @@ class DuplexShmController(IController):
|
|
|
662
670
|
except Exception as e:
|
|
663
671
|
logger.error("Error processing duplex frame: %s", e)
|
|
664
672
|
|
|
665
|
-
def _frame_to_mat(self, frame: Frame) -> Mat
|
|
673
|
+
def _frame_to_mat(self, frame: Frame) -> Optional[Mat]: # type: ignore[valid-type]
|
|
666
674
|
"""Convert frame to OpenCV Mat (reuse from OneWayShmController)."""
|
|
667
675
|
# Implementation is same as OneWayShmController
|
|
668
676
|
return OneWayShmController._create_mat_from_frame(self, frame) # type: ignore[arg-type]
|